| 1 | # Makefile for gitswitch-c |
| 2 | # Safe git identity switching with SSH/GPG isolation |
| 3 | |
| 4 | # Project configuration |
| 5 | PROJECT_NAME = gitswitch-c |
| 6 | VERSION = 1.0.2 |
| 7 | TARGET = gitswitch |
| 8 | |
| 9 | # Directories |
| 10 | SRCDIR = src |
| 11 | BUILDDIR = build |
| 12 | OBJDIR = $(BUILDDIR)/obj |
| 13 | BINDIR = $(BUILDDIR)/bin |
| 14 | TESTDIR = tests |
| 15 | DOCDIR = docs |
| 16 | |
| 17 | # Compiler and flags |
| 18 | CC = gcc |
| 19 | CFLAGS = -std=c11 -Wall -Wextra -Wstrict-prototypes \ |
| 20 | -Wmissing-prototypes -Wold-style-definition -Wredundant-decls \ |
| 21 | -Wbad-function-cast -Wnested-externs -Winit-self -Wlogical-op \ |
| 22 | -Wshadow -Wwrite-strings -Wcast-align -Wstrict-aliasing=2 \ |
| 23 | -Wmissing-include-dirs -Wdate-time -Wformat=2 -Winit-self \ |
| 24 | -Wswitch-default -Wunused -Werror-implicit-function-declaration |
| 25 | |
| 26 | # Security hardening flags |
| 27 | SECURITY_FLAGS_DEBUG = -fstack-protector-strong -fstack-clash-protection -fcf-protection \ |
| 28 | -Wl,-z,relro -Wl,-z,now -Wl,-z,noexecstack |
| 29 | SECURITY_FLAGS_RELEASE = -D_FORTIFY_SOURCE=2 -fstack-protector-strong \ |
| 30 | -fstack-clash-protection -fcf-protection \ |
| 31 | -Wl,-z,relro -Wl,-z,now -Wl,-z,noexecstack |
| 32 | |
| 33 | # Debug/Release configurations |
| 34 | DEBUG_FLAGS = -g -O0 -DDEBUG -fsanitize=address -fsanitize=undefined \ |
| 35 | -fno-omit-frame-pointer -Wpedantic $(SECURITY_FLAGS_DEBUG) |
| 36 | RELEASE_FLAGS = -O2 -DNDEBUG -s $(SECURITY_FLAGS_RELEASE) |
| 37 | |
| 38 | # Default to debug build |
| 39 | BUILD_TYPE ?= debug |
| 40 | ifeq ($(BUILD_TYPE),release) |
| 41 | CFLAGS += $(RELEASE_FLAGS) |
| 42 | else |
| 43 | CFLAGS += $(DEBUG_FLAGS) |
| 44 | endif |
| 45 | |
| 46 | # Include directories |
| 47 | INCLUDES = -I$(SRCDIR) |
| 48 | |
| 49 | # Libraries |
| 50 | LIBS = -lssl -lcrypto |
| 51 | # Note: TOML parsing library will be added (e.g., -ltoml or embedded parser) |
| 52 | |
| 53 | # Source files (Phase 2 - Configuration Management) |
| 54 | PHASE2_SOURCES = $(SRCDIR)/main.c $(SRCDIR)/error.c $(SRCDIR)/utils.c \ |
| 55 | $(SRCDIR)/display.c $(SRCDIR)/toml_parser.c $(SRCDIR)/config.c \ |
| 56 | $(SRCDIR)/accounts.c |
| 57 | |
| 58 | # Source files (Phase 3 - Git Operations) |
| 59 | PHASE3_SOURCES = $(PHASE2_SOURCES) $(SRCDIR)/git_ops.c |
| 60 | |
| 61 | # Source files (Phase 4 - SSH Security Framework) |
| 62 | PHASE4_SOURCES = $(PHASE3_SOURCES) $(SRCDIR)/ssh_manager.c |
| 63 | |
| 64 | # Source files (Phase 5 - GPG Environment Isolation) |
| 65 | PHASE5_SOURCES = $(PHASE4_SOURCES) $(SRCDIR)/gpg_manager.c |
| 66 | |
| 67 | SOURCES = $(PHASE5_SOURCES) |
| 68 | OBJECTS = $(SOURCES:$(SRCDIR)/%.c=$(OBJDIR)/%.o) |
| 69 | HEADERS = $(wildcard $(SRCDIR)/*.h) |
| 70 | |
| 71 | # Test files |
| 72 | TEST_SOURCES = $(wildcard $(TESTDIR)/*.c) |
| 73 | TEST_OBJECTS = $(TEST_SOURCES:$(TESTDIR)/%.c=$(OBJDIR)/test_%.o) |
| 74 | TEST_TARGETS = $(TEST_SOURCES:$(TESTDIR)/%.c=$(BINDIR)/test_%) |
| 75 | |
| 76 | # Default target |
| 77 | .PHONY: all |
| 78 | all: $(BINDIR)/$(TARGET) |
| 79 | |
| 80 | # Create directories |
| 81 | $(OBJDIR): |
| 82 | @mkdir -p $(OBJDIR) |
| 83 | |
| 84 | $(BINDIR): |
| 85 | @mkdir -p $(BINDIR) |
| 86 | |
| 87 | # Compile source files |
| 88 | $(OBJDIR)/%.o: $(SRCDIR)/%.c $(HEADERS) | $(OBJDIR) |
| 89 | @echo "Compiling $<..." |
| 90 | $(CC) $(CFLAGS) $(INCLUDES) -c $< -o $@ |
| 91 | |
| 92 | # Link main executable |
| 93 | $(BINDIR)/$(TARGET): $(OBJECTS) | $(BINDIR) |
| 94 | @echo "Linking $@..." |
| 95 | $(CC) $(CFLAGS) $(OBJECTS) -o $@ $(LIBS) |
| 96 | @echo "Build complete: $@" |
| 97 | |
| 98 | # Install target |
| 99 | .PHONY: install |
| 100 | install: $(BINDIR)/$(TARGET) |
| 101 | @echo "Installing $(TARGET)..." |
| 102 | install -d $(DESTDIR)/usr/local/bin |
| 103 | install -m 755 $(BINDIR)/$(TARGET) $(DESTDIR)/usr/local/bin/$(TARGET) |
| 104 | @echo "Installation complete" |
| 105 | |
| 106 | # Uninstall target |
| 107 | .PHONY: uninstall |
| 108 | uninstall: |
| 109 | @echo "Uninstalling $(TARGET)..." |
| 110 | rm -f $(DESTDIR)/usr/local/bin/$(TARGET) |
| 111 | @echo "Uninstall complete" |
| 112 | |
| 113 | # Test compilation |
| 114 | $(OBJDIR)/test_%.o: $(TESTDIR)/%.c $(HEADERS) | $(OBJDIR) |
| 115 | @echo "Compiling test $<..." |
| 116 | $(CC) $(CFLAGS) $(INCLUDES) -c $< -o $@ |
| 117 | |
| 118 | # Test executables (exclude main.o to avoid multiple main functions) |
| 119 | $(BINDIR)/test_%: $(OBJDIR)/test_%.o $(filter-out $(OBJDIR)/main.o,$(OBJECTS)) | $(BINDIR) |
| 120 | @echo "Linking test $@..." |
| 121 | $(CC) $(CFLAGS) $^ -o $@ $(LIBS) |
| 122 | |
| 123 | # Build and run tests |
| 124 | .PHONY: test |
| 125 | test: $(TEST_TARGETS) |
| 126 | @echo "Running tests..." |
| 127 | @for test in $(TEST_TARGETS); do \ |
| 128 | echo "Running $$test..."; \ |
| 129 | $$test || exit 1; \ |
| 130 | done |
| 131 | @echo "All tests passed!" |
| 132 | |
| 133 | # Static analysis |
| 134 | .PHONY: analyze |
| 135 | analyze: |
| 136 | @echo "Running static analysis..." |
| 137 | @command -v cppcheck >/dev/null 2>&1 && \ |
| 138 | cppcheck --enable=all --std=c11 --suppress=missingIncludeSystem $(SRCDIR) || \ |
| 139 | echo "cppcheck not found - skipping static analysis" |
| 140 | |
| 141 | # Code formatting |
| 142 | .PHONY: format |
| 143 | format: |
| 144 | @echo "Formatting code..." |
| 145 | @command -v clang-format >/dev/null 2>&1 && \ |
| 146 | clang-format -i $(SOURCES) $(HEADERS) || \ |
| 147 | echo "clang-format not found - skipping formatting" |
| 148 | |
| 149 | # Security scan |
| 150 | .PHONY: security-scan |
| 151 | security-scan: |
| 152 | @echo "Running security scan..." |
| 153 | @command -v flawfinder >/dev/null 2>&1 && \ |
| 154 | flawfinder $(SRCDIR) || \ |
| 155 | echo "flawfinder not found - skipping security scan" |
| 156 | |
| 157 | # Memory check (requires valgrind) |
| 158 | .PHONY: memcheck |
| 159 | memcheck: $(BINDIR)/$(TARGET) |
| 160 | @echo "Running memory check..." |
| 161 | @command -v valgrind >/dev/null 2>&1 && \ |
| 162 | valgrind --tool=memcheck --leak-check=full --show-leak-kinds=all \ |
| 163 | --track-origins=yes --verbose --log-file=valgrind.log \ |
| 164 | $(BINDIR)/$(TARGET) --help || \ |
| 165 | echo "valgrind not found - skipping memory check" |
| 166 | |
| 167 | # Documentation generation |
| 168 | .PHONY: docs |
| 169 | docs: |
| 170 | @echo "Generating documentation..." |
| 171 | @mkdir -p $(DOCDIR) |
| 172 | @command -v doxygen >/dev/null 2>&1 && \ |
| 173 | doxygen Doxyfile || \ |
| 174 | echo "doxygen not found - skipping documentation generation" |
| 175 | |
| 176 | # Clean targets |
| 177 | .PHONY: clean |
| 178 | clean: |
| 179 | @echo "Cleaning build files..." |
| 180 | rm -rf $(BUILDDIR) |
| 181 | rm -f valgrind.log |
| 182 | rm -f *.core core.* |
| 183 | |
| 184 | .PHONY: distclean |
| 185 | distclean: clean |
| 186 | @echo "Cleaning all generated files..." |
| 187 | rm -rf $(DOCDIR) |
| 188 | |
| 189 | # Development helpers |
| 190 | .PHONY: debug |
| 191 | debug: BUILD_TYPE=debug |
| 192 | debug: all |
| 193 | |
| 194 | .PHONY: release |
| 195 | release: BUILD_TYPE=release |
| 196 | release: all |
| 197 | |
| 198 | # Quick development cycle |
| 199 | .PHONY: dev |
| 200 | dev: clean debug test |
| 201 | |
| 202 | # Show build information |
| 203 | .PHONY: info |
| 204 | info: |
| 205 | @echo "Project: $(PROJECT_NAME) v$(VERSION)" |
| 206 | @echo "Target: $(TARGET)" |
| 207 | @echo "Build type: $(BUILD_TYPE)" |
| 208 | @echo "Compiler: $(CC)" |
| 209 | @echo "CFLAGS: $(CFLAGS)" |
| 210 | @echo "Sources: $(SOURCES)" |
| 211 | @echo "Objects: $(OBJECTS)" |
| 212 | |
| 213 | # Dependencies check |
| 214 | .PHONY: deps |
| 215 | deps: |
| 216 | @echo "Checking dependencies..." |
| 217 | @echo "Required tools:" |
| 218 | @command -v $(CC) >/dev/null 2>&1 && echo " $(CC)" || echo " $(CC) - REQUIRED" |
| 219 | @command -v make >/dev/null 2>&1 && echo " make" || echo " make - REQUIRED" |
| 220 | @echo "Optional tools:" |
| 221 | @command -v cppcheck >/dev/null 2>&1 && echo " cppcheck" || echo " cppcheck - for static analysis" |
| 222 | @command -v clang-format >/dev/null 2>&1 && echo " clang-format" || echo " clang-format - for formatting" |
| 223 | @command -v valgrind >/dev/null 2>&1 && echo " valgrind" || echo " valgrind - for memory checking" |
| 224 | @command -v flawfinder >/dev/null 2>&1 && echo " flawfinder" || echo " flawfinder - for security scanning" |
| 225 | @command -v doxygen >/dev/null 2>&1 && echo " doxygen" || echo " doxygen - for documentation" |
| 226 | |
| 227 | # Help target |
| 228 | .PHONY: help |
| 229 | help: |
| 230 | @echo "$(PROJECT_NAME) Makefile" |
| 231 | @echo "" |
| 232 | @echo "Targets:" |
| 233 | @echo " all Build the project (default)" |
| 234 | @echo " debug Build debug version" |
| 235 | @echo " release Build release version" |
| 236 | @echo " test Build and run tests" |
| 237 | @echo " install Install to system" |
| 238 | @echo " uninstall Remove from system" |
| 239 | @echo " clean Remove build files" |
| 240 | @echo " distclean Remove all generated files" |
| 241 | @echo " format Format source code" |
| 242 | @echo " analyze Run static analysis" |
| 243 | @echo " security-scan Run security scan" |
| 244 | @echo " memcheck Run memory checker" |
| 245 | @echo " docs Generate documentation" |
| 246 | @echo " deps Check dependencies" |
| 247 | @echo " info Show build information" |
| 248 | @echo " dev Quick development cycle (clean + debug + test)" |
| 249 | @echo " dist Create distribution tarball" |
| 250 | @echo " rpm Build RPM package" |
| 251 | @echo " help Show this help" |
| 252 | @echo "" |
| 253 | @echo "Variables:" |
| 254 | @echo " BUILD_TYPE debug (default) or release" |
| 255 | @echo " CC Compiler (default: gcc)" |
| 256 | @echo " DESTDIR Installation prefix" |
| 257 | |
| 258 | # RPM package building |
| 259 | PACKAGE = gitswitcher |
| 260 | RPM_VERSION = $(VERSION) |
| 261 | |
| 262 | .PHONY: dist rpm |
| 263 | dist: clean |
| 264 | @echo "Creating distribution tarball..." |
| 265 | tar czf $(PACKAGE)-$(RPM_VERSION).tar.gz \ |
| 266 | --exclude='.git*' \ |
| 267 | --exclude='*.o' \ |
| 268 | --exclude='build' \ |
| 269 | --exclude='*.core' \ |
| 270 | --exclude='valgrind.log' \ |
| 271 | --transform 's,^,$(PACKAGE)-$(RPM_VERSION)/,' \ |
| 272 | src/ *.md Makefile $(PACKAGE).spec |
| 273 | |
| 274 | rpm: dist |
| 275 | @echo "Building RPM package..." |
| 276 | @command -v rpmbuild >/dev/null 2>&1 || (echo "rpmbuild not available - install rpm-build package" && exit 1) |
| 277 | mkdir -p ~/rpmbuild/{BUILD,RPMS,SOURCES,SPECS,SRPMS} |
| 278 | cp $(PACKAGE)-$(RPM_VERSION).tar.gz ~/rpmbuild/SOURCES/ |
| 279 | cp $(PACKAGE).spec ~/rpmbuild/SPECS/ |
| 280 | rpmbuild -ba ~/rpmbuild/SPECS/$(PACKAGE).spec |
| 281 | @echo "RPM packages created in ~/rpmbuild/RPMS/" |
| 282 | |
| 283 | # Prevent make from removing intermediate files |
| 284 | .SECONDARY: $(OBJECTS) $(TEST_OBJECTS) |