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