Makefile: Difference between revisions

4,189 bytes added ,  26 days ago
m
Bot: Replace deprecated source tag with syntaxhighlight
[unchecked revision][unchecked revision]
(Added "What Is A" and a basic example to aid complete beginners to makefiles)
m (Bot: Replace deprecated source tag with syntaxhighlight)
 
(32 intermediate revisions by 13 users not shown)
Line 1:
{{Rating|1}}
 
A Makefile is a file which controls the 'make' command. Make is available on virtually every platform, and is used to control the build process of a project. Once a Makefile has been written for a project, make can easily and efficiently build your project and create the required output file(s).
= What is a Makefile? =
A Makefile is a file which controls the 'make' command. The 'make' command is available with most C toolchains (such as GCC) and is used to aid in build process of a project. Once the makefile has been written for a project, the 'make' command can easily and efficiently build your project and create the required output file(s).
 
Make canreads read dependancydependency information from your makefileMakefile, andfigures ensureout thatwhich output files areneed built(re-)building in(because thethey correctare order,either andmissing canor evenolder detectthan whentheir sourcecorresponding input files), orexecutes otherthe dependanciesnecessary havecommands beento updated,actually and''do'' canthe perform(re-)building. onlyThis thecompares favourably to "build stepsbatchfiles" that arealways rebuild the whole requiredproject.
 
In doing this, make is not limited to any specific set of languages. Any tool that takes some input file and generates an output file can be controlled via make.
Your makefile can also contain other commands (or "targets") to remove built files (usually referred to as 'clean') or to create an archive of your project, ready for release.
 
A Makefile can offer several different "targets", which can be invoked by 'make <target>'. A frequently-used target is 'make clean', which removes any temporary files, or 'make install', which installs the project in its target location - but you can also give a specific output file as a target, and have make process only that file. Invoking make without a target parameter builds the default target (which is usually to build the whole project).
= Makefile Tutorial =
Setting up a simple Makefile is easy. You can even go a long way ''without'' a Makefile, simply by using make's internal rulesets, however setting up a global project Makefile that works as intended is quite a bit harder.
 
But 'make' requires a well-written Makefile to do these things efficiently and reliably.
The 'make' manual will tell you about the hundreds of things you can do with a Makefile, but it doesn't give you an example for a ''good'' Makefile. The following examples cover mainly the [http://sf.net/projects/pdclib PDCLib] Makefile, and show some of the "tricks" that may not be that obvious to the 'make' beginner. It aims at GNU 'make', and uses some expressions specific to that particular 'make' implementation (for example the patsubst function). Adaption to any special needs you might have should not be too hard once you got the idea, however.
 
= Makefile tutorial =
 
The 'make' manual will tell you about the hundreds of things you can do with a Makefile, but it doesn't give you an example for a ''good'' Makefile. The following examples are mostly shaped after the real-life [http://pdclib.e43.eu PDCLib] Makefile I used at the time, and shows some of the "tricks" used therein that may not be that obvious to the make beginner.
 
The Makefile creates only one project-wide linker library, but it should be easy to expand it for multiple binaries/libraries.
 
