Skip to content

Instantly share code, notes, and snippets.

@lbussy
Last active April 20, 2025 12:40
Show Gist options
  • Save lbussy/617aebfd9578bdcf058d9396dccae093 to your computer and use it in GitHub Desktop.
Save lbussy/617aebfd9578bdcf058d9396dccae093 to your computer and use it in GitHub Desktop.
Generic C and C++ Makefile
# -----------------------------------------------------------------------------
# MIT License
#
# Copyright (c) 2025 Lee C. Bussy (@lbussy)
#
# 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
# provided 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.
# -----------------------------------------------------------------------------
# Makefile for building most C++ or C++ and C projects
# Debug flag
DEBUG := -DDEBUG_WSPR_TRANSMIT
# C++ Version
CXXVER := 17
# Prefix for executing tests, "sudo" if required
SUDO := sudo
# Add any required submodules by their src path
SUBMODULE_SRCDIRS := $(wildcard ../../WSPR-Message/src)
SUBMODULE_SRCDIRS += $(wildcard ../../Broadcom-Mailbox/src)
# Get project name from Git, revert to directory name
#
# Project name from remote origin URL (fallback to 'unknown' if not available)
PRJ := $(shell git config --local remote.origin.url 2>/dev/null | sed -n 's#.*/\([^.]*\)\.git#\1#p')
# If PRJ is empty, fallback to current directory name
PRJ := $(or $(PRJ),$(shell basename $$(pwd)))
# If PRJ is "src", use the parent directory name
PRJ := $(shell [ "$(PRJ)" = "src" ] && basename $$(dirname $$(pwd)) || echo "$(PRJ)")
# Convert PRJ to lowercase for EXE_NAME
EXE_NAME := $(shell echo $(PRJ) | tr '[:upper:]' '[:lower:]')
# Output Items
OUT := $(EXE_NAME) # Normal release binary
TEST_OUT := $(EXE_NAME)_test # Debug/test binary
# Strip whitespace from comments
OUT := $(strip $(OUT))
TEST_OUT := $(strip $(TEST_OUT))
# Output directories
OBJ_DIR_RELEASE = build/obj/release
OBJ_DIR_DEBUG = build/obj/debug
DEP_DIR = build/dep
BIN_DIR = build/bin
# ────────────────────────────────────────────────────────────────────────────────
# Find your project’s own .c/.cpp (this will pick up ./main.cpp or ./main.c):
LOCAL_C_SOURCES := $(shell find . -type f -name '*.c')
LOCAL_CPP_SOURCES := $(shell find . -type f -name '*.cpp')
# Find in each submodule—but explicitly skip any “main” there:
SUBMODULE_C_SOURCES := $(shell find $(SUBMODULE_SRCDIRS) -type f -name '*.c' ! -name 'main.c')
SUBMODULE_CPP_SOURCES := $(shell find $(SUBMODULE_SRCDIRS) -type f -name '*.cpp' ! -name 'main.cpp')
# Combine them:
C_SOURCES := $(LOCAL_C_SOURCES) $(SUBMODULE_C_SOURCES)
CPP_SOURCES := $(LOCAL_CPP_SOURCES) $(SUBMODULE_CPP_SOURCES)
# Strip any leading ../../ from the submodule paths so your object rules work:
REL_C_SOURCES := $(patsubst ../../%,%,$(C_SOURCES))
REL_CPP_SOURCES := $(patsubst ../../%,%,$(CPP_SOURCES))
# Regenerate your object‐lists exactly as before:
C_OBJECTS := $(patsubst %.c, $(OBJ_DIR_RELEASE)/%.o,$(REL_C_SOURCES))
CPP_OBJECTS := $(patsubst %.cpp, $(OBJ_DIR_RELEASE)/%.o,$(REL_CPP_SOURCES))
C_DEBUG_OBJECTS := $(patsubst %.c, $(OBJ_DIR_DEBUG)/%.o,$(REL_C_SOURCES))
CPP_DEBUG_OBJECTS := $(patsubst %.cpp, $(OBJ_DIR_DEBUG)/%.o,$(REL_CPP_SOURCES))
# ────────────────────────────────────────────────────────────────────────────────
# Linker Flags
LDFLAGS := -lpthread -latomic
# Get packages for linker from PKG_CONFIG_PATH
# LDFLAGS += $(shell pkg-config --cflags --libs libgpiod)
# LDFLAGS += $(shell pkg-config --libs libgpiodcxx)
# Strip whitespace from LDFLAGS
LDFLAGS := $(strip $(LDFLAGS))
# Collect dependency files
DEPFILES := $(wildcard $(DEP_DIR)/**/*.d)
-include $(DEPFILES)
# Compiler Executables
CXX = g++
CC = gcc
# Allow threading using all available processors
MAKEFLAGS := -j$(nproc)
# Compiler Flags
#
# Common flags for both C and C++ compilers
COMMON_FLAGS := -Wall -Werror -fmax-errors=10 -MMD -MP
# C Flags
CFLAGS := $(COMMON_FLAGS)
# C Debug Flags
C_DEBUG_FLAGS := $(CFLAGS) -g $(DEBUG) # Debug flags
# C Release Flags
C_RELEASE_FLAGS := $(CFLAGS) -O2 # Release optimized
# C++ Flags
CXXFLAGS := -Wno-psabi -lstdc++fs -std=c++$(CXXVER)
CXXFLAGS += $(COMMON_FLAGS) $(COMM_CXX_FLAGS)
# Include paths
CXXFLAGS += -I$(abspath .)
CXXFLAGS += $(foreach dir,$(SUBMODULE_SRCDIRS),-I$(abspath $(dir)))
# C++ Debug Flags
CXX_DEBUG_FLAGS := $(CXXFLAGS) -g $(DEBUG) # Debug flags
# C++ Release Flags
CXX_RELEASE_FLAGS := $(CXXFLAGS) -O2 # Release optimized
# Strip whitespaces
C_DEBUG_FLAGS := $(strip $(C_DEBUG_FLAGS))
C_RELEASE_FLAGS := $(strip $(C_RELEASE_FLAGS))
CXX_DEBUG_FLAGS := $(strip $(CXX_DEBUG_FLAGS))
CXX_RELEASE_FLAGS := $(strip $(CXX_RELEASE_FLAGS))
# Enable verbose output if VERBOSE=1 is specified during the build
VERBOSE ?= 0
ifeq ($(VERBOSE), 1)
Q :=
else
Q := @
endif
##
# Compile & link rules
##
# Generic debug compile (your own code)
$(OBJ_DIR_DEBUG)/%.o: %.c
$(Q)mkdir -p $(dir $@)
$(Q)mkdir -p $(DEP_DIR)/$(dir $*)
$(Q)echo "Compiling (debug) $< into $@"
$(Q)$(CC) $(C_DEBUG_FLAGS) -MF $(DEP_DIR)/$*.d -c $< -o $@
$(OBJ_DIR_DEBUG)/%.o: %.cpp
$(Q)mkdir -p $(dir $@)
$(Q)mkdir -p $(DEP_DIR)/$(dir $*)
$(Q)echo "Compiling (debug) $< into $@"
$(Q)$(CXX) $(CXX_DEBUG_FLAGS) -MF $(DEP_DIR)/$*.d -c $< -o $@
# Auto‑generate debug compile rules for each submodule
define MAKE_DEBUG_SUBMOD_RULES
$(OBJ_DIR_DEBUG)/$(patsubst ../../%,%,$(1))/%.o: $(1)/%.cpp
$(Q)mkdir -p $$(@D)
$(Q)mkdir -p $(DEP_DIR)/$(dir $$*)
$(Q)echo "Compiling (debug) $$< into $$@"
$(Q)$(CXX) $(CXX_DEBUG_FLAGS) -MF $(DEP_DIR)/$$*.d -c $$< -o $$@
$(OBJ_DIR_DEBUG)/$(patsubst ../../%,%,$(1))/%.o: $(1)/%.c
$(Q)mkdir -p $$(@D)
$(Q)mkdir -p $(DEP_DIR)/$(dir $$*)
$(Q)echo "Compiling (debug) $$< into $$@"
$(Q)$(CC) $(C_DEBUG_FLAGS) -MF $(DEP_DIR)/$$*.d -c $$< -o $$@
endef
$(foreach dir,$(SUBMODULE_SRCDIRS),$(eval $(call MAKE_DEBUG_SUBMOD_RULES,$(dir))))
# Link debug binary
build/bin/$(TEST_OUT): $(CPP_DEBUG_OBJECTS) $(C_DEBUG_OBJECTS)
$(Q)mkdir -p $(BIN_DIR)
$(Q)echo "Linking debug: $(TEST_OUT)"
$(Q)$(CXX) $(CXX_DEBUG_FLAGS) $^ -o $@ $(LDFLAGS)
# Generic release compile (your own code)
$(OBJ_DIR_RELEASE)/%.o: %.cpp
$(Q)mkdir -p $(dir $@)
$(Q)mkdir -p $(DEP_DIR)/$(dir $*)
$(Q)echo "Compiling (release) $< into $@"
$(Q)$(CXX) $(CXX_RELEASE_FLAGS) -MF $(DEP_DIR)/$*.d -c $< -o $@
$(OBJ_DIR_RELEASE)/%.o: %.c
$(Q)mkdir -p $(dir $@)
$(Q)mkdir -p $(DEP_DIR)/$(dir $*)
$(Q)echo "Compiling (release) $< into $@"
$(Q)$(CC) $(C_RELEASE_FLAGS) -MF $(DEP_DIR)/$*.d -c $< -o $@
# Auto‑generate release compile rules for each submodule
define MAKE_RELEASE_SUBMOD_RULES
$(OBJ_DIR_RELEASE)/$(patsubst ../../%,%,$(1))/%.o: $(1)/%.cpp
$(Q)mkdir -p $$(@D)
$(Q)mkdir -p $(DEP_DIR)/$(dir $$*)
$(Q)echo "Compiling (release) $$< into $$@"
$(Q)$(CXX) $(CXX_RELEASE_FLAGS) -MF $(DEP_DIR)/$$*.d -c $$< -o $$@
$(OBJ_DIR_RELEASE)/$(patsubst ../../%,%,$(1))/%.o: $(1)/%.c
$(Q)mkdir -p $$(@D)
$(Q)mkdir -p $(DEP_DIR)/$(dir $$*)
$(Q)mkdir -p $(DEP_DIR)/$(dir $$*)
$(Q)echo "Compiling (release) $$< into $$@"
$(Q)$(CC) $(C_RELEASE_FLAGS) -MF $(DEP_DIR)/$$*.d -c $$< -o $$@
endef
$(foreach dir,$(SUBMODULE_SRCDIRS),$(eval $(call MAKE_RELEASE_SUBMOD_RULES,$(dir))))
# Link release binary
build/bin/$(OUT): $(CPP_OBJECTS) $(C_OBJECTS)
$(Q)mkdir -p $(BIN_DIR)
$(Q)echo "Linking release: $(OUT)"
$(Q)$(CXX) $(CXX_RELEASE_FLAGS) $^ -o $@ $(LDFLAGS)
##
# Phony targets
##
.PHONY: clean release debug test gdb lint macros help
clean:
$(Q)echo "Cleaning up build artifacts."
$(Q)rm -rf build
release: build/bin/$(OUT)
$(Q)echo "Release build completed successfully."
debug: build/bin/$(TEST_OUT)
$(Q)echo "Debug build completed successfully."
test: debug
$(Q)$(SUDO) ./build/bin/$(TEST_OUT) -i /usr/local/etc/wspr.ini
gdb: debug
$(Q)$(SUDO) sudo gdb --args ./build/bin/$(TEST_OUT) -i /usr/local/etc/wspr.ini
lint:
$(Q)command -v cppcheck >/dev/null 2>&1 || { \
echo "Warning: cppcheck not installed."; exit 1; }
$(Q)echo "Running cppcheck…"
$(Q)cppcheck --std=c++$(CXXVER) --enable=all --inconclusive \
--force --inline-suppr --quiet $(CPP_SOURCES)
macros:
$(Q)echo "Project macros:"
$(Q)$(CXX) $(CXXFLAGS) -dM -E -x c++ /dev/null \
| grep -v '^#define _' || true
help:
$(Q)echo "Available targets:"
$(Q)echo " release Build optimized binary"
$(Q)echo " debug Build debug binary"
$(Q)echo " test Run tests"
$(Q)echo " gdb Debug with gdb"
$(Q)echo " lint Static analysis"
$(Q)echo " macros Show macros"
$(Q)echo " clean Remove build artifacts"
$(Q)echo " help This message"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment