.PHONY: all clean test install uninstall doc lint format check coverage help
CC = gcc
CXX = g++
CFLAGS = -Wall -Wextra -pedantic -O2 -std=c11
CXXFLAGS = -Wall -Wextra -pedantic -O2 -std=c++17
LDFLAGS = -lpthread -lm
PREFIX = /usr/local
SRC_DIR = src
BUILD_DIR = build
OBJ_DIR = $(BUILD_DIR)/obj
BIN_DIR = $(BUILD_DIR)/bin
DOC_DIR = docs
TEST_DIR = tests
C_SOURCES = $(wildcard $(SRC_DIR)/*.c)
CXX_SOURCES = $(wildcard $(SRC_DIR)/*.cpp)
C_OBJECTS = $(patsubst $(SRC_DIR)/%.c,$(OBJ_DIR)/%.o,$(C_SOURCES))
CXX_OBJECTS = $(patsubst $(SRC_DIR)/%.cpp,$(OBJ_DIR)/%.o,$(CXX_SOURCES))
OBJECTS = $(C_OBJECTS) $(CXX_OBJECTS)
TEST_SOURCES = $(wildcard $(TEST_DIR)/*.c)
TEST_OBJECTS = $(patsubst $(TEST_DIR)/%.c,$(OBJ_DIR)/test_%.o,$(TEST_SOURCES))
TEST_BINS = $(patsubst $(TEST_DIR)/%.c,$(BIN_DIR)/test_%,$(TEST_SOURCES))
TARGET = $(BIN_DIR)/myapp
VERSION = $(shell git describe --tags --always 2>/dev/null || echo "unknown")
BUILD_DATE = $(shell date +%Y-%m-%d)
all: $(TARGET)
$(OBJ_DIR) $(BIN_DIR):
mkdir -p $@
$(TARGET): $(OBJECTS) | $(BIN_DIR)
$(CXX) $(CXXFLAGS) -o $@ $^ $(LDFLAGS)
$(OBJ_DIR)/%.o: $(SRC_DIR)/%.c | $(OBJ_DIR)
$(CC) $(CFLAGS) -DVERSION=\"$(VERSION)\" -c $< -o $@
$(OBJ_DIR)/%.o: $(SRC_DIR)/%.cpp | $(OBJ_DIR)
$(CXX) $(CXXFLAGS) -DVERSION=\"$(VERSION)\" -c $< -o $@
$(OBJ_DIR)/test_%.o: $(TEST_DIR)/%.c | $(OBJ_DIR)
$(CC) $(CFLAGS) -I$(SRC_DIR) -c $< -o $@
$(BIN_DIR)/test_%: $(OBJ_DIR)/test_%.o $(filter-out $(OBJ_DIR)/main.o,$(OBJECTS)) | $(BIN_DIR)
$(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)
test: $(TEST_BINS)
@echo "Running tests..."
@for test in $(TEST_BINS); do \
echo "Running $$test"; \
$$test || exit 1; \
done
@echo "All tests passed!"
install: $(TARGET)
mkdir -p $(PREFIX)/bin
cp $(TARGET) $(PREFIX)/bin/
chmod 755 $(PREFIX)/bin/$(notdir $(TARGET))
uninstall:
rm -f $(PREFIX)/bin/$(notdir $(TARGET))
doc:
@if command -v doxygen >/dev/null 2>&1; then \
doxygen Doxyfile; \
else \
echo "Error: doxygen not found"; \
exit 1; \
fi
lint:
@if command -v cppcheck >/dev/null 2>&1; then \
cppcheck --enable=all --suppress=missingIncludeSystem $(SRC_DIR); \
else \
echo "Warning: cppcheck not found, skipping lint"; \
fi
format:
@if command -v clang-format >/dev/null 2>&1; then \
find $(SRC_DIR) -name '*.c' -o -name '*.cpp' -o -name '*.h' | xargs clang-format -i; \
else \
echo "Warning: clang-format not found, skipping format"; \
fi
check: lint
@echo "Static analysis complete"
coverage: CFLAGS += --coverage
coverage: CXXFLAGS += --coverage
coverage: LDFLAGS += --coverage
coverage: clean test
@if command -v gcov >/dev/null 2>&1; then \
gcov $(SRC_DIR)/*.c $(SRC_DIR)/*.cpp; \
else \
echo "Error: gcov not found"; \
exit 1; \
fi
clean:
rm -rf $(BUILD_DIR)
rm -f *.gcov *.gcda *.gcno
distclean: clean
rm -rf $(DOC_DIR)/html $(DOC_DIR)/latex
help:
@echo "Available targets:"
@echo " all - Build the application (default)"
@echo " test - Build and run tests"
@echo " install - Install to $(PREFIX)"
@echo " uninstall - Remove from $(PREFIX)"
@echo " doc - Generate documentation"
@echo " lint - Run static analysis"
@echo " format - Format source code"
@echo " check - Run checks (lint)"
@echo " coverage - Generate code coverage report"
@echo " clean - Remove build artifacts"
@echo " distclean - Remove everything including docs"
@echo ""
@echo "Version: $(VERSION)"
@echo "Build Date: $(BUILD_DATE)"
show-vars:
@echo "CC = $(CC)"
@echo "CXX = $(CXX)"
@echo "CFLAGS = $(CFLAGS)"
@echo "CXXFLAGS = $(CXXFLAGS)"
@echo "LDFLAGS = $(LDFLAGS)"
@echo "C_SOURCES = $(C_SOURCES)"
@echo "CXX_SOURCES = $(CXX_SOURCES)"
@echo "OBJECTS = $(OBJECTS)"
@echo "TARGET = $(TARGET)"
@echo "VERSION = $(VERSION)"
.SUFFIXES:
.SUFFIXES: .c .cpp .o
.DELETE_ON_ERROR:
-include $(OBJECTS:.o=.d)