(Note: There are several different flavours of make around, and POSIX defines a common denominator for them. This tutorial specifically targets GNU make. See the [[Talk:Makefile#Which_make_to_target.3F | discussion page]] for further info.)
 
== Basics ==
 
It is best practice to name your makefileMakefile <code>Makefile</code> (without an extension), because when ''make'' is executed without any parameters, it will by default look for that file in the current directory. It also makes the file show up prominently on top of directory listings, at least on Unix machines.
 
Generally speaking, a makefileMakefile consists of ''definitions,'' and ''rules''.
 
A ''definition'' declares a variable, and assigns a value to it. Its overallgeneral syntax is ''<code>VARIABLE := Value''</code>.
 
'''Note:''' Frequently, you will see declarationsdefinitions withusing "=" instead of ":=". That is usuallySuch a typo; such a variable declarationdefinition will result in the assignment being evaluated ''every time the variable is used''., Unlesswhile you":=" knowevaluates exactlyonly once at the startup of make, which is usually what you arewant. Don't go changing other people's doingMakefiles, usethough - ":=" foris variablea GNU extension to the make declarationssyntax.
 
A rule defines a ''target'', 0..n ''dependencies'', and 0..n ''commands''. The general idea is that ''make'' checks if the target (file) is there; if it isn't, or any of the dependencies is newer than the target, the commands are executed. The general syntax is:
 
<syntaxhighlight lang="make">
<pre>
target: dependency
command
</syntaxhighlight>
</pre>
 
Both ''dependency'' and ''command'' are optional. There might be more than one ''command'' line, in which case they are executed in sequence.
Note that the command ''must be tab-indented''. If your editor environment is set to replace tabs with spaces, you have to undo that setting while editing a makefile.
 
Note that commands ''must be tab-indented''. If your editor environment is set to replace tabs with spaces, you have to undo that setting while editing a Makefile.
What makes makefiles so hard to read, for the beginner, is that we are not looking at an imperative program here that is executed top-down; 'make' reads the ''whole'' makefile, and then hops from rule to rule to satisfy whatever target you gave it on the command line.
 
What makes Makefiles so hard to read, for the beginner, is that it is not procedural in syntax (i.e., executed top-down), but functional: 'make' reads the ''whole'' Makefile, building a dependency tree, and then resolves the dependencies by hopping from rule to rule as necessary until the target you gave it on the command line has successfully been resolved.
I won't go into further details. This is not a man page, but a tutorial, so I will show you how a makefile is build, and the ideas behind each line.
 
But let's not go into internal details of 'make'. This is a tutorial, not a man page, so it will show you how a real-life Makefile could be built, and the ideas behind each line.
== Basic Example ==
 
== Automated Testing ==
Lets say you currently build your OS (or other project) using a batch file that looks like this:
<pre>
gcc -c main.c -o main.o -Wall
gcc -c ports.c -o ports.o -Wall
ld -o kernel.o main.o ports.o -i -e _main -Ttext 0x7E00
objcopy -R .note -R .comment -S -O binary kernel.o kernel.bin
</pre>
 
As the Makefile presented in this tutorial takes care of automated testing in a special way, this approach will be explained up front, so the related parts of the tutorial will make sense to you.
You can start out into the world of makefiles by creating a file named "Makefile" that contains the following:
 
As stated above, this tutorial is mainly derived from work done on the PDCLib project - which builds a single linker library. In that project, there is strictly one library function per source file. In each such source file, there is a test driver attached for conditional compilation, like this:
<pre>
<syntaxhighlight lang="C">
all:
#ifdef TEST
gcc -c main.c -o main.o -Wall
int main()
gcc -c ports.c -o ports.o -Wall
{
ld -o kernel.o main.o ports.o -e _main -Ttext 0x7E00
/* Test function code here */
objcopy -R .note -R .comment -S -O binary kernel.o kernel.bin
return NUMBER_OF_TEST_ERRORS;
</pre>
}
 
#endif
Here we are creating a single rule to build a target called "all" that has no dependencies, and providing the list of commands to build this target. With this file in your source directory, executing the command "make" will then find this file, and compile your OS (or other project). (Note specifically that all the commands must start with a single tab character, not spaces.)
</syntaxhighlight>
 
Thus, when that source is compiled with <code>gcc -c</code>, it results in an object file with the library function code; when compiled with <code>gcc -DTEST</code>, it gives a test driver executable for that function. Returning the number of errors allows to do a grand total of errors encountered (see below).
== Special macros ==
 
There are several special predefined macros, of which the following are the most useful:
 
$@ # the target of the rule
$< # the first dependency
$^ # all dependencies
$? # all *changed* dependencies
$+ # all dependencies, preserving duplicates and ordering
 
Once you get more familiar with 'make', check out the others in the make manual.
 
== File Lists ==
 
First, I assemble various "file lists" are assembled which Iare needneeded later in the Makefile.
 
=== Non-Source Files ===
<pre>
# This is a list of all non-source files that are part of the distribution.
AUXFILES := Makefile Readme.txt
</pre>
Further down I have a target ''"dist"'', which packages all source and header files into a tarball. I create lists of the sources and headers anyway, but for auxiliary files which are not used anywhere else in the Makefile, I need this explicit list to have them included in the tarball.
 
<syntaxhighlight lang="make">
=== Project Directories ===
# This is a list of all non-source files that are part of the distribution.
<pre>
AUXFILES := Makefile Readme.txt
PROJDIRS := functions includes internals
</syntaxhighlight>
</pre>
Those are my subdirectories. It could be your sub projects, or whatever. I could use similar "auto detection" for them as I do for my source files further down, but as I like to have temporary subdirectories in my project (for testing, keeping reference docs etc.), that wouldn't work.
 
Further down we will have a target ''"dist"'', which packages all required files into a tarball for distribution. Lists of the sources and headers are created anyway. But there are commonly some auxiliary files, which are not referenced anywhere else in the Makefile but should still end up in the tarball. These are listed here.
''Note that this is not a recursive approach; there is no Makefile in those subdirectories. Dependencies are hard enough to get right if you use one Makefile. Google for a very good paper on the subject titled "recursive make considered harmful"; not only is a single Makefile easier to maintain (once you learned a couple of tricks), it's also more efficient!''
 
=== RecursionProject Directories ===
<syntaxhighlight lang="make">
<pre>
PROJDIRS := functions includes internals
SRCFILES := $(shell find $(PROJDIRS) -mindepth 1 -maxdepth 3 -name "*.c")
</syntaxhighlight>
HDRFILES := $(shell find $(PROJDIRS) -mindepth 1 -maxdepth 3 -name "*.h")
Those are subdirectories holding the actual sources. (Or rather, searched for source files automatically, see below.) These could be subprojects, or whatever. We could simply search for source files starting with the current working directory, but if you like to have temporary subdirectories in your project (for testing, keeping reference sources etc.), that wouldn't work.
</pre>
While it should be obvious to see what these two lines do, it took some experimentation to get it right given the typical GNU-style documentation of ''make'' (which is correct, but not very helpful). I now have a list of all source and header files in my project directories.
 
''Note that this is not a recursive approach; there is no Makefile in those subdirectories. Dependencies are hard enough to get right if you use one Makefile. At the bottom of this article is a link to a very good paper on the subject titled "recursive make considered harmful"; not only is a single Makefile easier to maintain (once you learned a couple of tricks), it can also be more efficient!''
''The -mindepth option keeps any top-level files out of the result set, like tmp.c or test.c or whatever you might have created for testing something on the fly. The -maxdepth option stops the 'find' to recurse into the CVS/ directories I used to have. You might want to adapt this to your requirements.''
 
=== AutomatedSources Testing,and pt. 1Headers ===
<syntaxhighlight lang="make">
I think it's time I explain my approach to testing. I write strictly one library function per source file. Then, I add a test driver to that source file:
SRCFILES := $(shell find $(PROJDIRS) -type f -name "\*.c")
<pre>
HDRFILES := $(shell find $(PROJDIRS) -type f -name "\*.h")
#ifdef TEST
</syntaxhighlight>
int main()
It should be obvious to see what these two lines do. We now have a list of all source and header files in our project directories.
{
/* Test function code here */
return NUMBER_OF_TEST_ERRORS;
}
#endif
</pre>
Thus, when I compile that source with ''gcc -c'', I get an object file with the library function code; when I compile with ''gcc -DTEST'', I get a test driver executable for that function. Returning the number of errors allows me to do a grand total of errors encountered (see below).
 
=== Object Files and Test Driver Executables ===
<syntaxhighlight lang="make">
<pre>
OBJFILES := $(patsubst %.c,%.o,$(SRCFILES))
TSTFILES := $(patsubst %.c,%.t_t,$(SRCFILES))
</syntaxhighlight>
</pre>
''OBJFILES'' should be clear - a list of the source files, with ''*.c'' exchanged for ''*.o''. ''TSTFILES'' does the same for the extensionfilename suffix ''*.t_t'', which Iwe will choseuse for myour test driver executables.
 
:''Note: This tutorial initially used ''*.t'' instead of ''*_t'' here; this, however, kept us from handling dependencies for test driver executables seperately from those for library object files, which was necessary. See next section.''
 
=== Dependencies, pt. 1 ===
Many people edit their Makefile every time they add/change an ''#include'' somewhere in their code. Did(Or youforget know that ''GCC'' canto do this automatically for you? Yesso, it can. Trust me. Although the approach looks a little backward. For each source file, ''GCC'' will create a ''dependency file'' (which I made endresulting in ''*.d''), which contains a Makefile dependency rule listing that source file's includes. (And more,some butscratching seeof belowheads.)
 
<pre>
But most compilers - including [[GCC]] - can do this automatically for you! Although the approach looks a little backward. For each source file, ''GCC'' will create a ''dependency file'' (which is canonically made to end in ''*.d''), which contains a Makefile dependency rule listing that source file's includes. (And more, but see below.)
DEPFILES := $(patsubst %.c,%.d,$(SRCFILES))
 
</pre>
We need two seperate sets of dependency files; one for the library objects, and one for the test driver executables (which usually have additional includes, and thus dependencies, not needed for the OBJFILES).
<syntaxhighlight lang="make">
DEPFILES := $(patsubst %.c,%.d,$(SRCFILES))
TSTDEPFILES := $(patsubst %,%.d,$(TSTFILES))
</syntaxhighlight>
 
=== Distribution Files ===
The last list is the one with all sources, headers, and auxiliary files that should end up in a distribution tarball.
<syntaxhighlight lang="make">
<pre>
ALLFILES := $(SRCFILES) $(HDRFILES) $(AUXFILES)
</syntaxhighlight>
</pre>
 
== Phony.PHONY ==
The next one can take you by surprise. When you write a rule for ''make clean'', and there happens to be a file named ''clean'' in your working directory, you might be surprised to find that ''make'' does nothing, because the "target" of the rule ''clean'' already exists. To avoid this, declare such rules as ''phony'', i.e. disable the checking for a file of that name. These will be executed every time:
<syntaxhighlight lang="make">
<pre>
.PHONY: all clean dist testcheck testdrivers todolist
</syntaxhighlight>
</pre>
 
== CFLAGS ==
If you thought ''-Wall'' does tell you everything, you'll be in for a rude surprise now. If you don't even use ''-Wall'', shame on you. ;)
<syntaxhighlight lang="make">
<pre>
CFLAGSWARNINGS := -Wall -Wextra -pedantic -Wshadow -Wpointer-arith -Wcast-align \
-Wwrite-strings -Wmissing-prototypes -Wmissing-declarations \
-Wredundant-decls -Wnested-externs -Winline -Wno-long-long \
-Wconversion -Wstrict-prototypes
CFLAGS := -g -std=gnu99 $(WARNINGS)
</pre>
</syntaxhighlight>
I suggest you add them one by one instead of all at once. ;) These flags are for C, there are more to be used for C++.
It is suggested to add new warning options to your project one at a time instead of all at once, to avoid getting swamped in warnings. ;) These flags are merely recommendations for C work. If you use C++, you need different ones. Check out the GCC manual; each major compiler update changes the list of available warning options.
 
