Makefile 笔记

来源:互联网 发布:犯罪心理学 知乎 编辑:程序博客网 时间:2024/05/05 15:51
一,自动管理依赖 -- 为什么要写 makefile

假设我们有一个源文件 a.cpp, 如果想编译链接它怎么做呢?
逻辑上我们要:
a.cpp -(编译)-> a.o
a.o -(链接)-> a.out

我们可以通过g++命令行方式完成以上操作:

g++ -c a.cpp -o a.o

g++ a.o -o a.out

我们还可以写一个makefile,然后用make命令完成以上操作:

a.out: a.o    g++ a.o -o a.outa.o: a.cpp    g++ -c a.cpp -o a.o

然后执行

make makefile


这样看来makefile并没有什么特别的用途,对我而言

首先,每次只敲"make makefile"要比每次都敲两条g++命令方便多了

另外,如果 a.out 不仅仅需要链接 a.o 还需要链接 b.o, c.o, d.o,这些目标文件分别由源文件 a.cpp, b.cpp, c.cpp, d.cpp 编译生成,当其中一些源文件发生变化时,如果用命令行方式,我们需要“人工”地判断哪些源文件发生了变化,“人工”地重编这些文件,再把它们链在一起。

make自动管理依赖的作用在这里就非常有用了,我只需执行"make makefile", make会自动检查更新,只编译发生变化的源文件。

makefile解释:

<target>: <prerequesit>

<tab><command>

<target> 我们要生成的目标,比如说,我们想要生成a.o,这个目标就是a.o,想要生成 a.out,这个目标就是 a.out

<prerequesit> 目标所依赖的前提,生成 a.out, 需要 a.o,那么 a.o 就是 a.out 的前提,

当执行make命令时,make 会自动检查到 a.o 的更新,如果 a.o 更新了, make就会重新生成 a.out, 

如果 a.o 不存在时,make 会自动查找生成 a.o 的规则,生成 a.o,所以,make 的执行顺序类似于函数调用,makefile中定义的第一条规则是默认的入口

<command> 是生成目标要执行的命令,makefile规定<command>之前一定要用<tab>


二,makefile的执行顺序:

执行"make makefile"时,make默认要生成的唯一目标是makefile中的第一个目标。

然后用类似递归的方法生成一个目标

将当前目标的每一个前提条件当目标,查找规则并生成该前提条件

生成全部前提条件后,生成当前目标


三,隐含规则

a.o: a.cpp
    g++ -c a.cpp -o a.o

这样的规则太直接,太明显,默认成为隐含规则

比如我们有a.cpp, b.cpp, c.cpp,最后要生成a.out
makefile 如下

a.out: a.o b.o c.o    g++ a.o b.o c.o -o a.outa.o: a.cpp    g++ -c a.cpp -o a.ob.o: b.cpp    g++ -c b.cpp -o b.oc.o: c.cpp    g++ -c c.cpp -o c.o


这样写非常的啰嗦。因为有了隐含规则,makefile只要写成
a.out: a.o b.o c.o    g++ a.o b.o c.o -o a.out

就可以了

1. Automatically manage dependency -- Why Mekefile

I have a source file a.cpp and I want to generate a executable file a.out
Logically:
a.cpp -(compile)-> a.o
a.o -(link)-> a.out

Command:
g++ -c a.cpp -o a.o
g++ a.o -o a.out

Makefile:

a.out:a.o    g++ a.o -o a.outa.o:a.cpp    g++ -c a.cpp -o a.o

Explain:
<Target>:<Dependency>
<tab><command to execute>

To build <Target>, say, "a.out", it requires <Dependency>, e.g. "a.o", that is when a.o is changed, a.out need to rebuild.
to build <Target>, it also requires to execute the <command to execute>, e.g. g++ a.o -o a.out
make sure that it has a table before the command

What is the benefit?
The difference between makefile and the command is that the makefile defines a dependency. In above case, we don't see any benefit as a.out only depends on one source file to  change.
Think if a.out depends on tens of source file, even more than hundreds of source file, instead of compile all the source file one by one, we can only compile those which has been changed. and, instead of manually decide which one has been changed, we can relay on make command as well as makefile who specify the dependency.


