(转)从头开始写项目makefile

1. 基本规则

一般一个稍大的linux项目会有很多个源文件组成,最终的可执行程序也是由这许多个源文件编译链接而成的。编译是把一个.c或.cpp文件编译成中间代码.o文件,链接是就使用这些中间代码文件生成可执行文件。比如在当前项目目录下有如下源文件:

1
2
3
# ls  
common.h debug.c debug.h ipc.c ipc.h main.c tags timer.c timer.h tools.c tools.h
#

以上源代码可以这样编译:

1
# gcc -o target_bin main.c debug.c ipc.c timer.c tools.c

如果之后修改了其中某一个文件(如tools.c),再执行一下上一行代码即可,但如果有成千上万个源文件这样编译肯定是不够合理的。此时我们可以按下面步骤来编译:

1
2
3
4
5
6
# gcc -c debug.c  
# gcc -c ipc.c
# gcc -c main.c
# gcc -c timer.c
# gcc -c tools.c
# gcc -o target_bin main.o debug.o ipc.o timer.o tools.o

如果其中tools.c修改了,只需要编译该文件,再执行最后生成可执行文件的操作,也就是做如下两步操作即可:

1
2
# gcc -c tools.c  
# gcc -o target_bin main.o debug.o ipc.o timer.o tools.o

这样做看上去应该很合理了。但是如果修改了多个文件,就很可能忘了编译某一文件,那么运行时就很有可能出错。如果是common.h文件修改了,那么包含该头文件的所有.c文件都需要重新编译,这样一来的话就更复杂更容易出错了。看来这种方法也不够好,手动处理很容易出错。那有没有一种自动化的处理方式呢?有的,那就是写一个Makefile来处理编译过程。 下面给一个简单的Makefile,在源代码目录下建一个名为Makefile的文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
target_bin : main.o debug.o ipc.o timer.o tools.o  
>---gcc -o target_bin main.o debug.o ipc.o timer.o tools.o

main.o: main.c common.h
>---gcc -c main.c

debug.o: debug.c debug.h common.h
>---gcc -c debug.c

ipc.o: ipc.c ipc.h common.h
>---gcc -c ipc.c

timer.o: timer.c timer.h common.h
>---gcc -c timer.c

tools.o: tools.c tools.h common.h
>---gcc -c tools.c

然后在命令行上执行命令:

1
2
3
4
5
6
7
8
9
10
11
# make   
gcc -c main.c
gcc -c debug.c
gcc -c ipc.c
gcc -c timer.c
gcc -c tools.c
gcc -o target_bin main.o debug.o ipc.o timer.o tools.o
#
# ls
common.h common.h~ debug.c debug.h debug.o ipc.c ipc.h ipc.o main.c main.o Makefile Makefile~ tags target_bin timer.c timer.h timer.o tools.c tools.h tools.o
#

可见在该目录下生成了.o文件以及target_bin可执行文件。现在我们只需要执行一个make命令就可以完成所有编译工作,无需像之前一样手动执行所有动作,make命令会读取当前目录下的Makefile文件然后完成编译步骤。从编译过程输出到屏幕的内容看得到执行make命令之后所做的工作,其实就是我们之前手动执行的那些命令。现在来说一下什么是Makefile? 所谓Makefile我的理解其实就是由一组组编译规则组成的文件,每条规则格式大致为:

1
2
3
target ... : prerequisites ...   
>---command
...

其中target是目标文件,可以为可执行文件、*.o文件或标签。Prerequisites是产生target所需要的源文件或*.o文件,可以是另一条规则的目标。commond是要产生该目标需要执行的操作系统命令,该命令必须以tab(文中以>—标示tab字符)开头,不可用空格代替。 说白了就是要产生target,需要依赖后面的prerequisites文件,然后执行commond来产生来得到target。这和我们之前手动执行每条编译命令是一样的,其实就是定义好一个依赖关系,我们把产生每个文件的依赖文件写好,最终自动执行编译命令。 比如在我们给出的Makefile例子中target_bin main.o等就是target,main.o debug.o ipc.o timer.o tools.o是target_bin的prerequisites,gcc -o target_bin main.o debug.o ipc.o timer.o tools.o就是commond,把所有的目标文件编译为最终的可执行文件target,而main.c common.h是main.o的prerequisites,其gcc -c main.c命令生成target所需要的main.o文件。 在该例子中,Makefile工作过程如下:

  1. 首先查找第一条规则目标,第一条规则的目标称为缺省目标,只要缺省目标更新了就算完成任务了,其它工作都是为这个目的而做的。 该Makefile中第一条规则的目标target_bin,由于我们是第一次编译,target_bin文件还没生成,显然需要更新,但此时依赖文件main.o debug.o ipc.o timer.o tools.o都没有生成,所以需要先更新这些文件,然后才能更新target_bin。
  2. 所以make会进一步查找以这些依赖文件main.o debug.o ipc.o timer.o tools.o为目标的规则。首先找main.o,该目标也没有生成,该目标依赖文件为main.c common.h,文件存在,所以执行规则命令gcc -c main.c,生成main.o。其他target_bin所需要的依赖文件也同样操作。
  3. 最后执行gcc -o target_bin main.o debug.o ipc.o timer.o tools.o,更新target_bin。

在没有更改源代码的情况下,再次运行make:

1
2
3
# make  
make: `target_bin' is up to date.
#

得到提示目标target_bin已经是最新的了。 如果修改文件main.c之后,再运行make:

1
2
3
4
5
# vim main.c  
# make
gcc -c main.c
gcc -o target_bin main.o debug.o ipc.o timer.o tools.o
#

此时make会自动选择受影响的目标重新编译: 首先更新缺省目标,先检查target_bin是否需要更新,这需要检查其依赖文件main.o debug.o ipc.o timer.o tools.o是否需要更新。 其次发现main.o需要更新,因为main.o目标的依赖文件main.c最后修改时间比main.o晚,所以需要执行生成目标main.o的命令:gcc -c main.c更新main.o。 最后发现目标target_bin的依赖文件main.o有更新过,所以执行相应命令gcc -o target_bin main.o debug.o ipc.o timer.o tools.o更新target_bin。 总结下,执行一条规则步骤如下:

  1. 先检查它的依赖文件,如果依赖文件需要更新,则执行以该文件为目标的的规则。如果没有该规则但找到文件,那么该依赖文件不需要更新。如果没有该规则也没有该文件,则报错退出。
  2. 再检查该文件的目标,如果目标不存在或者目标存在但依赖文件修改时间比他要晚或某依赖文件已更新,那么执行该规则的命令。 由此可见,Makefile可以自动发现更新过的文件,自动重新生成目标,使用Makefile比自己手动编译比起来,不仅效率高,还减少了出错的可能性。

Makefile中有很多目标,我们可以编译其中一个指定目标,只需要在make命令后面带上目标名称即可。如果不指定编译目标的话make会编译缺省的目标,也就是第一个目标,在本文给出的Makefile第一个目标为target_bin。如果只修改了tools.c文件的话,我们可能只想看看我们的更改的源代码是否有语法错误而又不想重新编译这个工程的话可以执行如下命令:

1
2
3
# make tools.o   
gcc -c tools.c
#

编译成功,这里又引出一个问题,如果继续执行同样的命令:

1
2
3
# make tools.o  
make: `tools.o' is up to date.
#

我们先手动删掉tools.o文件再执行就可以了,怎么又是手动呢?我们要自动,要自动!!好吧,我们加一个目标来删除这些编译过程中产生的临时文件,该目标为clean。 我们在上面Makefile最后加上如下内容:

1
2
clean:  
>---rm *.o target_bin

当我们直接make命令时不会执行到该目标,因为没有被默认目标target_bin目标或以target_bin依赖文件为目标的目标包含在内。我们要执行该目标需要在make时指定目标即可。如下:

1
2
3
# make clean  
rm *.o target_bin
#

可见clean目标被执行到了,再执行make时make就会重新生成所有目标对应的文件,因为执行make clean时,那些文件被清除了。 clean目标应该存在与你的Makefile当中,它既可以方便你的二次编译,又可以保持的源文件的干净。该目标一般放在最后,不可放在最开头,否则会被当做缺省目标被执行,这很可能不是你的意愿。 最后总结一下,Makefile只是告诉了make命令如何来编译和链接程序,告诉make命令生成目标文件需要的文件,具体的编译链接工作是你的目标对应的命令在做。 给一个今天完整的makefile:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
target_bin : main.o debug.o ipc.o timer.o tools.o  
>---gcc -o target_bin main.o debug.o ipc.o timer.o tools.o

main.o: main.c common.h
>---gcc -c main.c

debug.o: debug.c debug.h common.h
>---gcc -c debug.c

ipc.o: ipc.c ipc.h common.h
>---gcc -c ipc.c

timer.o: timer.c timer.h common.h
>---gcc -c timer.c

tools.o: tools.c tools.h common.h
>---gcc -c tools.c

clean:
>---rm *.o target_bin

2. 隐含规则自动推导

上一节的Makefile勉强可用,但还写的比较繁琐,不够简洁。对每一个.c源文件,都需要写一个生成其对应的.o目标文件的规则,如果有几百个或上千个源文件,都手动来写,还不是很麻烦,这也不够自动化啊。 这样,我们把生成.o目标文件的规则全部删除掉,就是这样一个Makefile文件:

1
2
3
4
5
target_bin : main.o debug.o ipc.o timer.o tools.o  
>---gcc -o target_bin main.o debug.o ipc.o timer.o tools.o

clean:
>---rm *.o target_bin

这下简洁了不少,这样也能用吗?试试看吧先,make一下:

1
2
3
4
5
6
7
8
# make  
cc -c -o main.o main.c
cc -c -o debug.o debug.c
cc -c -o ipc.o ipc.c
cc -c -o timer.o timer.c
cc -c -o tools.o tools.c
gcc -o target_bin main.o debug.o ipc.o timer.o tools.o
#

原来酱紫都可以啊!!target_bin后面那一群依赖文件怎么生成呢?不是没有生成*.o目标文件的规则了吗?再看屏幕编译输出内容:

1
2
3
4
5
cc    -c -o main.o main.c  
cc -c -o debug.o debug.c
cc -c -o ipc.o ipc.c
cc -c -o timer.o timer.c
cc -c -o tools.o tools.c

怎么长的和之前不太一样呢,尤其是前面那个cc是何物? 其实make可以自动推导文件以及文件依赖关系后面的命令,于是我们就没必要去在每一个*.o文件后都写上类似的命令,因为,我们的 make 会自动推导依赖文件,并根据隐含规则自己推导命令。所以上面.o文件是由于make自动推导出的依赖文件以及命令来生成的。 下面来看看make是如何推导的。 命令make –p可以打印出很多默认变量和隐含规则。Makefile变量可以理解为C语言的宏,直接展开即可(后面会讲到)。取出我们关心的部分:

1
2
3
4
5
6
7
8
9
10
# default  
OUTPUT_OPTION = -o $@
# default
CC = cc
# default
COMPILE.c = $(CC) $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) –c
# Implicit Rules
%.o: %.c
# commands to execute (built-in):
>---$(COMPILE.c) $(OUTPUT_OPTION) $<

其中cc是一个符号链接,指向gcc,这就可以解释为什么我们看到的编译输出为cc,其实还是使用gcc在编译。

1
2
3
# ll /usr/bin/cc    
lrwxrwxrwx. 1 root root 3 Dec 3 2013 /usr/bin/cc -> gcc
#

变量$(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH)都为空。所以%.o: %.c规则命令展开为:

1
cc    -c -o $@ $<

再看屏幕输出编译内容,摘取一条:

1
cc    -c -o main.o main.c

不是看出点什么?$@和main.o对应,$<和main.c对应。其实$@$<是两个变量。$@为规则中的目标,$<为规则中的第一个依赖文件。%.o:%.c是一种称为模式规则的特殊规则。因为main.o符合该模模式,再推导出依赖文件main.c,最终推导出整个规则为:

1
2
main.o : main.c:  
>--- cc -c -o main.o main.c

其余几个目标也同样推导。make自动推导的功能为我们减少了不少的Makefile代码,尤其是对源文件比较多的大型工程,我们的Makefile可以不用写得那么繁琐了。 最后,今天的Makefile相对于上一节进化成这个样子了:

1
2
3
4
5
target_bin : main.o debug.o ipc.o timer.o tools.o  
>---gcc -o target_bin main.o debug.o ipc.o timer.o tools.o

clean:
>---rm *.o target_bin

3. 变量的使用

仔细研究我们的之前Makefile发现,我们还有改进的地方,就是此处:

1
2
target_bin : main.o debug.o ipc.o timer.o tools.o  
>---gcc -o target_bin main.o debug.o ipc.o timer.o tools.o

如果增加一个源文件xx.c的话,需要在两处或多处增加xx.o文件。我们可以使用变量来解决这个问题。之前说过,Makefile的变量就像C语言的宏一样,使用时在其位置上直接展开。变量在声明时赋予初值,在引用变量时需要给在变量名前加上“$”符号,但最好用小括号“()”或是大括号“{}”把变量给包括起来。 默认目标target_bin也在多处出现了,该文件也可以使用变量代替。 修改我们的Makefile如下:

1
2
3
4
5
6
7
SRC_OBJ = main.o debug.o ipc.o timer.o tools.o  
SRC_BIN = target_bin
$(SRC_BIN) : $(SRC_OBJ)
>---gcc -o $(SRC_BIN) $(SRC_OBJ)

clean:
>---rm $(SRC_OBJ) $(SRC_BIN)

这样每次有新增的文件是只需要在SRC_OBJ变量里面增加一个文件即可。要修改最终目标的名字是可以只修改变量SRC_BIN。 其实在之前还说过特殊变量:

  • $@,表示规则中的目标。
  • $<,表示规则中的第一个依赖文件。
  • $?,表示规则中所有比目标新的条件,组成一个列表,以空格分隔。 $^:,表示规则中的所有条件,组成一个列表,以空格分隔。 上一节我们看到make -p有很多自定义的变量,比如CC。其中很多变量我们可以直接使用或修改其变量值或增加值。我们的Makefile中可以使用CC(默认值为cc)、RM(默认值为rm -f)。

由此可见我们的Makefile还可以进一步修改:

1
2
3
4
5
6
SRC_OBJ = main.o debug.o ipc.o timer.o tools.o  
SRC_BIN = target_bin
$(SRC_BIN) : $(SRC_OBJ)
>---$(CC) -o $@ $^
clean:
>---$(RM) $(SRC_OBJ) $(SRC_BIN)

这样的Makefile编译也是可用的。 但是这样的Makefile还是需要我们手动添加文件,还是不够自动化,最好增删文件都要修改Makefile。伟大的人类真是太懒了!!于是乎,他们发明了一个函数wilcard(函数后面会讲到),它可以用来获取指定目录下的所有的.c文件列表。这样的话我们可以自动获取当前目录下所有.c源文件,然后通过其他方法再得到.o文件列表,这样的话就不需要在每次增删文件时去修改Makefile了。所谓其他方法这里给出两种:

  1. 使用patsubst函数。在$(patsubst %.c,%.o,$(dir) )中,patsubst把$(dir)中的变量符合后缀是.c的全部替换成.o。
  2. 变量值的替换。 我们可以替换变量中的共有的部分,其格式是“$(var:a=b)”“${var:a=b}”,其意思是,把变量“var”中所有以“a”字串“结尾”的“a”替换成“b”字串。

修改后的Makefile如下:

1
2
3
4
5
6
7
8
9
10
11
# SRC_OBJ = $(patsubst %.c, %.o, $(wildcard *.c))                                                                                                                                          

SRC = $(wildcard *.c)
SRC_OBJ = $(SRC:.c=.o)
SRC_BIN = target_bin

$(SRC_BIN) : $(SRC_OBJ)
>---$(CC) -o $@ $^

clean:
>---$(RM) $(SRC_OBJ) $(SRC_BIN)

其中# 后面的内容为注释。 这样终于满足了那些懒人的想法了。可见在使用变量时,的确可以是编译变得更自动化。

其实变量的定义有三种运算符=、:=、?=、+=

  1. =运算符可以读取到后面定义的变量。比如:
    1
    2
    3
    4
    5
    VAR = $(VAR2)  
    VAR2 = hello_make

    all:
    >---@echo =====$(VAR)=====

运行结果为:

1
2
3
#  
=====hello_make=====
#

但是这种定义可能会导致并非我们意愿的事发生,并不是很符合C语言的编程习惯。

  1. :=运算符在遇到变量定义时立即展开。
    1
    2
    3
    4
    5
    VAR := $(VAR2)                                                                                         
    VAR2 = hello_make

    all:
    >---@echo =====$(VAR)=====

运行结果为:

1
2
3
#  
==========
#
  1. ?=运算符在复制之前先做判断变量是否已经存在。例如var1 ?= $(var2)的意思是:如果var1没有定义过,那么?=相当于=,如果var1先前已经定义了,则什么也不做,不会给var重新赋值。
  2. +=运算符是给变了追加值。如果变量还没有定义过就直接用+=赋值,那么+=相当于=

如何使用这几个运算符要看实际情况,有时一个大的工程可能有许多Makefile组成,变量可能在多个Makefile中都在使用,这时可能使用+=比较好。使用:=有时可能比要好。 有时在编译程序时,我们需要编译器给出警告,或加入调试信息,或告知编译器优化可执行文件。编译时C编译器的选项CFLAGS使用的较多,默认没有提供值,我们可以给该变量赋值。有时我们还需要使用链接器选项LFLAGS告诉链接器链接时需要的库文件。可能我们还需要给出包含头文件的路径,因为头文件很可能和源文件不再同一目录。所以,我们今天的Makefile加上部分注释又更新了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# A commonMakefile for c programs, version 1.0  
# Copyright (C)2014 shallnew \at 163 \dot com

CFLAGS += -g -Wall-Werror -O2
CPPFLAGS += -I.-I./inc
LDFLAGS +=-lpthread

# SRC_OBJ =$(patsubst %.c, %.o, $(wildcard *.c))
SRC_FILES =$(wildcard *.c)
SRC_OBJ =$(SRC_FILES:.c=.o)
SRC_BIN =target_bin

$(SRC_BIN) :$(SRC_OBJ)
>---$(CC) -o $@$^ $(LDFLAGS)

clean:
>---$(RM)$(SRC_OBJ) $(SRC_BIN)

编译:

1
2
3
4
5
6
7
8
# make  
cc -g -Wall-Werror -O2 -I. -I./inc -c -o debug.odebug.c
cc -g -Wall-Werror -O2 -I. -I./inc -c -o ipc.oipc.c
cc -g -Wall-Werror -O2 -I. -I./inc -c -o main.omain.c
cc -g -Wall-Werror -O2 -I. -I./inc -c -o timer.otimer.c
cc -g -Wall-Werror -O2 -I. -I./inc -c -o tools.otools.c
cc -o target_bindebug.o ipc.o main.o timer.o tools.o -lpthread
#

可见我们的预编译选项,编译选项都用到了,之前我们说过make的使用隐含规则自动推导:

1
COMPILE.c = $(CC) $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) –c

其中变量CFLAGS 和 CPPFLAGS均是我们给出的,变量$(TARGET_ARCH)未给,所以在编译输出可以看到-c前面有2个空,最早未给变量是有四个空。 目前给出的Makefile基本上可以适用于那些源代码全部在同一目录下的简单项目,并且基本上在增删文件时不需要再去手动修改Makefile代码。在新的一个项目只需要把该Makefile拷贝到源代码目录下,再修改一下你需要编译的可执行文件名称以及你需要的编译连接选项即可。 后面章节将会讲到如何写多目录源代码工程下的Makefile。 最后,今天的最终Makefile是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# A commonMakefile for c programs, version 1.0  
# Copyright (C)2014 shallnew \at 163 \dot com

CFLAGS += -g -Wall-Werror -O2
CPPFLAGS += -I.-I./inc
LDFLAGS +=-lpthread

# SRC_OBJ =$(patsubst %.c, %.o, $(wildcard *.c))
SRC_FILES =$(wildcard *.c)
SRC_OBJ =$(SRC_FILES:.c=.o)
SRC_BIN =target_bin

$(SRC_BIN) :$(SRC_OBJ)
>---$(CC) -o $@$^ $(LDFLAGS)

clean:
>---$(RM)$(SRC_OBJ) $(SRC_BIN)

3. 伪目标

一般情况下,Makefile都会有一个clean目标,用于清除编译过程中产生的二进制文件。我们在第一节的Makefile就用到了这个 clean目标,该目标没有任何依赖文件,并且该目标对应的命令执行后不会生产clean文件。 像这种特点目标,它的规则所定义的命令不是去创建文件,而仅仅通过make指定目标来执行一些特定系统命令或其依赖为目标的规则(如all),称为伪目标。 一个Makefile一般都不会只有一个伪目标,如果按Makefile的“潜规则”以及其约定俗成的名字来说的话,在较大的项目的Makefile中比较常用的为目标有这些:

  • all:执行主要的编译工作,通常用作缺省目标,放在最前面。
  • Install:执行编译后的安装工作,把可执行文件、配置文件、文档等分别拷到不同的安装目录。
  • clean:删除编译生成的二进制文件。
  • distclean:删除除源文件之外的所有中间生成文件,如配置文件,文档等。
  • tags:为vim等编辑器生成tags文件。
  • help:打印当前Makefile的帮助信息,比如有哪些目标可以有make指定去执行。 等。

make处理Makefile时,首先读取所有规则,建立关系依赖图。然后从缺省目标(第一个目标)或指定的目标开始执行。像clean,tags这样的目标一般不会作为缺省目标,也不会跟缺省目标有任何依赖关系,所以 make 无法生成它的依赖关系和决定它是否要执行。所以要执行这样的目标时,必须要显示的指定make该目标。就像前面我们清楚便已产生的中间二进制文件一样,需要显示执行命令:make clean。 伪目标也可以作为默认目标(如all),并且可以为其指定依赖文件。 我们先将version 1.0的Makefile完善下,我们可以加入帮助信息,tags等功能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
# A common Makefile for c programs, version 1.1  

# Copyright (C) 2014 shallnew \at 163 \dot com

CFLAGS += -g -Wall -Werror -O2
CPPFLAGS += -I. -I./inc
LDFLAGS += -lpthread

# SRC_OBJ = $(patsubst %.c, %.o, $(wildcard *.c))
SRC_FILES = $(wildcard *.c)
SRC_OBJ = $(SRC_FILES:.c=.o)
SRC_BIN = target_bin

all : $(SRC_BIN)

$(SRC_BIN) : $(SRC_OBJ)
>---$(CC) -o $@ $^ $(LDFLAGS)

obj : $(SRC_OBJ)

tags:
>---ctags -R

help:
>---@echo "===============A common Makefile for cprograms=============="
>---@echo "Copyright (C) 2014 liuy0711 \at 163 \dotcom"
>---@echo "The following targets are support:"
>---@echo
>---@echo " all - (==make) compile and link"
>---@echo " obj - just compile, without link"
>---@echo " clean - clean target"
>---@echo " distclean - clean target and otherinformation"
>---@echo " tags - create ctags for vim editor"
>---@echo " help - print help information"
>---@echo
>---@echo "To make a target, do 'make [target]'"
>---@echo "========================= Version 1.1======================="

# clean target
clean:
>---$(RM) $(SRC_OBJ) $(SRC_BIN) $(SRC_BIN).exe

distclean:
>---$(RM) $(SRC_OBJ) $(SRC_BIN) $(SRC_BIN).exe tags *~

make会把执行的命令打印在屏幕上,如果我们不想把命令打印在屏幕上,只显示命令结果时,直接在命令前面加上符号“@”就可以实现。如上面help目标一样,只显示命令结果。一般我们会在make时都会输出“Compiling xxx.c…”,不输出编译时的命令。我们在后面写Makefile时可以模仿。 如果当前目录下存在一个和伪目标同名的文件时(如clean),此时如果执行命令make clean后出现如下结果:

1
2
3
4
# touch clean  
# make clean
make: `clean' is up to date.
#

这是因为clean文件没有依赖文件,make认为目标clean是最新的不会去执行规则对应的命令。为了解决这个问题,我们可以明确地将该目标声明为伪目标。将一个目标声明为伪目标需要将它作为特殊目标.PHONY”的依赖。如下:

1
.PHONY : clean

这条规则写在clean:规则的后面也行,也能起到声明clean是伪目标的作用 这样修改一下之前Makefile,将所有伪目标都作为.PHONY的依赖:

1
.PHONY : all obj tag help clean disclean

这样在当前目录下存在文件clean时执行:

1
2
3
# make clean  
rm -f debug.o ipc.o main.o timer.o tools.o target_bin target_bin.exe
#

发现问题解决。 最后,给出今天最终的Makefile:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
# A common Makefile for c programs, version 1.1                                                                                                                                            
# Copyright (C) 2014 shallnew \at 163 \dot com

CFLAGS += -g -Wall -Werror -O2
CPPFLAGS += -I. -I./inc
LDFLAGS += -lpthread

# SRC_OBJ = $(patsubst %.c, %.o, $(wildcard *.c))
SRC_FILES = $(wildcard *.c)
SRC_OBJ = $(SRC_FILES:.c=.o)
SRC_BIN = target_bin

all : $(SRC_BIN)

$(SRC_BIN) : $(SRC_OBJ)
>---$(CC) -o $@ $^ $(LDFLAGS)

obj : $(SRC_OBJ)

tag:
>---ctags -R

help:
>---@echo "===============A common Makefile for cprograms=============="
>---@echo "Copyright (C) 2014 liuy0711 \at 163 \dotcom"
>---@echo "The following targets are support:"
>---@echo
>---@echo " all - (==make) compile and link"
>---@echo " obj - just compile, without link"
>---@echo " clean - clean target"
>---@echo " distclean - clean target and other information"
>---@echo " tags - create ctags for vim editor"
>---@echo " help - print help information"
>---@echo
>---@echo "To make a target, do 'make [target]'"
>---@echo "========================= Version 1.1======================="

# clean target
clean:
>---$(RM) $(SRC_OBJ) $(SRC_BIN) $(SRC_BIN).exe

distclean:
>---$(RM) $(SRC_OBJ) $(SRC_BIN) $(SRC_BIN).exe tags *~

.PHONY : all obj tag help clean disclean

5. 嵌套执行

在大一些的项目里面,所有源代码不会只放在同一个目录,一般各个功能模块的源代码都是分开的,各自放在各自目录下,并且头文件和.c源文件也会有各自的目录,这样便于项目代码的维护。这样我们可以在每个功能模块目录下都写一个Makefile,各自Makefile处理各自功能的编译链接工作,这样我们就不必把所有功能的编译链接都放在同一个Makefile里面,这可使得我们的Makefile变得更加简洁,并且编译的时候可选择编译哪一个模块,这对分块编译有很大的好处。 现在我所处于工程目录树如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
.  

├── include
│ ├── common.h
│ ├── ipc
│ │ └── ipc.h
│ └── tools
│ ├── base64.h
│ ├── md5.h
│ └── tools.h
├── Makefile
├── src
│ ├── ipc
│ │ ├── inc
│ │ ├── Makefile
│ │ └── src
│ │ └── ipc.c
│ ├── main
│ │ ├── inc
│ │ ├── Makefile
│ │ └── src
│ │ ├── main.c
│ │ └── main.c~
│ └── tools
│ ├── inc
│ ├── Makefile
│ └── src
│ ├── base64.c
│ ├── md5.c
│ └── tools.c
└── tags

13 directories, 16 files

这样组织项目源码要比之前合理一些,那这样怎么来写Makefile呢?我们可以在每个目录下写一个Makefile,通过最顶层的Makefile一层一层的向下嵌套执行各层Makefile。那么我们最顶层的Makefile简单点的话可以这样写:

1
2
3
4
5
6
7
8
9
10
11
12
# top Makefile for xxx  

all :
>---$(MAKE) -C src

tags:
>---ctags -R

clean :
>---$(MAKE) -C src clean

.PHONY : all clean tags

命令:

1
>---$(MAKE) -C src

就是进入src目录继续执行该目录下的Makefile。然后src目录下的Makefile在使用同样的方法进入下一级目录tools、main、ipc,再执行该目录下的Makefile。其实这样有些麻烦,我们可以直接从顶层目录进入最后的目录执行make。再加入一些伪目标完善下,我们的顶层Makefile就出来了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# Top Makefile for C program  

# Copyright (C) 2014 shallnew \at 163 \dot com

all :
>---$(MAKE) -C src/ipc
>---$(MAKE) -C src/tools
>---$(MAKE) -C src/main

tags:
>---ctags -R

help:
>---@echo "===============A common Makefilefor c programs=============="
>---@echo "Copyright (C) 2014 liuy0711 \at 163\dot com"
>---@echo "The following targets aresupport:"
>---@echo
>---@echo " all - (==make) compile and link"
>---@echo " obj - just compile, withoutlink"
>---@echo " clean - clean target"
>---@echo " distclean - clean target and otherinformation"
>---@echo " tags - create ctags for vimeditor"
>---@echo " help - print help information"
>---@echo
>---@echo "To make a target, do 'make[target]'"
>---@echo "========================= Version2.0 ======================="

obj:
>---$(MAKE) -C src/ipc obj
>---$(MAKE) -C src/tools obj
>---$(MAKE) -C src/main obj

clean :
>---$(MAKE) -C src/ipc clean
>---$(MAKE) -C src/tools clean
>---$(MAKE) -C src/main clean

distclean:
>---$(MAKE) -C src/ipc distclean
>---$(MAKE) -C src/tools distclean
>---$(MAKE) -C src/main distclean

.PHONY : all clean distclean tags help

当我们这样组织源代码时,最下面层次的Makefile怎么写呢?肯定不可以将我们上一节的Makefile(version 1.1)直接拷贝到功能模块目录下,需要稍作修改。不能所有的模块都最终生成各自的可执行文件吧,我们目前是一个工程,所以最后只会生成一个可执行程序。我们这样做,让主模块目录生成可执行文件,其他模块目录生成静态库文件,主模块链接时要用其他模块编译产生的库文件来生成最终的程序。将上一节Makefile稍作修改得出编译库文件Makefile和编译可执行文件Makefile分别如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# A Makefile to generate archive file  
# Copyright (C) 2014 shallnew \at 163 \dot com


CFLAGS += -g -Wall -Werror -O2
CPPFLAGS += -I. -I./inc -I../../include

# SRC_OBJ = $(patsubst %.c, %.o, $(wildcard *.c))
SRC_FILES = $(wildcard src/*.c)
SRC_OBJ = $(SRC_FILES:.c=.o)
SRC_LIB = libtools.a

all : $(SRC_LIB)

$(SRC_LIB) : $(SRC_OBJ)
>---$(AR) rcs $@ $^
>---cp $@ ../../libs

obj : $(SRC_OBJ)

# clean target
clean:
>---$(RM) $(SRC_OBJ) $(SRC_LIB)

distclean:
>---$(RM) $(SRC_OBJ) $(SRC_LIB) tags *~

.PHONY : all obj clean disclean

====================

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# A Makefile to generate executive file                                                                                                                                                     
# Copyright (C) 2014 shallnew \at 163 \dot com

CFLAGS += -g -Wall -Werror -O2
CPPFLAGS += -I. -I./inc -I../../include
LDFLAGS += -lpthread -L../../libs -ltools -lipc


# SRC_OBJ = $(patsubst %.c, %.o, $(wildcard *.c))
SRC_FILES = $(wildcard src/*.c)
SRC_OBJ = $(SRC_FILES:.c=.o)
SRC_BIN = target_bin

all : $(SRC_BIN)

$(SRC_BIN) : $(SRC_OBJ)
>---$(CC) -o $@ $^ $(LDFLAGS)

obj : $(SRC_OBJ)

# clean target
clean:
>---$(RM) $(SRC_OBJ) $(SRC_BIN) $(SRC_BIN).exe

distclean:
>---$(RM) $(SRC_OBJ) $(SRC_BIN) $(SRC_BIN).exe tags*~

.PHONY : all obj clean disclean

最后在顶层执行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# make clean  

make -C src/ipc clean
make[1]: Entering directory`/home/Myprojects/example_make/version-3.0/src/ipc'
rm -f src/ipc.o libipc.a
make[1]: Leaving directory`/home/Myprojects/example_make/version-3.0/src/ipc'
make -C src/tools clean
make[1]: Entering directory `/home/Myprojects/example_make/version-3.0/src/tools'
rm -f src/base64.o src/md5.o src/tools.o libtools.a
make[1]: Leaving directory`/home/Myprojects/example_make/version-3.0/src/tools'
make -C src/main clean
make[1]: Entering directory`/home/Myprojects/example_make/version-3.0/src/main'
rm -f src/main.o target_bin target_bin.exe
make[1]: Leaving directory`/home/Myprojects/example_make/version-3.0/src/main'
# make
make -C src/ipc
make[1]: Entering directory`/home/Myprojects/example_make/version-3.0/src/ipc'
cc -g -Wall -Werror -O2 -I. -I./inc-I../../include -c -o src/ipc.osrc/ipc.c
ar rcs libipc.a src/ipc.o
cp libipc.a ../../libs
make[1]: Leaving directory `/home/Myprojects/example_make/version-3.0/src/ipc'
make -C src/tools
make[1]: Entering directory`/home/Myprojects/example_make/version-3.0/src/tools'
cc -g -Wall -Werror -O2 -I. -I./inc-I../../include -c -o src/base64.osrc/base64.c
cc -g -Wall -Werror -O2 -I. -I./inc -I../../include -c -o src/md5.o src/md5.c
cc -g -Wall -Werror -O2 -I. -I./inc-I../../include -c -o src/tools.osrc/tools.c
ar rcs libtools.a src/base64.o src/md5.o src/tools.o
cp libtools.a ../../libs
make[1]: Leaving directory`/home/Myprojects/example_make/version-3.0/src/tools'
make -C src/main
make[1]: Entering directory`/home/Myprojects/example_make/version-3.0/src/main'
cc -g -Wall -Werror -O2 -I. -I./inc-I../../include -c -o src/main.osrc/main.c
cc -o target_bin src/main.o -lpthread -L../../libs -ltools-lipc
make[1]: Leaving directory`/home/Myprojects/example_make/version-3.0/src/main'
#

最后生成了可执行程序文件。这样的话一个工程的各个模块就变得独立出来了,不但源码分开了,而且各自有各自的Makefile,并且各个功能模块是可独立编译的。 我们发现顶层Makefile还有可以改进的地方,就是在进入下一层目录是要重复写多次,如下:

1
2
3
>---$(MAKE) -C src/ipc  
>---$(MAKE) -C src/tools
>---$(MAKE) -C src/main

每增加一个目录都要在多个伪目标里面加入一行,这样不够自动化啊,于是我们想到shell的循环语 句,我们可以在每条规则的命令处使用for循环。如下:

1
2
3
4
5
6
7
DIR = src  
SUBDIRS = $(shell ls $(DIR))

all :
>---@for subdir in $(SUBDIRS); \
>---do $(MAKE) -C $(DIR)/$$subdir; \
>---done

这样懒人有可以高兴很久了。不过还有问题: 上面for循环会依次进入系统命令ls列出的目录,但我们对每个目录的make顺序可能有要求,在该项目当中,main目录下的Makefile必须最后执行,因为最终的链接需要其他目录编译生成的库文件,否则会执行失败。并且在当前的Makefile中,当子目录执行make出现错误时,make不会退出。在最终执行失败的情况下,我们很难根据错误的提示定位出具体是是那个目录下的Makefile出现错误。这给问题定位造成了很大的困难。为了避免这样的问题,在命令执行错误后make退出。 所以将刚才的Makefile修改为如下

1
2
3
4
5
6
7
DIR = src  
SUBDIRS = $(shell ls $(DIR))

all :
>---@for subdir in $(SUBDIRS); \
>---do $(MAKE) -C $(DIR)/$$subdir || exit 1; \
>---done

这样在执行出错时立马退出,但这样还是没有解决问题,编译错误还是会出现。那怎么解决呢? 我们可以通过增加规则来限制make执行顺序,这样就要用到伪目标,对每一个模块我们都为他写一条规则,每个模块名称是目标,最后需要执行的模块目标又是其他模块的目标,这样就限制了make顺序。在执行到最后需要执行的目标时,发现存在依赖,于是先更新依赖的目标,这样就不会出错了。并且这样的话,我们还可以对指定模块进行编译,比如我只修改了tools模块,我只想看看我修改的这个模块代码是否可以编译通过,我可以在编译时这样:

1
2
3
4
5
6
7
8
9
10
# make tools  
make -C src/tools
make[1]: Entering directory`/home/Myprojects/example_make/version-2.1/src/tools'
cc -g -Wall -Werror -O2 -I. -I./inc-I../../include -c -o src/base64.o src/base64.c
cc -g -Wall -Werror -O2 -I. -I./inc-I../../include -c -o src/md5.osrc/md5.c
cc -g -Wall -Werror -O2 -I. -I./inc-I../../include -c -o src/tools.osrc/tools.c
ar rcs libtools.a src/base64.o src/md5.o src/tools.o
cp libtools.a ../../libs
make[1]: Leaving directory`/home/Myprojects/example_make/version-2.1/src/tools'
#

还有另外一种方法也可以解决此问题,就是手动列出需要进入执行的模块名称(这里就是目录了),把最后需要执行的模块放在最后,这样for循环执行时最后需要编译链接的模块就放在最后了,不会像我们之前那样make是按照使用系统命令ls列出模块目录的顺序来执行。ls列出目录是按照每个目录的名称来排序的,我们总不能要求写代码的时候最后执行的模块的名称必须是以z开头的吧,总之不现实。 我们的顶层Makefile又进化了,也是这一节最终Makefile:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
# Top Makefile for C program  
# Copyright (C) 2014 shallnew \at 163 \dot com

DIR = src
MODULES = $(shell ls $(DIR))
# MODULES = ipc main tools

all : $(MODULES)

$(MODULES):
>---$(MAKE) -C $(DIR)/$@

main:tools ipc

obj:
>---@for subdir in $(MODULES); \
>---do $(MAKE) -C $(DIR)/$$subdir $@; \
>---done

clean :
>---@for subdir in $(MODULES); \
>---do $(MAKE) -C $(DIR)/$$subdir $@; \
>---done

distclean:
>---@for subdir in $(MODULES); \
>---do $(MAKE) -C $(DIR)/$$subdir $@; \
>---done


tags:
>---ctags -R

help:
>---@echo "===============A common Makefilefor c programs=============="
>---@echo "Copyright (C) 2014 liuy0711 \at 163\dot com"
>---@echo "The following targets aresupport:"
>---@echo
>---@echo " all - (==make) compile and link"
>---@echo " obj - just compile, withoutlink"
>---@echo " clean - clean target"
>---@echo " distclean - clean target and otherinformation"
>---@echo " tags - create ctags for vimeditor"
>---@echo " help - print help information"
>---@echo
>---@echo "To make a target, do 'make[target]'"
>---@echo "========================= Version2.0 ======================="

.PHONY : all clean distclean tags help

6.参数传递、条件判断、include

在多个Makefile嵌套调用时,有时我们需要传递一些参数给下一层Makefile。比如我们在顶层Makefile里面定义的打开调试信息变量DEBUG_SYMBOLS,我们希望在进入子目录执行子Makefile时该变量仍然有效,这是需要将该变量传递给子Makefile,那怎么传递呢?这里有两种方法:

  1. 在上层Makefile中使用”export”关键字对需要传递的变量进行声明。比如:

    1
    2
    DEBUG_SYMBOLS = TRUE  
    export DEBUG_SYMBOLS

    当不希望将一个变量传递给子 make 时,可以使用指示符 “unexport”来声明这个变量。 export一般用法是在定义变量的同时对它进行声明。如下:

    1
    export DEBUG_SYMBOLS = TRUE
  1. 在命令行上指定变量。比如:
    1
    $(MAKE) -C xxx DEBUG_SYMBOLS = TRUE

这样在进入子目录xxx执行make时该变量也有效。

像编程语言一样,Makefile也有自己的条件语句。条件语句可以根据一个变量值来控制make的执行逻辑。比较常用的条件语句是ifeq –else-endif、ifneq-else-endif、ifdef-else-endif。 ifeq关键字用来判断参数是否相等。 比如判断是否生成调试信息可以这么用:

1
2
3
4
5
ifeq ($(DEBUG_SYMBOLS), TRUE)  
>---CFLAGS += -g -Wall -Werror -O0
else
>---CFLAGS += -Wall -Werror -O2
endif

Ifneq和ifeq作用相反,此关键字是用来判断参数是否不相等。 ifdef关键字用来判断一个变量是否已经定义。 后两个关键字用法和ifeq类似。

现在我们继续改进我们上一节的Makefile,上一节的Makefile完成Makefile的嵌套调用,每一个模块都有自己的Makefile。其实每个模块的Makefile都大同小异,只需要改改最后编译成生成的目标名称或者编译链接选项,规则都差不多,那么我们是否可以考虑将规则部分提取出来,每个模块只需修改各自变量即可。这样是可行的,我们将规则单独提取出来,写一个Makefile.rule,将他放在顶层Makefile同目录下,其他模块内部的Makefile只需要include该Makefile就可以了。如下:

1
include $(SRC_BASE)/Makefile.rule

include类似于C语言的头文件包含,你把它理解为为本替换就什么都明白了。 这样以后规则有修改的话我们直接修改该Makefile就可以了,就不用进入每一个模块去修改,这样也便于维护。 这样我们今天顶层Makefile稍作修改:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
# Top Makefile for C program                                                                                                                                                               
# Copyright (C) 2014 shallnew \at 163 \dot com

export DEBUG_SYMBOLS = TRUE

DIR = src
MODULES = $(shell ls $(DIR))
# MODULES = ipc main tools

all : $(MODULES)

$(MODULES):
>---$(MAKE) -C $(DIR)/$@

main:tools ipc

clean :
>---@for subdir in $(MODULES); \
>---do $(MAKE) -C $(DIR)/$$subdir $@; \
>---done

distclean:
>---@for subdir in $(MODULES); \
>---do $(MAKE) -C $(DIR)/$$subdir $@; \
>---done

tags:
>---ctags -R

help:
>---@echo "===============A common Makefilefor c programs=============="
>---@echo "Copyright (C) 2014 liuy0711 \at 163\dot com"
>---@echo "The following targets aresupport:"
>---@echo
>---@echo " all - (==make) compile and link"
>---@echo " clean - clean target"
>---@echo " distclean - clean target and otherinformation"
>---@echo " tags - create ctags for vimeditor"
>---@echo " help - print help information"
>---@echo
>---@echo "To make a target, do 'make[target]'"
>---@echo "========================= Version2.2 ======================="

.PHONY : all clean distclean tags help

目前我们顶层目录下的目录树为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
.  
├── include
│ ├── common.h
│ ├── ipc
│ │ └── ipc.h
│ └── tools
│ ├── base64.h
│ ├── md5.h
│ └── tools.h
├── libs
├── Makefile
├── Makefile.rule
└── src
├── ipc
│ ├──inc
│ ├──Makefile
│ └──src
│ └── ipc.c
├── main
│ ├──inc
│ ├──Makefile
│ └──src
│ ├── main.c
│ └── main.c~
└── tools
├── inc
├── Makefile
└── src
├── base64.c
├── md5.c
└── tools.c

14 directories, 16 files

每个子模块下的Makefile删除规则后修改为如下:

1
2
3
4
5
6
7
8
9
10
11
SRC_BASE = ../..  

CFLAGS +=
CPPFLAGS += -I. -I./inc -I$(SRC_BASE)/include

# SRC_OBJ = $(patsubst %.c, %.o, $(wildcard *.c))
SRC_FILES = $(wildcard src/*.c)
SRC_OBJ = $(SRC_FILES:.c=.o)
SRC_LIB = libtools.a

include $(SRC_BASE)/Makefile.rule

而处于顶层目录下的Makefile.rule专门处理各模块编译链接时需要的规则。内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# Copyright (C) 2014 shallnew \at 163 \dot com                                                                                                                                             

ifeq ($(DEBUG_SYMBOLS), TRUE)
>---CFLAGS += -g -Wall -Werror -O0
else
>---CFLAGS += -Wall -Werror -O2
endif

all : $(SRC_BIN) $(SRC_LIB)

ifneq ($(SRC_BIN),)
$(SRC_BIN) : $(SRC_OBJ)
>---$(CC) -o $@ $^ $(LDFLAGS)
endif

ifneq ($(SRC_LIB),)
$(SRC_LIB) : $(SRC_OBJ)
>---$(AR) rcs $@ $^
>---cp $@ $(SRC_BASE)/libs
endif

# clean target
clean:
>---$(RM) $(SRC_OBJ) $(SRC_LIB) $(SRC_BIN)$(SRC_BIN).exe

distclean:
>---$(RM) $(SRC_OBJ) $(SRC_LIB) $(SRC_BIN)$(SRC_BIN).exe $(SRC_BASE)/libs/* $(SRC_BASE)/tags *~

.PHONY : all clean disclean
~

我们将Makefile.rule放在顶层有可能会一不小心在命令行上面执行了该Makefile,如下:

1
2
3
# make -f Makefile.rule  
make: Nothing tobe done for `all'.
#

由于我们没有定义变量$(SRC_BIN)$(SRC_LIB),伪目标all没有任何依赖,所以编译是无法成功的。这里我们我们应该禁止直接执行该Makefile。 在make里面有这样一个变量:MAKELEVEL,它在多级调用的 make 执行过程中。变量代表了调用的深度。在 make 一级级的执行过程中变量MAKELEVEL的值不断的发生变化,通过它的值我们可以了解当前make 递归调用的深度。顶层的MAKELEVEL的值为“0” 、下一级时为“1” 、再下一级为“2”…….,所以我们希望一个子目录的Makefile必须被上层 make 调用才可以执行,而不允许直接执行,我们可以判断变量MAKELEVEL来控制。所以我们这一节最终的Makefile.rule为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# Copyright (C)2014 shallnew \at 163 \dot com  

ifeq ($(DEBUG_SYMBOLS),TRUE)
>---CFLAGS +=-g -Wall -Werror -O0
else
>---CFLAGS +=-Wall -Werror -O2
endif

ifeq($(MAKELEVEL), 0)
all : msg
else
all : $(SRC_BIN)$(SRC_LIB)
endif

ifneq ($(SRC_BIN),)
$(SRC_BIN) :$(SRC_OBJ)
>---$(CC) -o $@$^ $(LDFLAGS)
endif

ifneq($(SRC_LIB),)
$(SRC_LIB) :$(SRC_OBJ)
>---$(AR) rcs$@ $^
>---cp $@$(SRC_BASE)/libs
endif

msg:
>---@echo"You cannot directily execute this Makefile! This Makefile should calledby toplevel Makefile."

# clean target
clean:
>---$(RM)$(SRC_OBJ) $(SRC_LIB) $(SRC_BIN) $(SRC_BIN).exe

distclean:
>---$(RM)$(SRC_OBJ) $(SRC_LIB) $(SRC_BIN) $(SRC_BIN).exe $(SRC_BASE)/libs/*$(SRC_BASE)/tags *~

.PHONY : all cleandisclean

此时再直接执行该Makefile:

1
2
3
# make -f Makefile.rule  
You cannot directily execute this Makefile! This Makefile should called by toplevel Makefile.
#

7. 统一目标输出目录

上一节我们把规则单独提取出来,方便了Makefile的维护,每个模块只需要给出关于自己的一些变量,然后再使用统一的规则Makefile。这一节我们继续改进我们的Makefile,到目前为止我们的Makefile编译链接输出的目标都在源文件同目录下或模块Makefile同一目录下,当一个项目大了之后,这样会显得很乱,寻找编译输出的文件也比较困难。既然Makefile本身就是按照我们的的规则来编译链接程序,那么我们就可以指定其编译链接目标的目录,这样,我们可以清楚输出文件的地方,并且在清除已编译的目标时直接删除指定目录即可,不需要一层一层的进入源代码目录进行删除,这样又提高了效率。

既然要统一目标输出目录,那么该目录就需要存在,所以我们可以增加一条规则来创建这些目录,包括创建可执行文件的目录、链接库文件的目录以及.o文件的目录。并且目录还可以通过条件判断根据是否产生调试信息来区分开相应的目标文件。一般一个工程的顶层目录下都会有一个build目录来存放编译的目标文件结果,目前我的工程目录下通过Makefile创建的目录build的目录树如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
build/            //build根目录  
├── unix //unix平台项目下不带调试信息输出目录
│ ├── bin //存放可执行文件目录
│ ├── lib //存放可文件目录
│ └── obj //存放.o文件目录,该目录下将每个模块生成的.o文件各自的目录下面
│ ├── ipc
│ ├── main
│ └── tools
└── unix_dbg ////unix平台项目下带调试信息输出目录
├── bin
├── lib
└── obj
├── ipc
├── main
└── tools

14 directories, 0 files
```
以上目录中bin和lib目录在顶层Makefile中创建,obj及其下面模块子目录在各模块的Makefile里面创建。
顶层Makefile创建目录如下:

ifeq ($(DEBUG_SYMBOLS), TRUE)

—BUILDDIR = ./build/$(PLATFORM)_dbg
else
—BUILDDIR = ./build/$(PLATFORM)
endif

all : $(BUILDDIR) $(MODULES)

$(BUILDDIR):

—@echo “ Create directory $@ …”
—mkdir -p $(BUILDDIR)/bin $(BUILDDIR)/lib

1
2
3
我们在all目标里面增加了其依赖目标BUILDDIR,该目标对应的规则为创建bin目录和lib目录。这样每次编译之前都会创建目录。

各模块内部Makefile创建生成.O文件的目录,如上目录树所示。类似于顶层Makefile,各模块内部Makefile需要根据平台、编译调试信息、以及模块名称来生成需要的目录名称,然后再增加创建该目录的规则。因为每个模块都会做这些处理,所以我们将这部分写在规则Makefile(Makefile.rule)里面,如下:

……

define a root build directory base on the platform

if without a SRC_BASE defined, just use local src directory

ifeq ($(SRC_BASE),)

—BUILDDIR = $(MOD_SRC_DIR)
—OBJDIR = $(MOD_SRC_DIR)
—LIBDIR = $(MOD_SRC_DIR)
—BINDIR = $(MOD_SRC_DIR)
else
—ifeq ($(DEBUG_SYMBOLS), TRUE)
—>—BUILDDIR = $(SRC_BASE)/build/$(PLATFORM)_dbg
—else
—>—BUILDDIR = $(SRC_BASE)/build/$(PLATFORM)
—endif
—OBJDIR = $(BUILDDIR)/obj/$(MODULE)
—LIBDIR = $(BUILDDIR)/lib
—BINDIR = $(BUILDDIR)/bin
endif
……
ifeq ($(MAKELEVEL), 0)
all : msg
else
all : lib bin
endif

lib : $(OBJDIR) $(SRC_LIB)

bin : $(OBJDIR) $(SRC_BIN)

$(OBJDIR) :

—@echo “ MKDIR $(notdir $@)…”
—@mkdir -p $@
……

1
此时我们编译一下后查看build目录:

build/
└── unix_dbg
├── bin
├── lib
└── obj
├── ipc
├── main
└── tools

7 directories, 0 files

1
2
3
4
5
6
由于我们是开启了调试信息,所以创建了unix_dbg目录,并且该目录下创建了bin、lib、obj目录及其模块目录,但我们没有发现有文件存放在里面。

到目前为止,这一节仅仅讲述如何创建统一的目标文件存放目录,但是要想将编译生成的目标文件自动生成到这些目录还没有完成。其实我们只需要给目标加上路径即可,但还是有一些详细的地方需要处理,具体的我们会在下一节中讲到,这一节暂不给出最后的Makefile。

### 8. 模式规则
上一节讲到目录创建成功,目标文件没有生产到对应目录下,这里我们先给目标文件加上对应目录,这样的话产生对应的目标文件会直接生成到对应目录。我们先给库文件目标和可执行文件目标加上路径,如下:

lib : $(OBJDIR) $(LIBDIR)/$(SRC_LIB)

bin : $(OBJDIR) $(BINDIR)/$(SRC_BIN)

$(OBJDIR) :

—@echo “ MKDIR $(notdir $@)…”
—@mkdir -p $@

ifneq ($(SRC_BIN),)
$(BINDIR)/$(SRC_BIN) : $(SRC_OBJ)

—$(CC) -o $@ $^ $(LDFLAGS)
endif

ifneq ($(SRC_LIB),)
$(LIBDIR)/$(SRC_LIB) : $(SRC_OBJ)

—$(AR) rcs $@ $^
—cp $@ $(SRC_BASE)/libs
endif

1
此时再执行make,完成后查看build目录树:

build/
└── unix_dbg
├── bin
│ └── target_bin
├── lib
│ ├── libipc.a
│ └── libtools.a
└── obj
├── ipc
├── main
└── tools

1
可以看到,生成的目标是在对应目录下。我们乘胜追击,把.o文件也将其修改了。我们之前的每个模块Makefile大致是这样写的:

SRC_BASE = ../..

CFLAGS +=
CPPFLAGS += -I. -I./inc -I$(SRC_BASE)/include

SRC_OBJ = $(patsubst %.c, %.o, $(wildcard *.c))

SRC_FILES = $(wildcard src/*.c)
SRC_OBJ = $(SRC_FILES:.c=.o)
SRC_LIB = xx.a

include $(SRC_BASE)/Makefile.rule

1
其中SRC_OBJ在此处给出,然后再在Makefile.rule中使用,此处的.o文件会在.c文件相同目录下生成,所以我们现在需要将.o文件加上路径,由于取得路径是在Makefile.rule里面,所以我们可以统一在Makefile.rule里面给变量SRC_OBJ赋值,大致如下:

SRC_OBJ = $(patsubst %.c, $(OBJDIR)/%.o, $(notdir $(SRC_FILES)))

1
2
这里用到函数patsubst、notdir,关于函数会在后面讲到。这样.o文件作为目标生成之后就会生成到相应目录里面了。
此时再编译:

make

make[1]: Entering directory /home/Myprojects/example_make/version-2.9/src/ipc' make[1]: *** No rule to make target../../build/unix_dbg/obj/ipc/ipc.o’, needed by ../../build/unix_dbg/lib/libipc.a'. Stop. make[1]: Leaving directory/home/Myprojects/example_make/version-2.9/src/ipc’
make: *** [ipc] Error 2

1
2
发现出错了,并且是在生成目标文件ipc.o时没有成功,查看build目录树也没有生成.o文件。为什么会生成失败呢?
我们没有给出生成.o目标的规则,之前可以生成是因为make有通过隐含规则来自动推导的能力(这个之前有讲到,链接过去)。在我们没有修改之前,生成.o通过隐含规则来完成:

%.o: %.c

commands to execute (built-in):

—$(COMPILE.c) $(OUTPUT_OPTION) $<

1
该模式规则中目标文件是$(OBJDIR)/%.o,那么现在有了符合生成我们需要的.o文件的规则了,编译一下:

make

make[1]: Entering directory /home/Myprojects/example_make/version-2.9/src/ipc' make[1]: *** No rule to make target../../build/unix_dbg/obj/ipc/ipc.o’, needed by ../../build/unix_dbg/lib/libipc.a'. Stop. make[1]: Leaving directory/home/Myprojects/example_make/version-2.9/src/ipc’
make: *** [ipc] Error 2
#

1
2
3
4
5
6
发现还是不对,不是已经增加了模式规则了吗,为何还是没有生成.o文件。
我们这里先说说静态模式规则:

一个规则中可以有多个目标,规则所定义的命令对所有的目标有效。一个具有多目标的规则相当于多个规则。 规则的命令对不同的目标的执行效果不同, 因为在规则的命令中可能使用了自动化变量 `“$@”` 。 多目标规则意味着所有的目标具有相同的依赖文件。多目标通常用在以下两种情况:虽然在多目标的规则中, 可以根据不同的目标使用不同的命令 (在命令行中使用自动化变量 `“$@”` )。但是, 多目标的规则并不能做到根据目标文件自动改变依赖文件 (像上边例子中使用自动化变量“$@”改变规则的命令一样) 。需要实现这个目的是,要用到make的静态模式。

静态模式规则是这样一个规则:规则存在多个目标, 并且不同的目标可以根据目标文件的名字来自动构造出依赖文件。静态模式规则比多目标规则更通用, 它不需要多个目标具有相同的依赖。但是静态模式规则中的依赖文件必须是相类似的而不是完全相同的。静态模式规则语法如下:

<targets …>: : <prereq-patterns …>

….

1
比如下面是一个静态模式规则:

objects = foo.o bar.o

all: $(objects)

$(objects): %.o: %.c
$(CC) -c $(CFLAGS) $< -o $@

1
该规则描述了所有的.o文件的依赖文件为对应的.c文件,对于目标“foo.o” ,取其茎“foo”替代对应的依赖模式“%.c”中的模式字符“%”之后可得到目标的依赖文件“foo.c”。这就是目标“foo.o”的依赖关系“foo.o: foo.c”,规则的命令行描述了如何完成由“foo.c”编译生成目标“foo.o” 。命令行中“$<”和“$@”是自动化变量,“$<” 表示规则中的第一个依赖文件, “$@” 表示规则中的目标文件。上边的这个规则描述了以下两个具体的规则:

foo.o : foo.c

—$(CC) -c $(CFLAGS) foo.c -o foo.o
bar.o : bar.c
—$(CC) -c $(CFLAGS) bar.c -o bar.o

1
2
3
4
5
6
7
8
9
注:该示例与其相关描述摘抄于互联网,描述很不错,估计比我讲的详细)

那静态模式规则和普通的模式规则(非静态模式规则)有什么去区别呢?两者都是用目标模式和依赖模式来构建目标的规则中的文件依赖关系,两者不同的地方是 make 在执行时使用它们的时机。
静态模式规则只能用在规则中明确指出的那些文件的重建过程中。不能用在除此之外的任何文件的重建过程中,并且它对指定的每一个目标来说是唯一的。如果一个目标存在于两个规则,并且这两个规则都定义了命令, make 执行时就会提示错误。
非静态模式规则可被用在任何和它相匹配的目标上,当一个目标文件同时符合多个目标模式时,make将会把第一个目标匹配的模式规则作为重建它的规则。

那有没有想过如果我们指定了模式规则后,那还有隐含规则呢,那怎么选择执行哪一个模式规则呢?Makefile中明确指定的模式规则会覆盖隐含模式规则。就是说如果在Makefile中出现了一个对目标文件合适可用的模式规则,那么make就不会再为这个目标文件寻找其它隐含规则,而直接使用在Makefile中出现的这个规则。在使用时,明确规则永远优先于隐含规则。

我们继续说之前的那个问题,我们定义了模式规则后还是没有生成.o文件,我们现在将其改为静态规则再试试就看,如下:

$(SRC_OBJ) : $(OBJDIR)/%.o : %.c

—$(CC) -c $(CFLAGS) $(CPPFLAGS) $< -o $@

1
执行后:

make

make[1]: Entering directory /home/Myprojects/example_make/version-2.9/src/ipc' make[1]: *** No rule to make targetipc.c’, needed by ../../build/unix_dbg/obj/ipc/ipc.o'. Stop. make[1]: Leaving directory/home/Myprojects/example_make/version-2.9/src/ipc’
make: *** [ipc] Error 2
#

1
2
3
4
5
6
7
8
9
发现提示没有文件ipc.c,这说明没有生成.o的原因是没有.c文件,我很好奇的是为何使用非静态模式为何不提示呢?(还没搞懂,再研究研究,知道的可以给个提示哈~~)

缺少依赖文件,为何没有*.c文件,仔细想想我们的.o文件没有和.c文件在同一目录。在我们工程中,将源代码和二进制文件(.o 文件和可执行文件)安排在不同的目录来进行区分管理。这种情况下,我们可以使用 make 提供的目录搜索依赖文件功能。该功能在下一节讲述,这一节说的够多了,有点累了。可惜最终还是没有给出一个可用的Makefile,在下一节将会给出。

### 9. 目标搜索
在一个较大的工程中,一般会将源代码和二进制文件(.o 文件和可执行文件)安排在不同的目录来进行区分管理。这种情况下,我们可以使用 make 提供的目录搜索依赖文件功能(在指定的若干个目录下自动搜索依赖文件)。在Makefile中,使用依赖文件的目录搜索功能。当工程的目录结构发生变化后,就可以做到不更改 Makefile的规则,只更改依赖文件的搜索目录。

在我们上一节出现的问题当中,我们将.c文件统一放在src目录下,没有和Makefile目录在同一目录下,因此没有办法寻找到.o文件的依赖文件。make程序有一个特殊的变量VPATH,该变量可以指定依赖文件的搜索路径,当规则的依赖文件在当前目录不存在时,make 会在此变量所指定的目录下去寻找这些依赖文件。通常我们都是用此变量来指定规则的依赖文件的搜索路径。
定义变量 “VPATH”时,使用空格或者冒号(:)将多个需要搜索的目录分开。make搜索目录的顺序是按照变量“VPATH”定义中的目录顺序进行的,当前目录永远是第一搜索目录。例如如下定义

VPATH += ./src

1
2
3
指定了依赖搜索目录为当前目录下的src目录,我们可以在Makefile.rules里面添加给VPATH变量赋值,而在包含该Makefile.rules之前给出当前模块.c文件所在目录。

其实我们也可以直接指定依赖文件的路径,这样也是可以的,如下:

$(SRC_OBJ) : $(OBJDIR)/%.o : $(MOD_SRC_DIR)/%.c

—$(CC) -c $(CFLAGS) $(CPPFLAGS) $< -o $@

1
2
3
4
5
6
7
8
9
10
11
12

但是这样在我们更改了工程目录结构之后,对应的依赖文件没有在同一目录下,又变得麻烦了,所以还不如直接给VPATH变量赋值,我们只需要指定源码所在的目录即可。

其实我们还有另外一种搜索文件路径方法:使用vpath关键字(注意不是VPATH变量), 它和VPATH类似,但是它可以为不同类型的文件(由文件名区分)指定不同的搜索目录。使用方法有三种:
1. vpath PATTERN DIRECTORIES
为所有符合模式“PATTERN”的文件指定搜索目录“DIRECTORIES” 。多个目录使用空格或者冒号(:)分开。
2. vpath PATTERN
清除之前为符合模式“PATTERN”的文件设置的搜索路径。
3. vpath
清除所有已被设置的文件搜索路径。

vapth 使用方法中的“PATTERN”需要包含模式字符“%”;例如上面的定义:

VPATH += ./src

1
可以写为:

vpath %.c ./src

1
现在给一个我们的Makefile.rules:

Copyright (C) 2014 shallnew \at 163 \dot com

if without a platform defined, give value “unknow” to PLATFORM

ifndef PLATFORM

—PLATFORM = unknow
endif

define a root build directory base on the platform

if without a SRC_BASE defined, just use local src directory

ifeq ($(SRC_BASE),)

—BUILDDIR = $(MOD_SRC_DIR)
—OBJDIR = $(MOD_SRC_DIR)
—LIBDIR = $(MOD_SRC_DIR)
—BINDIR = $(MOD_SRC_DIR)
else
—ifeq ($(DEBUG_SYMBOLS), TRUE)
—>—BUILDDIR = $(SRC_BASE)/build/$(PLATFORM)_dbg
—else
—>—BUILDDIR = $(SRC_BASE)/build/$(PLATFORM)
—endif
—OBJDIR = $(BUILDDIR)/obj/$(MODULE)
—LIBDIR = $(BUILDDIR)/lib
—BINDIR = $(BUILDDIR)/bin
endif

update compilation flags base on “DEBUG_SYMBOLS”

ifeq ($(DEBUG_SYMBOLS), TRUE)

—CFLAGS += -g -Wall -Werror -O0
else
—CFLAGS += -Wall -Werror -O2
endif

VPATH += $(MOD_SRC_DIR)

SRC_OBJ = $(patsubst %.c, $(OBJDIR)/%.o, $(notdir $(SRC_FILES)))

ifeq ($(MAKELEVEL), 0)
all : msg
else
all : lib bin
endif

lib : $(OBJDIR) $(LIBDIR)/$(SRC_LIB)

bin : $(OBJDIR) $(BINDIR)/$(SRC_BIN)

$(OBJDIR) :

—mkdir -p $@

ifneq ($(SRC_BIN),)
$(BINDIR)/$(SRC_BIN) : $(SRC_OBJ)

—$(CC) -o $@ $^ $(LDFLAGS)
endif

ifneq ($(SRC_LIB),)
$(LIBDIR)/$(SRC_LIB) : $(SRC_OBJ)

—$(AR) rcs $@ $^
—cp $@ $(SRC_BASE)/libs
endif

$(SRC_OBJ) : $(OBJDIR)/%.o : %.c

—$(CC) -c $(CFLAGS) $(CPPFLAGS) $< -o $@

msg:

—@echo “You cannot directily execute this Makefile! This Makefile should called by toplevel Makefile.”

clean target

clean:
ifneq ($(SRC_LIB),)

—>—$(RM) $(SRC_OBJ) $(LIBDIR)/$(SRC_LIB)
endif
ifneq ($(SRC_BIN),)
—>—$(RM) $(SRC_OBJ) $(BINDIR)/$(SRC_BIN)
endif

.PHONY : all clean

1
2
3
4
5
6
7
8
9
10
11
12
13

### 10. make内嵌函数及make命令显示
这一节我们讲一下make的函数,在之前的章节已经讲到了几个函数:wildcard、patsubst、notdir、shell等。一般函数的调用格式如下:
`$(funcname arguments)`

`$(funcname arguments)`
其中funcname是需要调用函数的函数名称,应该是make内嵌函数;arguments是函数参数,参数和函数名之间使用空格分割,如果存在多个参数时,参数之间使用逗号“,”分开。函数调用以“$”开头,使用成对的圆括号或花括号把函数名和参数括起,一般使用圆括号。
下面来看一下常用的一些函数:

1. 获取匹配模式文件名函数—wildcard 。
用法:`$(wildcard PATTERN)`
该函数会列出当前目录下所有符合模式“PATTERN”格式的文件名。返回空格分割的、存在当前目录下的所有符合模式“PATTERN”的文件名。
例如:

SRC_FILES = $(wildcard src/*.c)

1
2
3
4
5
6
7
8
返回src目录下所有.c文件列表。
2. 字符串替换函数—subst。
用法:`$(subst FROM,TO,TEXT)`
该函数把字串“TEXT”中的“FROM”字符替换为“TO”,返回替换后的新字符串。
3. 模式替换函数—patsubst。
用法:`$(patsubst PATTERN,REPLACEMENT,TEXT)`
该函数搜索“TEXT”中以空格分开的单词,将符合模式“TATTERN”替换为“REPLACEMENT” 。参数“PATTERN”中可以使用模式通配符“%”,来代表一个单词中的若干字符。如果参数“REPLACEMENT”中也包含一个“%” ,那么“REPLACEMENT”中的“%”将是“TATTERN”中的那个“%”所代表的字符串。
例如:

SRC_OBJ = $(patsubst %.c, %.o, $(SRC_FILES))

1
2
3
4
5
将SRC_FILES中所有.c文件替换为.o返回给变量SRC_OBJ。
此函数功能类似之前讲过的变量替换,http://blog.csdn.net/shallnet/article/details/37529935
变量替换格式是“$(var:a=b)”或“${var:a=b}”,其意思是,把变量“var”中所有以“a”字串“结尾”的“a”替换成“b”字串。
例如我们存在一个代表所有.c 文件的变量。定义为“src_files = a.c b.c c.c” 。
为了得到这些.c文件所对应的.o源文件。如下两种使用可以得到同一种结果:

$(objects:.c=.o)
$(patsubst %.c,%.o,$( src_files))

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
4.    过滤函数—filter。
用法:$(filter PATTERN…,TEXT)
该函数过滤掉字串“TEXT”中所有不符合模式“PATTERN”的单词,保留所有符合此模式的单词。可以使用多个模式。模式中一般需要包含模式字符“%” 。存在多个模式时,模式表达式之间使用空格分割。返回空格分割的“TEXT”字串中所有符合模式“PATTERN”的字串。
5. 反过滤函数—filter-out。
用法:`$(filter-out PATTERN...,TEXT)`
和“filter”函数实现的功能相反。过滤掉字串“TEXT”中所有符合模式“PATTERN” 的单词, 保留所有不符合此模式的单词。 可以有多个模式。存在多个模式时,模式表达式之间使用空格分割。
6. 取目录函数—dir。
用法:`$(dir NAMES…)`
从文件名序列“NAMES…”中取出各个文件名的目录部分。文件名的目录部分就是包含在文件名中的最后一个斜线`( “/” )` (包括斜线)之前的部分。返回空格分割的文件名序列“NAMES…”中每一个文件的目录部分。如果文件名中没有斜线,认为此文件为当前目录`( “./” )`下的文件。
7. 取文件名函数——notdir。
用法:`$(notdir NAMES…)`
从文件名序列“NAMES…”中取出非目录部分。目录部分是指最后一个斜线`( “/” )` (包括斜线)之前的部分。删除所有文件名中的目录部分,只保留非目录部分。文件名序列“NAMES…”中每一个文件的非目录部分。
8. 取后缀函数—suffix。
用法:`$(suffix NAMES…) `
函数从文件名序列“NAMES…”中取出各个文件名的后缀。后缀是文件名中最后一个以点“.”开始的(包含点号)部分,如果文件名中不包含一个点号,则为空。 返回以空格分割的文件名序列“NAMES…”中每一个文件的后缀序列。
9. 取前缀函数—basename。
用法:`$(basename NAMES…)`
从文件名序列“NAMES…”中取出各个文件名的前缀部分(点号之后的部分) 。前缀部分指的是文件名中最后一个点号之前的部分。 返回空格分割的文件名序列“NAMES…”中各个文件的前缀序列。如果文件没有前缀,则返回空字串。

这里仅仅讲到一些常用的函数,还有一些函数没有讲到,用到的时候可以去翻翻makefile手册。

通常情况下make在编译时会打印出当前正在执行的命令,当编译链接选项很长时,会输出很多东西在屏幕上,如果我 不想再屏幕上看到很多东西,我们可以在命令前面加上@,这样命令就不会输出到屏幕了。我们这样尝试修改下:

make

make[1]: Entering directory /home/Myprojects/example_make/version-3.1/src/ipc' make[1]: Leaving directory/home/Myprojects/example_make/version-3.1/src/ipc’
make[1]: Entering directory /home/Myprojects/example_make/version-3.1/src/tools' make[1]: Leaving directory/home/Myprojects/example_make/version-3.1/src/tools’
make[1]: Entering directory /home/Myprojects/example_make/version-3.1/src/main' make[1]: Leaving directory/home/Myprojects/example_make/version-3.1/src/main’

1
2
3
发现只有进入目录和退出目录的显示,这样很难知道目前编译过程。其实我们可以在规则命令处加入一行类似打印:
`@echo "do something......"`
这样可以输出目前正在做的事,又不会输出正在执行命令。现在将规则修改下如下:

$(OBJDIR) :

—@echo “ MKDIR $(notdir $@)…”
—@mkdir -p $@

ifneq ($(SRC_BIN),)
$(BINDIR)/$(SRC_BIN) : $(SRC_OBJ)

—@echo “ LINK $(notdir $@)…”
—@$(CC) -o $@ $^ $(LDFLAGS)
endif

ifneq ($(SRC_LIB),)
$(LIBDIR)/$(SRC_LIB) : $(SRC_OBJ)

—@echo “ ARCHIVE $(notdir $@)…”
—@$(AR) rcs $@ $^
—@echo “ COPY $@ to $(SRC_BASE)/libs”
—@cp $@ $(SRC_BASE)/libs
endif

$(SRC_OBJ) : $(OBJDIR)/%.o : %.c

—@echo “ COMPILE $(notdir $<)…”
—@$(CC) -c $(CFLAGS) $(CPPFLAGS) $< -o $@

1
编译输出如下:

make

make[1]: Entering directory /home/Myprojects/example_make/version-3.1/src/ipc' COMPILE ipc.c... ARCHIVE libipc.a... COPY ../../build/unix_dbg/lib/libipc.a to ../../libs make[1]: Leaving directory/home/Myprojects/example_make/version-3.1/src/ipc’
make[1]: Entering directory /home/Myprojects/example_make/version-3.1/src/tools' COMPILE base64.c... COMPILE md5.c... COMPILE tools.c... ARCHIVE libtools.a... COPY ../../build/unix_dbg/lib/libtools.a to ../../libs make[1]: Leaving directory/home/Myprojects/example_make/version-3.1/src/tools’
make[1]: Entering directory /home/Myprojects/example_make/version-3.1/src/main' COMPILE main.c... LINK target_bin... make[1]: Leaving directory/home/Myprojects/example_make/version-3.1/src/main’

1
2
其中目录切换的输出仍然很多,我们可以将其关闭,这需要使用到make的参数,在make -C是指定--no-print-
directory参数。我们将顶层目录下Makefile规则修改如下:

$(BUILDDIR):

—@echo “ Create directory $@ …”
—mkdir -p $(BUILDDIR)/bin $(BUILDDIR)/lib

$(MODULES):

—@$(MAKE) -C $(DIR)/$@ MODULE=$@ –no-print-directory

main:tools ipc

clean :

—@for subdir in $(MODULES); \ —do $(MAKE) -C $(DIR)/$$subdir MODULE=$$subdir $@ –no-print-directory; \ —done
编译输出:

make

COMPILE ipc.c...  
ARCHIVE libipc.a...  
COPY ../../build/unix_dbg/lib/libipc.a to ../../libs  
COMPILE base64.c...  
COMPILE md5.c...  
COMPILE tools.c...  
ARCHIVE libtools.a...  
COPY ../../build/unix_dbg/lib/libtools.a to ../../libs  
COMPILE main.c...  

LINK target_bin…

make clean

rm -f ../../build/unix_dbg/obj/ipc/ipc.o ../../build/unix_dbg/lib/libipc.a
rm -f ../../build/unix_dbg/obj/main/main.o ../../build/unix_dbg/bin/target_bin
rm -f ../../build/unix_dbg/obj/tools/base64.o ../../build/unix_dbg/obj/tools/md5.o
../../build/unix_dbg/obj/tools/tools.o ../../build/unix_dbg/lib/libtools.a

#

这样看上去输出清爽多了。其实我们也可以使用make -s 来全面禁止命令的显示。



>【版权声明:转载请保留出处:http://blog.csdn.net/shallnet/article/details/37358655】
坚持原创技术分享,您的支持将鼓励我继续创作!