== Rules ==
 
Now come the rules, in their typical backward manner (top-level rule first). The topmost rule is the default one (if 'make' is called without an explicit target). It is common practice to have the first rule called "all".
 
=== Top-Level Targets ===
<syntaxhighlight lang="make">
<pre>
all: pdclib.a
 
pdclib.a: $(OBJFILES)
@ar r pdclib.a $?
 
clean:
-@$(RM) $(wildcard $(OBJFILES) $(DEPFILES) $(TSTFILES) $(REGFILES) pdclib.a pdclib.tgz)
 
dist:
@tar czf pdclib.tgz $(ALLFILES)
</syntaxhighlight>
</pre>
The ''@'' at the beginning of thea line tells ''make'' to be quiet, i.e. not to echo the executed commands on the console prior to executing them. The Unix credo is "no news is good news". You don't get a list of processed files with ''cp'' or ''tar'', either, so it's completely beyond me why developers chose to have their Makefiles churn out endless lists of useless garbage. One very practical advantage of shutting up ''make'' is that you actually get to ''see'' those compiler warnings, instead of having them drowned out by noise.
 
The ''-'' at the beginning of a line tells ''make'' to continue even in case an error is encountered (default behaviour is to terminate the whole build).
The awkward looking loop in the ''clean'' rule is to avoid confusing error messages if the files to be deleted don't exist.
 
