6
\$\begingroup\$

This makefile (for GNU Make) has grown slowly as I've been building code samples from this site and others.

One thing that's different from most project makefiles is that each binary tends to have its own particular compilation flags, so I've had to allow flexibility in overriding warnings, libraries, and other aspects independently.

I have subdirectories

stackexchange/review/
stackexchange/golf/
stackexchange/stackoverflow/

and so on. This common makefile resides in stackexchange/Makefile and is symlinked into each subdirectory. The subdirectories each have source files named using the question number, and a files.mak which looks something like:

142807: CXXFLAGS += -fconcepts
142807: CXXFLAGS += -fopenmp

159439.o: CXXVER = c++11

162913: PKGS += opencv

164699: CXXVER = c++17
164699: CXXFLAGS += -fconcepts

164972: PKGS += Qt5Widgets
164972: LINK.o = $(LINK.cc)
164972.o: 164972.cpp 164972_moc.cpp

OPTIMIZED += 266283 266216
    
USING_GTEST += 269711 270693

25678481: LDLIBS += -lm
25678481.o: CFLAGS += -Wfloat-conversion

2918353: PKGS += Qt5Core

42110050: LDLIBS += -ljpeg -lX11

42504487.o: CFLAGS += -Ofast -ftree-vectorize -fopt-info-vec-missed -mavx2 -msse4

(Note: the above is an amalgamation of review, golf and overflow makefiles, so the question numbers might not match what you're expecting).

The Makefile which includes files.mak is:

SHELL = bash

CC := gcc-11
CXX := g++-11

COMPILE.s = $(AS) $(ASFLAGS) $(TARGET_MACH)
AS := nasm

CXXVER := c++20
CVER := c17
WARNINGS = -Wall -Wextra -Wwrite-strings -Wno-parentheses
WARNINGS += -Wpedantic -Warray-bounds
WARNINGS += -Wconversion
#WARNINGS += -fanalyzer
CXX_WARNINGS += $(if $(PKGS),,-Weffc++)
CC_WARNINGS += -Wstrict-prototypes -fanalyzer
DEBUG_OPTIONS += -fPIC -gdwarf-4

CXXFLAGS += -std=$(CXXVER) -fconcepts $(DEBUG_OPTIONS) $(WARNINGS) $(CXX_WARNINGS) $(INCLUDES)
CXXFLAGS += -Wuseless-cast
CFLAGS += -std=$(CVER) $(DEBUG_OPTIONS) $(WARNINGS) $(CC_WARNINGS) $(INCLUDES)
CFLAGS += -Wconversion
LDLIBS += $(LIBS)

# These EXTRA_FOO varibles allow users to add to FOO (as alternative
# to completely overriding them) with Make command-line arguments.
CXXFLAGS += $(EXTRA_CXXFLAGS)
CFLAGS += $(EXTRA_CFLAGS)
WARNINGS += $(EXTRA_WARNINGS)

CXXFLAGS += $(patsubst -I%,-isystem %,$(if $(PKGS),$(shell pkg-config --cflags $(PKGS))))
CFLAGS += $(patsubst -I%,-isystem %,$(if $(PKGS),$(shell pkg-config --cflags $(PKGS))))
LDLIBS += $(if $(PKGS),$(shell pkg-config --libs $(PKGS)))

PYTHON=/usr/bin/python3

VALGRIND_ARGS := --leak-check=full
#VALGRIND_ARGS += -q

# Prevent test programs from competing too hard for resources (particularly memory, which can lead
# to thrashing swap).
MAX_MEM_KB=1048576
MAX_CPU_SECS=60
LIMITS = ulimit -S -v $(MAX_MEM_KB) -t $(MAX_CPU_SECS)

PROGNAME = ./$(patsubst %.exe,%,$<)
# User can supply command-line arguments in RUNARGS (which will be
# shell-processed) and standard input in INPUT.
export INPUT
print_cmd = printf '%s ' $(TOOL)  $(PROGNAME); $(if $(RUNARGS),/usr/bin/printf '%q ' $(RUNARGS);)
print_cmd += $(if $(subst environment,,$(origin INPUT)),/usr/bin/printf '<<<%q\n' "$$INPUT",echo);
#print_cmd += echo $(origin INPUT)

RUN = $(PROGNAME) $(RUNARGS)
#RUN += $(if $(subst environment,,$(origin INPUT)),<<<"$$INPUT")
RUN += <<<"$$INPUT"
RUN += $(POSTPROC)

.PHONY: %.run %.time %.valgrind %.shellcheck
.DELETE_ON_ERROR:

%.exe: %.cc
    @$(MAKE) $*

%.exe: %.cpp
    @$(MAKE) $*

%.exe: %.c
    @$(MAKE) $*

%.run: %.exe
    @$(print_cmd)
    @$(LIMITS); exec $(TOOL) $(RUN)

%.run: %.py
    @$(print_cmd)
    @$(LIMITS); exec $(TOOL) $(PYTHON) $(RUN)

%.run: %.sh
    @$(print_cmd)
    @$(LIMITS); if test -x $<; then exec $(TOOL) $(RUN); else exec $(TOOL) $(SHELL) $(RUN); fi

%.run: %.dc
    @$(print_cmd)
    @$(LIMITS); exec $(TOOL) $(RUN)

%.run: %.sed
    @$(print_cmd)
    @$(LIMITS); exec $(TOOL) sed $(SEDFLAGS) -f $(RUN)

c%.run: c%.class
    @$(print_cmd)
    @$(LIMITS); exec $(TOOL) java c$* $(wordlist 2,9999,$(RUN))

%.time:
    $(MAKE) TOOL=time $*.run

%.valgrind:
    $(MAKE) TOOL='valgrind $(VALGRIND_ARGS)' $*.run

%.shellcheck: %.sh
    shellcheck -f gcc $(SHELLCHECK_ARGS) $<

%.shellcheck: %
    shellcheck -f gcc $(SHELLCHECK_ARGS) $<


%_moc.cpp: %.h
    moc -o $@ $<

%_moc.cpp: %.cpp
    moc -o $@ $<

%_ui.h: %.ui
    uic -p -o $@ $<

%.s: %.c
    $(CC) $(OUTPUT_OPTION) $< -S $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH)