2. Recursive -- Order in makefile

In above case, the rule to generate a.out is specified before rule for a.o. in makefile, which is odd. why?
Cause make work in a recursive way.
The 1st rule specified in makefile is the default target for make
However, to build a.out it needs a.o as specified in its dependency, so, make has to look for the rule of a.o in makefile, which is the 2nd rule in makefile and create a.o based the rule.
pretty much like a recursive order.

Instead of build the default target in makefile, we can specify the target we'd like to build by command 
"make <target>"


3. Too obviouse to express explicitly -- Implicit rule in makefile

I have my source file a.cpp b.cpp c.cpp and I want to generate a executable file a.out
Logically:
    a.cpp -(compile)-> a.o
    b.cpp -(compile)-> b.o
    c.cpp -(compile)-> c.o
    a.o + b.o + c.o -(link)-> a.out

Makefile:
a.out:a.o b.o c.o     g++ a.o b.o c.o -o a.outa.o:a.cpp     g++ -c a.cpp -o a.ob.o:b.cpp     g++ -c b.cpp -o b.oc.o:c.cpp     g++ -c c.cpp -o c.o

Isn't it too simple for makefile to specify that a.o depends on a.cpp, b.o depends on b.cpp ...?
More than just simple, it's not that helpful, as we described in section 1, if the dependency rule only have one item in the dependency list, the rule is not helpful that much.
So, such rule
<fileName>.o:<fileName>.cpp
     g++ -c <fileName>.cpp -o <fileName>.o
become implicit in makefile, that they are exist automatically and we don't specify them explicitly

so, the makefile becomes:
a.out:a.o b.o c.o     g++ a.o b.o c.o -o a.out

which only describes the essential dependence we would like to know

4. bored with typing a list of .o twice in both dependence and command -- makefile macro

well, now a makefile can be as simple as:
a.out:a.o b.o c.o d.o e.o
     g++ a.o b.o c.o d.o e.o -o a.out

I hate to type a.o ... e.o twice. 
Worse, when to modify the makefile adding a new .o to depend, I will have to modify 2 places

Normally in C/C++ we have macro to solve such repetition problem, same thing goes in makefile though in name of macro

we define we  define a macro OBJ in makefile and things become a lot simpler:
OBJ := a.o b.o c.o d.o e.oa.out:$(OBJ)     g++ $(OBJ) -o a.out

that is we can define and refer to a macro:
define macro:
macro:= string

refer to macro:
$(macro)


5 I want to put .o and .cpp in different folder -- pattern in makefile

One good aspect of hidden rule is that it's avoid repetition.
With the help of hidden rule, following dependence:
    a.cpp -(compile)-> a.o
    b.cpp -(compile)-> b.o
    c.cpp -(compile)-> c.o
    a.o -(link)-> a.out
makefile get a lot simpler which only needs to specify one rule:
    a.out:a.o b.o c.o
         g++ a.o b.o c.o -o a.out

However, if I want to put object file and source file in different folder, I cannotrelay on the hidden rule.
I will have to specify the dependency on my own:
obj/a.out:obj/a.o     g++ obj/a.o -o obj/a.outobj/a.o:src/a.cpp     g++ -c src/a.cpp -o obj/a.oobj/b.o:src/b.cpp     g++ -c src/b.cpp -o obj/b.oobj/c.o:src/c.cpp     g++ -c src/c.cpp -o obj/c.o

It works, however, really painful to repeat.
What we do?

If we can modify the hidden rule and put the directory info to it, then everything will be perfect.
We can not. 

But, lucky enough, we can define a pattern, to me, customized "hidden rule", for the same purpose :)
The idea is that when it requires a target for a purpose, e.g. requires obj/a.o to link to a.out, and the target, obj/a.o, is in a certain shape (pattern) like "obj/<xxx>.o", so we can define a general rule for target with the same shape.
obj/%.o:src/%.cpp      g++ -c $< -o $@

In dependency:
first %, for wild-card matching
2nd %, I guess refer to the first one
In command
$< refer to the dependence
$@ refer to the target

So the makefile becomes a lot simpler:
obj/a.out:obj/a.o obj/b.o obj/c.o     g++ obj/a.o obj/b.o obj/c.o -o obj/a.outobj/%.o:src/%.cpp     g++ -c $< -o $@