The ''$(RM)'' in the ''clean'' rule is the platform-independent command to remove a file.
 
The ''$?'' in the ''pdclib.a'' rule is an internal variable, which ''make'' expands to list all dependencies to the rule ''which are newer than the target''.
 
=== Automated Testing, pt. 2 ===
<syntaxhighlight lang="make">
<pre>
testcheck: testdrivers
-@rc=0; count=0; \
-@rc=0; count=0; for file in $(TSTFILES); do ./$$file; rc=`expr $$rc + $$?`; \
for file in $(TSTFILES); do \
count=`expr $$count + 1`; done; echo; echo "Tests executed: $$count Tests failed: $$rc"
echo " TST $$file"; ./$$file; \
rc=`expr $$rc + $$?`; count=`expr $$count + 1`; \
done; \
echo; echo "Tests executed: $$count Tests failed: $$rc"
 
testdrivers: $(TSTFILES)
</syntaxhighlight>
</pre>
Call it crude, but it works beautifully for me. The leading ''-'' means that 'make' will not abort when encountering an error, but continue with the loop.
 
IfThe ''echo " TST $$file"'' in there is useful in case you get a ''SEGFAULT'' or something like that, addfrom aone temporaryof ''echoyour $$file''test todrivers. the(Without loop to getechoing the namedrivers ofas thethey testare driverexecuted, thisyou happenswould in.be Forat ''normal''a testloss failures, add a diagnosticas to thewhich testone drivercrashed.)
 
