# # Makefile for tbx unit testing # # Copyright (C) 2021 Christos Choutouridis # # The MIT License (MIT) # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. # # notes: # ============== # This makefile provides support for unix-like local builds and # docker based builds for gcc and clang. # ** msvc is not currently supported. # # Use cases: # 1) build localy using **local rules** in terminal. # example: # make -j4 MK_ARG="-std=c++14 -fconcepts" build-gcc # # 2) build localy using **docker based rules** via terminal or script # in order to compile for all compilers/versions/dialects. # example in bash: # #!/bin/bash # for im in gcc:8 gcc:9 gcc:10; do # for dial in -std=c++14 -std=c++17; do # make IMAGE="$im" MK_ARG="$dial" dock-gcc # done # done # # 3) build inside a docker instance using **local rules** in order to # build and test using a CD/CI system. # example in a yml file: # image: gcc:8 # build: # stage: build # - make MK_ARG="-std=c++14" build-gcc # - make MK_ARG="-std=c++14 -fconcepts" build-gcc # - make MK_ARG="-std=c++17" build-gcc # - make MK_ARG="-std=c++17 -fconcepts" build-gcc # - make MK_ARG="-std=c++2a" build-gcc # ... etc # test: # stage: test # - bin/utlTest # ... etc # # # ============== Project settings ============== # # note: STM32f101RC/D/E device configuration on SMT32CubeF1 library requires: # 1) STM2F101xE pre-define # 2) startup_stm32f101xe.s startup file # # Project's name PROJECT := tbx # Excecutable's name TARGET := tbxTest # Source directories list(space seperated). Relative path, under current directory only SRC_DIR_LIST := tests gtest # Source files list(space seperated). SRC_FILES_LIST := # Include directories list(space seperated). Relative path INC_DIR_LIST := ../include gtest ifeq ($(OS), Windows_NT) INC_DIR_LIST += mingw-std-threads endif # Exclude files list(space seperated). Filenames only. # EXC_FILE_LIST := bad.cpp old.cpp # Build directories BUILD_DIR := bin OBJ_DIR := $(BUILD_DIR)/obj DEP_DIR := $(BUILD_DIR)/.dep # ============== Compiler settings ============== CLANGXX := clang++ GCCXX := g++ CSIZE := size ODUMP := objdump OCOPY := objcopy # Compiler flags for debug and release DEB_CFLAGS := -std=gnu++17 -DDEBUG -g3 -Wall -Wextra -fmessage-length=0 REL_CFLAGS := -std=gnu++17 -g3 -Wall -Wextra -O2 -fmessage-length=0 # Pre-defines PRE_DEFS := ifeq ($(OS), Windows_NT) PRE_DEFS += WIN_TRHEADS endif # ============== Linker settings ============== # Linker flags LDFLAGS := -pthread # Map output file MAP_FILE := output.map MAP_FLAG := -Xlinker -Map=$(BUILD_DIR)/$(MAP_FILE) # ============== Docker settings ============== # We need: # 1) Bind the entire project directory(the dir that icludes all the code) as volume. # 2) In docker instance change to working directory(where the makefile is). # For utl we use the directories `${PWD%/*}` and `test`. # We double-$$ for double evaluating. DOCKER_VOL_DIR := "$${PWD%/*}" DOCKER_WRK_DIR := test DOCKER_RUN := docker run --rm -v $(DOCKER_VOL_DIR):/usr/src/$(PROJECT) -w /usr/src/$(PROJECT)/$(DOCKER_WRK_DIR) # ============== Default settings ============== # compiler and compiler flags. By default docker is not used. CFLAGS := $(DEB_CFLAGS) CXX := $(GCCXX) DOCKER := # # =========== Main body and Patterns =========== # ifeq ($(OS), Windows_NT) TARGET := $(TARGET).exe endif INC := $(foreach dir,$(INC_DIR_LIST),-I$(dir)) DEF := $(foreach def,$(PRE_DEFS),-D$(def)) EXC := $(foreach fil,$(EXC_FILE_LIST), \ $(foreach dir,$(SRC_DIR_LIST),$(wildcard $(dir)/$(fil))) \ ) # source files, object and dependencies list # recursive search into current and source directories SRC := $(wildcard *.cpp) SRC += $(foreach dir,$(SRC_DIR_LIST),$(wildcard $(dir)/*.cpp)) SRC += $(foreach dir,$(SRC_DIR_LIST),$(wildcard $(dir)/**/*.cpp)) #SRC := $(abspath $(filter-out $(EXC),$(SRC))) SRC := $(filter-out $(EXC),$(SRC)) OBJ := $(foreach file,$(SRC:%.cpp=%.o),$(OBJ_DIR)/$(file)) DEP := $(foreach file,$(SRC:%.cpp=%.d),$(DEP_DIR)/$(file)) # Make Dependencies pattern. # This little trick enables recompilation only when dependencies change # and it does so for changes both in source AND header files ;) # # It is based on Tom Tromey's method. # # Invoke cpp to create makefile rules with dependencies for each source file $(DEP_DIR)/%.d: %.cpp @mkdir -p $(@D) @$(DOCKER) $(CXX) -E $(CFLAGS) $(INC) $(DEF) -MM -MT $(OBJ_DIR)/$(<:.cpp=.o) -MF $@ $< # objects depent on .cpp AND dependency files, which have an empty recipe $(OBJ_DIR)/%.o: %.cpp $(DEP_DIR)/%.d @mkdir -p $(@D) $(DOCKER) $(CXX) -c $(CFLAGS) $(INC) $(DEF) -o $@ $< # empty recipe for dependency files. This prevents make errors $(DEP): # now include all dependencies # After all they are makefile dependency rules ;) include $(wildcard $(DEP)) # main target rule $(BUILD_DIR)/$(TARGET): $(OBJ) @mkdir -p $(@D) @echo Linking to target: $(TARGET) $(DOCKER) $(CXX) $(LDFLAGS) $(MAP_FLAG) -o $(@D)/$(TARGET) $(OBJ) $(DOCKER) $(ODUMP) -h -S $(BUILD_DIR)/$(TARGET) > $(BUILD_DIR)/$(basename $(TARGET)).list # $(DOCKER) $(OCOPY) -O ihex $(BUILD_DIR)/$(TARGET) $(BUILD_DIR)/$(basename $(TARGET)).hex @echo @echo Print size information @$(CSIZE) $(@D)/$(TARGET) @echo Done .PHONY: clean clean: @echo Cleaning build directories @rm -rf $(OBJ_DIR) @rm -rf $(DEP_DIR) @rm -rf $(BUILD_DIR) # # ================ Local build rules ================= # examples: # make MK_ARG="-std=c++14 -fconcepts" build-gcc # make MK_ARG="-std=c++17" build-clang # .PHONY: build-gcc build-gcc: CFLAGS += $(MK_ARG) build-gcc: $(BUILD_DIR)/$(TARGET) .PHONY: build-clang build-clang: CXX := $(CLANGXX) build-clang: CFLAGS += $(MK_ARG) build-clang: $(BUILD_DIR)/$(TARGET) .PHONY: debug debug: $(BUILD_DIR)/$(TARGET) .PHONY: test_asan test_asan: CFLAGS += -fsanitize=address -fsanitize=leak -fsanitize=bounds-strict test_asan: LDFLAGS += -fsanitize=address -fsanitize=leak -fsanitize=bounds-strict test_asan: $(BUILD_DIR)/$(TARGET) .PHONY: test_tsan test_tsan: CFLAGS += -fsanitize=thread test_tsan: LDFLAGS += -fsanitize=thread test_tsan: $(BUILD_DIR)/$(TARGET) .PHONY: release release: CFLAGS := $(REL_CFLAGS) release: $(BUILD_DIR)/$(TARGET) .PHONY: all all: clean release # # ================ Docker based rules ================ # examples: # make IMAGE="gcc:8.3" MK_ARG="-std=c++14 -fconcepts" dock-gcc # make IMAGE="a-clang-image" MK_ARG="-std=c++17" dock-clang # .PHONY: dock-gcc dock-gcc: DOCKER := $(DOCKER_RUN) $(IMAGE) dock-gcc: CFLAGS += $(MK_ARG) dock-gcc: $(BUILD_DIR)/$(TARGET) .PHONY: dock-clang dock-clang: CXX := $(CLANGXX) dock-clang: DOCKER := $(DOCKER_RUN) $(IMAGE) dock-clang: CFLAGS += $(MK_ARG) dock-clang: $(BUILD_DIR)/$(TARGET)