Timeline
Timeline
2025-11-01
init
This article introduces the basic concepts and syntax of Makefiles, including core features such as rules (targets, dependencies, commands), phony targets, automatic variables, wildcards, static pattern rules, pattern rules, and double-colon rules, and discusses advanced usage such as command execution, echo control, error handling, recursive invocation, and environment variable passing. The article emphasizes that Makefiles are mainly used to determine which parts of a large program need to be recompiled, especially for C/C++ projects, while pointing out that interpreted languages usually do not need such build tools.
Reference documents:
There is also a well-translated article:
Introduction to Makefiles
Makefiles are used to help decide which parts of a large program need to be recompiled.**In the vast majority of cases, only C or C++ files need to be compiled.**Other languages usually have their own set of tools similar in purpose to Make.The use of Make is not limited to programming.。
Besides Make, there are other popular build systems to choose from, such as SCons, CMake, Bazel, and Ninja. Some code editors, like Microsoft Visual Studio, have their own built-in build tools. Java has build tools such as Ant, Maven, and Gradle, and other languages like Go and Rust each have their own build tools.
Interpreted languages like Python, Ruby, and JavaScript do not need anything like Makefiles. The goal of Makefiles is to compile everything that needs to be compiled based on which files have changed. However,When a file in an interpreted language changes, there is no need to recompile; the program will use the latest version of the source file at runtime.。
Makefile Syntax
A Makefile consists of a series of rules and a rule looks like this:
1234 | targets: prerequisites command command command |
targetsrefers to file names, with multiple file names separated by spaces. Usually, a rule corresponds to only one file.commandsusually a series of steps used to make one or more targets. They need to start with a tab character, not spaces.prerequisitesare also file names, with multiple file names separated by spaces. Before running the target’scommandsyou need to ensure these files exist. They are also called Depends。
Example:
12345678 | blah: blah.o cc blah.o -o blah # Runs thirdblah.o: blah.c cc -c blah.c -o blah.o # Runs secondblah.c: echo "int main() { return 0; }" > blah.c # Runs first |
-coption compiles only without linking,-o fileputs the output of the preceding command into a file file Middle.
Targets
Depends
Example:
1234567 | some_file: other_file echo "This will run second, because it depends on other_file" touch some_fileother_file: echo "This will run first" touch other_file |
targetsome_fileDependsother_file. When we runmake, the default target (i.e.some_file, because it is the first) its build will be invoked.The build system first looks at the target’s dependency list. If there are outdated target files among them, the build system will first perform target builds for these dependencies, and only then will it get to the default target.. On the second executionmake, the commands under the default target and dependency targets will no longer run, because both already exist.
Phony targets or virtual targets
12345 | some_file: other_file touch some_fileother_file: echo "nothing" |
Similar to the aboveother_fileSuch targets are commonly known as phony targets or virtual targets。
The All Targets
1234567891011 | all: one two threeone: touch onetwo: touch twothree: touch threeclean: rm -f one two three |
The targets executed by default when running the make command
Multiple Targets
123456789 | all: f1.o f2.of1.o f2.o: echo $@# Equivalent to:# f1.o# echo $@# f2.o# echo $@ |
When a rule has multiple targets, the commands under this rule will run once for each target.
$@is an automatic variable that refers to the target name (Automatic Variables).
Automatic Variables
Wildcard*
In Make,%and*are both called wildcards, but they are two completely different things.*will search your file system to match file names.
It is recommended to always usewildcarda function to wrap it, otherwise you may fall into a trap.
Notwildcardwrapped*has nothing to recommend it except to add confusion.
123 | # Print out the file information of each .c fileprint: $(wildcard *.c) ls -la $? |
*cannot be used directly in variable definitions.when
*When no files match, it will remain as is (unless it iswildcardwrapped by a function).
12345678910111213141516 | thing_wrong := *.o # Please don't do this! '*.o' will not be replaced with the actual file namething_right := $(wildcard *.o)all: one two three four# It fails because $(thing_wrong) is the string "*.o"one: $(thing_wrong)# If there is no file matching this pattern, it will remain as *.o :(two: *.o # Works as expected! In this case, nothing will be executedthree: $(thing_right)# Same as rule threefour: $(wildcard *.o) |
Wildcard%
- When used in ‘match’ mode, itmatches one or more characters in a string, this kind of matching is called stem matching.
- When used in ‘replace’ mode, it replaces the matched stem.
%Mostly used in rule definitions and some specific functions.
Automatic variables
automatic variables
Although there are many automatic variables, only a few are commonly used
$@
The current rule’starget file name (target)
$?
In the current rulelist of dependency files newer than the target file
Example: for the rule:foo: a.c b.c, ifb.cupdate →$?→b.c
$<
The current rule’sfirst dependency file (prerequisite)
$^
The current rule’sall dependency files (deduplicated)
Fancy Rules
Implicit rules
Make loves C compilation, and every time it expresses its affection, it does some ‘confusing things’. The most confusing part might be its magical rules, which Make calls ‘implicit rules’. They are not recommended.
The implicit rules are listed below:
- When compiling C programs: use
$(CC) -c $(CPPFLAGS) $(CFLAGS)a command of the form,n.owill ben.cautomatically generated. - When compiling C++ programs: use
$(CXX) -c $(CPPFLAGS) $(CXXFLAGS)a command of the form,n.owill ben.ccorn.ppautomatically generated. - When linking a single object file: by running
$(CC) $(LDFLAGS) n.o $(LOADLIBES) $(LDLIBS)command,nwill ben.oautomatically generated.
The meanings of the variables used by the above implicit rules are as follows:
CC: the program for compiling C programs, defaults toccCXX: the program for compiling C++ programs, defaults tog++CFLAGS: extra flags provided to the C compilerCXXFLAGS: extra flags provided to the C++ compilerCPPFLAGS: extra flags provided to the C preprocessorLDFLAGS: extra flags provided to the compiler when it should invoke the linker
Examples of implicit rules:
123456789101112 | CC = gcc # Flag for implicit rulesCFLAGS = -g # Flag for implicit rules. Turn on debug info# Implicit rule #1: blah is built via the C linker implicit rule# Implicit rule #2: blah.o is built via the C compilation implicit rule, because blah.c existsblah: blah.oblah.c: echo "int main() { return 0; }" > blah.cclean: rm -f blah* |
Static pattern rules
12 | targets ...: target-pattern: prereq-patterns ... commands |
Its essence is: the given targettargetbytarget-patternBeforetargetsis matched in (using wildcard%). The matched content is called the stem. Then,Substitute the stem intoprereq-patternand thereby generate the target’sprerequisitespart.
A typical use case of static pattern rules is to.ccompile files into.ofile.
manually method:
12345678910111213141516 | objects = foo.o bar.o all.oall: $(objects)# These files compile via implicit rulesfoo.o: foo.cbar.o: bar.call.o: all.call.c: echo "int main() { return 0; }" > all.c%.c: touch $@clean: rm -f *.c *.o all |
static pattern method:
1234567891011121314151617 | objects = foo.o bar.o all.oall: $(objects)# These files compile via implicit rules# Syntax - targets ...: target-pattern: prereq-patterns ...# In the case of the first target, foo.o, the target-pattern matches foo.o and sets the "stem" to be "foo".# It then replaces the '%' in prereq-patterns with that stem$(objects): %.o: %.call.c: echo "int main() { return 0; }" > all.c%.c: touch $@clean: rm -f *.c *.o all |
Explanation:
1 | $(objects): %.o: %.c |
This line is crucial; it means:
For
$(objects)(i.e., foo.o, bar.o, all.o), if there is a matching rule%.o, then it depends on the corresponding%.cfile.
For example:
foo.oDependsfoo.cbar.oDependsbar.call.oDependsall.c
This is actually equivalent to:
123 | foo.o: foo.cbar.o: bar.call.o: all.c |
But it is automatically generated in one line, demonstrating the power of static pattern rules。
12345 | all.c: echo "int main() { return 0; }" > all.c%.c: touch $@ |
whenmakefindsfoo.oNeedfoo.cwhen it does not exist,
it will trigger%.c:rule to create an empty filefoo.c
whenmakeNeedall.owhen, it will triggerall.c:rule (generating a file with content)
It can also be written like this:
12345678910111213141516171819202122 | # Define the target file listobjects = foo.o bar.o all.o# final targetall: $(objects) $(CC) -o all $(objects)# Generic compilation rule: any .o is compiled from the .c with the same name%.o: %.c $(CC) -c $< -o $@# Generate all.c (if it does not exist)all.c: echo "int main() { return 0; }" > all.c# If there is no corresponding .c file, automatically create an empty file%.c: touch $@# Cleanupclean: rm -f *.c *.o all |
Static pattern rules and filters
Example:
12345678910111213141516 | obj_files = foo.result bar.o lose.osrc_files = foo.raw bar.c lose.call: $(obj_files)$(filter %.o,$(obj_files)): %.o: %.c echo "target: $@ prereq: $<"$(filter %.result,$(obj_files)): %.result: %.raw echo "target: $@ prereq: $<" %.c %.raw: touch $@clean: rm -f $(src_files) |
Declaring phony targets
1 | |
- means
allandcleanis not an actual file, but a logical command. - Avoid having a file named
allthat causes make to be confused.
Main target (entry)
1 | all: $(obj_files) |
Default target
allDepends on allobj_files。When executing
makeMake will build:- foo.result
- bar.o
- lose.o
Usefilterrules for filtering different types of files
for.oFile rules:
12 | $(filter %.o,$(obj_files)): %.o: %.c @echo "target: $@ prereq: $<" |
Explanation:
$(filter %.o,$(obj_files))
→ Fromobj_filesfilter out.ofiles ending with:1bar.o lose.oAfter expansion, it is equivalent to:
12
bar.o lose.o: %.o: %.c @echo "target: $@ prereq: $<"
%.o: %.cYes Static pattern rules:- Targets of the form
xxx.o, depend on the same-namedxxx.c。 $@: represents the target file name (e.g.bar.o)$<: represents the first dependency file (e.g.bar.c)
- Targets of the form
This rule does not actually compile, it just prints information:
12 | target: bar.o prereq: bar.ctarget: lose.o prereq: lose.c |
for.resultFile rules:
12 | $(filter %.result,$(obj_files)): %.result: %.raw @echo "target: $@ prereq: $<" |
Similarly:
$(filter %.result,$(obj_files))→ the result isfoo.resultAfter expansion, it is equivalent to:
12
foo.result: %.result: %.raw @echo "target: $@ prereq: $<"
Meaning: to generatefoo.result, which requiresfoo.raw
Pattern rules
An example:
123 | # Define a pattern rule that compiles every .c file into a .o file%.o : %.c $(CC) -c $(CFLAGS) $(CPPFLAGS) $< -o $@ |
A pattern rule contains a%, this%matches any non-empty string, other characters match themselves. A pattern rule’sprerequisitein%represents in the target%the same stem that was matched.
Another example:
1234 | # Define a pattern rule that has no pattern in the prerequisites.# This just creates empty .c files when needed.%.c: touch $@ |
Double-colon rules
12 | target :: prerequisites commands |
meaning:
- A target can be defined multiple times, and each rule exists independently.
- As long as one rule’s prerequisite file is updated, that rule’s commands will be executed separately.
Example:
12345 | foo :: a echo "rule 1 triggered by a"foo :: b echo "rule 2 triggered by b" |
Execution:
1 | $ make foo |
Assume:
athanfoonewer (updated)bthanfooolder (not updated)
Output result:
1 | rule 1 triggered by a |
ifa、bare bothfoonewer, then output:
12 | rule 1 triggered by arule 2 triggered by b |
Commands and Execution
Echo/Silent commands
Add one before a command@The symbol will suppress the output of the command.
You can also usemake -sadd before each command@。
123 | all: @echo "This make line will not be printed" echo "But this will" |
Command execution
Each command runs in a new shell (or the effect is equivalent to running in a new shell).
1234567891011 | all: cd .. # The cd above does not affect this line, because each command is effectively run in a new shell echo `pwd` # This cd command affects the next because they are on the same line cd ..;echo `pwd` # Same as above cd ..; \ echo `pwd` |
Changing the default shell
The system default shell is/bin/sh, which can be changed by changingSHELLthe value of the variable to change it:
1234 | SHELL=/bin/bashcool: echo "Hello from bash" |
Error handling:-k,-iand-
make -kwill cause the build to continue even if errors are encountered. Often used to view all of Make’s errors at once.
Adding before a command-will suppress errors.
make -iis equivalent to adding before each command-。
1234 | one: # This error will be printed but ignored, and make will continue to run -false touch one |
interrupt or killmake
Beforemakeduring the process, usingctrl+c, then the newly built target will be deleted.
makeRecursive usage
To recursively apply a makefile, use$(MAKE)rather thanmake, because$(MAKE)will automatically inherit the current make’s command-line options (such as-j、-setc.), while directly callingmakewill not pass these flags.
12345678 | new_contents = "hello:\n\ttouch inside_file"all: mkdir -p subdir printf $(new_contents) | sed -e 's/^ //' > subdir/makefile cd subdir && $(MAKE)clean: rm -rf subdir |
| Syntax | behavior | Recommended |
|---|---|---|
make | ordinary command, does not inherit make’s options | ❌ |
$(MAKE) | Special variable representing the current make program itself, automatically inheriting options. | ✅ |
Environment variables
export
directiveexportcarries a variable, and to sub-makecommands visible.
In the following example, the variablecoolyis exported so that makefiles in subdirectories can use it.
1234567891011121314151617 | new_contents = "hello:\n\\techo \$$(cooly)"all: mkdir -p subdir echo $(new_contents) | sed -e 's/^ //' > subdir/makefile @echo "---MAKEFILE CONTENTS---" @cd subdir && cat makefile @echo "---END MAKEFILE CONTENTS---" cd subdir && $(MAKE)# Note that variables and exports. They are set/affected globally.cooly = "The subdirectory can see me!"export cooly# This would nullify the line above: unexport coolyclean: rm -rf subdir |
Passing variables to the shell
Passing variables to the shell also requires export
12345678 | one=this will only work locallyexport two=we can run subcommands with thisall: @echo $(one) @echo $$one @echo $(two) @echo $$two |
.EXPORT_ALL_VARIABLES
.EXPORT_ALL_VARIABLES can export all variables.
12345678910111213141516 | .EXPORT_ALL_VARIABLES:new_contents = "hello:\n\techo \$$(cooly)"cooly = "The subdirectory can see me!"# This would nullify the line above: unexport coolyall: mkdir -p subdir echo $(new_contents) | sed -e 's/^ //' > subdir/makefile @echo "---MAKEFILE CONTENTS---" @cd subdir && cat makefile @echo "---END MAKEFILE CONTENTS---" cd subdir && $(MAKE)clean: rm -rf subdir |
tomakePassing arguments
| Options / Usage | Full command | Description | example |
|---|---|---|---|
-n/--dry-run | make --dry-run | Only display the commands that will be executed, but do not actually run them. Used for debugging Makefiles. | make --dry-run all(view the execution flow) |
-t/--touch | make --touch | Mark target files as ‘up to date’ (update timestamps), but do not actually execute commands. Often used to skip actual builds. | make --touch all |
-o <file>/--old-file=<file> | make --old-file=foo.o | Specify a file to be treated as an ‘old file’, so that targets depending on it will not be rebuilt. | make --old-file=main.o |
You can pass multiple targets to
make, for examplemake clean run testwill run sequentiallyclean、run、test。
variable
Types of variables
- Recursive variables(using
=)- Variables are looked up only when the command is executed, not at definition time. - Simply expanded variables(using
:=) - Just like ordinary imperative programming—only variables that have already been defined will be expanded.
Example:
12345678910 | # Recursive variable. Will print 'later'one = one ${later_variable}# Simply expanded variable. Will not print 'later'two := two ${later_variable}later_variable = laterall: echo $(one) echo $(two) |
123456 | one = hello# one gets defined as a simply expanded variable (:=) and thus can handle appendingone := ${one} thereall: echo $(one) |
?=
Set a value to a variable if it hasn’t been set yet, otherwise ignore it.
1234567 | one = helloone ?= will not be settwo ?= will be setall: echo $(one) echo $(two) |
+=
Used to append to a variable’s value:
12345 | foo := startfoo += moreall: echo $(foo) |
Command-line variable
Can pass throughoverrideto override variables from the command line.
Suppose we run a command like this using the following makefilemake option_one=hi, then the variableoption_one's value will be overridden.
1234567 | # Overrides command line argumentsoverride option_one = did_override# Does not override command line argumentsoption_two = not_overrideall: echo $(option_one) echo $(option_two) |
#define defines a command list
It has no relationship to the functiondefinewhatsoever.
Example:
1234567891011121314 | one = export blah="I was set!"; echo $$blahdefine twoexport blah=setecho $$blahendef# One and two are different.all: @echo "This prints 'I was set'" @$(one) @echo "This does not print 'I was set' because each command runs in a separate shell" @$(two) |
Note here that this is slightly different from the scenario of separating multiple commands with semicolons, because in the former, as expected, each command runs in a separate shell.
That is:Each command line(in the recipe) is in a new independent shell instance executed in. So changes such as environment variables or the current directory do not automatically carry over to the next line.
For example:
123 | all: export FOO=bar echo $$FOO |
will not print anything, because:
- Line 1 runs in one shell and sets the environment variable.
- Line 2 runs in another shell and cannot see the environment of the previous shell.
Target-specific variables
We can assign variables to specific targets.
1234567 | all: one = coolall: echo one is defined: $(one)other: echo one is nothing: $(one) |
Pattern-specific variables
We can for specific targets Mode Assign variable.
1234567 | %.c: one = coolblah.c: echo one is defined: $(one)other: echo one is nothing: $(one) |
Makefile conditional judgment
if/else
12345678 | foo = okall:ifeq ($(foo), ok) echo "foo equals ok"else echo "nope"endif |
strip checks whether the variable is empty
12345678910 | nullstring =foo = $(nullstring) # end of line; there is a space hereall:ifeq ($(strip $(foo)),) echo "foo is empty after being stripped"endififeq ($(nullstring),) echo "nullstring doesn't even have spaces"endif |
ifdef checks whether the variable is defined
ifdefIt does not expand variable references; it only checks whether the variable’s content is defined.
12345678910 | bar =foo = $(bar)all:ifdef foo echo "foo is defined"endififdef bar echo "but bar is not"endif |
$(makeflags)
MAKEFLAGSYes GNU Make built-in variables, it automatically saves the currentmakeall command-line options.
| Calling Method | MAKEFLAGSthe content of |
|---|---|
make | (Empty) |
make -i | i |
make -k | k |
make -ik | ik |
make -n -s | ns |
make -j4 | j4(including numeric arguments) |
12345678 | bar =foo = $(bar)all:# Search for the "-i" flag. MAKEFLAGS is just a list of single characters, one per flag. So look for "i" in this case.ifneq (,$(findstring i, $(MAKEFLAGS))) echo "i was passed to MAKEFLAGS"endif |
$(findstring i, $(MAKEFLAGS))
- This is one of the GNU Make built-in functions.
- Function: find the first string in the second string.
- If found, return the first string (here
"i"); - If not found, return an empty string.
ifneq (,$(...))
- Syntax:
ifneq (arg1, arg2)
Indicates that if arg1 and arg2 are not equal, execute the subsequent statements. - Here it is written as
(, $(findstring ...))
It means 'if the result of findstring Non-empty”。
Therefore the entire logic is equivalent to:
if
MAKEFLAGScontains the letteri, then execute echo.
function
function Mainly used for text processing. The syntax of a function call is$(fn, arguments)or${fn, arguments}. You can use built-in functionscallto create your own functions. Make has a large number of built-in functions.
123 | bar := ${subst not, totally, "I am not superman"}all: @echo $(bar) |
If you want to replace spaces or commas, you need to use variables:
12345678 | comma := ,empty:=space := $(empty) $(empty)foo := a b cbar := $(subst $(space),$(comma),$(foo))all: @echo $(bar) |
Don’t Arguments after the first parameter that contain spaces will be treated as part of the string.
123456789 | comma := ,empty:=space := $(empty) $(empty)foo := a b cbar := $(subst $(space), $(comma) , $(foo))all: # Output is ", a , b , c". Notice the spaces introduced @echo $(bar) |
patsubst string substitution
$(patsubst pattern,replacement,text)does the following:
“Finds whitespace-separated words in the text that match, and replaces them with
replacementreplace them. Herepatterncan contain a%as a wildcard to match any number of any characters in a word. Ifreplacementalso contains a%, then the content it represents will bepatternin%replaced by the matched content. Onlypatternandreplacementthe first in%will exhibit this behavior, and any subsequent%will remain unchanged.
$(text:pattern=replacement)is a shorthand.
There is also a shorthand that only replaces the suffix:$(text:suffix=replacement), where no wildcard is used%。
Note: In the shorthand form,do not add extra spaces, it will be treated as a search or replacement term.
1234567891011 | foo := a.o b.o l.a c.oone := $(patsubst %.o,%.c,$(foo))# This is a shorthand for the abovetwo := $(foo:%.o=%.c)# This is the suffix-only shorthand, and is also equivalent to the above.three := $(foo:.o=.c)all: echo $(one) echo $(two) echo $(three) |
foreach
functionforeachlooks like this:$(foreach var,list,text), it is used toconvert a space-separated list of words into another。
varrepresents each word in the looplistrepresents the variable to loop overtextused to expand each word.
Example: append an exclamation mark after each word:
1234567 | foo := who are you# For each "word" in foo, output that same word with an exclamation afterbar := $(foreach wrd,$(foo),$(wrd)!)all: # Output is "who! are! you!" @echo $(bar) |
if
1 | $(if condition, then-part, else-part) |
ifThe function is used tocheck whether its first argument is non-empty。if non-empty, run the second argument, otherwise run the third。
1234567 | foo := $(if this-is-not-empty,then!,else!)empty :=bar := $(if $(empty),then!,else!)all: @echo $(foo) @echo $(bar) |
call
Make supports creating basic functions. You simply ‘define’ a function by creating a variable, except that parameters are used.$(0)、$(1)etc. Then, you can use a special functioncallto call it, with the syntax$(call variable,param,param)。$(0)is the variable name, and$(1)、$(2)etc. are the parameters.
12345 | sweet_new_fn = Variable Name: $(0) First: $(1) Second: $(2) Empty Variable: $(3)all: # Outputs "Variable Name: sweet_new_fn First: go Second: tigers Empty Variable:" @echo $(call sweet_new_fn, go, tigers) |
shell
shell- calls the shell, butit replaces newlines with spaces in the output.
12 | all: @echo $(shell ls -la) # Very ugly because the newlines are gone! |
Other features
include
includeThe directive tellsmaketo read other makefiles; it is a line in the makefile, as shown below:
1 | include filenames... |
vpath
vpathThe directive is used to specify certainprerequisiteslocations, using the formatvpath <pattern> <directories, space/colon separated>。
vpathtells Make:which directories to look in when a dependency file is not in the current directory。
<pattern>you can use%, to match zero or more characters.
You can also use the variableVPATHto do this globally.
1234567891011121314 | vpath %.h ../headers ../other-directorysome_binary: ../headers blah.h touch some_binary../headers: mkdir ../headersblah.h: touch ../headers/blah.hclean: rm -rf ../headers rm -f some_binary |
1 | vpath %.h ../headers ../other-directory |
Meaning: for all files ending in .h (i.e., header files), if they are not found in the current directory, go in turn to../headersand../other-directoryto search.
Multi-line handling
When a command is too long, a backslash (\) allows us to use a multi-line format.
123 | some_file: echo This line is too long, so \ it is broken up into multiple lines |
.PHONY
Adding to a target.PHONYwillavoid treating a phony target as a file name.。
In the following example, even if the filecleanis created,make cleanit will still run..PHONYVery useful.
12345678 | some_file: touch some_file touch cleanclean: rm -f some_file rm -f clean |
.DELETE_ON_ERROR
If a command returns a non-zero exit code, thenmakewill stop running the corresponding rule (and will propagate to its dependencies). If a rule fails to build for the above reason, then having applied.DELETE_ON_ERRORafterwards, the target file of this rule will be deleted.
Unlike.PHONY,.DELETE_ON_ERRORapplies to all targets. Always use.DELETE_ON_ERRORis a good choice, even if for historical reasons,makedoes not support it.
12345678910 | .DELETE_ON_ERROR:all: one twoone: touch one falsetwo: touch two false |
Example
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849 | TARGET_EXEC := final_programBUILD_DIR := ./buildSRC_DIRS := ./src# Find all the C and C++ files we want to compile# Note the single quotes around the * expressions. Make will incorrectly expand these otherwise.SRCS := $(shell find $(SRC_DIRS) -name '*.cpp' -or -name '*.c' -or -name '*.s')# String substitution for every C/C++ file.# As an example, hello.cpp turns into ./build/hello.cpp.oOBJS := $(SRCS:%=$(BUILD_DIR)/%.o)# String substitution (suffix version without %).# As an example, ./build/hello.cpp.o turns into ./build/hello.cpp.dDEPS := $(OBJS:.o=.d)# Every folder in ./src will need to be passed to GCC so that it can find header filesINC_DIRS := $(shell find $(SRC_DIRS) -type d)# Add a prefix to INC_DIRS. So moduleA would become -ImoduleA. GCC understands this -I flagINC_FLAGS := $(addprefix -I,$(INC_DIRS))# The -MMD and -MP flags together generate Makefiles for us!# These files will have .d instead of .o as the output.CPPFLAGS := $(INC_FLAGS) -MMD -MP# The final build step.$(BUILD_DIR)/$(TARGET_EXEC): $(OBJS) $(CC) $(OBJS) -o $@ $(LDFLAGS)# Build step for C source$(BUILD_DIR)/%.c.o: %.c mkdir -p $(dir $@) $(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@# Build step for C++ source$(BUILD_DIR)/%.cpp.o: %.cpp mkdir -p $(dir $@) $(CXX) $(CPPFLAGS) $(CXXFLAGS) -c $< -o $@clean: rm -r $(BUILD_DIR)# Include the .d makefiles. The - at the front suppresses the errors of missing# Makefiles. Initially, all the .d files will be missing, and we don't want those# errors to show up.-include $(DEPS) |