=== Dependencies, pt. 2 ===
<syntaxhighlight lang="make">
<pre>
-include $(DEPFILES) $(TSTDEPFILES)
</syntaxhighlight>
</pre>
Further below, you will see how dependency files are ''generated''. Here, we ''include'' all of them, i.e. make the dependencies listed in them part of our Makefile. Never mind that they might not even exist when we run our Makefile the first time - the leading "-" again suppresses the errors.
This adds all the dependency rules auto-generated by ''GCC''. (see below)
 
=== Extracting TODO Statements ===
<syntaxhighlight lang="make">
<pre>
todolist:
-@for file in $(ALLFILES:Makefile=); do fgrep -H -e TODO -e FIXME $$file; done; true
</syntaxhighlight>
</pre>
ThisTaking all files in your project, with exception of the Makefile itself, this will ''grep'' all those ''TODO'' and ''FIXME'' comments from theyour files, and display them in the terminal. It's is nice to be remembered of what's is still missing before you do a release. To add another keyword, just addinsert another <code>-e keyword</code>. Don't forget to add <code>todolist</code> to your <code>.PHONY</code> list.
 
:'''Note:''' ''$(ALLFILES:Makefile=) is a list of everything in $(ALLFILES), except for the string "Makefile" which is replaced with nothing (i.e., removed from the list). This avoids a self-referring match, where the string "TODO" in the grep command would find itself. (Of course, it also avoids finding any *real* TODO's in the Makefile, but there's always a downside. ;-) )''
 
