Linux Makefile structure and documentation
The best tutorial that I have found for makefiles so far has been this one. One of your other options is to learn autotools and let them produce your makefiles for you, though a little outdated this is the tutorial that I found best to learn from.
How to document a makefile?
One nice touch is to provide a phony help
target that prints a summary of targets and options. From the Linux kernel Makefile
:
help:
@echo 'Cleaning targets:'
@echo ' clean - Remove most generated files but keep the config and'
@echo ' enough build support to build external modules'
@echo ' mrproper - Remove all generated files + config + various backup files'
@echo ' distclean - mrproper + remove editor backup and patch files'
@echo ''
@echo 'Configuration targets:'
@$(MAKE) -f $(srctree)/scripts/kconfig/Makefile help
@echo ''
It might be a bit of work to maintain the documentation this way, but I find it nicely separates what is intended for "users" versus what is intended for people maintaining the Makefile
itself (inline comments).
What is the purpose of a makefile?
Yes it is essentially just a list of instructions. The purpose of a makefile is to be easily build an executable that might take many commands to create (which would be a pain to compile over and over again manually).
What do the makefile symbols $@ and $ mean?
$@
is the name of the target being generated, and $<
the first prerequisite (usually a source file). You can find a list of all these special variables in the GNU Make manual.
For example, consider the following declaration:
all: library.cpp main.cpp
In this case:
$@
evaluates toall
$<
evaluates tolibrary.cpp
$^
evaluates tolibrary.cpp main.cpp
minimum c++ make file for linux
If it is a single file, you can type
make t
And it will invoke
g++ t.cpp -o t
This doesn't even require a Makefile in the directory, although it will get confused if you have a t.cpp and a t.c and a t.java, etc etc.
Also a real Makefile:
SOURCES := t.cpp
# Objs are all the sources, with .cpp replaced by .o
OBJS := $(SOURCES:.cpp=.o)
all: t
# Compile the binary 't' by calling the compiler with cflags, lflags, and any libs (if defined) and the list of objects.
t: $(OBJS)
$(CC) $(CFLAGS) -o t $(OBJS) $(LFLAGS) $(LIBS)
# Get a .o from a .cpp by calling compiler with cflags and includes (if defined)
.cpp.o:
$(CC) $(CFLAGS) $(INCLUDES) -c $<
How does kbuild actually work?
Kbuild's Makefiles aren't the easiest to read, but here's a high-level untangling (using the 4.0-rc3 kernel):
The top-level Makefile does
include $(srctree)/scripts/Kbuild.include
, where
$(srctree)
is the top-level kernel directory.Kbuild.include
defines various common stuff and helpers. Among these isbuild
:###
# Shorthand for $(Q)$(MAKE) -f scripts/Makefile.build obj=
# Usage:
# $(Q)$(MAKE) $(build)=dir
build := -f $(srctree)/scripts/Makefile.build objbuild
is used with a command like$(MAKE) $(build)=dir
to perform the build for the directorydir
. It makes use ofscripts/Makefile.build
.Returning to the top-level Makefile, there's the following:
$(vmlinux-dirs): prepare scripts
$(Q)$(MAKE) $(build)=$@vmlinux-dirs
contains a list of subdirectories to build (init, usr, kernel, etc.).$(Q)$(MAKE) $(build)=<subdirectory>
will be run for each subdirectory.The rule above compiles object files for both the kernel image and modules. Further down in the top-level Makefile, there's some additional module-specific stuff:
ifdef CONFIG_MODULES
...
modules: $(vmlinux-dirs) $(if $(KBUILD_BUILTIN),vmlinux) modules.builtin
# Do additional module-specific stuff using
# scripts/Makefile.modpost among other things
# (my comment).
...
...
endif # CONFIG_MODULESLooking into
scripts/Makefile.build
(the Makefile used by$(build)
) now, it begins by initializing theobj-*
lists and various other lists:# Init all relevant variables used in kbuild files so
# 1) they have correct type
# 2) they do not inherit any value from the environment
obj-y :=
obj-m :=
lib-y :=
lib-m :=A bit further down, it loads in the Kbuild file where
obj-y
,obj-m
, etc., are set:include $(kbuild-file)
Further down is the default rule, which has the
$(obj-y)
and$(obj-m)
lists as prerequisites:__build: $(if $(KBUILD_BUILTIN),$(builtin-target) $(lib-target) $(extra-y)) \
$(if $(KBUILD_MODULES),$(obj-m) $(modorder-target)) \
$(subdir-ym) $(always)
@:The
$(obj-y)
prerequisites come from$(builtin-target)
, which is defined as follows:builtin-target := $(obj)/built-in.o
...
$(builtin-target): $(obj-y) FORCE
$(call if_changed,link_o_target)The actual building seems to be performed by the following rule:
# Built-in and composite module parts
$(obj)/%.o: $(src)/%.c $(recordmcount_source) FORCE
$(call cmd,force_checksrc)
$(call if_changed_rule,cc_o_c)if_changed_rule
is fromKbuild.include
. The rule ends up running the following commands inMakefile.build
:define rule_cc_o_c
$(call echo-cmd,checksrc) $(cmd_checksrc) \
$(call echo-cmd,cc_o_c) $(cmd_cc_o_c); \
...
endef$(cmd_cc_o_c)
seems to be the actual compilation command. The usual definition (there are two possibilities inMakefile.build
, AFAICS) seems to be the following:cmd_cc_o_c = $(CC) $(c_flags) -c -o $@ $<
Unless set explicitly using e.g.
make CC=clang
,CC
defaults togcc
, as can be seen here in the top-level Makefile:ifneq ($(CC),)
ifeq ($(shell $(CC) -v 2>&1 | grep -c "clang version"), 1)
COMPILER := clang
else
COMPILER := gcc
endif
export COMPILER
endif
The way I untangled this was by doing a CTRL-C during a kernel build and seeing where make
reported the error. Another handy make
debugging technique is to use $(warning $(variable))
to print the value of variable
.
Building C-program out of source tree with GNU make
Here's the Makefile I've added to the documentation (currently in review so I'll post it here) :
# Set project directory one level above the Makefile directory. $(CURDIR) is a GNU make variable containing the path to the current working directory
PROJDIR := $(realpath $(CURDIR)/..)
SOURCEDIR := $(PROJDIR)/Sources
BUILDDIR := $(PROJDIR)/Build
# Name of the final executable
TARGET = myApp.exe
# Decide whether the commands will be shown or not
VERBOSE = TRUE
# Create the list of directories
DIRS = Folder0 Folder1 Folder2
SOURCEDIRS = $(foreach dir, $(DIRS), $(addprefix $(SOURCEDIR)/, $(dir)))
TARGETDIRS = $(foreach dir, $(DIRS), $(addprefix $(BUILDDIR)/, $(dir)))
# Generate the GCC includes parameters by adding -I before each source folder
INCLUDES = $(foreach dir, $(SOURCEDIRS), $(addprefix -I, $(dir)))
# Add this list to VPATH, the place make will look for the source files
VPATH = $(SOURCEDIRS)
# Create a list of *.c sources in DIRS
SOURCES = $(foreach dir,$(SOURCEDIRS),$(wildcard $(dir)/*.c))
# Define objects for all sources
OBJS := $(subst $(SOURCEDIR),$(BUILDDIR),$(SOURCES:.c=.o))
# Define dependencies files for all objects
DEPS = $(OBJS:.o=.d)
# Name the compiler
CC = gcc
# OS specific part
ifeq ($(OS),Windows_NT)
RM = del /F /Q
RMDIR = -RMDIR /S /Q
MKDIR = -mkdir
ERRIGNORE = 2>NUL || true
SEP=\\
else
RM = rm -rf
RMDIR = rm -rf
MKDIR = mkdir -p
ERRIGNORE = 2>/dev/null
SEP=/
endif
# Remove space after separator
PSEP = $(strip $(SEP))
# Hide or not the calls depending of VERBOSE
ifeq ($(VERBOSE),TRUE)
HIDE =
else
HIDE = @
endif
# Define the function that will generate each rule
define generateRules
$(1)/%.o: %.c
@echo Building $$@
$(HIDE)$(CC) -c $$(INCLUDES) -o $$(subst /,$$(PSEP),$$@) $$(subst /,$$(PSEP),$$<) -MMD
endef
# Indicate to make which targets are not files
.PHONY: all clean directories
all: directories $(TARGET)
$(TARGET): $(OBJS)
$(HIDE)echo Linking $@
$(HIDE)$(CC) $(OBJS) -o $(TARGET)
# Include dependencies
-include $(DEPS)
# Generate rules
$(foreach targetdir, $(TARGETDIRS), $(eval $(call generateRules, $(targetdir))))
directories:
$(HIDE)$(MKDIR) $(subst /,$(PSEP),$(TARGETDIRS)) $(ERRIGNORE)
# Remove all objects, dependencies and executable files generated during the build
clean:
$(HIDE)$(RMDIR) $(subst /,$(PSEP),$(TARGETDIRS)) $(ERRIGNORE)
$(HIDE)$(RM) $(TARGET) $(ERRIGNORE)
@echo Cleaning done !
Main features
- Automatic detection of
C
sources in specified folders - Multiple source folders
- Multiple corresponding target folders for object and dependency files
- Automatic rule generation for each target folder
- Creation of target folders when they don't exist
- Dependency management with
gcc
: Build only what is necessary - Works on
Unix
andDOS
systems - Written for
GNU Make
How to use this Makefile
To adapt this Makefile to your project you have to :
- Change the
TARGET
variable to match your target name - Change the name of the
Sources
andBuild
folders inSOURCEDIR
andBUILDDIR
- Change the verbosity level of the Makefile in the Makefile itself or in make call (
make all VERBOSE=FALSE
) - Change the name of the folders in
DIRS
to match your sources and build folders - If required, change the compiler and the flags
In this Makefile Folder0
, Folder1
and Folder2
are the equivalent to your FolderA
, FolderB
and FolderC
.
Note that I have not had the opportunity to test it on a Unix system at the moment but it works correctly on Windows.
Explanation of a few tricky parts :
Ignoring Windows mkdir errors
ERRIGNORE = 2>NUL || true
This has two effects :
The first one, 2>NUL
is to redirect the error output to NUL, so as it does not comes in the console.
The second one, || true
prevents the command from rising the error level. This is Windows stuff unrelated with the Makefile, it's here because Windows' mkdir
command rises the error level if we try to create an already-existing folder, whereas we don't really care, if it does exist that's fine. The common solution is to use the if not exist
structure, but that's not UNIX-compatible so even if it's tricky, I consider my solution more clear.
Creation of OBJS containing all object files with their correct path
OBJS := $(subst $(SOURCEDIR),$(BUILDDIR),$(SOURCES:.c=.o))
Here we want OBJS to contain all the object files with their paths, and we already have SOURCES which contains all the source files with their paths. $(SOURCES:.c=.o)
changes *.c in *.o for all sources, but the path is still the one of the sources.$(subst $(SOURCEDIR),$(BUILDDIR), ...)
will simply subtract the whole source path with the build path, so we finally have a variable that contains the .o files with their paths.
Dealing with Windows and Unix-style path separators
SEP=\\
SEP = /
PSEP = $(strip $(SEP))
This only exist to allow the Makefile to work on Unix and Windows, since Windows uses backslashes in path whereas everyone else uses slashes.
SEP=\\
Here the double backslash is used to escape the backslash character, which make
usually treats as an "ignore newline character" to allow writing on multiple lines.
PSEP = $(strip $(SEP))
This will remove the space char of the SEP
variable, which has been added automatically.
Automatic generation of rules for each target folder
define generateRules
$(1)/%.o: %.c
@echo Building $$@
$(HIDE)$(CC) -c $$(INCLUDES) -o $$(subst /,$$(PSEP),$$@) $$(subst /,$$(PSEP),$$<) -MMD
endef
That's maybe the trick that is the most related with your usecase. It's a rule template that can be generated with $(eval $(call generateRules, param))
where param
is what you can find in the template as $(1)
.
This will basically fill the Makefile with rules like this for each target folder :
path/to/target/%.o: %.c
@echo Building $@
$(HIDE)$(CC) -c $(INCLUDES) -o $(subst /,$(PSEP),$@) $(subst /,$(PSEP),$<) -MMD
How do you get the list of targets in a makefile?
Note: This answer has been updated to still work as of GNU make
v4.3 - let us know if you come across something that breaks.
This is an attempt to improve on Brent Bradburn's great approach as follows:
- uses a more robust command to extract the target names, which hopefully prevents any false positives (and also does away with the unnecessary
sh -c
) - does not invariably target the makefile in the current directory; respects makefiles explicitly specified with
-f <file>
- excludes hidden targets - by convention, these are targets whose name starts neither with a letter nor a digit
- makes do with a single phony target
- prefixes the command with
@
to prevent it from being echoed before execution
Curiously, GNU make
has no feature for listing just the names of targets defined in a makefile. While the -p
option produces output that includes all targets, it buries them in a lot of other information and also executes the default target (which could be suppressed with -f/dev/null
).
Place the following rule in a makefile for GNU make
to implement a target named list
that simply lists all target names in alphabetical order - i.e.: invoke as make list
:
.PHONY: list
list:
@LC_ALL=C $(MAKE) -pRrq -f $(lastword $(MAKEFILE_LIST)) : 2>/dev/null | awk -v RS= -F: '/(^|\n)# Files(\n|$$)/,/(^|\n)# Finished Make data base/ {if ($$1 !~ "^[#.]") {print $$1}}' | sort | egrep -v -e '^[^[:alnum:]]' -e '^$@$$'
Important: On pasting this, make sure that the last line is indented by exactly 1 actual tab char. (spaces do not work).
Note that sorting the resulting list of targets is the best option, since not sorting doesn't produce a helpful ordering in that the order in which the targets appear in the makefile is not preserved.
Also, the sub-targets of a rule comprising multiple targets are invariably output separately and will therefore, due to sorting, usually not appear next to one another; e.g., a rule starting with a z:
will not have targets a
and z
listed next to each other in the output, if there are additional targets.
Explanation of the rule:
.
PHONY: list
- declares target list a phony target, i.e., one not referring to a file, which should therefore have its recipe invoked unconditionally
LC_ALL=C
makes sure thatmake
's output in in English, as parsing of the output relies on that.Tip of the hat to Bastian Bittorf$(MAKE) -pRrq -f $(lastword $(MAKEFILE_LIST)) : 2>/dev/null
- Invokes
make
again in order to print and parse the database derived from the makefile:-p
prints the database-Rr
suppresses inclusion of built-in rules and variables-q
only tests the up-to-date-status of a target (without remaking anything), but that by itself doesn't prevent execution of recipe commands in all cases; hence:-f $(lastword $(MAKEFILE_LIST))
ensures that the same makefile is targeted as in the original invocation, regardless of whether it was targeted implicitly or explicitly with-f ...
.
Caveat: This will break if your makefile containsinclude
directives; to address this, define variableTHIS_FILE := $(lastword $(MAKEFILE_LIST))
before anyinclude
directives and use-f $(THIS_FILE)
instead.:
is a deliberately invalid target that is meant to ensure that no commands are executed;2>/dev/null
suppresses the resulting error message. Note: This relies on-p
printing the database nonetheless, which is the case as of GNU make 3.82. Sadly, GNU make offers no direct option to just print the database, without also executing the default (or given) task; if you don't need to target a specific Makefile, you may usemake -p -f/dev/null
, as recommended in theman
page.
- Invokes
-v RS=
- This is an awk idiom that breaks the input into blocks of contiguous non-empty lines.
/(^|\n)# Files(\n|$$)/,/(^|\n)# Finished Make data base/
- Matches the range of lines in the output that contains all targets, across paragraphs - by limiting parsing to this range, there is no need to deal with false positives from other output sections.
- Note: Between
make
versions 3.x and 4.3, paragraph structuring inmake
's output changed, so(^|\n)
/(\n|$$)
ensures that the lines that identify the start and the end of the cross-paragraph range of lines of interest are detected irrespective of whether they occur at the start or inside / at the end of a paragraph.
if ($$1 !~ "^[#.]")
- Selectively ignores blocks:
#
... ignores non-targets, whose blocks start with# Not a target:
.
... ignores special targets
- All other blocks should each start with a line containing only the name of an explicitly defined target followed by
:
- Selectively ignores blocks:
egrep -v -e '^[^[:alnum:]]' -e '^$@$$'
removes unwanted targets from the output:'^[^[:alnum:]]'
... excludes hidden targets, which - by convention - are targets that start neither with a letter nor a digit.'^$@$$'
... excludes thelist
target itself
Running make list
then prints all targets, each on its own line; you can pipe to xargs
to create a space-separated list instead.
Related Topics
How to Shield a CPU from the Linux Scheduler (Prevent It Scheduling Threads Onto That Cpu)
How to Delete All Files Older Than 3 Days When "Argument List Too Long"
Sanitize Environment with Command or Bash Script
Does Linux Kill Background Processes If We Close the Terminal from Which It Has Started
How to Set the Grep After Context to Be "Until the Next Blank Line"
Linux: Set Permission Only to Directories
Preserve Colouring After Piping Grep to Grep
Installing Node.Js on Debian 6.0
How to Learn the Structure of Linux Wireless Drivers (Mac80211)
Linux Free Shows High Memory Usage But Top Does Not
Internals of a Linux System Call
Remove the Last Page of a PDF File Using PDFtk
How to View Log Files in Linux and Apply Custom Filters While Viewing
How to Delete Multiple Files at Once in Bash on Linux
How the Util of iOStat Is Computed