# project.mk - one-makefile build system # (c) 2009 - 2011 Tom Schouten # Licence: GPLv2 - http://www.gnu.org/licenses/gpl-2.0.html # The build system uses the following principles: # # * One makefile to build everything. This means that all # dependencies are expressed in a single place, and no recursive # calls to make itself are necessary or even possible. # # * Modular: while there is only a single makefile project.mk, # per-module variables and makefile fragments are defined in a # module.mk file. # # Each module has a single build target. If that doesn't work # for you, make an intermediate .a library and have other # modules depend on it. # Module-local variables all start with "m" # ## provided by user in module.mk, required: # m_OBJ := list of binary objects for this module to be collected in .a ## optional, if the module has an associated external target (next to the .a) # m_TARGET := module's build target (library or executable) # m_DEPS := intra-project dependencies: list references to other modules # m_LDFLAGS := linker flags to build target # # ## usable in module.mk, provided by system # $(m_NAME) module name (relative directory to project.mk) # $(m_SRC) module's source directory # $(m_BUILD) module's build directory # # NOTES: # # Non-recursive make. See: # [1] http://miller.emu.id.au/pmiller/books/rmch/ # [2] http://www.xs4all.nl/~evbergen/nonrecursive-make.html # [3] http://make.mad-scientist.us/multi-arch.html # # The key point in making decent GNU makefiles seems to be # understanding how substitution works; i.e. at what time variables # expansions are performed. Original make is fully lazy functional, # but GNU make has some strict (imperative) features that are useful # when used in macros. # # The whole project tree is managed by a single make session, # constructed using file inclusion and the imperative (strict) # features of make: assignment using ":=" instead of "=". See section # 5.2 in [1]. # # Functions and macros are used for conciseness in many places. Note # that GNU make is very similar to Scheme[4]. # # ($call body, args...) # # Function application: subsitute arguments in body and expand the # resulting string. # # ($eval $(call body, args)) # # Macro expansion: generate code using function application and # interpret the result as makefile syntax. This is useful for # creating rules. # # # [4] http://okmij.org/ftp/Computation/#Makefile-functional .PHONY: all clean targets # Include variables specific to this build. include config.mk # Defined in Makefile.defs all: $(PLATFORM) # The build and source directories are defined outside this script. # Note that these need to be absolute paths. # Use some shortcut names. FIXME: remove shortcuts. build = $(if $(VERBOSE), $(1), @echo "$(patsubst $(BUILDDIR)/%,%,$@)"; $(1)) # Each subdirectory of the toplevel source dir that contains a # module.mk file is a build module. Gather them and make sure the # associated build directory structure is present. MKS := $(shell cd $(SRCDIR); echo */module.mk) MODULES := $(patsubst %/module.mk,%,$(MKS)) $(shell mkdir -p $(MODULES)) # Include project-specific configuration. This is to keep project.mk # free from per-project customizations. -include $(SRCDIR)/project.config.mk ### BUILD RULES # All file names use absolute paths. # Get list of sources and objects to construct the list of .d includes # generated by gcc -M, and a list of objects to collect in the .a # archive. # The .c deps are created using gcc -M, ignoring generated files (-MG) # and explicity prefixing the output rule with the directory of the # object file. The sed line prefixes all relative paths with # $(BUILDDIR). depstx := sed -r 's,\s(\w+/), $(BUILDDIR)/\1,g' # Gather dependencies from .c file $(BUILDDIR)/%.d: $(SRCDIR)/%.c $(call build, $(CC) $(CPPFLAGS) -M -MG -MT $(@:.d=.o) $< | $(depstx) >$@) # Preprocess only $(BUILDDIR)/%.cx: $(SRCDIR)/%.c $(call build, $(CC) $(CPPFLAGS) -E $< -o $@) _CC := $(CC) $(CPPFLAGS) $(CFLAGS) $(OPTI_CFLAGS) $(DEBUG_CFLAGS) # Compile .c -> .o $(BUILDDIR)/%.o: $(SRCDIR)/%.c $(call build, $(_CC) -o $@ -c $<) # Compile .c -> executable $(BUILDDIR)/%.test: $(SRCDIR)/%.c $(call build, $(_CC) $(LDFLAGS) $< -o $@) # Extract symbols # $(BUILDDIR) -> $(BUILDDIR) rules can use simpler patterns. %.syms: %.so $(call build, $(OBJDUMP) -T $< | grep '\.text' | awk '{print $$7;}' >$@) ### MODULES # As opposed to the generic rules above, a rule for each target is # constructed using macro expansion of the following template. # Template rule and macro function for applications and shared library # targets: , , define target_template TARGETS := $$(TARGETS) $(1) $(1): $(2) $$(call build, $$(CC) -o $(1) $(2) $(3) $$(LDFLAGS) $$(APP_LDFLAGS)) endef # target_rule: , , , , modules = $(foreach m, $(1), $(BUILDDIR)/$(m)/$(m).a) target_rule = $(eval $(call target_template, $(1)/$(strip $(2)), \ $(3) $(call modules, $(4)), $(5))) # Makefile tiemplate for each module. Once expanded, it gathers # module-specific data from the local m_ variables and builds a global # target list. define module_template m_NAME := $(1) m_OBJ := m_TARGET := m_DEPS := m_LDFLAGS := # For constructing paths to module-specific source and build dir in module.mk m_SRC := $(SRCDIR)/$(1) m_BUILD := $(BUILDDIR)/$(1) include $(SRCDIR)/$(1)/module.mk # Convert all objects to absolute paths. $(1)_OBJ := $$(addprefix $(BUILDDIR)/$(1)/, $$(m_OBJ)) # Each object has a dependency file. DEPS := $$(DEPS) $$($(1)_OBJ:.o=.d) # Each module is bundled in an .a archive. $(BUILDDIR)/$(1)/$(1).a: $$($(1)_OBJ) $$(call build, $(AR) rcs $$@ $$($(1)_OBJ)) # Create rule for target if defined. $$(if $$(m_TARGET), $$(call target_rule, \ $(BUILDDIR)/$(1), \ $$(m_TARGET), \ $$($(1)_OBJ), \ $$(m_DEPS), \ $$(m_LDFLAGS))) endef # Expand template for each module, include deps and define targets. $(foreach prog,$(MODULES),$(eval $(call module_template,$(prog)))) -include $(DEPS) targets: $(TARGETS) ### INSTALL & CLEAN clean: cd $(BUILDDIR); for d in $(MODULES); do (cd $$d ; rm -rf *); done cleansrc: cd $(SRCDIR); rm -f `find -name '*~'` # Dump some config variables in shell format. config.sh: config.mk @echo "BUILDDIR=$(BUILDDIR)" >>$@ @echo "SRCDIR=$(BUILDDIR)" >>$@