tidy up:
if we define a macro to remove the obj list duplication in dependency and command
OBJ := obj/a.o obj/b.o obj/c.oobj/a.out:$(OBJ)     g++ $(OBJ) -o obj/a.outobj/%.o:src/%.cpp     g++ -c $< -o $@


6 I have sources in different folder -- makefile pattern go further

Good to use common pattern:
obj/%.o:src/%.cpp
     g++ -c $< -o $@
to generate the object in shape of obj/<filename>.o, that is if it's requires a target of obj/<filename>.o, it will use the source file in folder src/ to generate it.
What if I want to have more than one source folder?

In specific that I want to have a folder for product code, say "src/", and another folder for test code, say "test/", and I want to put their .o into a common object folder, say  "obj/"
Only applying above rule will have those .o whose source file in "test/" failed to compile

What we do?

Actually we can specify a certain object to match a certain pattern.
If we have a.cpp & b.cpp in src/ and c.cpp in test/, like below
src/
a.cpp
b.cpp

test/
c.cpp

And we want to put them their .o in obj/, we need 2 patterns, one for "obj/a.o obj/b.o", and one for "obj/c.o"
and specify them like:
a.o b.o: obj/%.o:src/%.cpp     g++ -c $< -o $@c.o:obj/%.o:test/%.cpp     g++ -c $< -o $@


7 I hate to type every file name in makefile -- makefile macro substitution

If I want to compile all the files in folder "src/"
src/
a.cpp
b.cpp
c.cpp
e.cpp
...

I will have to specify a dependency list either explicitly or via macro in makefile, which is boring, and makefile needs to change every time I add a new file in the folder.
OBJ := obj/a.o\
          obj/b.o\
          obj/c.o\
          obj/d.o\
          obj/e.o\
          ...

I hate it.

first thing to my head is to define a macro like below
OBJ := src/*.cpp
It doesn't work, as OBJ is a macro. it will expanded as it be where it used, exactly "src/*.cpp", if a command doesn't support wildcard character "src/*.cpp", it won't work.

The proper way is to define the macro like below
SRC:= $(wildcard src/*.cpp)#SRC = a.cpp b.cpp c.cpp ...OBJ := $(SRC: src/%.cpp=obj/%.o)#OBJ := obj/a.o obj/b.o obj/c.o

First macro is define wildcard, which list all the .cpp files in pattern of "

working example:
src/
a.cpp
b.cpp

test/
c.cpp

target directory obj/

makefile:
SOURCE := $(wildcard src/*.cpp)SRCOBJ := $(SOURCE:src/%.cpp=obj/%.o)obj/a.out:$(SRCOBJ) obj/c.o     g++ $(SRCOBJ) obj/c.o -o obj/a.out$(SRCOBJ):obj/%.o:src/%.cpp     g++ -c $< -o $@obj/c.o:obj/%.o:test/%.cpp     g++ -c $< -o $@



8. I am not on my own -- work with 3rd party libraries

 So far, we have "src/" for my own source code, "test/" for my own test code, all their generation are put in "obj/"
Good enough for a project of all my own.

However, if I want to use other's lib, say, a unit test frame work provided by others, what I do.

For example, I want to use CppUTest which is installed in /usr/CppUTest
All its include files are in:
/usr/CppUTest/include
All its libs are in:
/usr/CppUTest/lib

We need 2 missions done to link it with my own code:
1. let "make" know where to find the header files
so we need to put in makefile:
CPPFLAGS += -I/usr/CppUTest/include

CPPFLAGS is used to specify compile options.
-Idir is directory option

2. link the 3rd party lib together with our objects
add 3rd party lib in the link command
a.out:a.o b.o c.o
     g++ a.o b.o c.o -L/usr/CPPUTest/lib -lCppUTest -lCppUTestExt -o a.out

-Ldir: directory option
-llib: lib option, no space between "-l" and "lib", and "lib" is the name of library without prefix and suffix, e.g. to link libMyLib.o, the option should written as -lMylib

For more options for GNU project, please refer to:
http://linux.die.net/man/1/g++


原创粉丝点击