#NEWPROJECT requires refactor.sh sources # Taken from https://stackoverflow.com/questions/30573481/how-to-write-a-makefile-with-separate-source-and-header-directories # Always build targets and unit tests together. This avoids the scenario wherein the targets are built, then the source files are edited, then the unit tests are built and, accidentally, outdated targets are packaged and shipped. I say this knowing full well that I never ship my targets to anyone ever. PREFIX := SUFFIX := SRCDIR := sources MAINDIR := $(SRCDIR)/main INCDIR := $(SRCDIR)/include OBJDIR := objects MAINOD := $(OBJDIR)/main BINDIR := build INSTDIR := /usr/local/bin REFACTOR := refactor.h TESTCFLAGS:= $(pkg-config --cflags criterion) TESTLIBS := $(pkg-config --libs criterion) TESTDIR := $(SRCDIR)/test TESTOD := $(OBJDIR)/test HEADERS := $(wildcard $(INCDIR)/*.h) MAINS := $(wildcard $(MAINDIR)/*.c) MAINOBJS := $(patsubst $(MAINDIR)/%.c, $(MAINOD)/%.o, $(MAINS)) TESTS := $(wildcard $(TESTDIR)/*.c) TESTOBJS := $(patsubst $(TESTDIR)/%.c, $(TESTOD)/%.o, $(TESTS)) UNITTESTS := $(patsubst $(TESTDIR)/%.c, $(BINDIR)/%, $(TESTS)) TARGETS := $(patsubst $(MAINDIR)/%.c, $(BINDIR)/%, $(MAINS)) SOURCES := $(wildcard $(SRCDIR)/*.c) OBJECTS := $(patsubst $(SRCDIR)/%.c, $(OBJDIR)/%.o, $(SOURCES)) CPPFLAGS := -I$(abspath $(INCDIR)) -MMD -MP -DUSE_LIBNOTIFY CFLAGS := -Wall -Werror -Wpedantic $(shell pkg-config --cflags libnotify) LDFLAGS := #-Lmath or whatever LDLIBS := $(shell pkg-config --libs libnotify libcrypto) #-lm or whatever define MANGLE = $(dir $(1))$(PREFIX)$(notdir $(1))$(SUFFIX) endef .PHONY: all clean all: $(TARGETS) $(UNITTESTS) @echo "Executables built: " $^ refactor: ./refactor.sh $(UNITTESTS): $(OBJECTS) $(TESTOBJS) | $(BINDIR) $(OBJDIR) $(TESTOD) @echo "Linking unit tests..." @echo "Object files for tests is " $(TESTOBJS) $(CC) $(LDFLAGS) $(OBJECTS) $(patsubst $(BINDIR)/%, $(TESTOD)/%.o, $@) $(LDLIBS) $(TESTLIBS) -o $(dir $@)$(PREFIX)$(notdir $@)$(SUFFIX) @echo "...Done linking unit tests" $(TARGETS): $(OBJECTS) $(MAINOBJS) | $(BINDIR) $(OBJDIR) $(MAINOD) @echo "Linking targets..." $(CC) $(LDFLAGS) $(OBJECTS) $(patsubst $(BINDIR)/%, $(MAINOD)/%.o, $@) $(LDLIBS) -o $(dir $@)$(PREFIX)$(notdir $@)$(SUFFIX) @echo "...Done linking targets" $(BINDIR) $(OBJDIR) $(MAINOD) $(TESTOD): mkdir --parents $@ $(OBJDIR)/%.o: $(SRCDIR)/%.c $(HEADERS) | $(OBJDIR) $(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@ $(MAINOD)/%.o: $(MAINDIR)/%.c $(HEADERS) | $(MAINOD) $(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@ $(TESTOD)/%.o: $(TESTDIR)/%.c $(HEADERS) | $(TESTOD) $(CC) $(CPPFLAGS) $(CFLAGS) $(TESTCFLAGS) -c $< -o $@ # $| evaluates to the order-only prerequisites, in this case: $(TARGETS) # The $(foreach ...) function will evaluate to a semicolon-separated list of 'cp $instdir' # Putting this $(foreach ...) into the recipe means that its result # cp a /usr/local/bin; cp b /usr/local/bin; # will be run as a shell command. # # Shamelessly derived from: # https://stackoverflow.com/questions/7918511/make-execute-an-action-for-each-prerequisite install: | $(TARGETS) @echo "Installing executables to $(INSTDIR)" @$(foreach target, $|, cp -v $(call MANGLE, $(target)) $(INSTDIR);) uninstall: | $(TARGETS) @echo "Removing executables from $(INSTDIR)" $(foreach target, $(notdir $(call MANGLE, $|)), rm -v $(INSTDIR)/$(target);) clean: $(RM) -rv $(BINDIR) $(OBJDIR) # Lazy kludge to run the first executable specified in $(TARGETS) # Could also be accomplished by running $< but this is easier to read run: all $(eval MANGLEDNAME=$(call MANGLE, $(word 1,$(TARGETS)))) @echo "Running " $(MANGLEDNAME) @$(MANGLEDNAME) # @$(dir $(word 1,$(TARGETS)))$(PREFIX)$(notdir $(word 1,$(TARGETS)))$(SUFFIX) # unit testing! test: $(TARGETS) $(UNITTESTS) @echo "Testing " $(word 1,$(UNITTESTS)) @$(dir $(word 1,$(UNITTESTS)))$(PREFIX)$(notdir $(word 1,$(UNITTESTS)))$(SUFFIX) .PHONY: variable variable: all $(eval mangle2=$(dir $(word 1,$(UNITTESTS)))$(PREFIX)$(notdir $(word 1,$(UNITTESTS)))$(SUFFIX)) $(eval mangle3=$(call MANGLE, $(UNITTESTS))) @echo "variable declared within a recipe: "$(mangle2) @echo "variable declared within a recipe using a canned recipe: "$(mangle3) # Honestly still not sure what this directive does. # It's an include directive, which processes all .d files that correspond to members of $(OBJ), # with the hyphen in front meaning that it suppresses/ignores errors (from nonexistent .d files) # Seems to be included under the stipulation that GNU make will auto-generate .d files based on # a given file's #include directives? # https://stackoverflow.com/questions/19114410/what-is-d-file-after-building-with-make -include $(OBJ:.o=.d)