User:Solar/Makefile
Note: This isn't finished, just a rough draft. I didn't test this exhaustively, and will probably not do so anytime soon. But what's there already might be helpful.
Sometimes, your project consists of more than one binary: You have to build several libraries and executables from a single source tree, but with different compiler / linker settings. This, too, can be comfortably covered with a single Makefile, but requires some more involved syntax. One example is presented here; there are, of course, many other ways to achieve this.
The basic idea is to create a subdirectory for each module (executable or library). In each these module subdirectories, the overall structure is identical:
- module-specific settings are stored in a file build.mk;
- headers with "global" project visibility (i.e., visible to other modules) are stored in includes;
- all other headers and source files are located in src;
- a directory obj exists for the compiler to put its object files.
At the top level, there is a directory bin and a directory lib for binaries and libraries, respectively. A directory includes will eventually contain links to all the "global" headers. And, of course, there is our Makefile.
. ├── bar │ ├── build.mk │ ├── includes │ │ └── global.h │ ├── obj │ └── src │ ├── interface.c │ ├── interface.h │ └── main.c ├── baz │ ├── build.mk │ ├── includes │ │ └── global.h │ ├── obj │ └── src │ ├── hashmap.c │ ├── hashmap.h │ └── algorithm.c ├── bin ├── includes ├── lib └── Makefile
The contents of the individual modules' build.mk files is similar to what is done in Makefile.am for Automake.
Example for a binary module:
# CFLAGS for module 'bar' CFLAGS_bar := # Executable to build in module 'bar' bar_PROGRAM := bar # Libraries that the executable depends on: bar_LIBRARIES := libbaz.a # Sources for the executable 'bar' (without headers) bar_SOURCES := interface.c main.c
Example for a library module:
# CFLAGS for module 'baz' CFLAGS_baz := # Archive / Lib to build in module 'baz' baz_ARCHIVE := libbaz.a # Sources for the archive / lib 'libbaz.a' (without headers) libbaz.a_SOURCES := hashmap.c algorithm.c
The main Makefile makes heavy use of GNU make's template functionality. It might look daunting at first, but if you look at it carefully, you will see it is pretty straightforward, and easily adaptable or expandable.
# Modules in the project (you could 'find' these, but stating
# them explicitly allows for subdirectories like 'tmp' or 'doc'
# without upsetting the build process.
MODULES := bar baz
# Global CFLAGS. Add to them if you must, but don't remove '-MMD -I includes',
# which is used for header dependency tracking.
CFLAGS_global := -MMD -I includes
# Global ARFLAGS.
ARFLAGS := rc
.PHONY: clean mrproper
# Add whatever should be your default / global target.
all:
@echo "Default target."
###################################################################
# What follows are several templates (think "functions"), which are
# later instantiated for each registered module ($(1) being the
# module name).
###################################################################
# Including a module's build.mk
define MK_template
include $(1)/build.mk
endef
# Setting a module's build rules for object files in <module>/obj.
define RULES_template
$(1)/obj/%.o: $(1)/src/%.c
$$(CC) $$(CFLAGS) $$(CFLAGS_global) $$(CFLAGS_$(1)) -c $$< -o $$@
endef
# Setting a module's build rules for executable targets.
# (Depending on its sources' object files and any libraries.)
# Also adds a module's dependency files to the global list.
define PROGRAM_template
DEPENDENCIES := $(DEPENDENCIES) $(patsubst %,$(2)/obj/%.d,$(basename $($(1)_SOURCES)))
bin/$(1): $(patsubst %,$(2)/obj/%.o,$(basename $($(1)_SOURCES))) $(foreach library,$($(1)_LIBRARIES),lib/$(library))
$$(LD) $$(LDFLAGS) $$(LDFLAGS_$(2)) $$^ -o $$@
endef
# Setting a module's build rules for archive targets.
# (Depending on its sources' object files.)
define ARCHIVE_template
DEPENDENCIES := $(DEPENDENCIES) $(patsubst %,$(2)/obj/%.d,$(basename $($(1)_SOURCES)))
lib/$(1): $(patsubst %,$(2)/obj/%.o,$(basename $($(1)_SOURCES)))
$$(AR) $$(ARFLAGS) $$@ $$?
endef
# Linking a module's global includes into the global include directory
# (where they will be available as <module>/filename.h).
define INCLUDE_template
ifeq ($(wildcard includes/$(1)),)
$$(shell ln -s ../$(1)/includes includes/$(1))
endif
endef
# Now, instantiating the templates for each module.
$(foreach module,$(MODULES),$(eval include $(module)/build.mk))
$(foreach module,$(MODULES),$(eval $(call RULES_template,$(module))))
$(foreach module,$(MODULES),$(eval $(foreach binary,$($(module)_PROGRAM),$(call PROGRAM_template,$(binary),$(module)))))
$(foreach module,$(MODULES),$(eval $(foreach library,$($(module)_ARCHIVE),$(call ARCHIVE_template,$(library),$(module)))))
$(foreach module,$(MODULES),$(eval $(call INCLUDE_template,$(module))))
# Include the dependency files (generated by GCC's -MMD option)
-include $(sort $(DEPENDENCIES))
clean:
$(RM) $(foreach mod,$(MODULES),$(mod)/obj/*.o)
mrproper: clean
$(RM) $(foreach mod,$(MODULES),$(mod)/obj/*.d) includes/* bin/* lib/*