- Example program:
Here, we will use a program that deals with some geometric shapes toillustrate writing a make file to compile a program.
The program is made up of 5 source files:
main.cpp
, a main program.Point.h
, header file for the Point class.Point.cpp
, implementation file for the Point class.Rectangle.h
, header file for the Rectangle class.Rectangle.cpp
, implementation file for the Rectangle class.
These files are available to download.
- Compiling source code separately:
Let's review what we need to do to compile this programseparately (i.e., to intermediateobject files) andthen link it into an executable named
main
.To just compile source code, use the
-c
flag with thecompiler...% g++ -c main.cpp % g++ -c Point.cpp % g++ -c Rectangle.cpp
This will generate the object files:
main.o
(formain.cpp
),Point.o
(forPoint.cpp
), andRectangle.o
(forRectangle.cpp
)
Then, to link the object files (
.o
) into an executable, weuse the compiler again (although this time it will just pass the.o
files on to thelinking stage):% g++ -o main main.o Point.o Rectangle.o
- Dependency chart:
We would like to know what files need to be regenerated when we changecertain parts of our program. We can determine this by creating adependency chart.
Let's start from our goal (i.e., to have an executable named
main
)...Linking Dependencies
The executable main is generated from 3 object files,main.o,Point.o andRectangle.o. Thus,maindepends on those 3 files.To generate main from those files, we link themtogether.
Compiling Dependencies
Next, the object (.o
) files depend on the.cpp
files. Namely:- main.o depends on main.cpp,
- Point.o depends on Point.cpp, and
- Rectangle.o depends on Rectangle.cpp.
You generate these object files by compiling the corresponding
.cpp
files.Include Dependencies
Finally, source code files (.cpp
and.h
) dependon header files (.h
) that they include:- 3 source code files include Point.h and
- 2 files include Rectangle.h.
Notice that there may be additional indirect include dependencies(e.g.,
Rectangle.cpp
depends onPoint.h
sinceRectangle.cpp
includesRectangle.h
, whichincludesPoint.h
).
Note:Include dependencies are a little different since the.h
files aren't used to generate other files, unlike.cpp
and.o
files.
Here is the complete dependency chart...
Dependency Chart for main
ExecutableDependencies go downward.
How to "get" or generate files goes upward.
Note:We've just listed all the dependencies for the executablemain. The executable depends on some thingsdirectly (like the.o
files) and some thingsindirectly (i.e., you need the.cpp
files to generatethe.o
files to generate the executable).
- Using dependency chart:
With such a dependency chart, we can answer questions about whatneeds to be regenerated if certain files change.
For example, suppose we change the file
main.cpp
...What has to be regenerated?Answer: First, recompile
main.cpp
to getmain.o
. Then, relink all the object files togetheragain to get the executablemain
.Now, suppose we changed
Point.h
?
Note:When either a.cpp
file changes or a header file included(directly or indirectly) by a.cpp
file changes, we haveto regenerate the corresponding.o
file.
- Make utility and make files:
Since it is tedious to recompile pieces of a program when somethingchanges, people often use themake utility instead.
Make needs a make file that encodes both thedependencies between files and the commands needed to generate files.
When you run the make utility, it examines themodification times of files and determines what needs to beregenerated. Files that areolder than the files they dependon must be regenerated. Regenerating one file may cause others tobecomeold, so that several files end up being regenerated.
Aside:Note the difference between the make utility (a program yourun) and amake file (that tells themake utilityhow to compile/link a specific program).
A make file should be named either Makefile (first letteruppercase) ormakefile (all lowercase). The make file should bein the same directory as the source code files.
Aside:Make files can have other file names, but then you'll have totell themake utility what the name of the make file is (usinga commandline option).
Finally, it is easiest if the make file is in the same directory as thesource code files.
- Writing a make file:
You can create a make file in any text editor.For this example, we will examine the parts of thisMakefile, which generates the executable
main
.Go ahead a download that make file.Simple make files have at least 2 parts:
- A set of variables, which specify things like the C++compiler/linker to use, flags for the compiler, etc.
- A set of targets, i.e., files that have to be generated.
Make files may have comments that (hopefully) add to readability. Anyline starting with a pound sign (
#
) is a comments. Notethat our make file has some comments.
Note:Keep in mind that make files use their own language, so don't expect things in them to look like programming languages youknow.
Variables
The first variable listed in our make file is for the compiler to use...CXX = g++
In general, the form for setting a variable is:
VARNAME = value
(The space around the
=
is optional for our version ofmake, but adds to readability).The second variable says what flags to pass to the C++ compiler...
CXXFLAGS = -Wall -g
Here, the variable includes the flag for all warnings and theone that addsdebugging information (so that the executable canlater be run under a debugger).
The 2 variables,
CXX
andCXXFLAGS
, are theones that our version ofmake expects to be set for the C++compiler and flags to the C++ compiler, respectively.
Aside:You can add your own variables, but make has a set of standardvariables, like these, which it uses for the compiler, flags, etc.
Targets
With the needed variables, we can deal with the targets, which arefiles that must begenerated.For each target, there are typically 1 or 2 lines in a make file.Those lines specify:
- its dependencies (easy to determine from a dependency chart)
- and possibly a command to generate the target (easy to determinefrom knowledge ofseparate compilation).
By default, the first target in the make file is the one that getsgenerated when you just say "
make
" at the commandline, soit should be the name of the executable.Target: Executable
Let's examine the first target in our make file (i.e., formain
)...For a target's dependency line, the target file name should be listed,followed by a colon (
:
) and the files it depends on:main: main.o Point.o Rectangle.o
For a target that is an executable, we just list the object files itdepends on.
On the next line, there should be a command that generates the target:
<Tab>$(CXX) $(CXXFLAGS) -o main main.o Point.o Rectangle.o
Note:This line MUST start with a<Tab>
(i.e., hitthe Tab key)...it cannot start with a regular space!
Note that the variables
CXX
andCXXFLAGS
are used, which means that the command is really:g++ -Wall -g -o main main.o Point.o Rectangle.o
Note:Using a variable in a make file involves using the dollar sign($
) and then the variable name in parentheses.
Targets: Object Files
Our next target is the object file,main.o
.For a target that is an object file, we list all files it depends on.This means its corresponding
.cpp
file, plus any headerfiles its.cpp
file includes (i.e., include directly orindirectly, but not system header files likeiostream
thataren't going to change).So, the dependencies line looks like:
main.o: main.cpp Point.h Rectangle.h
The generation command for this target is:
<Tab>$(CXX) $(CXXFLAGS) -c main.cpp
Make is kinda smart
We could have written themain.o
target more simply.Make already knows how to generate certain kinds of files.Let's take advantage ofmake's smarts for the next target...The next object file,
Point.o
depends onPoint.cpp
,however,make already knows that.o
files can begenerated from their corresponding.cpp
files, so all we needis:Point.o: Point.h
Note:Although some make utilities can automatically determine what.h
files a file depends on, some cannot, so you shouldlist theinclude dependencies.
We don't need any generation line since we set up thevariables
CXX
andCXXFLAGS
. Makewill use them to figure out a compilation command.Finally, there is only one target left,
Rectangle.o
.Taking advantage ofmake's smarts, it only requires:Rectangle.o: Rectangle.h Point.h
Note that
Rectangle.o
depends onPoint.h
indirectly (i.e., recall thatRectangle.cpp
includesRectangle.h
, which includesPoint.h
).
That's all for the make file!!! It just encodes the dependencies andthe generation commands for 1 executable and its 3 object files. - Using a make file:
With a make file for our executable, it is easy to compile thatexecutable.
Try using the make file we've given you.First, remove the old executable and object files (if there are any):
% rm main *.o
Run make with the command:
% make
Remember that this will cause make to generate the firsttarget in the make file, which is
main
.Since nothing has been compiled/linked, it will do:
g++ -Wall -g -c main.cpp g++ -Wall -g -c -o Point.o Point.cpp g++ -Wall -g -c -o Rectangle.o Rectangle.cpp g++ -Wall -g -o main main.o Point.o Rectangle.o
I.e., compile the 3
.cpp
files into object files, and thenlink the object files into an executable namedmain
.(If you have trouble, be sure that the make file is named Makefileand is in the same directory as the source code files.)
Now, type the make command again (let's explicitly tell it whattarget to generate this time) and you get:
% make main make: `main' is up to date.
Here, make tells you nothing has changed, so it has no work to do.
Next, suppose we change the program by adding some code to the end of
main()
(inmain.cpp
) to move the rectangleand then print out its new top right point:r1.move(2, 3); cout << "\nRectangle r1 moved by 2, 3--top right now at " << r1.get_top_right().get_x() << ", " << r1.get_top_right().get_y() << endl;
main.cpp
, adding those lines, and then save to disk.After the change, when we use make:
% make g++ -Wall -g -c main.cpp g++ -Wall -g -o main main.o Point.o Rectangle.o
it doesn't bother regenerating
Point.o
orRectangle.o
, since the files those depend on have notchanged.Finally, try changing
Point.h
and then re-make...% make g++ -Wall -g -c main.cpp g++ -Wall -g -c -o Point.o Point.cpp g++ -Wall -g -c -o Rectangle.o Rectangle.cpp g++ -Wall -g -o main main.o Point.o Rectangle.o
Since all the
.cpp
files depend on that header (eitherdirectly or indirectly), they all have to be recompiled and linkedtogether. - Additional notes on make:
Other versions of make may use different variables for C++instead of
CXX
andCXXFLAGS
.In a make file, if you need to continue a line, you cannot justcontinue it on the next line. You must end a line with a
\
(backslash, not the forward slash) to tellmake that the linecontinues. Only break lines where space would normally go.So, the first target could have been written:
main: main.o \
Point.o \
Rectangle.o
<Tab>$(CXX) $(CXXFLAGS) -o main \
main.o Point.o Rectangle.o
-
We have only described the basics of make here. Most versionsof make have additional capabilities.
原文地址:http://www.cs.bu.edu/teaching/cpp/writing-makefiles/
补充以下几点:
Makefile规则语法
通常规则的语法格式如下:
TARGETS : PREREQUISITES
COMMAND
...
或者:
TARGETS : PREREQUISITES ; COMMAND
COMMAND
...
规则中“TARGETS”可以是空格分开的多个文件名,也可以是一个标签(例如:执行清空的“clean”)。“TARGETS”的文件名可以使用通配符,格式“A(M)”表示档案文件(Linux下的静态库.a文件)的成员“M”(关于静态库的重建可参考第十一章使用make更新静态库文件)。通常规则只有一个目标文件(建议这么做),偶尔会在一个规则中需要多个目标。
书写规则是我们需要注意的几点:
1. 规则的命令部分有两种书写方式:a.命令可以和目标:依赖描述放在同一行。命令在依赖文件列表后并使用分号(;)和依赖文件列表分开。b.命令在目标:依赖的描述的下一行,作为独立的命令行。当作为独立的命令行时此行必须以[Tab]字符开始。在Makefile中,在第一个规则之后出现的所有以[Tab]字符开始的行都会被当作命令来处理。
2. Makefile中符号“$”有特殊的含义(表示变量或者函数的引用),在规则中需要使用符号“$”的地方,需要书写两个连续的(“$$”)。
3. 前边已提到过,对于Makefile中一个较长的行,我们可以使用反斜线“\”将其书写到几个独立的物理行上。虽然make对Makefile文本行的最大长度是没有限制的,但还是建议这样做。不仅书写方便而且更有利于别人的阅读(这也是一个程序员修养的体现)。
一个规则告诉“make”两件事:1.目标在什么情况下已经过期; 2. 如果需要重建目标时,如何去重建这个目标。目标是否过期是由那些使用空格分割的规则的依赖文件所决定的。当目标文件不存在或者目标文件的最后修改时间比依赖文件中的任何一个晚时,目标就会被创建或者重建。就是说执行规则命令行的前提条件是以下两者之一:1.目标文件不存在; 2. 目标文件存在,但是规则的依赖文件中存在一个依赖的最后修改时间比目标的最后修改时间晚。
规则的中心思想是:目标文件的内容是由依赖文件文件决定,依赖文件的任何一处改动,将导致目前已经存在的目标文件的内容过期。规则的命令为重建目标提供了方法。这些命令运行在系统shell之上。