Last active
April 5, 2017 14:37
-
-
Save BrainStone/73963be47810e2d228fffd451de66aae to your computer and use it in GitHub Desktop.
Super generic Makefile for C++
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#### PROJECT SETTINGS #### | |
# Must be either executable, dynamic_library or static_library | |
# Defaults to executable if a wrong value is used! | |
BIN_TYPE = executable | |
# The name of the executable to be created | |
# The extension gets appended automatically based on the type | |
BIN_NAME := prog | |
# The name of the documentation file | |
DOC_NAME := PROG-doc | |
# Compiler used | |
CXX ?= g++ | |
# Extension of source files used in the project | |
SRC_EXT = cpp | |
# Extension of header files used in the project | |
HEAD_EXT = hpp | |
# Path to the source directory, relative to the makefile | |
SRC_PATH = src | |
# Space-separated pkg-config libraries used by this project | |
LIBS = | |
# General compiler flags | |
COMPILE_FLAGS = -Wall -Wextra -Wpedantic | |
# Additional release-specific flags | |
RCOMPILE_FLAGS = -D__NDEBUG__ -O2 -g0 | |
# Additional debug-specific flags | |
DCOMPILE_FLAGS = -D__DEBUG__ -Og -g3 | |
# Add additional include paths | |
INCLUDES = -I $(SRC_PATH) | |
# General linker settings | |
LINK_FLAGS = | |
# Additional release-specific linker settings | |
RLINK_FLAGS = | |
# Additional debug-specific linker settings | |
DLINK_FLAGS = | |
# Destination directory, like a jail or mounted system | |
DESTDIR = / | |
# Install path (bin/ or lib/ and include/ are appended automatically depending on the type) | |
INSTALL_PREFIX = usr/local | |
#### END PROJECT SETTINGS #### | |
# Generally should not need to edit below this line | |
# Obtains the OS type, either 'Darwin' (OS X) or 'Linux' | |
UNAME_S:=$(shell uname -s) | |
# Adds correct file extension | |
ifeq ($(OS), Windows_NT) | |
ifeq ($(BIN_TYPE), static_library) | |
BIN_NAME := $(BIN_NAME).lib | |
else ifeq ($(BIN_TYPE), dynamic_library) | |
BIN_NAME := $(BIN_NAME).dll | |
COMPILE_FLAGS += -fPIC | |
else | |
BIN_NAME := $(BIN_NAME).exe | |
BIN_TYPE := executable | |
endif | |
else | |
ifeq ($(BIN_TYPE), static_library) | |
BIN_NAME := lib$(BIN_NAME).a | |
else ifeq ($(BIN_TYPE), dynamic_library) | |
BIN_NAME := lib$(BIN_NAME).so | |
COMPILE_FLAGS += -fPIC | |
else | |
BIN_TYPE := executable | |
endif | |
endif | |
# Function used to check variables. Use on the command line: | |
# make print-VARNAME | |
# Useful for debugging and adding features | |
print-%: ; @echo $*=$($*) | |
# Shell used in this makefile | |
# bash is used for 'echo -en' | |
SHELL = /bin/bash | |
# Clear built-in rules | |
.SUFFIXES: | |
# Programs for installation | |
INSTALL = install | |
INSTALL_PROGRAM = $(INSTALL) | |
INSTALL_DATA = $(INSTALL) -m 644 | |
# Append pkg-config specific libraries if need be | |
ifneq ($(LIBS),) | |
COMPILE_FLAGS += $(shell pkg-config --cflags $(LIBS)) | |
LINK_FLAGS += $(shell pkg-config --libs $(LIBS)) | |
endif | |
# Verbose option, to output compile and link commands | |
export V := false | |
export CMD_PREFIX := @ | |
ifeq ($(V),true) | |
CMD_PREFIX := | |
endif | |
# Combine compiler and linker flags | |
release: export CXXFLAGS := $(CXXFLAGS) $(COMPILE_FLAGS) $(RCOMPILE_FLAGS) | |
release: export LDFLAGS := $(LDFLAGS) $(LINK_FLAGS) $(RLINK_FLAGS) | |
debug: export CXXFLAGS := $(CXXFLAGS) $(COMPILE_FLAGS) $(DCOMPILE_FLAGS) | |
debug: export LDFLAGS := $(LDFLAGS) $(LINK_FLAGS) $(DLINK_FLAGS) | |
# Build and output paths | |
release: export BUILD_PATH := build/release | |
release: export BIN_PATH := bin/release | |
debug: export BUILD_PATH := build/debug | |
debug: export BIN_PATH := bin/debug | |
install: export BIN_PATH := bin/release | |
# Find all source files in the source directory, sorted by most | |
# recently modified | |
ifeq ($(UNAME_S),Darwin) | |
SOURCES = $(shell find $(SRC_PATH) -name '*.$(SRC_EXT)' | sort -k 1nr | cut -f2-) | |
else | |
SOURCES = $(shell find $(SRC_PATH) -name '*.$(SRC_EXT)' -printf '%T@\t%p\n' \ | |
| sort -k 1nr | cut -f2-) | |
endif | |
# Find all header files in the source directory | |
HEADERS = $(shell (cd $(SRC_PATH); find . -name '*.$(HEAD_EXT)')) | |
# fallback in case the above fails | |
rwildcard = $(foreach d, $(wildcard $1*), $(call rwildcard,$d/,$2) \ | |
$(filter $(subst *,%,$2), $d)) | |
ifeq ($(SOURCES),) | |
SOURCES := $(call rwildcard, $(SRC_PATH), *.$(SRC_EXT)) | |
endif | |
# Set the object file names, with the source directory stripped | |
# from the path, and the build path prepended in its place | |
OBJECTS = $(SOURCES:$(SRC_PATH)/%.$(SRC_EXT)=$(BUILD_PATH)/%.o) | |
# Set the dependency files that will be used to add header dependencies | |
DEPS = $(OBJECTS:.o=.d) | |
# Macros for timing compilation | |
ifeq ($(UNAME_S),Darwin) | |
CUR_TIME = awk 'BEGIN{srand(); print srand()}' | |
TIME_FILE = $(dir $@).$(notdir $@)_time | |
START_TIME = $(CUR_TIME) > $(TIME_FILE) | |
END_TIME = read st < $(TIME_FILE) ; \ | |
$(RM) $(TIME_FILE) ; \ | |
st=$$((`$(CUR_TIME)` - $$st)) ; \ | |
echo $$st | |
else | |
TIME_FILE = $(dir $@).$(notdir $@)_time | |
START_TIME = date '+%s' > $(TIME_FILE) | |
END_TIME = read st < $(TIME_FILE) ; \ | |
$(RM) $(TIME_FILE) ; \ | |
st=$$((`date '+%s'` - $$st - 86400)) ; \ | |
echo `date -u -d @$$st '+%H:%M:%S'` | |
endif | |
# Version macros | |
# Comment/remove this section to remove versioning | |
USE_VERSION := false | |
# If this isn't a git repo or the repo has no tags, git describe will return non-zero | |
ifeq ($(shell git describe > /dev/null 2>&1 ; echo $$?), 0) | |
USE_VERSION := true | |
VERSION := $(shell git describe --tags --long --dirty="-DEV-$(shell git diff 2> /dev/null | sha1sum | cut -c-7)" --always | \ | |
sed 's/v\([0-9]*\)\.\([0-9]*\)\.\([0-9]*\)-\?.*-\([0-9]*\)-g\(.*\)/\1 \2 \3 \4 \5/g') | |
VERSION_MAJOR := $(word 1, $(VERSION)) | |
VERSION_MINOR := $(word 2, $(VERSION)) | |
VERSION_PATCH := $(word 3, $(VERSION)) | |
VERSION_REVISION := $(word 4, $(VERSION)) | |
VERSION_HASH := $(word 5, $(VERSION)) | |
VERSION_STRING_SHORT := \ | |
"$(VERSION_MAJOR).$(VERSION_MINOR).$(VERSION_PATCH).$(VERSION_REVISION)" | |
VERSION_STRING := \ | |
"$(VERSION_MAJOR).$(VERSION_MINOR).$(VERSION_PATCH).$(VERSION_REVISION) ($(VERSION_HASH))" | |
override CXXFLAGS := $(CXXFLAGS) \ | |
-D VERSION_MAJOR=$(VERSION_MAJOR) \ | |
-D VERSION_MINOR=$(VERSION_MINOR) \ | |
-D VERSION_PATCH=$(VERSION_PATCH) \ | |
-D VERSION_REVISION=$(VERSION_REVISION) \ | |
-D VERSION_HASH=\"$(VERSION_HASH)\" \ | |
-D VERSION_STRING_SHORT=\"$(VERSION_STRING_SHORT)\" \ | |
-D VERSION_STRING=\"$(VERSION_STRING)\" | |
endif | |
# Standard, non-optimized release build | |
.PHONY: release | |
release: dirs | |
ifeq ($(USE_VERSION), true) | |
@echo "Beginning release build v"$(VERSION_STRING) | |
else | |
@echo "Beginning release build" | |
endif | |
@$(START_TIME) | |
@$(MAKE) all --no-print-directory | |
@echo -n "Total build time: " | |
@$(END_TIME) | |
# Debug build for gdb debugging | |
.PHONY: debug | |
debug: dirs | |
ifeq ($(USE_VERSION), true) | |
@echo "Beginning debug build v"$(VERSION_STRING) | |
else | |
@echo "Beginning debug build" | |
endif | |
@$(START_TIME) | |
@$(MAKE) all --no-print-directory | |
@echo -n "Total build time: " | |
@$(END_TIME) | |
# Create the directories used in the build | |
.PHONY: dirs | |
dirs: | |
@echo "Creating directories" | |
@mkdir -p $(dir $(OBJECTS)) | |
@mkdir -p $(BIN_PATH) | |
# Installs to the set path | |
.PHONY: install | |
install: | |
ifeq ($(BIN_TYPE), executable) | |
@echo "Installing to $(DESTDIR)$(INSTALL_PREFIX)/bin" | |
@$(INSTALL_PROGRAM) $(BIN_PATH)/$(BIN_NAME) $(DESTDIR)$(INSTALL_PREFIX)/bin | |
else | |
@echo "Installing library to $(DESTDIR)$(INSTALL_PREFIX)/lib" | |
@$(INSTALL_PROGRAM) $(BIN_PATH)/$(BIN_NAME) $(DESTDIR)$(INSTALL_PREFIX)/lib | |
@echo "Installing headers to $(DESTDIR)$(INSTALL_PREFIX)/include" | |
@(cd $(SRC_PATH); for header in $(HEADERS); do dir="$(DESTDIR)$(INSTALL_PREFIX)/include/$$(dirname "$$header")"; mkdir -p "$$dir"; $(INSTALL_DATA) "$$header" "$$dir";done) | |
ifeq ($(BIN_TYPE), dynamic_library) | |
@ldconfig | |
endif | |
endif | |
# Uninstalls the program | |
.PHONY: uninstall | |
uninstall: | |
ifeq ($(BIN_TYPE), executable) | |
@echo "Removing $(DESTDIR)$(INSTALL_PREFIX)/bin/$(BIN_NAME)" | |
@$(RM) $(DESTDIR)$(INSTALL_PREFIX)/bin/$(BIN_NAME) | |
else | |
@echo "Removing library $(DESTDIR)$(INSTALL_PREFIX)/lib/$(BIN_NAME)" | |
@$(RM) $(DESTDIR)$(INSTALL_PREFIX)/lib/$(BIN_NAME) | |
@echo "Removing headers from $(DESTDIR)$(INSTALL_PREFIX)/include" | |
@(cd $(DESTDIR)$(INSTALL_PREFIX)/include; $(RM) $(HEADERS)) | |
ifeq ($(BIN_TYPE), dynamic_library) | |
@ldconfig | |
endif | |
endif | |
# Removes all build files | |
.PHONY: clean | |
clean: | |
@echo "Deleting $(BIN_NAME) symlink" | |
@$(RM) $(BIN_NAME) | |
@echo "Deleting directories" | |
@$(RM) -r build | |
@$(RM) -r bin | |
# Main rule, checks the executable and symlinks to the output | |
all: $(BIN_PATH)/$(BIN_NAME) | |
@echo "Making symlink: $(BIN_NAME) -> $<" | |
@$(RM) $(BIN_NAME) | |
@ln -s $(BIN_PATH)/$(BIN_NAME) $(BIN_NAME) | |
# Link the executable | |
$(BIN_PATH)/$(BIN_NAME): $(OBJECTS) | |
@echo "Linking: $@" | |
@$(START_TIME) | |
ifeq ($(BIN_TYPE), executable) | |
$(CMD_PREFIX)$(CXX) $(OBJECTS) $(LDFLAGS) -o $@ | |
else ifeq ($(BIN_TYPE), static_library) | |
$(CMD_PREFIX)$(AR) rcs $@ $(OBJECTS) | |
else ifeq ($(BIN_TYPE), dynamic_library) | |
$(CMD_PREFIX)$(CXX) $(OBJECTS) -shared $(LDFLAGS) -o $@ | |
endif | |
@echo -en "\t Link time: " | |
@$(END_TIME) | |
# Add dependency files, if they exist | |
-include $(DEPS) | |
# Source file rules | |
# After the first compilation they will be joined with the rules from the | |
# dependency files to provide header dependencies | |
$(BUILD_PATH)/%.o: $(SRC_PATH)/%.$(SRC_EXT) | |
@echo "Compiling: $< -> $@" | |
@$(START_TIME) | |
$(CMD_PREFIX)$(CXX) $(CXXFLAGS) $(INCLUDES) -MP -MMD -c $< -o $@ | |
@echo -en "\t Compile time: " | |
@$(END_TIME) | |
# Generate Doxygen documentation | |
# Doesn't exits if doxygen or the Doxyfile doesn't exist | |
ifneq ("$(wildcard doc/Doxyfile)", "") | |
ifneq ("$(shell which doxygen)", "") | |
.PHONY: doc | |
doc: | |
@${RM} "$(DOC_NAME).*" | |
@doxygen doc/Doxyfile | |
@(cd doc/gen/html; zip -r9 ../../../"$(DOC_NAME).zip" *) | |
ifneq ("$(shell which pdflatex)", "") | |
@(cd doc/gen/latex; make) | |
@cp doc/gen/latex/refman.pdf "$(DOC_NAME).pdf" | |
endif | |
# Clear Doxygen documentation | |
.PHONY: cleandoc | |
cleandoc: | |
@echo "Deleting: $(DOC_NAME).*" | |
@${RM} -r doc/gen "$(DOC_NAME).*" | |
endif | |
endif |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment