#
# Makefile
# Copyright 2019
# by John Ryland
#
# Allow variables to be expanded again on a second pass
.SECONDEXPANSION:
BASENAME = $(shell basename $(PWD))
PLATFORM = $(shell uname -s)
ARCH = $(shell uname -m)
COMPILER = $(shell c++ --version | tr [a-z] [A-Z] | grep -o 'CLANG\|GCC' | head -n 1)
COMPILER_VER = $(shell c++ --version | grep -o "[0-9]*\.[0-9]*" | head -n 1)
PROJECT_FILE = $(BASENAME).pro
CXX = g++
CXX_FLAGS = -Wall -Wextra -std=c++17 -I. $(patsubst %, -I%, $(INCLUDE_PATHS))
DEBUG_FLAGS = -O0 -g --coverage
# DEBUG_FLAGS = -O0 -g -pg
RELEASE_FLAGS = -O3 -DNDEBUG
TEST_FLAGS = -DUNIT_TEST
SHARED_CXX_FLAGS = -fpic
SHARED_LD_FLAGS = -fpic -shared
STATIC_CXX_FLAGS = -fno-pic -static -DSTATIC
STATIC_LD_FLAGS = -fno-pic
TEST_LD_FLAGS = --coverage
#
# TODO: Refactor the dgb/rel and shared/static permutations in to call functions with args to expand these
#
define AddCode
$(1)_dbg_shared_OBJECTS = $(patsubst %.cpp, build/.objs/dbg/shared/%.o, $(2))
$(1)_dbg_static_OBJECTS = $(patsubst %.cpp, build/.objs/dbg/static/%.o, $(2))
$(1)_rel_shared_OBJECTS = $(patsubst %.cpp, build/.objs/rel/shared/%.o, $(2))
$(1)_rel_static_OBJECTS = $(patsubst %.cpp, build/.objs/rel/static/%.o, $(2))
# OBJECTS += $$($(1)_dbg_shared_OBJECTS) $$($(1)_dbg_static_OBJECTS) $$($(1)_rel_shared_OBJECTS) $$($(1)_rel_static_OBJECTS)
dbg_shared_OBJECTS += $$($(1)_dbg_shared_OBJECTS)
dbg_static_OBJECTS += $$($(1)_dbg_static_OBJECTS)
rel_shared_OBJECTS += $$($(1)_rel_shared_OBJECTS)
rel_static_OBJECTS += $$($(1)_rel_static_OBJECTS)
SOURCES += $(2)
endef
# Libs
define AddLibraryInternal
$(eval $(call AddCode,$(1),$(2)))
dbg_shared_LIBS += build/lib/lib$(1)_d.so
dbg_static_LIBS += build/lib/lib$(1)_d.a
rel_shared_LIBS += build/lib/lib$(1).so
rel_static_LIBS += build/lib/lib$(1).a
LIBS += build/lib/lib$(1)_d.so build/lib/lib$(1)_d.a build/lib/lib$(1).so build/lib/lib$(1).a
TARGETS += $(1)
$(1)_SOURCES = $(2)
build/lib/lib$(1)_d.so: $$($(1)_dbg_shared_OBJECTS)
$$(MAKE_TARGET_DIR)
$$(CXX) $$(CXX_FLAGS) $$(DEBUG_FLAGS) $$(SHARED_LD_FLAGS) $$^ -o $$@
build/lib/lib$(1)_d.a: $$($(1)_dbg_static_OBJECTS)
$$(MAKE_TARGET_DIR)
# $$(CXX) $$(CXX_FLAGS) $$(DEBUG_FLAGS) $$(STATIC_LD_FLAGS) $$^ -o $$@
ar rcs $$@ $$^
build/lib/lib$(1).so: $$($(1)_rel_shared_OBJECTS)
$$(MAKE_TARGET_DIR)
$$(CXX) $$(CXX_FLAGS) $$(RELEASE_FLAGS) $$(SHARED_LD_FLAGS) $$^ -o $$@
strip -S $$@
build/lib/lib$(1).a: $$($(1)_rel_static_OBJECTS)
$$(MAKE_TARGET_DIR)
# $$(CXX) $$(CXX_FLAGS) $$(RELEASE_FLAGS) $$(STATIC_LD_FLAGS) $$^ -o $$@
ar rcs $$@ $$^
strip -S $$@
endef
# AddLibrary function which can be called to add a library target to a project
AddLibrary = $(eval $(call AddLibraryInternal,$(1),$(2)))
# Plugins
define AddPluginInternal
$(eval $(call AddCode,$(1),$(2)))
dbg_shared_PLUGINS += build/plugins/$(1)_d.so
dbg_static_PLUGINS += build/plugins/$(1)_d.a
rel_shared_PLUGINS += build/plugins/$(1).so
rel_static_PLUGINS += build/plugins/$(1).a
PLUGINS += build/plugins/$(1)_d.so build/plugins/$(1)_d.a build/plugins/$(1).so build/plugins/$(1).a
TARGETS += $(1)
$(1)_SOURCES = $(2)
build/plugins/$(1)_d.so: $$($(1)_dbg_shared_OBJECTS) $$$$(dbg_shared_LIBS)
$$(MAKE_TARGET_DIR)
$$(CXX) $$(CXX_FLAGS) $$(DEBUG_FLAGS) $$(SHARED_LD_FLAGS) $$^ -o $$@
build/plugins/$(1)_d.a: $$($(1)_dbg_static_OBJECTS) $$$$(dbg_static_LIBS)
$$(MAKE_TARGET_DIR)
# $$(CXX) $$(CXX_FLAGS) $$(DEBUG_FLAGS) $$(STATIC_LD_FLAGS) $$^ -o $$@
ar rcs $$@ $$^
build/plugins/$(1).so: $$($(1)_rel_shared_OBJECTS) $$$$(rel_shared_LIBS)
$$(MAKE_TARGET_DIR)
$$(CXX) $$(CXX_FLAGS) $$(RELEASE_FLAGS) $$(SHARED_LD_FLAGS) $$^ -o $$@
strip -S $$@
build/plugins/$(1).a: $$($(1)_rel_static_OBJECTS) $$$$(rel_static_LIBS)
$$(MAKE_TARGET_DIR)
# $$(CXX) $$(CXX_FLAGS) $$(RELEASE_FLAGS) $$(STATIC_LD_FLAGS) $$^ -o $$@
ar rcs $$@ $$^
strip -S $$@
endef
# AddPlugin function which can be called to add a plugin target to a project
AddPlugin = $(eval $(call AddPluginInternal,$(1),$(2)))
# Exes
define AddExecutableInternal
$(eval $(call AddCode,$(1),$(2)))
dbg_shared_EXES += build/bin/$(1)_d
dbg_static_EXES += build/bin/$(1)_static_d
rel_shared_EXES += build/bin/$(1)
rel_static_EXES += build/bin/$(1)_static
EXES += build/bin/$(1)_d build/bin/$(1)_static_d build/bin/$(1) build/bin/$(1)_static
TARGETS += $(1)
$(1)_SOURCES = $(2)
build/bin/$(1)_d: $$($(1)_dbg_shared_OBJECTS) $$$$(dbg_shared_LIBS)
$$(MAKE_TARGET_DIR)
$$(CXX) $$(CXX_FLAGS) $$(DEBUG_FLAGS) $$^ -o $$@
build/bin/$(1)_static_d: $$($(1)_dbg_static_OBJECTS) $$$$(dbg_static_LIBS) $$$$(dbg_static_PLUGINS)
$$(MAKE_TARGET_DIR)
$$(CXX) $$(CXX_FLAGS) $$(DEBUG_FLAGS) $$(STATIC_LD_FLAGS) $$^ -o $$@
build/bin/$(1): $$($(1)_rel_shared_OBJECTS) $$$$(rel_shared_LIBS)
$$(MAKE_TARGET_DIR)
$$(CXX) $$(CXX_FLAGS) $$(RELEASE_FLAGS) $$^ -o $$@
strip -S $$@
build/bin/$(1)_static: $$($(1)_rel_static_OBJECTS) $$$$(rel_static_LIBS) $$$$(rel_static_PLUGINS)
$$(MAKE_TARGET_DIR)
$$(CXX) $$(CXX_FLAGS) $$(RELEASE_FLAGS) $$(STATIC_LD_FLAGS) $$^ -o $$@
strip -S $$@
endef
# AddExecutable function which can be called to add an executable target to a project
AddExecutable = $(eval $(call AddExecutableInternal,$(1),$(2)))
# Test Code
define AddTestCode
$(1)_dbg_tests_OBJECTS = $(patsubst %.cpp, build/.objs/dbg/tests/%.o, $(2))
$(1)_rel_tests_OBJECTS = $(patsubst %.cpp, build/.objs/rel/tests/%.o, $(2))
dbg_tests_OBJECTS += $$($(1)_dbg_tests_OBJECTS)
rel_tests_OBJECTS += $$($(1)_rel_tests_OBJECTS)
SOURCES += $(2)
endef
# Tests
define AddTestInternal
$(eval $(call AddTestCode,$(1),$(2)))
# Debug
TESTS += build/tests/$(1)_d.log
build/tests/$(1)_d: $$($(1)_dbg_tests_OBJECTS) $$$$(dbg_static_LIBS)
$$(MAKE_TARGET_DIR)
$$(CXX) $$(CXX_FLAGS) $$(DEBUG_FLAGS) $$(STATIC_LD_FLAGS) $$(TEST_LD_FLAGS) $$^ -o $$@
build/tests/$(1)_d.log: build/tests/$(1)_d
# build/tests/$(1)_d | tee $$@
build/tests/$(1)_d > $$@
# Release
TESTS += build/tests/$(1).log
build/tests/$(1): $$($(1)_rel_tests_OBJECTS) $$$$(rel_static_LIBS)
$$(MAKE_TARGET_DIR)
$$(CXX) $$(CXX_FLAGS) $$(RELEASE_FLAGS) $$(STATIC_LD_FLAGS) $$(TEST_LD_FLAGS) $$^ -o $$@
build/tests/$(1).log: build/tests/$(1)
# build/tests/$(1) | tee $$@
build/tests/$(1) > $$@
endef
# AddTest function which can be called to add a test to a project
AddTest = $(eval $(call AddTestInternal,$(1),$(2)))
# Targets
all: $$(TESTS) $$(PLUGINS) $$(LIBS) $$(EXES) .tags Docs/html/index.html lldb-nvim.json
@cat build/tests/*.log | grep "FAIL"
@grep -n "TO""DO:" Makefile $(SOURCES) *.h
@echo "Successful Build"
clean:
rm -rf build
# ctags
.tags:
# $$(TESTS) $$(PLUGINS) $$(LIBS) $$(EXES)
@echo "Rebuilding tags"
@cscope -q -R -b $(SOURCES) *.h *.hpp
@ctags -f $@ --tag-relative=yes --sort=yes --c++-kinds=+p --fields=+iaS --extras=+q $(SOURCES) *.h *.hpp
# Docs
Docs/html/index.html: Docs/Doxyfile $$(SOURCES) Docs
doxygen Docs/Doxyfile 2>&1 | sed 's|${PWD}/\(.*\)|\1|' > build/doxygen.log
# Project
-include $(PROJECT_FILE)
MAKE_TARGET_DIR = @mkdir -p `dirname $@`
# TODO: It should be possible to combine the depends generation and compilation steps (gcc can output both, -MMD)
dbg_shared_DEPENDS = $(patsubst build/.objs/dbg/shared/%.o, build/.deps/dbg/shared/%.cpp.d, $(dbg_shared_OBJECTS))
dbg_static_DEPENDS = $(patsubst build/.objs/dbg/static/%.o, build/.deps/dbg/static/%.cpp.d, $(dbg_static_OBJECTS))
dbg_tests_DEPENDS = $(patsubst build/.objs/dbg/tests/%.o, build/.deps/dbg/tests/%.cpp.d, $(dbg_tests_OBJECTS))
rel_shared_DEPENDS = $(patsubst build/.objs/rel/shared/%.o, build/.deps/rel/shared/%.cpp.d, $(rel_shared_OBJECTS))
rel_static_DEPENDS = $(patsubst build/.objs/rel/static/%.o, build/.deps/rel/static/%.cpp.d, $(rel_static_OBJECTS))
rel_tests_DEPENDS = $(patsubst build/.objs/rel/tests/%.o, build/.deps/rel/tests/%.cpp.d, $(rel_tests_OBJECTS))
$(dbg_shared_DEPENDS): build/.deps/dbg/shared/%.cpp.d: %.cpp
$(MAKE_TARGET_DIR)
$(CXX) $(CXX_FLAGS) -MT $(patsubst %.cpp, build/.objs/dbg/shared/%.o, $<) -MQ dependancies -MQ .tags -MQ project -MM $< -MF $@ > /dev/null
$(dbg_static_DEPENDS): build/.deps/dbg/static/%.cpp.d: %.cpp
$(MAKE_TARGET_DIR)
$(CXX) $(CXX_FLAGS) -MT $(patsubst %.cpp, build/.objs/dbg/static/%.o, $<) -MM $< -MF $@ > /dev/null
$(dbg_tests_DEPENDS): build/.deps/dbg/tests/%.cpp.d: %.cpp
$(MAKE_TARGET_DIR)
$(CXX) $(CXX_FLAGS) -MT $(patsubst %.cpp, build/.objs/dbg/tests/%.o, $<) -MM $< -MF $@ > /dev/null
$(rel_shared_DEPENDS): build/.deps/rel/shared/%.cpp.d: %.cpp
$(MAKE_TARGET_DIR)
$(CXX) $(CXX_FLAGS) -MT $(patsubst %.cpp, build/.objs/rel/shared/%.o, $<) -MM $< -MF $@ > /dev/null
$(rel_static_DEPENDS): build/.deps/rel/static/%.cpp.d: %.cpp
$(MAKE_TARGET_DIR)
$(CXX) $(CXX_FLAGS) -MT $(patsubst %.cpp, build/.objs/rel/static/%.o, $<) -MM $< -MF $@ > /dev/null
$(rel_tests_DEPENDS): build/.deps/rel/tests/%.cpp.d: %.cpp
$(MAKE_TARGET_DIR)
$(CXX) $(CXX_FLAGS) -MT $(patsubst %.cpp, build/.objs/rel/tests/%.o, $<) -MM $< -MF $@ > /dev/null
$(dbg_shared_OBJECTS): build/.objs/dbg/shared/%.o: %.cpp build/.deps/dbg/shared/%.cpp.d
$(MAKE_TARGET_DIR)
# $(CXX) $(CXX_FLAGS) $(DEBUG_FLAGS) $(SHARED_CXX_FLAGS) -c $< -o $@ -MT $@ -MD -MF $@.d
$(CXX) $(CXX_FLAGS) $(DEBUG_FLAGS) $(SHARED_CXX_FLAGS) -c $< -o $@
$(dbg_static_OBJECTS): build/.objs/dbg/static/%.o: %.cpp build/.deps/dbg/static/%.cpp.d
$(MAKE_TARGET_DIR)
$(CXX) $(CXX_FLAGS) $(DEBUG_FLAGS) $(STATIC_CXX_FLAGS) -c $< -o $@
$(dbg_tests_OBJECTS): build/.objs/dbg/tests/%.o: %.cpp build/.deps/dbg/tests/%.cpp.d
$(MAKE_TARGET_DIR)
$(CXX) $(CXX_FLAGS) $(DEBUG_FLAGS) $(TEST_FLAGS) $(STATIC_CXX_FLAGS) -c $< -o $@
$(rel_shared_OBJECTS): build/.objs/rel/shared/%.o: %.cpp build/.deps/rel/shared/%.cpp.d
$(MAKE_TARGET_DIR)
$(CXX) $(CXX_FLAGS) $(RELEASE_FLAGS) $(SHARED_CXX_FLAGS) -c $< -o $@
$(rel_static_OBJECTS): build/.objs/rel/static/%.o: %.cpp build/.deps/rel/static/%.cpp.d
$(MAKE_TARGET_DIR)
$(CXX) $(CXX_FLAGS) $(RELEASE_FLAGS) $(STATIC_CXX_FLAGS) -c $< -o $@
$(rel_tests_OBJECTS): build/.objs/rel/tests/%.o: %.cpp build/.deps/rel/tests/%.cpp.d
$(MAKE_TARGET_DIR)
$(CXX) $(CXX_FLAGS) $(RELEASE_FLAGS) $(TEST_FLAGS) $(STATIC_CXX_FLAGS) -c $< -o $@
.PHONY: all clean project dependancies paths system_paths debug vim_project_support
-include $(dbg_shared_DEPENDS)
-include $(dbg_static_DEPENDS)
-include $(dbg_tests_DEPENDS)
-include $(rel_shared_DEPENDS)
-include $(rel_static_DEPENDS)
-include $(rel_tests_DEPENDS)
debug:
@echo "BASENAME = $(BASENAME)"
@echo "PROJECT_FILE = $(PROJECT_FILE)"
@echo "PLATFORM = $(PLATFORM)"
@echo "ARCH = $(ARCH)"
@echo "COMPILER = $(COMPILER)"
@echo "VERSION = $(COMPILER_VER)"
define item_svn_status
$(eval TMP := $(word 1,$(subst _, ,$(filter %$(abspath $F),$(SVN_ST)))))$(if $(TMP),$(TMP), )
endef
define generate_versioned_tree_items
@printf '$(2)'
$(eval ITEMS := $(3))
$(eval LAST := $(if $(ITEMS),$(word $(words $(ITEMS)), $(ITEMS)),))
@printf '$(foreach F, $(ITEMS),\n$(1) $(if $(filter $F,$(LAST)),┗━,┣━)$(call item_svn_status) $(notdir $F)\t\t\t\t\t\t\t $(abspath $F))\n'
endef
# $(foreach F, $(ITEMS),$(call show_target_tree,$(1),$(if $(filter $(F),$(LAST)),b┗━,a┣━),$(F),$(if $(filter $(F),$(LAST)), ,┃)))
define show_target_tree
$(eval LAST2 := $(4))
$(eval TOK1 := $(if $(filter $(3),$(LAST2)),┗━,┣━))
$(eval TOK2 := $(if $(filter $(3),$(LAST2)),\ ,┃))
$(eval TOK3 := $(if $(filter $(3),$(LAST2)),,┃ ┃\n))
@printf '$(1) $(TOK1) $(3)'
$(eval DEPS := $($(3)_SOURCES))
$(call generate_versioned_tree_items,$(1) $(TOK2)\ , ,$(filter %.cpp,$(DEPS)))
@printf '$(TOK3)'
endef
define generate_tree_items
@printf '$(2)'
$(eval ITEMS := $(3))
@printf '\n┃ ┃ \n'
$(eval LAST1 := $(if $(ITEMS),$(word $(words $(ITEMS)), $(ITEMS)),))
$(foreach F, $(ITEMS),$(call show_target_tree,$(1), ,$(F),$(LAST1)))
endef
# $(call generate_versioned_tree_items,┃ ┃ ,┃ ┗━ Includes, $(filter %.h,$(DEPS)))
# $(eval DEPS := $(shell make -n build/$(LAST) | grep " .*\.cpp " | sed 's/.* \(.*\)\.cpp .*/\1.cpp /g' | tr -d '\n'))
# $(eval DEPS := $($(LAST)_SOURCES))
# @printf '$(foreach F, $(ITEMS), \n $(F) )\n'
# @printf '$(eval $(foreach F, $(ITEMS), \n $(F)))\n'
# @printf '$(foreach F, $(ITEMS),\n$(1) ┣━ $F ┃ ┣━ Sources $(I2))\n'
# @printf '$(foreach F, $(ITEMS),\n$(1) ┣━ $F $(eval $(call generate_versioned_tree_items,┃ ┃ ,┃ ┣━ Sources, $(I2))))\n'
# @printf '$(foreach F, $(ITEMS), $(F) $($(LAST)_SOURCES) )\n'
# @printf '$(foreach F, $(ITEMS),\n$(1) $(if $(filter $F,$(LAST)),┗━,┣━) $F $(eval $(call generate_versioned_tree_items,┃ ┃ ,┃ ┣━ Sources, $(I2))))\n'
# @printf '$(foreach F, $(ITEMS),\n$(1) $(if $(filter $F,$(LAST)),┗━,┣━) $F $(call generate_versioned_tree_items,┃ ┃ ,┃ ┣━ Sources, $(filter %.cpp,$(DEPS))))\n'
vim_project_support:
@echo 'true'
project:
@printf '$(PROJECT)\n┃\n'
$(eval SVN_ST := $(shell svn st `svn info --show-item wc-root` | tr ' ' '_'))
$(call generate_tree_items,┃ ,┣━ Targets, $(filter %,$(TARGETS)))
$(call generate_versioned_tree_items,┃ ,┃\n┣━ Sources, $(filter %.cpp,$^))
$(call generate_versioned_tree_items,┃ ,┃\n┣━ Includes, $(filter %.h,$^))
$(call generate_versioned_tree_items,┃ ,┃\n┣━ Docs, $(filter %.md,$(DOCS)))
$(call generate_versioned_tree_items, ,┃\n┗━ Project, $(filter-out %.d,$(MAKEFILE_LIST)))
# Output the user search paths (for vim/editor integration)
paths:
@echo $(INCLUDE_PATHS)
# Output the searched system paths the compiler will use (for vim/editor integration)
system_paths:
@echo | $(CXX) -Wp,-v -x c++ - -fsyntax-only 2>&1 | grep "^ " | grep -v "(" | tr -d "\n"
dependancies:
@echo $(patsubst %,\'%\',$^)
# TODO: doesn't work with multiple calls to AddApplication, can only be one target, perhaps need a DEBUG_TARGET in .pro file?
lldb-nvim.json: $(PROJECT_FILE)
@echo ' {' > $@
@echo ' "variables": { "target": "'$(dbg_shared_EXES)'" },' >> $@
@echo ' "modes": { "code": {}, "debug": { ' >> $@
@echo ' "setup": [ "target create {target}", [ "bp", "set" ] ], "teardown": [ [ "bp", "save" ], "target delete" ] ' >> $@
@echo ' } },' >> $@
@echo ' "breakpoints": { "@ll": [ ] }' >> $@
@echo ' }' >> $@