您的位置:首页 > 运维架构 > Linux

Maemo Linux手机平台系列分析:(17) Maemo应用开发: GNU make 与makefile

2008-05-07 23:19 1091 查看

这部分的内容:

什么是GNU make

Make是如何工作的

一个简单的例子

解剖makefile

· 默认目标

关于makefile的几种名字

问题

增加make的目标

一次Make一个目标

伪关键字

指定默认目标

其它的通用伪目标

makefile变量

常用变量

Recursive variables

Simple variables

Automatic variables

使用pkg-config

什么是GNU Make ?
下面我们介绍能自动编译的工具,这是很有用的。如果你对GNU make很熟悉,或者能自如的写自己的Makefile,可以跳过这里。
“make”程序是GNU 工程中一个很强大的工具,有助于实现软件开发过程的自动化编译。在使用gcc或者其它工具时,你可能经常输入同样的参数。在修改一个文件后,你会注意到你必须重新编译整个工程。这些事情用全手工的方式不是不能做,但你可以采用更好的方法。
GNU make是一个软件自动编译工具。这个就是你的助手!通过Makefile文件,你可以把所有的源文件和目标文件列出来,同时指出它们的依赖关系。Make使用文件的时间戳和我们在makefile文件中指定的一些规则来自动编译。
使用make 也可以干些其它的事情。比如,你可以很容易的创建一个能安装的目标程序;一个文档目标;有些人还使用make来更新最新的软件,等等。简而言之,make足够强大,用好它,事半功倍。

Make如何工作的呢?




[一个项目中不同文件中间的依赖关系]
Make的目的就是去满足设定的目标。每一个目标都有它自己的依赖。Make开始会检查所有的依赖是否存在以及是不是有一个比目标还旧的时间戳。如果是这样的话,make什么也不做,因为没有什么被修改过。
如果某个文件修改了,make将会重新编译其影响到的模块。比如,上图中的file3.c被修改过,make会重新编译file3.o, 进而编译libutil.a, 然后重新生成目标app,其它的则不会被重新编译。



[file3.c 被修改过,make时,会重新build依赖file3.c的目标和最终的程序 ]
假设我们给file1.c这个文件增加一个函数,同时修改了util.h。从上图中我们可以看到,make会重新编译file1.o,file2.o, libutil.a,main.o,app。

看一个简单的实际的例子
在深入了解makefile语法之前,我们先看看make干些什么事情。用一个简单的例子作为演示。
在C语言中,写header文件和source文件是很平常的事情。下面我们修改文件中的接口函数,
我们需要告诉make两点:

如果一个文件内容改变了,会影响到其它文件吗?由于一个接口可以影响多个文件,所以对于每一个目标,我们需要列出其依赖文件。

重新生成目标文件的命令?

如下:两个C文件和一个头文件:

/**
* The old faithful hello world.
*
* This maemo code example is licensed under a MIT-style license,
* that can be found in the file called "License" in the same
* directory as this file.
* Copyright (c) 2007 Nokia Corporation. All rights reserved.
*/

#include <stdio.h> /* printf */
#include <unistd.h> /* EXIT_* */
#include "hello_api.h" /* sayhello */

int main(int argc, char **argv) {
sayhello();

return EXIT_SUCCESS;
}

[ Contents of simple-make-files/hello.c ]

/**
* Implementation of sayhello.
*
* This maemo code example is licensed under a MIT-style license,
* that can be found in the file called "License" in the same
* directory as this file.
* Copyright (c) 2007 Nokia Corporation. All rights reserved.
*/

#include <stdio.h> /* printf */
#include "hello_api.h" /* sayhello declaration */

void sayhello(void) {
printf("Hello world!/n");
}

[ Contents of simple-make-files/hello_func.c ]

/**
* Interface description for the hello_func module.
*
* This maemo code example is licensed under a MIT-style license,
* that can be found in the file called "License" in the same
* directory as this file.
* Copyright (c) 2007 Nokia Corporation. All rights reserved.
*/

#ifndef INCLUDE_HELLO_API_H
#define INCLUDE_HELLO_API_H
/* The above is protection against circular header inclusion. */

/* Function to print out "Hello world!/n". */
extern void sayhello(void);

#endif
/* ifndef INCLUDE_HELLO_API_H */

我们可以手动写出编译的命令:

gcc -Wall hello.c hello_func.c -o hello

或者可以分为3步:

gcc -Wall -c hello.c -o hello.o
gcc -Wall -c hello_func.c -o hello_func.o
gcc -Wall hello.o hello_func.o -o hello

两种情况下,目标程序是一样的。
第一种情况下,我们让gcc一次性处理所有的源文件,并且把链接目标代码,进而把代码存入hello。
第二种情况,我们首先让gcc为每一个源文件生成一个二进制目标文件,之后再让gcc把这些目标文件链接在一块,同样把链接过的代码存储到hello中。
请注意:当gcc读取整个C源文件时,它也会读取头文件,因为在C代码中使用了“#include”预处理。
用文件可以描述上面的过程:

# define default target (first target = default)
//定义默认目标 (第一个目标就是默认目标)
# it depends on 'hello.o' (which will be created if necessary)
# and hello_func.o (same as hello.o)
//这个目标就依赖hello.o和hello_func.o

hello: hello.o hello_func.o
gcc -Wall hello.o hello_func.o -o hello

// 定义hello.o目标,它依赖hello.c和hello_api.h,如果hello_api.h修改了,就会重新build这个hello.o
# define hello.o target
# it depends on hello.c (and is created from it)
# also depends on hello_api.h which would mean that
# changing the hello.h api would force make to rebuild
# this target (hello.o).
# gcc -c: compile only, do not link //-c参数:编译但不链接
hello.o: hello.c hello_api.h
gcc -Wall -c hello.c -o hello.o

# define hello_func.o target
# it depends on hello_func.c (and is created from)
# and hello_api.h (since that's its declaration)
hello_func.o: hello_func.c hello_api.h
gcc -Wall -c hello_func.c -o hello_func.o

[Makefile内容 ]
这个文件没有使用GNU make的任何变量,是非常简单的格式。后来我们会看到可以用更简短的方式实现这种相同的功能。
你可以测试一下makefile:

user@system:~$ make
gcc -Wall -c hello.c -o hello.o
gcc -Wall -c hello_func.c -o hello_func.o
gcc -Wall hello.o hello_func.o -o hello

[ 运行 make. ]

user@system:~$ ls -la
total 58
drwxr-xr-x 3 user user 456 Aug 11 15:04 .
drwxr-xr-x 13 user user 576 Aug 11 14:56 ..
-rw-r--r-- 1 user user 699 Jun 1 14:48 Makefile
-rwxr-xr-x 1 user user 4822 Aug 11 15:04 hello
-rw-r--r-- 1 user user 150 Jun 1 14:48 hello.c
-rw-r--r-- 1 user user 824 Aug 11 15:04 hello.o
-rw-r--r-- 1 user user 220 Jun 1 14:48 hello_api.h
-rw-r--r-- 1 user user 130 Jun 1 14:48 hello_func.c
-rw-r--r-- 1 user user 912 Aug 11 15:04 hello_func.o

[ 运行make之后生成的文件. ]

user@system:~$ ./hello
Hello world!

[执行程序. ]

解剖 makefile
从上面的简单例子,我们可以减少一些Make语法。
下面是我们可以学习的规则:

注释是用“#”字符开始的。在读取makefile时,会忽略#号以及其后面的部分,知道该行结束。

反斜杠/可以用来续行。在makefile中最重要的是美元$符号,用来访问变量的内容。

空行会被忽略的。

一行以第0列开始,并且包含一个冒号:。冒号左边是命令,即目标;冒号右边指定的任何文件名都是目标的依赖。它们被称之为先决条件

以tab空行开始的行是生成目标的命令集合。

使用这些规则,我们现在可以简化makefile如下:

当hello的先决条件之一有一个发生了改变,都会重新生成hello目标。
gcc -Wall hello.o hello_func.o -o hello