%.s: %.cpp
    $(CXX) $(OUTPUT_OPTION) $< -S $(CXXFLAGS) $(CPPFLAGS) $(TARGET_ARCH)

%.s: CPPFLAGS += -DASM_OUTPUT

%.s: CFLAGS += -fverbose-asm
%.s: CXXFLAGS += -fverbose-asm


c%.class: c%.java
    javac $^

%.o: %.s
    as -g --32 -o $@ $<

%.s: %.S
    intel2gas -o $@ $<


# Google Test objects

GTEST_DIR = /usr/src/googletest/googletest
GMOCK_DIR = /usr/src/googletest/googlemock

# All Google Test headers.  Usually you shouldn't change this
# definition.
GTEST_HEADERS = $(wildcard /usr/include/gtest/*.h \
                           /usr/include/gtest/internal/*.h)

# Usually you shouldn't tweak such internal variables, indicated by a
# trailing _.
GTEST_SRCS_ = $(wildcard $(GTEST_DIR)/src/*.cc $(GTEST_DIR)/src/*.h) $(GTEST_HEADERS)

# For simplicity and to avoid depending on Google Test's
# implementation details, the dependencies specified below are
# conservative and not optimized.  This is fine as Google Test
# compiles fast and for ordinary users its source rarely changes.
VPATH += $(GTEST_DIR)/src
VPATH += $(GMOCK_DIR)/src
gtest%.o: CXXFLAGS += -isystem $(GTEST_DIR) -isystem $(GTEST_DIR)/include
gtest%.o: gtest%.cc $(GTEST_SRCS_)
    $(COMPILE.cpp) $(OUTPUT_OPTION) $<

gmock%.o: override INCLUDES += -I$(GMOCK_DIR) -isystem $(GMOCK_DIR)

OPTIMIZED += gtest%.o gmock%.o


include files.mak


### Keep these after files.mak

$(OPTIMIZED) $(patsubst %,%.s,$(OPTIMIZED)): CFLAGS += -O3 -march=native
$(OPTIMIZED) $(patsubst %,%.s,$(OPTIMIZED)): CXXFLAGS += -O3 -march=native

$(USING_GTEST): gtest_main.o gtest-all.o
$(USING_GTEST): INCLUDES += -isystem $(GTEST_DIR)/include
$(USING_GTEST): LDLIBS += -pthread
$(USING_GTEST): LINK.o = $(LINK.cc)
    
ELF_SIGNATURE = 7f454c46  # ASCII: ^?, E, L, F

clean::
    $(RM) *~ *.o
    find . -type f -executable \
           -exec sh -c "hexdump -n 4 -e '4/1 \"%02x\"' \$$1 | grep -qx $(ELF_SIGNATURE)" sh {} \; \
           -delete

I'd like a general review of the structure and the rules. Can I reduce the amount I need to copy/paste when using Qt+moc and/or GTest?

\$\endgroup\$
6
  • \$\begingroup\$ How do you support a project/question, where multiple files are needed? For eg. a post where a.[hc] and b.[hc] are needed for main.c? Do you combine them all in a single file? Can your makefile support a stackexchange/review/<question>/<files> structure? \$\endgroup\$
    – hjpotter92
    Apr 4, 2018 at 0:45
  • \$\begingroup\$ Usually, I combine the files into a single source file. Where that's not possible (because either the Makefile itself or the relationship between headers and sources is at the heart of the question), then I create a subdirectory with a Makefile that begins with include ../../include.mak to quickly get to the same baseline. \$\endgroup\$ Apr 4, 2018 at 8:21
  • \$\begingroup\$ Have you seen this question? It's an automated script that, among other things, creates a CMake file from a CodeReview question. \$\endgroup\$
    – Edward
    Apr 4, 2018 at 10:53
  • \$\begingroup\$ I hadn't seen that - thanks @Edward. Is there a good reason to go through CMake rather than just using Make directly? \$\endgroup\$ Apr 4, 2018 at 11:39
  • \$\begingroup\$ The advantage to CMake is that it creates cross-platform targets. So it creates makefiles on Linux and Visual Studio scripts on Windows, etc. \$\endgroup\$
    – Edward
    Apr 4, 2018 at 11:47

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Browse other questions tagged or ask your own question.