From: Leandro Lucarella Date: Thu, 17 Sep 2009 13:30:56 +0000 (-0300) Subject: Initial import of the all mighty make based build system X-Git-Url: https://git.llucax.com/software/makeit.git/commitdiff_plain/a4419536621943012477a48fc1fc5b1c730a2834?ds=sidebyside Initial import of the all mighty make based build system This is a test/example of a nice make based build system that support automatic dependencies, variants, separated build directory, automatic generation of pre-compiled header, subproject embedding and some other goodies. The only important file is Lib.mak, all the rest is just an usage example and/or test. --- a4419536621943012477a48fc1fc5b1c730a2834 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..567609b --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +build/ diff --git a/Build.mak b/Build.mak new file mode 100644 index 0000000..0f18c0b --- /dev/null +++ b/Build.mak @@ -0,0 +1,15 @@ + +# Include sub-directories makefiles + +C := subproj +include $T/subproj/Build.mak + +C := lib1 +include $T/lib1/Build.mak + +C := lib2 +include $T/lib2/Build.mak + +C := prog +include $T/prog/Build.mak + diff --git a/Config.mak b/Config.mak new file mode 100644 index 0000000..e76a8a9 --- /dev/null +++ b/Config.mak @@ -0,0 +1,10 @@ + +# Use debug flavor by default +F := dbg + +# C compiler +CC := gcc + +# Use precompiled headers +GCH := 1 + diff --git a/Lib.mak b/Lib.mak new file mode 100644 index 0000000..259a659 --- /dev/null +++ b/Lib.mak @@ -0,0 +1,243 @@ + +ifndef Lib.mak.included +Lib.mak.included := 1 + +# These variables should be provided by the includer Makefile: +# P should be the project name, mostly used to handle include directories +# T should be the path to the top-level directory. +# C should be the path to the current directory. + +# Load top-level directory local configuration +sinclude $T/Config.mak + +# Verbosity flag (empty show nice messages, 1 be verbose) +# honour make -s flag +override V := $(if $(findstring s,$(MAKEFLAGS)),1,$V) + +# Flavor (variant), should be one of "dbg", "opt" or "cov" +F ?= opt + +# Use C++ linker by default +LINKER := $(CXX) + +# Use precompiled headers if non-empty +GCH ?= + + +# Directories +############## + +# Use absolute paths to avoid problems with automatic dependencies when +# building from subdirectories +T := $(abspath $T) + +# Name of the current directory, relative to $T +R := $(subst $T,,$(patsubst $T/%,%,$(CURDIR))) + +# Base directory where to put variants +D ?= $T/build + +# Generated files top directory +G ?= $D/$F + +# Objects (and other garbage like precompiled headers and dependency files) +# directory +O ?= $G/obj + +# Binaries directory +B ?= $G/bin + +# Libraries directory +L ?= $G/lib + +# Includes directory +I ?= $G/include + +# Generated includes directory +J ?= $G/geninc + + +# Functions +############ + +# Find sources files and get the corresponding object names +# The first argument should be the sources extension ("c" or "cpp" typically) +# It expects the variable $T and $O to be defined as commented previously in +# this file. $C should be defined to the path to the current directory relative +# to the top-level. +find_objects = $(patsubst $T/%.$1,$O/%.o,$(shell find $T/$C -name '*.$1')) + +# Abbreviate a file name. Cut the leading part of a file if it match to the $T +# directory, so it can be displayed as if it were a relative directory. Take +# just one argument, the file name. +abbr = $(addprefix $(shell echo $R | sed 's|/\?\([^/]\+\)/\?|../|g'),\ + $(subst $T,.,$(patsubst $T/%,%,$1))) + +# Execute a command printing a nice message if $V is empty +# The first argument is mandatory and it's the command to execute. The second +# and third arguments are optional and are the target name and command name to +# pretty print. +vexec = $(if $V,,\ + echo ' $(notdir $(if $3,$(strip $3),$(firstword $1))) \ + $(call abbr,$(if $2,$(strip $2),$@))' ; )$1 + +# Same as vexec but it silence the echo command (prepending a @). +exec = $(if $V,,@)$(call vexec,$1,$2,$3) + +# Compile a source file to an object, generating pre-compiled headers and +# dependencies. The pre-compiled headers are generated only if the system +# includes change. This function is designed to be used as a command in a rule. +# It takes one argument only, the type of file to compile (typically "c" or +# "cpp"). What to compile and the output files are built using the automatic +# variables from a rule. +define compile +$(if $(GCH),\ +@if test -f $O/$*.d; then \ + tmp=`mktemp`; \ + h=`awk -F: '!$$0 {f = 1} $$0 && f {print $$1}' $O/$*.d`; \ + grep -h '^#include <' $(call abbr,$<) $$h | sort -u > "$$tmp"; \ + if diff -q -w "$(call abbr,$O/$*.$1.h)" "$$tmp" > /dev/null 2>&1; \ + then \ + rm "$$tmp"; \ + else \ + mv "$$tmp" "$(call abbr,$O/$*.$1.h)"; \ + $(call vexec,$(COMPILE.$1) -o "$O/$*.$1.h.gch" "$O/$*.$1.h",\ + $O/$*.$1.h.gch); \ + fi \ +else \ + touch "$(call abbr,$O/$*.$1.h)"; \ +fi \ +) +$(call exec,$(COMPILE.$1) -o $@ -MMD -MP $(if $(GCH),-include $O/$*.$1.h) $<) +endef + +# Link object files to build an executable. The objects files are taken from +# the prerequisite files ($O/%.o). If in the prerequisite files are shared +# objects ($L/lib%.so), they are included as libraries to link to (-l%). This +# function is designed to be used as a command in a rule. The ouput name is +# taken from the rule automatic variables. If an argument is provided, it's +# included in the link command line. The variable LINKER is used to link the +# executable; for example, if you want to link a C++ executable, you should use +# LINKER := $(CXX). +link = $(call exec,$(LINKER) $(LDFLAGS) $(TARGET_ARCH) -o $@ $1 \ + $(patsubst $L/lib%.so,-l%,$(filter %.so,$^)) \ + $(foreach obj,$(filter %.o,$^),$(obj))) + + +# Overrided flags +################## + +# Warn about everything +override CPPFLAGS += -Wall + +# Use the includes directories to search for includes +override CPPFLAGS += -I$I -I$J + +# Be standard compilant +override CFLAGS += -std=c99 -pedantic +override CXXFLAGS += -std=c++98 -pedantic + +# Use the generated library directory to for libraries +override LDFLAGS += -L$L -Wall + +# Make sure the generated libraries can be found +export LD_LIBRARY_PATH := $L:$(LD_LIBRARY_PATH) + + +# Variant flags +################ + +ifeq ($F,dbg) +override CPPFLAGS += -ggdb -DDEBUG +endif + +ifeq ($F,opt) +override CPPFLAGS += -O2 -DNDEBUG +endif + +ifeq ($F,cov) +override CPPFLAGS += -ggdb -pg --coverage +override LDFLAGS += -pg --coverage +endif + + +# Automatic rebuilding when flags or commands changes +###################################################### + +# Re-compile C files if one of this variables changes +COMPILE.c.FLAGS := $(CC) ~ $(CPPFLAGS) ~ $(CFLAGS) ~ $(TARGET_ARCH) + +# Re-compile C++ files if one of this variables changes +COMPILE.cpp.FLAGS := $(CXX) ~ $(CPPFLAGS) ~ $(CXXFLAGS) ~ $(TARGET_ARCH) + +# Re-link binaries and libraries if one of this variables changes +LINK.o.FLAGS := $(LD) ~ $(LDFLAGS) ~ $(TARGET_ARCH) + + +# Default rules +################ + +$O/%.o: $T/%.c $G/compile-c-flags + $(call compile,c) + +$O/%.o: $T/%.cpp $G/compile-cpp-flags + $(call compile,cpp) + +$B/%: $G/link-o-flags + $(call link) + +$L/%.so: override CFLAGS += -fPIC +$L/%.so: override CXXFLAGS += -fPIC +$L/%.so: $G/link-o-flags + $(call link,-shared) + +.PHONY: clean +clean: + $(call exec,$(RM) -r $D,$D) + + +# Automatic dependency handling +################################ + +sinclude $(shell test -d $O && find $O -name '*.d') + + +# Create build directory structure +################################### + +# Create a file with flags used to trigger rebuilding when they change. The +# first argument is the name of the file where to store the flags, the second +# are the flags and the third argument is a text to be displayed if the flags +# have changed. This should be used as a rule action or something where +# a shell script is expected. +gen_rebuild_flags = if test x"$2" != x"`cat $1 2>/dev/null`"; then \ + test -f $1 && echo "$3"; \ + echo "$2" > $1 ; fi + +# Create $O, $B, $L, $I and $J directories and replicate the directory +# structure of the project into $O. Create one symlink "last" to the current +# build directory and another to use as include directory. It update the flags +# files to detect flag and/or compiler changes to force a rebuild. +# +# NOTE: the second mkdir can yield no arguments if the project don't have any +# subdirectories, that's why the current directory "." is included, so it +# won't show an error message in case of no subdirectories. +setup_build_dir__ := $(shell \ + mkdir -p $O $B $L $I $J; \ + mkdir -p . $(addprefix $O,$(patsubst $T%,%,\ + $(shell find $T -type d -not -path '$D*'))); \ + $(call gen_rebuild_flags,$G/compile-c-flags, \ + $(COMPILE.c.FLAGS),C compiler or flags;); \ + $(call gen_rebuild_flags,$G/compile-cpp-flags, \ + $(COMPILE.cpp.FLAGS),C++ compiler or flags;); \ + $(call gen_rebuild_flags,$G/link-o-flags, \ + $(LINK.o.FLAGS),linker or link flags;); \ + test -L $I/$P || ln -s $T $I/$P; \ + test -L $D/last || ln -s $F $D/last ) + +# Print any generated message (if verbose) +$(if $V,,$(if $(setup_build_dir__), \ + $(info !! Something changed: $(setup_build_dir__) \ + re-building affected files...))) + +endif diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..f78a4f0 --- /dev/null +++ b/Makefile @@ -0,0 +1,10 @@ + +# Top-level directory +T := . + +# Default goal for building this directory +.DEFAULT_GOAL := all + +# Include the top-level makefile +include $T/Toplevel.mak + diff --git a/README b/README new file mode 100644 index 0000000..083dea7 --- /dev/null +++ b/README @@ -0,0 +1,27 @@ + +This is a test/example for a nice Make based build system. + +The Config.mak should not be saved to the repositories usually, but in this +case it is because part of this demonstration is to show how to customize the +build system through Config.mak, and specially to show how a project can be +"embedded" into another tweaking the Config.mak. + +lib1 is a standalone C library compiled into a shared object. lib2 is another +shared library which uses lib1 and subproj, which is a standalone project +"embedded" into this one. subproj produces another standalone shared object. +Finally, prog is a program which uses lib1 and lib2. + +Every project have it's copy of Lib.mak and it's own Toplevel.mak, which has +some global permanent configuration (which doesn't depends on users taste, it +just have information about the project, like it's name, and does the work to +include Lib.mak, etc.). Lib.mak shouldn't be modified ever (unless you're +hacking the build system) and Toplevel.mak should be changes very rarely. + +Then each directory containing some library or program to build (or directories +to include) has a Build.mak, which has only the logic to build the +programs/libraries. A well-known Makefile is added to each directory where you +want to be able to do "make", just for convenience. This Makefile should be +created once, with the default target to build and path to the top-level +directory and never touched again. Build.mak should be changes only to add new +programs or libraries to build. + diff --git a/Toplevel.mak b/Toplevel.mak new file mode 100644 index 0000000..bcf2b56 --- /dev/null +++ b/Toplevel.mak @@ -0,0 +1,15 @@ + +# Project name +P := remake + +# Include the build system library +include $T/Lib.mak + +# Include the Build.mak for this directory +include $T/Build.mak + +# Phony rule to make all the targets (sub-makefiles can append targets to build +# to the $(all) variable). +.PHONY: all +all: $(all) + diff --git a/lib1/Build.mak b/lib1/Build.mak new file mode 100644 index 0000000..7661dc4 --- /dev/null +++ b/lib1/Build.mak @@ -0,0 +1,7 @@ + +$L/liblib1.so: LINKER := $(CC) +$L/liblib1.so: $(call find_objects,c) + +.PHONY: lib1 +lib1: $L/liblib1.so + diff --git a/lib1/Makefile b/lib1/Makefile new file mode 100644 index 0000000..fe10532 --- /dev/null +++ b/lib1/Makefile @@ -0,0 +1,10 @@ + +# Top-level directory +T := .. + +# Include the top-level build +include $T/Toplevel.mak + +# Default goal for building this directory +.DEFAULT_GOAL := $L/liblib1.so + diff --git a/lib1/lib1.c b/lib1/lib1.c new file mode 100644 index 0000000..e7212f4 --- /dev/null +++ b/lib1/lib1.c @@ -0,0 +1,9 @@ + +#include "lib1.h" +#include + +void lib1(void) +{ + printf("lib1()\n"); +} + diff --git a/lib1/lib1.h b/lib1/lib1.h new file mode 100644 index 0000000..2aafe60 --- /dev/null +++ b/lib1/lib1.h @@ -0,0 +1,11 @@ + +#ifdef __cplusplus +extern "C" { +#endif + +void lib1(void); + +#ifdef __cplusplus +} +#endif + diff --git a/lib2/Build.mak b/lib2/Build.mak new file mode 100644 index 0000000..02841c5 --- /dev/null +++ b/lib2/Build.mak @@ -0,0 +1,6 @@ + +$L/liblib2.so: $(call find_objects,cpp) $L/liblib1.so $L/libsubproj.so + +.PHONY: lib2 +lib2: $L/liblib2.so + diff --git a/lib2/Makefile b/lib2/Makefile new file mode 100644 index 0000000..99f8aad --- /dev/null +++ b/lib2/Makefile @@ -0,0 +1,10 @@ + +# Top-level directory +T := .. + +# Include the top-level build +include $T/Toplevel.mak + +# Default goal for building this directory +.DEFAULT_GOAL := $L/liblib2.so + diff --git a/lib2/lib2.cpp b/lib2/lib2.cpp new file mode 100644 index 0000000..d5c07c4 --- /dev/null +++ b/lib2/lib2.cpp @@ -0,0 +1,15 @@ + +#include "lib2.h" + +#include +#include + +#include + +void lib2(void) +{ + printf("lib2()\n"); + lib1(); + subproj(); +} + diff --git a/lib2/lib2.h b/lib2/lib2.h new file mode 100644 index 0000000..80dab51 --- /dev/null +++ b/lib2/lib2.h @@ -0,0 +1,3 @@ + +void lib2(void); + diff --git a/prog/Build.mak b/prog/Build.mak new file mode 100644 index 0000000..d3a59ed --- /dev/null +++ b/prog/Build.mak @@ -0,0 +1,8 @@ + +$B/prog: $(call find_objects,cpp) $L/liblib1.so $L/liblib2.so + +.PHONY: prog +prog: $B/prog + +all += prog + diff --git a/prog/Makefile b/prog/Makefile new file mode 100644 index 0000000..717a34e --- /dev/null +++ b/prog/Makefile @@ -0,0 +1,10 @@ + +# Top-level directory +T := .. + +# Default goal for building this directory +.DEFAULT_GOAL := prog + +# Include the top-level build +include $T/Toplevel.mak + diff --git a/prog/main.cpp b/prog/main.cpp new file mode 100644 index 0000000..19ec21f --- /dev/null +++ b/prog/main.cpp @@ -0,0 +1,15 @@ + +#include +#include + +#include + +int main() +{ + std::cout << "prog:main() start\n"; + lib1(); + lib2(); + std::cout << "prog:main() end\n"; + return 0; +} + diff --git a/subproj/Build.mak b/subproj/Build.mak new file mode 100644 index 0000000..01d8779 --- /dev/null +++ b/subproj/Build.mak @@ -0,0 +1,9 @@ + +$L/libsubproj.so: LINKER := $(CC) +$L/libsubproj.so: $(call find_objects,c) + +.PHONY: subproj +subproj: $L/libsubproj.so + +all += subproj + diff --git a/subproj/Config.mak b/subproj/Config.mak new file mode 100644 index 0000000..d7624aa --- /dev/null +++ b/subproj/Config.mak @@ -0,0 +1,10 @@ + +# Use the container project top-level directory as ours +T := .. + +# Include the "parent" project config +sinclude $T/Toplevel.mak + +# Include the "parent" project config +.DEFAULT_GOAL := subproj + diff --git a/subproj/Lib.mak b/subproj/Lib.mak new file mode 120000 index 0000000..f07a38e --- /dev/null +++ b/subproj/Lib.mak @@ -0,0 +1 @@ +../Lib.mak \ No newline at end of file diff --git a/subproj/Makefile b/subproj/Makefile new file mode 100644 index 0000000..f78a4f0 --- /dev/null +++ b/subproj/Makefile @@ -0,0 +1,10 @@ + +# Top-level directory +T := . + +# Default goal for building this directory +.DEFAULT_GOAL := all + +# Include the top-level makefile +include $T/Toplevel.mak + diff --git a/subproj/Toplevel.mak b/subproj/Toplevel.mak new file mode 100644 index 0000000..4738f47 --- /dev/null +++ b/subproj/Toplevel.mak @@ -0,0 +1,18 @@ + +# Project name +P := subproj + +# Load top-level directory local configuration +sinclude $T/Config.mak + +# Include the build system library +include $T/Lib.mak + +# Include the Build.mak for this directory +include $T/Build.mak + +# Phony rule to make all the targets (sub-makefiles can append targets to build +# to the $(all) variable). +.PHONY: all +all: $(all) + diff --git a/subproj/subproj.c b/subproj/subproj.c new file mode 100644 index 0000000..2188e52 --- /dev/null +++ b/subproj/subproj.c @@ -0,0 +1,9 @@ + +#include "subproj.h" +#include + +void subproj(void) +{ + printf("subproj()\n"); +} + diff --git a/subproj/subproj.h b/subproj/subproj.h new file mode 100644 index 0000000..d29126c --- /dev/null +++ b/subproj/subproj.h @@ -0,0 +1,11 @@ + +#ifdef __cplusplus +extern "C" { +#endif + +void subproj(void); + +#ifdef __cplusplus +} +#endif +