如果hello.c或者hello_api.h改变了,make会重新生成hello.o

如果hello_func.c或者hello_api.h改变了,Make会重新生成hello_func.o

默认目标
注意,我们需要告诉make:当没有任何命令行参数时,如何保证生成目标.
在makefile中,第一个目标就是默认目标。一个目标会achieves一些higher-level目标(就像把所有的组件链接为最终的程序)。
注意,就像魔术一样,make会自动获取不同组件之间的依赖关系,然后推导出创建hello的顺序,同时也会产生hello.o和hello_func.o。make会在需要时重新生成缺少的预置条件。
由于hello的预置条件不存在,而且hello是默认的目标程序,所以make会首先生成hello依赖的文件。你可以这样验证这个过程:不加任何命令运行make。你会看到命令的执行顺序,首先生成默认目标的先决条件,最后才生成hello目标程序。

user@system:~$ make
gcc -Wall -c hello.c -o hello.o
gcc -Wall -c hello_func.c -o hello_func.o
gcc -Wall hello.o hello_func.o -o hello

makefiles的名字
对于makefile的名字,我们推荐Makefile。这种名字不是GNU第一个尝试打开的文件,但它是最具移植性的一个。事实上,make尝试打开的顺序是:GNUMakefile, Makefile,最后才是makefile。
除非你确认你的makefile不能在别的系统上正常工作,否则你不要使用GNUMakefile。我们将使用Makefile。用Makefile而不用makefile主要是因为排序问题。在ASCII中,大写字母是在小写字母前面的。
当然你可以用-f参数强制指定make去读取哪个文件。

问题:
基于你的常识,运行make后,如果做下面的事情呢?

如何删除hello文件?

如何删除hello.o文件?

正在修改 hello_func.c?

正在修改hello.c?

正在修改hello_api.h?

正在删除Makefile?

可以考虑一下上面的问题。

增加make的目标
有时候,我们添加一些这样的目标:并不生成什么文件,而是执行一系列的命令。这种目标是非常有用的,比如clean。这种手法在makefile中是很常用的。这种目标叫做“伪目标”。
下面看一个例子:

# add a cleaning target (to get rid of the binaries)

# define default target (first target = default)
# it depends on 'hello.o' (which must exist)
# and hello_func.o
hello: hello.o hello_func.o
gcc -Wall hello.o hello_func.o -o hello

# define hello.o target
# it depends on hello.c (and is created from)
# also depends on hello_api.h which would mean that
# changing the hello.h api would force make to rebuild
# this target (hello.o).
# gcc -c: compile only, do not link
hello.o: hello.c hello_api.h
gcc -Wall -c hello.c -o hello.o

# define hello_func.o target
# it depends on hello_func.c (and is created from)
# and hello_api.h (since that's it's declaration)
hello_func.o: hello_func.c hello_api.h
gcc -Wall -c hello_func.c -o hello_func.o

# This is the definition of the target 'clean'
# Here we'll remove all the built binaries and
# all the object files that we might have generated
# Notice the -f flag for rm, it means "force" which
# in turn means that rm will try to remove the given
# files, and if there are none, then that's ok. Also
# it means that we have no writing permission to that
# file and have writing permission to the directory
# holding the file, rm will not then ask for permission
# interactivelly.
这个就是伪目标,主要是执行一些命令,在运行Make时,可以加上,
比如:make clean; 就是执行删除操作。
clean:
rm -f hello hello.o hello_func.o

有时候,我们不用默认的makefile文件,可以用一个参数-f指定:

user@system:~$ make -f Makefile.2
gcc -Wall -c hello.c -o hello.o
gcc -Wall -c hello_func.c -o hello_func.o
gcc -Wall hello.o hello_func.o -o hello

[ 告诉make使用Makefile.2作为makefile文件. ]
我们可以在make后面指定目标,这样可以不编译默认目标:

user@system:~$ make -f Makefile.2 clean
rm -f hello hello.o hello_func.o

[ 测试clean目标. ]