=== Dependencies, pt. 3 ===
 
Now comes the dependency magic I talked about earlier:. Note that this needs GCC 3.3 or newer.
<syntaxhighlight lang="make">
<pre>
%.o: %.c Makefile
@$(CC) $(CFLAGS) -DNDEBUG -MMD -MP -MT "$*.d $*.t $*.o" -g -std=c99 -I./includes -I./internals -c $< -o $@
</syntaxhighlight>
</pre>
Isn't it a beauty? ;-) Note that this needs GCC 3.3.x or newer.
 
The bunch of "M"-flags create a ''*.d-MMD'' fileflag alongsidegenerates the objectdependency file (%.d), which holdswill hold (in Makefile syntax) rules making all the ''generated'' filesfile (*%.o, *.t,in *.dthis case) depend on the source file ''and any non-system headers it includes''. That means the object file, the test driver, and the dependency file itself getgets recreated automatically whenever relevant sources are touched. (TheIf dependencyyou filewant requiresto recreatingalso toodepend ason thesystem sourceheaders edit(i.e., couldchecking havethem addedfor anotherupdates on each compile), use ''#include-MD'' instead. The ''-MP'' option adds empty dummy rules, which avoid errors should header files be removed from the filesystem.)
 
Compiling the object file actually looks like a side effect. ;-)
 
Note that the dependency list of the rule includes the Makefile itself. If you changed e.g. the ''CFLAGS'' in the Makefile, you want them to be applied, don't you? Using the $< macro ("first dependency") in the command makes sure we do not attempt to compile the Makefile as C source.
 
Of course we also need a rule for generating the test driver executables (and their dependency files):
=== The Rest ===
<pre>
%.t: %.c Makefile pdclib.a
@$(CC) $(CFLAGS) -DTEST -std=c99 -I./includes -I./internals $< pdclib.a -o $@
</pre>
This is somewhat of an anticlimax in its "triviality". My test drivers need to link against the ''PDCLib'' itself, but that's already all. This is not really perfect; if ''any'' PDCLib function was touched (and, thus, pdclib.a updated), it recreates ''all'' test drivers, even when not necessary. Ah well, no Makefile ever is perfect, but I'd rather have too much compiled than missing a dependency.
 
<syntaxhighlight lang="make">
= Advanced Techniques =
%_t: %.c Makefile pdclib.a
@$(CC) $(CFLAGS) -MMD -MP -DTEST $< pdclib.a -o $@
</syntaxhighlight>
 
Here you can see why test driver executables get a ''*_t'' suffix instead of a ''*.t'' extension: The ''-MMD'' option uses the basename (i.e., filename without extension) of the ''compiled'' file as basis for the dependency file. If we would compile the sources into ''abc.o'' and ''abc.t'', the dependency files would both be named ''abc.d'', overwriting each other.
== Conditional Evaluation ==
 
Other compilers differ a bit in their support for this, so look up their specifics when using something else than ''GCC''.
Sometimes it becomes useful to react on the existence or content of certain environment variables. For example, you might have to rely on the path to a certain framework being passed in FRAMEWORK_PATH. Perhaps the error message given by the compiler in case the variable is not set isn't that helpful, or it takes long until 'make' gets to the point where it actually detects the error.
 
=== Backups ===
Luckily, 'make' allows for conditional evaluation and manual error reporting, quite akin to the C preprocessor:
 
