Makefile · 10101 bytes Raw Blame History
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)