user@system:~$ ls -la
total 42
drwxr-xr-x 3 user user 376 Aug 11 15:08 .
drwxr-xr-x 13 user user 576 Aug 11 14:56 ..
-rw-r--r-- 1 user user 699 Jun 1 14:48 Makefile
-rw-r--r-- 1 user user 1279 Jun 1 14:48 Makefile.2
-rw-r--r-- 1 user user 150 Jun 1 14:48 hello.c
-rw-r--r-- 1 user user 220 Jun 1 14:48 hello_api.h
-rw-r--r-- 1 user user 130 Jun 1 14:48 hello_func.c

[ 运行过clean后,就没有中间文件和目标程序了. ]

PHONY伪关键字

假设,由于某种原因,一个叫clean的文件出现在我们准备运行make的目录中,如果此时我们还携带clean参数的话,make将会误认为clean目标早已存在,不会运行我们预先写好的rm指令。很显然,我们需要避免这种情况。这种情况下,GNU make提供了一个特殊的目标,叫做.PHONY, 我们把实际的伪目标作为.PHONY的依赖。这就告诉make,不要把一个clean文件名当作伪目标的结果。
实际上,多数开源项目都这么使用make。
示例makefile如下:

hello: hello.o hello_func.o
gcc -Wall hello.o hello_func.o -o hello

hello.o: hello.c hello_api.h
gcc -Wall -c hello.c -o hello.o

hello_func.o: hello_func.c hello_api.h
gcc -Wall -c hello_func.c -o hello_func.o

.PHONY: clean
clean:
rm -f hello hello.o hello_func.o


[ Using .PHONY (simple-make-files/Makefile.3) ]

指定一个默认的目标

我们知道make把第一个目标当作它的默认目标。如果我们想明确指定默认目标,如何能达到这个目的呢?
我们创建一个新的伪目标,并且把想要的目标当作这个伪目标的先决条件,这里你可以设用任何名字作为你的目标,不过all是一个非常通用的名字,这里我们就用它了。
你所要记住的一件事:这个all目标,就是在makefile中的第一个目标。如下:
.PHONY: all
all: hello

hello: hello.o hello_func.o gcc -Wall hello.o hello_func.o -o hello hello.o: hello.c hello_api.h gcc -Wall -c hello.c -o hello.o hello_func.o: hello_func.c hello_api.h gcc -Wall -c hello_func.c -o hello_func.o .PHONY: clean clean: rm -f hello hello.o hello_func.o


[ Explicit default goal (simple-make-files/Makefile.4) ]

其它通用的伪目标

在项目中,你将会遇到许多其它的伪目标,其中包括如下一些:

install
: install的先决条件是软件应用二进制文件。这个命令会指定软件的安装路径。

distclean
: 类似于clean, 为了清除一些目标文件,主要是为了清除一些有自动工具生成的中间文件。

package
: 它的先决条件与install的相同,但是不安装,仅仅是打一个包。这个包可以用于发布软件。

makefile中的变量

到目前为止,我们都是直接在makefile中明确指定要编译哪些文件。不过这种方式是很繁琐的。如果用一些变量,就会使得makefile有趣多了。有些程序把makefile中的变量称作宏。

变量的习惯。

在makefile中使用的变量有两种基本类型。一种是recursive。就是前面定义一个宏变量,后面在引用处直接替代掉。另外一种是simple, 就是这个变量的内容仅仅被执行一次。

Recursive 变量

写recursive variables的原则:

名字必须包含ASCII 字符和下划线。

一般大写。

长行可以使用反斜杠(/)分开,注意在反斜杠后面不要有任何空格。

We'll re-use the same makefile as before, but introduce some variables. You will also see the syntax on how to define the variables and how to use them (reference them):

# define the command that we use for compilation
CC = gcc -Wall

# which targets do we want to build by default?
# note that 'targets' is just a variable, its name
# does not have any special meaning to make
targets = hello

# first target defined will be the default target
# we use the variable to hold the dependencies
.PHONY: all
all: $(targets)

hello: hello.o hello_func.o
$(CC) hello.o hello_func.o -o hello