Backing up your files is a perfect candidate of a task that should be automated. However, this is generally achieved through a [[Code_Management#Version_Control_Systems|version control system]].
<pre>
ifndef FRAMEWORK_PATH
$(error FRAMEWORK_PATH is not set. Please set to the path where you installed "Framework".)
endif
 
Below is an example of a ''backup'' target, which creates a dated 7-Zip archive of the directory where the Makefile resides.
ifneq ($(FRAMEWORK_PATH),/usr/lib/framework)
$(warning FRAMEWORK_PATH is set to $(FRAMEWORK_PATH), not /usr/lib/framework. Are you sure this is correct?)
endif
</pre>
 
<syntaxhighlight lang="make">
= A Final Word =
THISDIR := $(shell basename `pwd`)
TODAY := $(shell date +%Y-%m-%d)
BACKUPDIR := projectBackups/$(TODAY)
 
backup: clean
Hope this helps you in creating your own Makefile, and then forgetting about it (as you should, because 'make' should lessen your workload, not add more).
@tar cf - ../$(THISDIR) | 7za a -si ../$(BACKUPDIR)/$(THISDIR).$(TODAY)_`date +%H%M`.tar.7z
</syntaxhighlight>
 
== Summary ==
--[[User:Solar|Solar]] 04:05, 21 January 2008 (CST)
 
A well-written Makefile can make maintaining a code base much easier, as it can wrap complex command sequences into simple 'make' invocations. Especially for large projects, it also cuts back on compilation times when compared to a dumb "build.sh" script. And, once written, modifications to a well-written Makefile are seldom necessary.
 
= SeeAdvanced AlsoTechniques =
 
Some of the stuff we did above already ''was'' pretty advanced, and no mistake. But we needed those features for the basic yet convenient setup. Below you will find some even trickier stuff, which might not be for everybody but is immensely helpful ''if'' you need it.
 
== Conditional Evaluation ==
 
Sometimes it is useful to react on the existence or content of certain environment variables. For example, you might have to rely on the path to a certain framework being passed in FRAMEWORK_PATH. Perhaps the error message given by the compiler in case the variable is not set is not helpful, or it takes long until 'make' gets to the point where it actually detects the error. You want to fail early, and with a meaningful error message.
 
Luckily, 'make' allows for conditional evaluation and manual error reporting, quite akin to the C preprocessor:
 
<syntaxhighlight lang="make">
ifndef FRAMEWORK_PATH
$(error FRAMEWORK_PATH is not set. Please set to the path where you installed "Framework".)
endif
 
ifneq ($(FRAMEWORK_PATH),/usr/lib/framework)
$(warning FRAMEWORK_PATH is set to $(FRAMEWORK_PATH), not /usr/lib/framework. Are you sure this is correct?)
endif
</syntaxhighlight>
 
== Multi-Target ==
 
Preliminary work has been done to write an improved Makefile, which allows generating multiple binaries with individual settings while still being easy to configure and use. While this is not yet in "tutorial" format, commented source can be found [[User:Solar/Makefile | here]].
 
=See Also=
===Articles===
* [[User:Solar/Makefile]], A more complex example capable of building multiple executables and libraries from a single Makefile.
===External Links===
* [http://jaws.rootdirectory.de JAWS], a pre-configured [http://www.cmake.org CMake] setup which, while not geared toward OS development, is a definite step forward from "naked" Makefiles.
* [http://aegis.sourceforge.net/auug97.pdf Recursive Make Considered Harmful] by Peter Miller<br />A paper detailing why the traditional approach of recursive make invocations harms performance and reliability.
* [http://www.xs4all.nl/~evbergen/nonrecursive-make.html Implementing non-recursive make] by Emile van Bergen<br />Further input on the subject of non-recursive, low-maintenance Makefile creation.
* [http://www.gnu.org/software/make/manual/ Manual for GNU make]
* [https://www.goodreads.com/book/show/583690.Managing_Projects_with_GNU_Make Managing Projects with GNU Make], the O'Reilly book on GNU Make
 
----
[[Category:Tutorials|Makefile]]
[[Category:Build Tools]]
 
[[de:Makefile]]