hello.o: hello.c hello_api.h
$(CC) -c hello.c -o hello.o

hello_func.o: hello_func.c hello_api.h
$(CC) -c hello_func.c -o hello_func.o

# we'll make our cleaning target more powerful
# we remove the targets that we build by default and also
# remove all object files
.PHONY: clean
clean:
rm -f $(targets) *.o


[ The makefile with some variables (simple-make-files/Makefile.5) ]

CC
变量是标准的变量,用来表示编译器的命令。

Simple variables

Suppose you have a makefile like this:

CC = gcc -Wall

# we want to add something to the end of the variable
CC = $(CC) -g

hello.o: hello.c hello_api.h
$(CC) -c hello.c -o hello.o


上面会陷入无穷循环。

How do we correct this? make actually provides two mechanisms for this. This is the solution with simple variables:

CC := gcc -Wall

# we want to add something to the end of the variable
CC := $(CC) -g

hello.o: hello.c hello_api.h
$(CC) -c hello.c -o hello.o


我们注意到:等号变成了:=。这就是simple变量。无论合适引用simple变量,make仅仅替代文本,而不执行它。

另外一种方式:= --> +=,我们一般常用这种方式。

CC = gcc -Wall

# we want to add something to the end of the variable
CC += -g

hello.o: hello.c hello_api.h
$(CC) -c hello.c -o hello.o


自动变量,这是在makefile中大量使用的。

make预定义了一组变量,可以避免大量的打字工作,下面是最有用的一些:

$<
: 表示第一个先决条件.

$^
: 所有的先决条件.

$@
: 目标.

$?
: 列出比目标文件新的一些先决条件。

By rewriting our makefile using automatic variables we get:

# add the warning flag to CFLAGS-variable
CFLAGS += -Wall

targets = hello

.PHONY: all
all: $(targets)

hello: hello.o hello_func.o
$(CC) $^-o $@ // $(CC) hello.o hello_func.o -o hello

hello.o: hello.c hello_api.h
$(CC) $(CFLAGS) -c ___FCKpd___6lt; -o $@ // $(CC) $(CFLAGS) -C hello.c -o hello.o

hello_func.o: hello_func.c hello_api.h
$(CC) $(CFLAGS) -c ___FCKpd___6lt; -o $@ // $(CC) $(CFLAGS) -c hello_func.c -o hello_func.o

.PHONY: clean
clean:
$(RM) $(targets) *.o


[ Contents of simple-make-files/Makefile.9 ]

同pkg-config一块使用

现在你可以写简单的Makefile了,下面我们开始使用pkg-config,这可是一个很常用的参数,用于指定Header file和Lib的路径。我们使用一个GNU make的函数:$(shell command params) :
# define a list of pkg-config packages we want to use
pkg_packages := gtk+-2.0 hildon-1 #### 这里定义pc文件: gtk+-2.0.pc hildon-1.pc

# get the necessary flags for compiling
PKG_CFLAGS := $(shell pkg-config --cflags $(pkg_packages)) #### 替换
# get the necessary flags for linking
PKG_LDFLAGS := $(shell pkg-config --libs $(pkg_packages)) #### 替换

# additional flags
# -Wall: warnings
# -g: debugging
ADD_CFLAGS := -Wall -g

# combine the flags (so that CFLAGS/LDFLAGS from the command line
# still work).
CFLAGS := $(PKG_CFLAGS) $(ADD_CFLAGS) $(CFLAGS)
LDFLAGS := $(PKG_LDFLAGS) $(LDFLAGS)

targets = hildon_helloworld-1

.PHONY: all
all: $(targets)

hildon_helloworld-1: hildon_helloworld-1.o
$(CC) $^-o $@$(LDFLAGS)

hildon_helloworld-1.o: hildon_helloworld-1.c
$(CC) $(CFLAGS) -c ___FCKpd___7lt; -o $@

.PHONY: clean
clean:
$(RM) $(targets) *.o


[ Contents of simple-make-files/Makefile.10 ]

An up-to-date manual for GNU make can be found in http://www.gnu.org/software/make/manual/make.html
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息