diff --git a/source/archives.rst b/source/archives.rst index 9c9e19e..2862580 100644 --- a/source/archives.rst +++ b/source/archives.rst @@ -1,7 +1,8 @@ 使用make更新函数库文件 ====================== -函数库文件也就是对Object文件(程序编译的中间文件)的打包文件。在Unix下,一般是由命令 ``ar`` 来完成打包工作。 +函数库文件也就是对Object文件(程序编译的中间文件)的打包文件。在Unix下,一般是由 +命令 ``ar`` 来完成打包工作。 函数库文件的成员 ---------------- @@ -10,7 +11,8 @@ archive(member) -这个不是一个命令,而一个目标和依赖的定义。一般来说,这种用法基本上就是为了 ``ar`` 命令来服务的。如:: +这个不是一个命令,而一个目标和依赖的定义。一般来说,这种用法基本上就是为了 +``ar`` 命令来服务的。如:: foolib(hack.o) : hack.o ar cr foolib hack.o @@ -30,13 +32,18 @@ 函数库成员的隐含规则 -------------------- -当make搜索一个目标的隐含规则时,一个特殊的特性是,如果这个目标是 ``a(m)`` 形式的,其会把目标变成 ``(m)`` 。于是,如果我们的成员是 ``%.o`` 的模式定义,并且如果我们使用 ``make foo.a(bar.o)`` 的形式调用Makefile时,隐含规则会去找 ``bar.o`` 的规则,如果没有定义 `bar.o`` 的规则,那么内建隐含规则生效,make会去找 ``bar.c`` 文件来生成 ``bar`` .o,如果找得到的话,make执行的命令大致如下:: +当make搜索一个目标的隐含规则时,一个特殊的特性是,如果这个目标是 ``a(m)`` 形式 +的,其会把目标变成 ``(m)`` 。于是,如果我们的成员是 ``%.o`` 的模式定义,并且如果 +我们使用 ``make foo.a(bar.o)`` 的形式调用Makefile时,隐含规则会去找 ``bar.o`` 的 +规则,如果没有定义 ``bar.o`` 的规则,那么内建隐含规则生效,make会去找 ``bar.c`` +文件来生成 ``bar.o`` ,如果找得到的话,make执行的命令大致如下:: cc -c bar.c -o bar.o ar r foo.a bar.o rm -f bar.o -还有一个变量要注意的是 ``$%`` ,这是专属函数库文件的自动化变量,有关其说明请参见“自动化变量”一节。 +还有一个变量要注意的是 ``$%`` ,这是专属函数库文件的自动化变量,有关其说明请参 +见“自动化变量”一节。 函数库文件的后缀规则 -------------------- @@ -63,6 +70,8 @@ 注意事项 -------- -在进行函数库打包文件生成时,请小心使用make的并行机制( ``-j`` 参数)。如果多个 ``ar`` 命令在同一时间运行在同一个函数库打包文件上,就很有可以损坏这个函数库文件。所以,在make未来的版本中,应该提供一种机制来避免并行操作发生在函数打包文件上。 +在进行函数库打包文件生成时,请小心使用make的并行机制( ``-j`` 参数)。如果多个 +``ar`` 命令在同一时间运行在同一个函数库打包文件上,就很有可以损坏这个函数库文件 +。所以,在make未来的版本中,应该提供一种机制来避免并行操作发生在函数打包文件上。 但就目前而言,你还是应该不要尽量不要使用 ``-j`` 参数。 diff --git a/source/conditionals.rst b/source/conditionals.rst index a49efa1..a0f7add 100644 --- a/source/conditionals.rst +++ b/source/conditionals.rst @@ -1,7 +1,8 @@ 使用条件判断 ============ -使用条件判断,可以让make根据运行时的不同情况选择不同的执行分支。条件表达式可以是比较变量的值,或是比较变量和常量的值。 +使用条件判断,可以让make根据运行时的不同情况选择不同的执行分支。条件表达式可以是比较变量的值, +或是比较变量和常量的值。 示例 ---- @@ -20,9 +21,13 @@ $(CC) -o foo $(objects) $(normal_libs) endif -可见,在上面示例的这个规则中,目标 ``foo`` 可以根据变量 ``$(CC)`` 值来选取不同的函数库来编译程序。 +可见,在上面示例的这个规则中,目标 ``foo`` 可以根据变量 ``$(CC)`` 值来选取不同的函数库来 +编译程序。 -我们可以从上面的示例中看到三个关键字: ``ifeq`` 、 ``else`` 和 ``endif`` 。 ``ifeq`` 的意思表示条件语句的开始,并指定一个条件表达式,表达式包含两个参数,以逗号分隔,表达式以圆括号括起。 ``else`` 表示条件表达式为假的情况。 ``endif`` 表示一个条件语句的结束,任何一个条件表达式都应该以 ``endif`` 结束。 +我们可以从上面的示例中看到三个关键字: ``ifeq`` 、 ``else`` 和 ``endif`` 。 ``ifeq`` 的 +意思表示条件语句的开始,并指定一个条件表达式,表达式包含两个参数,以逗号分隔,表达式以圆括号括 +起。 ``else`` 表示条件表达式为假的情况。 ``endif`` 表示一个条件语句的结束,任何一个条件表达 +式都应该以 ``endif`` 结束。 当我们的变量 ``$(CC)`` 值是 ``gcc`` 时,目标 ``foo`` 的规则是: @@ -89,7 +94,8 @@ endif -这个示例中使用了 ``strip`` 函数,如果这个函数的返回值是空(Empty),那么 ```` 就生效。 +这个示例中使用了 ``strip`` 函数,如果这个函数的返回值是空(Empty),那么 +```` 就生效。 第二个条件关键字是 ``ifneq`` 。语法是: @@ -109,7 +115,9 @@ ifdef -如果变量 ```` 的值非空,那到表达式为真。否则,表达式为假。当然, ```` 同样可以是一个函数的返回值。注意, ``ifdef`` 只是测试一个变量是否有值,其并不会把变量扩展到当前位置。还是来看两个例子: +如果变量 ```` 的值非空,那到表达式为真。否则,表达式为假。当然, +```` 同样可以是一个函数的返回值。注意, ``ifdef`` 只是测试一个变量 +是否有值,其并不会把变量扩展到当前位置。还是来看两个例子: 示例一: @@ -142,10 +150,13 @@ ifndef -这个我就不多说了,和 `ifdef`` 是相反的意思。 +这个我就不多说了,和 ``ifdef`` 是相反的意思。 -在 ```` 这一行上,多余的空格是被允许的,但是不能以 ``Tab`` 键做为开始(不然就被认为是命令)。而注释符 ``#`` 同样也是安全的。 ``else`` 和 ``endif`` 也一样,只要不是以 ``Tab`` 键开始就行了。 +在 ```` 这一行上,多余的空格是被允许的,但是不能以 ``Tab`` 键 +作为开始(不然就被认为是命令)。而注释符 ``#`` 同样也是安全的。 ``else`` 和 ``endif`` +也一样,只要不是以 ``Tab`` 键开始就行了。 -特别注意的是,make是在读取Makefile时就计算条件表达式的值,并根据条件表达式的值来选择语句,所以,你最好不要把自动化变量(如 ``$@`` 等)放入条件表达式中,因为自动化变量是在运行时才有的。 +特别注意的是,make是在读取Makefile时就计算条件表达式的值,并根据条件表达式的值来选择语句, +所以,你最好不要把自动化变量(如 ``$@`` 等)放入条件表达式中,因为自动化变量是在运行时才有的。 而且为了避免混乱,make不允许把整个条件语句分成两部分放在不同的文件中。 diff --git a/source/functions.rst b/source/functions.rst index a1fd3d0..73bc472 100644 --- a/source/functions.rst +++ b/source/functions.rst @@ -1,7 +1,8 @@ 使用函数 ======== -在Makefile中可以使用函数来处理变量,从而让我们的命令或是规则更为的灵活和具有智能。make所支持的函数也不算很多,不过已经足够我们的操作了。函数调用后,函数的返回值可以当做变量来使用。 +在Makefile中可以使用函数来处理变量,从而让我们的命令或是规则更为的灵活和具有智能。make +所支持的函数也不算很多,不过已经足够我们的操作了。函数调用后,函数的返回值可以当做变量来使用。 函数的调用语法 -------------- @@ -16,7 +17,11 @@ ${ } -这里, ```` 就是函数名,make支持的函数不多。 ```` 为函数的参数,参数间以逗号 ``,`` 分隔,而函数名和参数之间以“空格”分隔。函数调用以 ``$`` 开头,以圆括号或花括号把函数名和参数括起。感觉很像一个变量,是不是?函数中的参数可以使用变量,为了风格的统一,函数和变量的括号最好一样,如使用 ``$(subst a,b,$(x))`` 这样的形式,而不是 ``$(subst a,b, ${x})`` 的形式。因为统一会更清楚,也会减少一些不必要的麻烦。 +这里, ```` 就是函数名,make支持的函数不多。 ```` 为函数的参数, +参数间以逗号 ``,`` 分隔,而函数名和参数之间以“空格”分隔。函数调用以 ``$`` 开头,以圆括号 +或花括号把函数名和参数括起。感觉很像一个变量,是不是?函数中的参数可以使用变量,为了风格的 +统一,函数和变量的括号最好一样,如使用 ``$(subst a,b,$(x))`` 这样的形式,而不是 +``$(subst a,b, ${x})`` 的形式。因为统一会更清楚,也会减少一些不必要的麻烦。 还是来看一个示例: @@ -28,7 +33,11 @@ foo:= a b c bar:= $(subst $(space),$(comma),$(foo)) -在这个示例中, ``$(comma)`` 的值是一个逗号。 ``$(space)`` 使用了 ``$(empty)`` 定义了一个空格, ``$(foo)`` 的值是 ``a b c`` , ``$(bar)`` 的定义用,调用了函数 ``subst`` ,这是一个替换函数,这个函数有三个参数,第一个参数是被替换字串,第二个参数是替换字串,第三个参数是替换操作作用的字串。这个函数也就是把 ``$(foo)`` 中的空格替换成逗号,所以 ``$(bar)``的值是 ``a,b,c`` 。 +在这个示例中, ``$(comma)`` 的值是一个逗号。 ``$(space)`` 使用了 ``$(empty)`` 定义了 +一个空格, ``$(foo)`` 的值是 ``a b c`` , ``$(bar)`` 的定义用,调用了函数 ``subst`` , +这是一个替换函数,这个函数有三个参数,第一个参数是被替换字串,第二个参数是替换字串,第三个参数 +是替换操作作用的字串。这个函数也就是把 ``$(foo)`` 中的空格替换成逗号,所以 ``$(bar)`` 的值 +是 ``a,b,c`` 。 字符串处理函数 -------------- @@ -60,7 +69,11 @@ patsubst $(patsubst ,,) - 名称:模式字符串替换函数。 -- 功能:查找 ```` 中的单词(单词以“空格”、“Tab”或“回车”“换行”分隔)是否符合模式 ```` ,如果匹配的话,则以 ```` 替换。这里, ```` 可以包括通配符 ``%`` ,表示任意长度的字串。如果 ```` 中也包含 ``%`` ,那么, ```` 中的这个 ``%`` 将是 ```` 中的那个 ``%`` 所代表的字串。(可以用 ``\`` 来转义,以 ``\%`` 来表示真实含义的 ``%`` 字符) +- 功能:查找 ```` 中的单词(单词以“空格”、“Tab”或“回车”“换行”分隔)是否符合模式 +```` ,如果匹配的话,则以 ```` 替换。这里, ```` 可以 +包括通配符 ``%`` ,表示任意长度的字串。如果 ```` 中也包含 ``%`` ,那么, +```` 中的这个 ``%`` 将是 ```` 中的那个 ``%`` 所代表的字串。 +(可以用 ``\`` 来转义,以 ``\%`` 来表示真实含义的 ``%`` 字符) - 返回:函数返回被替换过后的字符串。 - 示例: @@ -68,9 +81,14 @@ patsubst $(patsubst %.c,%.o,x.c.c bar.c) -把字串 ``x.c.c bar.c`` 符合模式 ``%.c`` 的单词替换成 ``%.o`` ,返回结果是 ``x.c.o bar.o`` +把字串 ``x.c.c bar.c`` 符合模式 ``%.c`` 的单词替换成 ``%.o`` ,返回结果是 +``x.c.o bar.o`` -- 备注:这和我们前面“变量章节”说过的相关知识有点相似。如 ``$(var:=;)`` 相当于 ``$(patsubst ,,$(var))`` ,而 ``$(var: =)`` 则相当于 ``$(patsubst %,%,$(var))`` 。 +- 备注:这和我们前面“变量章节”说过的相关知识有点相似。如 +``$(var:=;)`` 相当于 +``$(patsubst ,,$(var))`` ,而 +``$(var: =)`` 则相当于 +``$(patsubst %,%,$(var))`` 。 例如有:: @@ -123,7 +141,8 @@ filter $(filter ,) - 名称:过滤函数 -- 功能:以 ```` 模式过滤 ```` 字符串中的单词,保留符合模式 ```` 的单词。可以有多个模式。 +- 功能:以 ```` 模式过滤 ```` 字符串中的单词,保留符合模式 +```` 的单词。可以有多个模式。 - 返回:返回符合模式 ```` 的字串。 - 示例: @@ -143,7 +162,8 @@ filter-out $(filter-out ,) - 名称:反过滤函数 -- 功能:以 ```` 模式过滤 ```` 字符串中的单词,去除符合模式 ```` 的单词。可以有多个模式。 +- 功能:以 ```` 模式过滤 ```` 字符串中的单词,去除符合模式 +```` 的单词。可以有多个模式。 - 返回:返回不符合模式 ```` 的字串。 - 示例: @@ -176,7 +196,8 @@ word - 名称:取单词函数 - 功能:取字符串 ```` 中第 ```` 个单词。(从一开始) -- 返回:返回字符串 ```` 中第 ```` 个单词。如果 ```` 比 ```` 中的单词数要大,那么返回空字符串。 +- 返回:返回字符串 ```` 中第 ```` 个单词。如果 ```` 比 ```` 中的 +单词数要大,那么返回空字符串。 - 示例: ``$(word 2, foo bar baz)`` 返回值是 ``bar`` 。 wordlist @@ -187,8 +208,11 @@ wordlist $(wordlist ,,) - 名称:取单词串函数 -- 功能:从字符串 ```` 中取从 ```` 开始到 ```` 的单词串。 ```` 和 ```` 是一个数字。 -- 返回:返回字符串 ```` 中从 ```` 到 ```` 的单词字串。如果 ```` 比 ```` 中的单词数要大,那么返回空字符串。如果 ```` 大于 ```` 的单词数,那么返回从 ```` 开始,到 ```` 结束的单词串。 +- 功能:从字符串 ```` 中取从 ```` 开始到 ```` 的单词串。 ```` +和 ```` 是一个数字。 +- 返回:返回字符串 ```` 中从 ```` 到 ```` 的单词字串。如果 ```` +比 ```` 中的单词数要大,那么返回空字符串。如果 ```` 大于 ```` 的单词数, +那么返回从 ```` 开始,到 ```` 结束的单词串。 - 示例: ``$(wordlist 2, 3, foo bar baz)`` 返回值是 ``bar baz`` 。 words @@ -202,7 +226,8 @@ words - 功能:统计 ```` 中字符串中的单词个数。 - 返回:返回 ```` 中的单词数。 - 示例: ``$(words, foo bar baz)`` 返回值是 ``3`` 。 -- 备注:如果我们要取 ```` 中最后的一个单词,我们可以这样: ``$(word $(words ),)`` 。 +- 备注:如果我们要取 ```` 中最后的一个单词,我们可以这样: +``$(word $(words ),)`` 。 firstword ~~~~~~~~~ @@ -217,18 +242,23 @@ firstword - 示例: ``$(firstword foo bar)`` 返回值是 ``foo``。 - 备注:这个函数可以用 ``word`` 函数来实现: ``$(word 1,)`` 。 -以上,是所有的字符串操作函数,如果搭配混合使用,可以完成比较复杂的功能。这里,举一个现实中应用的例子。我们知道,make使用 ``VPATH`` 变量来指定“依赖文件”的搜索路径。于是,我们可以利用这个搜索路径来指定编译器对头文件的搜索路径参数 ``CFLAGS`` ,如: +以上,是所有的字符串操作函数,如果搭配混合使用,可以完成比较复杂的功能。这里,举一个现实中 +应用的例子。我们知道,make使用 ``VPATH`` 变量来指定“依赖文件”的搜索路径。于是,我们可以 +利用这个搜索路径来指定编译器对头文件的搜索路径参数 ``CFLAGS`` ,如: .. code-block:: makefile override CFLAGS += $(patsubst %,-I%,$(subst :, ,$(VPATH))) -如果我们的 ``$(VPATH)`` 值是 ``src:../headers`` ,那么 ``$(patsubst %,-I%,$(subst :, ,$(VPATH)))`` 将返回 ``-Isrc -I../headers`` ,这正是cc或gcc搜索头文件路径的参数。 +如果我们的 ``$(VPATH)`` 值是 ``src:../headers`` ,那么 +``$(patsubst %,-I%,$(subst :, ,$(VPATH)))`` 将返回 ``-Isrc -I../headers`` , +这正是cc或gcc搜索头文件路径的参数。 文件名操作函数 -------------- -下面我们要介绍的函数主要是处理文件名的。每个函数的参数字符串都会被当做一个或是一系列的文件名来对待。 +下面我们要介绍的函数主要是处理文件名的。每个函数的参数字符串都会被当做一个或是一系列的文件名 +来对待。 dir ~~~ @@ -238,7 +268,8 @@ dir $(dir ) - 名称:取目录函数——dir。 -- 功能:从文件名序列 ```` 中取出目录部分。目录部分是指最后一个反斜杠( ``/`` )之前的部分。如果没有反斜杠,那么返回 ``./`` 。 +- 功能:从文件名序列 ```` 中取出目录部分。目录部分是指最后一个反斜杠( ``/`` )之前 +的部分。如果没有反斜杠,那么返回 ``./`` 。 - 返回:返回文件名序列 ```` 的目录部分。 - 示例: ``$(dir src/foo.c hacks)`` 返回值是 ``src/ ./`` 。 @@ -250,7 +281,8 @@ notdir $(notdir ) - 名称:取文件函数——notdir。 -- 功能:从文件名序列 ```` 中取出非目录部分。非目录部分是指最後一个反斜杠( ``/`` )之后的部分。 +- 功能:从文件名序列 ```` 中取出非目录部分。非目录部分是指最後一个反斜杠( ``/`` ) +之后的部分。 - 返回:返回文件名序列 ```` 的非目录部分。 - 示例: ``$(notdir src/foo.c hacks)`` 返回值是 ``foo.c hacks`` 。 @@ -276,7 +308,8 @@ basename - 名称:取前缀函数——basename。 - 功能:从文件名序列 ```` 中取出各个文件名的前缀部分。 - 返回:返回文件名序列 ```` 的前缀序列,如果文件没有前缀,则返回空字串。 -- 示例: ``$(basename src/foo.c src-1.0/bar.c hacks)`` 返回值是 ``src/foo src-1.0/bar hacks`` 。 +- 示例: ``$(basename src/foo.c src-1.0/bar.c hacks)`` 返回值是 +``src/foo src-1.0/bar hacks`` 。 addsuffix ~~~~~~~~~ @@ -310,22 +343,31 @@ join $(join ,) - 名称:连接函数——join。 -- 功能:把 ```` 中的单词对应地加到 ```` 的单词后面。如果 ```` 的单词个数要比 ```` 的多,那么, ```` 中的多出来的单词将保持原样。如果 ```` 的单词个数要比 ```` 多,那么, ```` 多出来的单词将被复制到 ```` 中。 +- 功能:把 ```` 中的单词对应地加到 ```` 的单词后面。如果 ```` 的 +单词个数要比 ```` 的多,那么, ```` 中的多出来的单词将保持原样。如果 +```` 的单词个数要比 ```` 多,那么, ```` 多出来的单词将被复制到 +```` 中。 - 返回:返回连接过后的字符串。 - 示例: ``$(join aaa bbb , 111 222 333)`` 返回值是 ``aaa111 bbb222 333`` 。 foreach 函数 ------------ -foreach函数和别的函数非常的不一样。因为这个函数是用来做循环用的,Makefile中的foreach函数几乎是仿照于Unix标准Shell(/bin/sh)中的for语句,或是C-Shell(/bin/csh)中的foreach语句而构建的。它的语法是: +foreach函数和别的函数非常的不一样。因为这个函数是用来做循环用的,Makefile中的foreach函数 +几乎是仿照于Unix标准Shell(/bin/sh)中的for语句,或是C-Shell(/bin/csh)中的foreach语句 +而构建的。它的语法是: .. code-block:: makefile $(foreach ,,) -这个函数的意思是,把参数 ```` 中的单词逐一取出放到参数 ```` 所指定的变量中,然后再执行 ```` 所包含的表达式。每一次 ```` 会返回一个字符串,循环过程中, ```` 的所返回的每个字符串会以空格分隔,最后当整个循环结束时, ```` 所返回的每个字符串所组成的整个字符串(以空格分隔)将会是foreach函数的返回值。 +这个函数的意思是,把参数 ```` 中的单词逐一取出放到参数 ```` 所指定的变量中, +然后再执行 ```` 所包含的表达式。每一次 ```` 会返回一个字符串,循环过程中, +```` 的所返回的每个字符串会以空格分隔,最后当整个循环结束时, ```` 所返回的 +每个字符串所组成的整个字符串(以空格分隔)将会是foreach函数的返回值。 -所以, ```` 最好是一个变量名, ```` 可以是一个表达式,而 ```` 中一般会使用 ```` 这个参数来依次枚举 ```` 中的单词。举个例子: +所以, ```` 最好是一个变量名, ```` 可以是一个表达式,而 ```` 中一般会 +使用 ```` 这个参数来依次枚举 ```` 中的单词。举个例子: .. code-block:: makefile @@ -333,9 +375,12 @@ foreach函数和别的函数非常的不一样。因为这个函数是用来做 files := $(foreach n,$(names),$(n).o) -上面的例子中, ``$(name)`` 中的单词会被挨个取出,并存到变量 ``n`` 中, ``$(n).o`` 每次根据 ``$(n)`` 计算出一个值,这些值以空格分隔,最后作为foreach函数的返回,所以, ``$(files)`` 的值是 ``a.o b.o c.o d.o`` 。 +上面的例子中, ``$(name)`` 中的单词会被挨个取出,并存到变量 ``n`` 中, ``$(n).o`` 每次 +根据 ``$(n)`` 计算出一个值,这些值以空格分隔,最后作为foreach函数的返回,所以, ``$(files)`` +的值是 ``a.o b.o c.o d.o`` 。 -注意,foreach中的 ```` 参数是一个临时的局部变量,foreach函数执行完后,参数 ```` 的变量将不在作用,其作用域只在foreach函数当中。 +注意,foreach中的 ```` 参数是一个临时的局部变量,foreach函数执行完后,参数 ```` +的变量将不在作用,其作用域只在foreach函数当中。 if 函数 ------- @@ -352,22 +397,29 @@ if函数很像GNU的make所支持的条件语句——ifeq(参见前面所述 $(if ,,) -可见,if函数可以包含“else”部分,或是不含。即if函数的参数可以是两个,也可以是三个。 ```` 参数是if的表达式,如果其返回的为非空字符串,那么这个表达式就相当于返回真,于是, ```` 会被计算,否则 ```` 会被计算。 +可见,if函数可以包含“else”部分,或是不含。即if函数的参数可以是两个,也可以是三个。 +```` 参数是if的表达式,如果其返回的为非空字符串,那么这个表达式就相当于返回真, +于是, ```` 会被计算,否则 ```` 会被计算。 -而if函数的返回值是,如果 ```` 为真(非空字符串),那个 ```` 会是整个函数的返回值,如果 ```` 为假(空字符串),那么 ```` 会是整个函数的返回值,此时如果 ```` 没有被定义,那么,整个函数返回空字串。 +而if函数的返回值是,如果 ```` 为真(非空字符串),那个 ```` +会是整个函数的返回值,如果 ```` 为假(空字符串),那么 ```` 会是 +整个函数的返回值,此时如果 ```` 没有被定义,那么,整个函数返回空字串。 所以, ```` 和 ```` 只会有一个被计算。 call函数 -------- -call函数是唯一一个可以用来创建新的参数化的函数。你可以写一个非常复杂的表达式,这个表达式中,你可以定义许多参数,然后你可以call函数来向这个表达式传递参数。其语法是: +call函数是唯一一个可以用来创建新的参数化的函数。你可以写一个非常复杂的表达式,这个表达式中, +你可以定义许多参数,然后你可以call函数来向这个表达式传递参数。其语法是: .. code-block:: makefile $(call ,,,...,) -当make执行这个函数时, ```` 参数中的变量,如 ``$(1)`` 、 ``$(2)`` 等,会被参数 ```` 、 ```` 、 ```` 依次取代。而 ```` 的返回值就是 call 函数的返回值。例如: +当make执行这个函数时, ```` 参数中的变量,如 ``$(1)`` 、 ``$(2)`` 等,会 +被参数 ```` 、 ```` 、 ```` 依次取代。而 ```` 的 +返回值就是 call 函数的返回值。例如: .. code-block:: makefile @@ -385,7 +437,9 @@ call函数是唯一一个可以用来创建新的参数化的函数。你可以 此时的 ``foo`` 的值就是 ``b a`` 。 -需要注意:在向 call 函数传递参数时要尤其注意空格的使用。call 函数在处理参数时,第2个及其之后的参数中的空格会被保留,因而可能造成一些奇怪的效果。因而在向call函数提供参数时,最安全的做法是去除所有多余的空格。 +需要注意:在向 call 函数传递参数时要尤其注意空格的使用。call 函数在处理参数时,第2个及其之后的 +参数中的空格会被保留,因而可能造成一些奇怪的效果。因而在向call函数提供参数时,最安全的做法是 +去除所有多余的空格。 origin函数 ---------- @@ -396,7 +450,8 @@ origin函数不像其它的函数,他并不操作变量的值,他只是告 $(origin ) -注意, ```` 是变量的名字,不应该是引用。所以你最好不要在 ```` 中使用 ``$`` 字符。Origin函数会以其返回值来告诉你这个变量的“出生情况”,下面,是origin函数的返回值: +注意, ```` 是变量的名字,不应该是引用。所以你最好不要在 ```` 中使用 + ``$`` 字符。Origin函数会以其返回值来告诉你这个变量的“出生情况”,下面,是origin函数的返回值: ``undefined`` 如果 ```` 从来没有定义过,origin函数返回这个值 ``undefined`` @@ -413,7 +468,10 @@ origin函数不像其它的函数,他并不操作变量的值,他只是告 ``automatic`` 如果 ```` 是一个命令运行中的自动化变量。关于自动化变量将在后面讲述。 -这些信息对于我们编写Makefile是非常有用的,例如,假设我们有一个Makefile其包了一个定义文件Make.def,在 Make.def中定义了一个变量“bletch”,而我们的环境中也有一个环境变量“bletch”,此时,我们想判断一下,如果变量来源于环境,那么我们就把之重定义了,如果来源于Make.def或是命令行等非环境的,那么我们就不重新定义它。于是,在我们的Makefile中,我们可以这样写: +这些信息对于我们编写Makefile是非常有用的,例如,假设我们有一个Makefile其包了一个定义文件 +Make.def,在 Make.def中定义了一个变量“bletch”,而我们的环境中也有一个环境变量“bletch”, +此时,我们想判断一下,如果变量来源于环境,那么我们就把之重定义了,如果来源于Make.def或是命令行 +等非环境的,那么我们就不重新定义它。于是,在我们的Makefile中,我们可以这样写: .. code-block:: makefile @@ -423,31 +481,39 @@ origin函数不像其它的函数,他并不操作变量的值,他只是告 endif endif -当然,你也许会说,使用 ``override`` 关键字不就可以重新定义环境中的变量了吗?为什么需要使用这样的步骤?是的,我们用 ``override`` 是可以达到这样的效果,可是 ``override`` 过于粗暴,它同时会把从命令行定义的变量也覆盖了,而我们只想重新定义环境传来的,而不想重新定义命令行传来的。 +当然,你也许会说,使用 ``override`` 关键字不就可以重新定义环境中的变量了吗?为什么需要使用这样 +的步骤?是的,我们用 ``override`` 是可以达到这样的效果,可是 ``override`` 过于粗暴,它同时 +会把从命令行定义的变量也覆盖了,而我们只想重新定义环境传来的,而不想重新定义命令行传来的。 shell函数 --------- -shell函数也不像其它的函数。顾名思义,它的参数应该就是操作系统Shell的命令。它和反引号“`”是相同的功能。这就是说,shell函数把执行操作系统命令后的输出作为函数返回。于是,我们可以用操作系统命令以及字符串处理命令awk,sed等等命令来生成一个变量,如: +shell函数也不像其它的函数。顾名思义,它的参数应该就是操作系统Shell的命令。它和反引号“`”是 +相同的功能。这就是说,shell函数把执行操作系统命令后的输出作为函数返回。于是,我们可以用操作 +系统命令以及字符串处理命令awk,sed等等命令来生成一个变量,如: .. code-block:: makefile contents := $(shell cat foo) files := $(shell echo *.c) -注意,这个函数会新生成一个Shell程序来执行命令,所以你要注意其运行性能,如果你的Makefile中有一些比较复杂的规则,并大量使用了这个函数,那么对于你的系统性能是有害的。特别是Makefile的隐晦的规则可能会让你的shell函数执行的次数比你想像的多得多。 +注意,这个函数会新生成一个Shell程序来执行命令,所以你要注意其运行性能,如果你的Makefile中 +有一些比较复杂的规则,并大量使用了这个函数,那么对于你的系统性能是有害的。特别是Makefile的 +隐晦的规则可能会让你的shell函数执行的次数比你想像的多得多。 控制make的函数 -------------- -make提供了一些函数来控制make的运行。通常,你需要检测一些运行Makefile时的运行时信息,并且根据这些信息来决定,你是让make继续执行,还是停止。 +make提供了一些函数来控制make的运行。通常,你需要检测一些运行Makefile时的运行时信息,并且 +根据这些信息来决定,你是让make继续执行,还是停止。 .. code-block:: makefile $(error ) -产生一个致命的错误, ```` 是错误信息。注意,error函数不会在一被使用就会产生错误信息,所以如果你把其定义在某个变量中,并在后续的脚本中使用这个变量,那么也是可以的。例如: +产生一个致命的错误, ```` 是错误信息。注意,error函数不会在一被使用就会产生错误 +信息,所以如果你把其定义在某个变量中,并在后续的脚本中使用这个变量,那么也是可以的。例如: 示例一: diff --git a/source/implicit_rules.rst b/source/implicit_rules.rst index 3e5efaa..6c88983 100644 --- a/source/implicit_rules.rst +++ b/source/implicit_rules.rst @@ -1,28 +1,42 @@ 隐含规则 ======== -在我们使用Makefile时,有一些我们会经常使用,而且使用频率非常高的东西,比如,我们编译C/C++的源程序为中间目标文件(Unix下是 ``.o`` 文件,Windows下是 ``.obj`` 文件)。本章讲述的就是一些在Makefile中的“隐含的”,早先约定了的,不需要我们再写出来的规则。 +在我们使用Makefile时,有一些我们会经常使用,而且使用频率非常高的东西,比如,我们编译C/C++的 +源程序为中间目标文件(Unix下是 ``.o`` 文件,Windows下是 ``.obj`` 文件)。本章讲述的就是 +一些在Makefile中的“隐含的”,早先约定了的,不需要我们再写出来的规则。 -“隐含规则”也就是一种惯例,make会按照这种“惯例”心照不喧地来运行,那怕我们的Makefile中没有书写这样的规则。例如,把 ``.c`` 文件编译成 ``.o`` 文件这一规则,你根本就不用写出来,make会自动推导出这种规则,并生成我们需要的 ``.o`` 文件。 +“隐含规则”也就是一种惯例,make会按照这种“惯例”心照不喧地来运行,那怕我们的Makefile中没有书写 +这样的规则。例如,把 ``.c`` 文件编译成 ``.o`` 文件这一规则,你根本就不用写出来,make会自动 +推导出这种规则,并生成我们需要的 ``.o`` 文件。 -“隐含规则”会使用一些我们系统变量,我们可以改变这些系统变量的值来定制隐含规则的运行时的参数。如系统变量 ``CFLAGS`` 可以控制编译时的编译器参数。 +“隐含规则”会使用一些我们系统变量,我们可以改变这些系统变量的值来定制隐含规则的运行时的参数。 +如系统变量 ``CFLAGS`` 可以控制编译时的编译器参数。 -我们还可以通过“模式规则”的方式写下自己的隐含规则。用“后缀规则”来定义隐含规则会有许多的限制。使用“模式规则”会更回得智能和清楚,但“后缀规则”可以用来保证我们Makefile的兼容性。 -我们了解了“隐含规则”,可以让其为我们更好的服务,也会让我们知道一些“约定俗成”了的东西,而不至于使得我们在运行Makefile时出现一些我们觉得莫名其妙的东西。当然,任何事物都是矛盾的,水能载舟,亦可覆舟,所以,有时候“隐含规则”也会给我们造成不小的麻烦。只有了解了它,我们才能更好地使用它。 +我们还可以通过“模式规则”的方式写下自己的隐含规则。用“后缀规则”来定义隐含规则会有许多的限制。 +使用“模式规则”会更回得智能和清楚,但“后缀规则”可以用来保证我们Makefile的兼容性。 +我们了解了“隐含规则”,可以让其为我们更好的服务,也会让我们知道一些“约定俗成”了的东西,而不至于 +使得我们在运行Makefile时出现一些我们觉得莫名其妙的东西。当然,任何事物都是矛盾的,水能载舟, +亦可覆舟,所以,有时候“隐含规则”也会给我们造成不小的麻烦。只有了解了它,我们才能更好地使用它。 使用隐含规则 ------------ -如果要使用隐含规则生成你需要的目标,你所需要做的就是不要写出这个目标的规则。那么,make会试图去自动推导产生这个目标的规则和命令,如果 make可以自动推导生成这个目标的规则和命令,那么这个行为就是隐含规则的自动推导。当然,隐含规则是make事先约定好的一些东西。例如,我们有下面的一个Makefile: +如果要使用隐含规则生成你需要的目标,你所需要做的就是不要写出这个目标的规则。那么,make会试图去 +自动推导产生这个目标的规则和命令,如果 make可以自动推导生成这个目标的规则和命令,那么这个行为 +就是隐含规则的自动推导。当然,隐含规则是make事先约定好的一些东西。例如,我们有下面的一个Makefile: .. code-block:: makefile foo : foo.o bar.o cc –o foo foo.o bar.o $(CFLAGS) $(LDFLAGS) -我们可以注意到,这个Makefile中并没有写下如何生成 ``foo.o`` 和 ``bar.o`` 这两目标的规则和命令。因为make的“隐含规则”功能会自动为我们自动去推导这两个目标的依赖目标和生成命令。 +我们可以注意到,这个Makefile中并没有写下如何生成 ``foo.o`` 和 ``bar.o`` 这两目标的规则和命令。 +因为make的“隐含规则”功能会自动为我们自动去推导这两个目标的依赖目标和生成命令。 -make会在自己的“隐含规则”库中寻找可以用的规则,如果找到,那么就会使用。如果找不到,那么就会报错。在上面的那个例子中,make调用的隐含规则是,把 ``.o`` 的目标的依赖文件置成 ``.c`` ,并使用C的编译命令 ``cc –c $(CFLAGS) foo.c`` 来生成 ``foo.o`` 的目标。也就是说,我们完全没有必要写下下面的两条规则: +make会在自己的“隐含规则”库中寻找可以用的规则,如果找到,那么就会使用。如果找不到,那么就会报错。 +在上面的那个例子中,make调用的隐含规则是,把 ``.o`` 的目标的依赖文件置成 ``.c`` ,并使用C的 +编译命令 ``cc –c $(CFLAGS) foo.c`` 来生成 ``foo.o`` 的目标。也就是说,我们完全没有必要 +写下下面的两条规则: .. code-block:: makefile @@ -31,24 +45,37 @@ make会在自己的“隐含规则”库中寻找可以用的规则,如果找 bar.o : bar.c cc –c bar.c $(CFLAGS) -因为,这已经是“约定”好了的事了,make和我们约定好了用C编译器 ``cc`` 生成 ``.o`` 文件的规则,这就是隐含规则。 +因为,这已经是“约定”好了的事了,make和我们约定好了用C编译器 ``cc`` 生成 ``.o`` 文件的规则, +这就是隐含规则。 -当然,如果我们为 ``.o`` 文件书写了自己的规则,那么make就不会自动推导并调用隐含规则,它会按照我们写好的规则忠实地执行。 +当然,如果我们为 ``.o`` 文件书写了自己的规则,那么make就不会自动推导并调用隐含规则,它会按照 +我们写好的规则忠实地执行。 -还有,在make的“隐含规则库”中,每一条隐含规则都在库中有其顺序,越靠前的则是越被经常使用的,所以,这会导致我们有些时候即使我们显示地指定了目标依赖,make也不会管。如下面这条规则(没有命令): +还有,在make的“隐含规则库”中,每一条隐含规则都在库中有其顺序,越靠前的则是越被经常使用的,所以, +这会导致我们有些时候即使我们显示地指定了目标依赖,make也不会管。如下面这条规则(没有命令): .. code-block:: makefile foo.o : foo.p -依赖文件 ``foo.p`` (Pascal程序的源文件)有可能变得没有意义。如果目录下存在了 ``foo.c`` 文件,那么我们的隐含规则一样会生效,并会通过 ``foo.c`` 调用C的编译器生成 ``foo.o`` 文件。因为,在隐含规则中,Pascal的规则出现在C的规则之后,所以,make找到可以生成 ``foo.o`` 的C的规则就不再寻找下一条规则了。如果你确实不希望任何隐含规则推导,那么,你就不要只写出“依赖规则”,而不写命令。 +依赖文件 ``foo.p`` (Pascal程序的源文件)有可能变得没有意义。如果目录下存在了 ``foo.c`` 文件, +那么我们的隐含规则一样会生效,并会通过 ``foo.c`` 调用C的编译器生成 ``foo.o`` 文件。因为,在 +隐含规则中,Pascal的规则出现在C的规则之后,所以,make找到可以生成 ``foo.o`` 的C的规则就不再 +寻找下一条规则了。如果你确实不希望任何隐含规则推导,那么,你就不要只写出“依赖规则”,而不写命令。 隐含规则一览 ------------ -这里我们将讲述所有预先设置(也就是make内建)的隐含规则,如果我们不明确地写下规则,那么,make就会在这些规则中寻找所需要规则和命令。当然,我们也可以使用make的参数 ``-r`` 或 ``--no-builtin-rules`` 选项来取消所有的预设置的隐含规则。 +这里我们将讲述所有预先设置(也就是make内建)的隐含规则,如果我们不明确地写下规则,那么,make就 +会在这些规则中寻找所需要规则和命令。当然,我们也可以使用make的参数 ``-r`` 或 ``--no-builtin-rules`` +选项来取消所有的预设置的隐含规则。 -当然,即使是我们指定了 ``-r`` 参数,某些隐含规则还是会生效,因为有许多的隐含规则都是使用了“后缀规则”来定义的,所以,只要隐含规则中有 “后缀列表”(也就一系统定义在目标 ``.SUFFIXES`` 的依赖目标),那么隐含规则就会生效。默认的后缀列表是:.out, .a, .ln, .o, .c, .cc, .C, .p, .f, .F, .r, .y, .l, .s, .S, .mod, .sym, .def, .h, .info, .dvi, .tex, .texinfo, .texi, .txinfo, .w, .ch .web, .sh, .elc, .el。具体的细节,我们会在后面讲述。 +当然,即使是我们指定了 ``-r`` 参数,某些隐含规则还是会生效,因为有许多的隐含规则都是使用了 +“后缀规则”来定义的,所以,只要隐含规则中有 “后缀列表”(也就一系统定义在目标 ``.SUFFIXES`` +的依赖目标),那么隐含规则就会生效。默认的后缀列表是: +.out, .a, .ln, .o, .c, .cc, .C, .p, .f, .F, .r, .y, .l, .s, .S, .mod, .sym, +.def, .h, .info, .dvi, .tex, .texinfo, .texi, .txinfo, .w, .ch .web, .sh, .elc, .el。 +具体的细节,我们会在后面讲述。 还是先来看一看常用的隐含规则吧。 @@ -58,7 +85,8 @@ make会在自己的“隐含规则”库中寻找可以用的规则,如果找 #. 编译C++程序的隐含规则。 - ``.o`` 的目标的依赖目标会自动推导为 ``.cc`` 或是 ``.C`` ,并且其生成命令是 ``$(CXX) –c $(CPPFLAGS) $(CFLAGS)`` 。(建议使用 ``.cc`` 作为C++源文件的后缀,而不是 ``.C`` ) + ``.o`` 的目标的依赖目标会自动推导为 ``.cc`` 或是 ``.C`` ,并且其生成命令是 + ``$(CXX) –c $(CPPFLAGS) $(CFLAGS)`` 。(建议使用 ``.cc`` 作为C++源文件的后缀,而不是 ``.C`` ) #. 编译Pascal程序的隐含规则。 @@ -74,22 +102,29 @@ make会在自己的“隐含规则”库中寻找可以用的规则,如果找 #. 预处理Fortran/Ratfor程序的隐含规则。 - ``.f`` 的目标的依赖目标会自动推导为 ``.r`` 或 ``.F`` 。这个规则只是转换Ratfor或有预处理的Fortran程序到一个标准的Fortran程序。其使用的命令是: + ``.f`` 的目标的依赖目标会自动推导为 ``.r`` 或 ``.F`` 。这个规则只是转换Ratfor + 或有预处理的Fortran程序到一个标准的Fortran程序。其使用的命令是: - ``.F`` ``$(FC) –F $(CPPFLAGS) $(FFLAGS)`` - ``.r`` ``$(FC) –F $(FFLAGS) $(RFLAGS)`` #. 编译Modula-2程序的隐含规则。 - ``.sym`` 的目标的依赖目标会自动推导为 ``.def`` ,并且其生成命令是: ``$(M2C) $(M2FLAGS) $(DEFFLAGS)`` 。 ``.o`` 的目标的依赖目标会自动推导为 ``.mod`` ,并且其生成命令是: ``$(M2C) $(M2FLAGS) $(MODFLAGS)`` 。 + ``.sym`` 的目标的依赖目标会自动推导为 ``.def`` ,并且其生成命令是: + ``$(M2C) $(M2FLAGS) $(DEFFLAGS)`` 。 ``.o`` 的目标的依赖目标会自动推导为 ``.mod`` , + 并且其生成命令是: ``$(M2C) $(M2FLAGS) $(MODFLAGS)`` 。 #. 汇编和汇编预处理的隐含规则。 - ``.o`` 的目标的依赖目标会自动推导为 ``.s`` ,默认使用编译品 ``as`` ,并且其生成命令是: ``$ (AS) $(ASFLAGS)`` 。 ``.s`` 的目标的依赖目标会自动推导为 ``.S`` ,默认使用C预编译器 ``cpp`` ,并且其生成命令是: ``$(AS) $(ASFLAGS)`` 。 + ``.o`` 的目标的依赖目标会自动推导为 ``.s`` ,默认使用编译品 ``as`` ,并且其生成 + 命令是: ``$ (AS) $(ASFLAGS)`` 。 ``.s`` 的目标的依赖目标会自动推导为 ``.S`` , + 默认使用C预编译器 ``cpp`` ,并且其生成命令是: ``$(AS) $(ASFLAGS)`` 。 #. 链接Object文件的隐含规则。 - ```` 目标依赖于 ``.o`` ,通过运行C的编译器来运行链接程序生成(一般是 ``ld`` ),其生成命令是: ``$(CC) $(LDFLAGS) .o $(LOADLIBES) $(LDLIBS)`` 。这个规则对于只有一个源文件的工程有效,同时也对多个Object文件(由不同的源文件生成)的也有效。例如如下规则:: + ```` 目标依赖于 ``.o`` ,通过运行C的编译器来运行链接程序生成(一般是 ``ld`` ), + 其生成命令是: ``$(CC) $(LDFLAGS) .o $(LOADLIBES) $(LDLIBS)`` 。这个规则对于 + 只有一个源文件的工程有效,同时也对多个Object文件(由不同的源文件生成)的也有效。例如如下规则:: x : y.o z.o @@ -103,15 +138,18 @@ make会在自己的“隐含规则”库中寻找可以用的规则,如果找 rm -f y.o rm -f z.o - 如果没有一个源文件(如上例中的x.c)和你的目标名字(如上例中的x)相关联,那么,你最好写出自己的生成规则,不然,隐含规则会报错的。 + 如果没有一个源文件(如上例中的x.c)和你的目标名字(如上例中的x)相关联,那么,你最好写出自己 + 的生成规则,不然,隐含规则会报错的。 #. Yacc C程序时的隐含规则。 - ``.c`` 的依赖文件被自动推导为 ``n.y`` (Yacc生成的文件),其生成命令是: ``$(YACC) $(YFALGS)`` 。(“Yacc”是一个语法分析器,关于其细节请查看相关资料) + ``.c`` 的依赖文件被自动推导为 ``n.y`` (Yacc生成的文件),其生成命令是: ``$(YACC) $(YFALGS)`` 。 + (“Yacc”是一个语法分析器,关于其细节请查看相关资料) #. Lex C程序时的隐含规则。 - ``.c`` 的依赖文件被自动推导为 ``n.l`` (Lex生成的文件),其生成命令是: ``$(LEX) $(LFALGS)`` 。(关于“Lex”的细节请查看相关资料) + ``.c`` 的依赖文件被自动推导为 ``n.l`` (Lex生成的文件),其生成命令是: ``$(LEX) $(LFALGS)`` 。 + (关于“Lex”的细节请查看相关资料) #. Lex Ratfor程序时的隐含规则。 @@ -119,16 +157,23 @@ make会在自己的“隐含规则”库中寻找可以用的规则,如果找 #. 从C程序、Yacc文件或Lex文件创建Lint库的隐含规则。 - ``.ln`` (lint生成的文件)的依赖文件被自动推导为 ``n.c`` ,其生成命令是: ``$(LINT) $(LINTFALGS) $(CPPFLAGS) -i`` 。对于 ``.y`` 和 ``.l`` 也是同样的规则。 + ``.ln`` (lint生成的文件)的依赖文件被自动推导为 ``n.c`` ,其生成命令是: + ``$(LINT) $(LINTFALGS) $(CPPFLAGS) -i`` 。对于 ``.y`` 和 ``.l`` 也是同样的规则。 隐含规则使用的变量 ------------------ -在隐含规则中的命令中,基本上都是使用了一些预先设置的变量。你可以在你的makefile中改变这些变量的值,或是在make的命令行中传入这些值,或是在你的环境变量中设置这些值,无论怎么样,只要设置了这些特定的变量,那么其就会对隐含规则起作用。当然,你也可以利用make的 ``-R`` 或 ``--no–builtin-variables`` 参数来取消你所定义的变量对隐含规则的作用。 +在隐含规则中的命令中,基本上都是使用了一些预先设置的变量。你可以在你的makefile中改变这些变量的值, +或是在make的命令行中传入这些值,或是在你的环境变量中设置这些值,无论怎么样,只要设置了这些特定的变量, +那么其就会对隐含规则起作用。当然,你也可以利用make的 ``-R`` 或 ``--no–builtin-variables`` +参数来取消你所定义的变量对隐含规则的作用。 -例如,第一条隐含规则——编译C程序的隐含规则的命令是 ``$(CC) –c $(CFLAGS) $(CPPFLAGS)`` 。Make默认的编译命令是 ``cc`` ,如果你把变量 ``$(CC)`` 重定义成 ``gcc`` ,把变量 ``$(CFLAGS)`` 重定义成 ``-g`` ,那么,隐含规则中的命令全部会以 ``gcc –c -g $(CPPFLAGS)`` 的样子来执行了。 +例如,第一条隐含规则——编译C程序的隐含规则的命令是 ``$(CC) –c $(CFLAGS) $(CPPFLAGS)`` 。 +Make默认的编译命令是 ``cc`` ,如果你把变量 ``$(CC)`` 重定义成 ``gcc`` ,把变量 ``$(CFLAGS)`` +重定义成 ``-g`` ,那么,隐含规则中的命令全部会以 ``gcc –c -g $(CPPFLAGS)`` 的样子来执行了。 -我们可以把隐含规则中使用的变量分成两种:一种是命令相关的,如 ``CC`` ;一种是参数相的关,如 ``CFLAGS`` 。下面是所有隐含规则中会用到的变量: +我们可以把隐含规则中使用的变量分成两种:一种是命令相关的,如 ``CC`` ;一种是参数相的关,如 +``CFLAGS`` 。下面是所有隐含规则中会用到的变量: 关于命令的变量。 ~~~~~~~~~~~~~~~~ @@ -176,43 +221,67 @@ make会在自己的“隐含规则”库中寻找可以用的规则,如果找 隐含规则链 ---------- -有些时候,一个目标可能被一系列的隐含规则所作用。例如,一个 ``.o`` 的文件生成,可能会是先被Yacc的[.y]文件先成 ``.c`` ,然后再被C的编译器生成。我们把这一系列的隐含规则叫做“隐含规则链”。 +有些时候,一个目标可能被一系列的隐含规则所作用。例如,一个 ``.o`` 的文件生成,可能会是先被 +Yacc的[.y]文件先成 ``.c`` ,然后再被C的编译器生成。我们把这一系列的隐含规则叫做“隐含规则链”。 -在上面的例子中,如果文件 ``.c`` 存在,那么就直接调用C的编译器的隐含规则,如果没有 ``.c`` 文件,但有一个 ``.y`` 文件,那么Yacc的隐含规则会被调用,生成 ``.c`` 文件,然后,再调用C编译的隐含规则最终由 ``.c`` 生成 ``.o`` 文件,达到目标。 +在上面的例子中,如果文件 ``.c`` 存在,那么就直接调用C的编译器的隐含规则,如果没有 ``.c`` 文件, +但有一个 ``.y`` 文件,那么Yacc的隐含规则会被调用,生成 ``.c`` 文件,然后,再调用C编译的隐含 +规则最终由 ``.c`` 生成 ``.o`` 文件,达到目标。 -我们把这种 ``.c`` 的文件(或是目标),叫做中间目标。不管怎么样,make会努力自动推导生成目标的一切方法,不管中间目标有多少,其都会执着地把所有的隐含规则和你书写的规则全部合起来分析,努力达到目标,所以,有些时候,可能会让你觉得奇怪,怎么我的目标会这样生成?怎么我的 makefile发疯了? +我们把这种 ``.c`` 的文件(或是目标),叫做中间目标。不管怎么样,make会努力自动推导生成目标的 +一切方法,不管中间目标有多少,其都会执着地把所有的隐含规则和你书写的规则全部合起来分析,努力达到 +目标,所以,有些时候,可能会让你觉得奇怪,怎么我的目标会这样生成?怎么我的 makefile发疯了? -在默认情况下,对于中间目标,它和一般的目标有两个地方所不同:第一个不同是除非中间的目标不存在,才会引发中间规则。第二个不同的是,只要目标成功产生,那么,产生最终目标过程中,所产生的中间目标文件会被以 ``rm -f`` 删除。 +在默认情况下,对于中间目标,它和一般的目标有两个地方所不同:第一个不同是除非中间的目标不存在, +才会引发中间规则。第二个不同的是,只要目标成功产生,那么,产生最终目标过程中,所产生的中间目标 +文件会被以 ``rm -f`` 删除。 -通常,一个被makefile指定成目标或是依赖目标的文件不能被当作中介。然而,你可以明显地说明一个文件或是目标是中介目标,你可以使用伪目标 ``.INTERMEDIATE`` 来强制声明。(如: ``.INTERMEDIATE : mid`` ) +通常,一个被makefile指定成目标或是依赖目标的文件不能被当作中介。然而,你可以明显地说明一个 +文件或是目标是中介目标,你可以使用伪目标 ``.INTERMEDIATE`` 来强制声明。 +(如: ``.INTERMEDIATE : mid`` ) -你也可以阻止make自动删除中间目标,要做到这一点,你可以使用伪目标 ``.SECONDARY`` 来强制声明(如: ``.SECONDARY : sec`` )。你还可以把你的目标,以模式的方式来指定(如: ``%.o`` )成伪目标 ``.PRECIOUS`` 的依赖目标,以保存被隐含规则所生成的中间文件。 +你也可以阻止make自动删除中间目标,要做到这一点,你可以使用伪目标 ``.SECONDARY`` 来强制声明 +(如: ``.SECONDARY : sec`` )。你还可以把你的目标,以模式的方式来指定(如: ``%.o`` )成 +伪目标 ``.PRECIOUS`` 的依赖目标,以保存被隐含规则所生成的中间文件。 -在“隐含规则链”中,禁止同一个目标出现两次或两次以上,这样一来,就可防止在make自动推导时出现无限递归的情况。 +在“隐含规则链”中,禁止同一个目标出现两次或两次以上,这样一来,就可防止在make自动推导时出现 +无限递归的情况。 -Make会优化一些特殊的隐含规则,而不生成中间文件。如,从文件 ``foo.c`` 生成目标程序 ``foo`` ,按道理,make会编译生成中间文件 ``foo.o`` ,然后链接成 ``foo`` ,但在实际情况下,这一动作可以被一条 ``cc`` 的命令完成( ``cc –o foo foo.c`` ),于是优化过的规则就不会生成中间文件。 +Make会优化一些特殊的隐含规则,而不生成中间文件。如,从文件 ``foo.c`` 生成目标程序 ``foo`` , +按道理,make会编译生成中间文件 ``foo.o`` ,然后链接成 ``foo`` ,但在实际情况下,这一动作可以 +被一条 ``cc`` 的命令完成( ``cc –o foo foo.c`` ),于是优化过的规则就不会生成中间文件。 定义模式规则 ------------ -你可以使用模式规则来定义一个隐含规则。一个模式规则就好像一个一般的规则,只是在规则中,目标的定义需要有 ``%`` 字符。 ``%`` 的意思是表示一个或多个任意字符。在依赖目标中同样可以使用 ``%`` ,只是依赖目标中的 ``%`` 的取值,取决于其目标。 +你可以使用模式规则来定义一个隐含规则。一个模式规则就好像一个一般的规则,只是在规则中,目标的定义 +需要有 ``%`` 字符。 ``%`` 的意思是表示一个或多个任意字符。在依赖目标中同样可以使用 ``%`` , +只是依赖目标中的 ``%`` 的取值,取决于其目标。 -有一点需要注意的是, ``%`` 的展开发生在变量和函数的展开之后,变量和函数的展开发生在make载入Makefile时,而模式规则中的 ``%`` 则发生在运行时。 +有一点需要注意的是, ``%`` 的展开发生在变量和函数的展开之后,变量和函数的展开发生在make载入 +Makefile时,而模式规则中的 ``%`` 则发生在运行时。 模式规则介绍 ~~~~~~~~~~~~ -模式规则中,至少在规则的目标定义中要包含 ``%`` ,否则,就是一般的规则。目标中的 ``%`` 定义表示对文件名的匹配, ``%`` 表示长度任意的非空字符串。例如: ``%.c`` 表示以 ``.c`` 结尾的文件名(文件名的长度至少为3),而 ``s.%.c`` 则表示以 ``s.`` 开头, ``.c`` 结尾的文件名(文件名的长度至少为5)。 +模式规则中,至少在规则的目标定义中要包含 ``%`` ,否则,就是一般的规则。目标中的 ``%`` 定义 +表示对文件名的匹配, ``%`` 表示长度任意的非空字符串。例如: ``%.c`` 表示以 ``.c`` 结尾的 +文件名(文件名的长度至少为3),而 ``s.%.c`` 则表示以 ``s.`` 开头, ``.c`` 结尾的文件名 +(文件名的长度至少为5)。 -如果 ``%`` 定义在目标中,那么,目标中的 ``%`` 的值决定了依赖目标中的 ``%`` 的值,也就是说,目标中的模式的 ``%`` 决定了依赖目标中 ``%`` 的样子。例如有一个模式规则如下: +如果 ``%`` 定义在目标中,那么,目标中的 ``%`` 的值决定了依赖目标中的 ``%`` 的值,也就是说, +目标中的模式的 ``%`` 决定了依赖目标中 ``%`` 的样子。例如有一个模式规则如下: .. code-block:: makefile %.o : %.c ; ; -其含义是,指出了怎么从所有的 ``.c`` 文件生成相应的 ``.o`` 文件的规则。如果要生成的目标是 ``a.o b.o`` ,那么 ``%c`` 就是 ``a.c b.c`` 。 +其含义是,指出了怎么从所有的 ``.c`` 文件生成相应的 ``.o`` 文件的规则。如果要生成的目标是 +``a.o b.o`` ,那么 ``%c`` 就是 ``a.c b.c`` 。 -一旦依赖目标中的 ``%`` 模式被确定,那么,make会被要求去匹配当前目录下所有的文件名,一旦找到,make就会规则下的命令,所以,在模式规则中,目标可能会是多个的,如果有模式匹配出多个目标,make就会产生所有的模式目标,此时,make关心的是依赖的文件名和生成目标的命令这两件事。 +一旦依赖目标中的 ``%`` 模式被确定,那么,make会被要求去匹配当前目录下所有的文件名,一旦找到, +make就会规则下的命令,所以,在模式规则中,目标可能会是多个的,如果有模式匹配出多个目标,make就 +会产生所有的模式目标,此时,make关心的是依赖的文件名和生成目标的命令这两件事。 模式规则示例 ~~~~~~~~~~~~ @@ -224,7 +293,8 @@ Make会优化一些特殊的隐含规则,而不生成中间文件。如,从 %.o : %.c $(CC) -c $(CFLAGS) $(CPPFLAGS) $< -o $@ -其中, ``$@`` 表示所有的目标的挨个值, ``$<`` 表示了所有依赖目标的挨个值。这些奇怪的变量我们叫“自动化变量”,后面会详细讲述。 +其中, ``$@`` 表示所有的目标的挨个值, ``$<`` 表示了所有依赖目标的挨个值。这些奇怪的变量我们 +叫“自动化变量”,后面会详细讲述。 下面的这个例子中有两个目标是模式的: @@ -233,47 +303,76 @@ Make会优化一些特殊的隐含规则,而不生成中间文件。如,从 %.tab.c %.tab.h: %.y bison -d $< -这条规则告诉make把所有的 ``.y`` 文件都以 ``bison -d .y`` 执行,然后生成 ``.tab.c`` 和 ``.tab.h`` 文件。(其中, ```` 表示一个任意字符串)。如果我们的执行程序 ``foo`` 依赖于文件 ``parse.tab.o`` 和 ``scan.o`` ,并且文件 ``scan.o`` 依赖于文件 ``parse.tab.h`` ,如果 ``parse.y`` 文件被更新了,那么根据上述的规则, ``bison -d parse.y`` 就会被执行一次,于是, ``parse.tab.o`` 和 ``scan.o`` 的依赖文件就齐了。(假设, ``parse.tab.o`` 由 ``parse.tab.c`` 生成,和 ``scan.o`` 由 ``scan.c`` 生成,而 ``foo`` 由 ``parse.tab.o`` 和 ``scan.o`` 链接生成,而且 ``foo`` 和其 ``.o`` 文件的依赖关系也写好,那么,所有的目标都会得到满足) +这条规则告诉make把所有的 ``.y`` 文件都以 ``bison -d .y`` 执行,然后生成 ``.tab.c`` +和 ``.tab.h`` 文件。(其中, ```` 表示一个任意字符串)。如果我们的执行程序 ``foo`` +依赖于文件 ``parse.tab.o`` 和 ``scan.o`` ,并且文件 ``scan.o`` 依赖于文件 ``parse.tab.h`` , +如果 ``parse.y`` 文件被更新了,那么根据上述的规则, ``bison -d parse.y`` 就会被执行一次, +于是, ``parse.tab.o`` 和 ``scan.o`` 的依赖文件就齐了。(假设, ``parse.tab.o`` 由 +``parse.tab.c`` 生成,和 ``scan.o`` 由 ``scan.c`` 生成,而 ``foo`` 由 ``parse.tab.o`` +和 ``scan.o`` 链接生成,而且 ``foo`` 和其 ``.o`` 文件的依赖关系也写好,那么,所有的目标都会得到满足) 自动化变量 ~~~~~~~~~~ -在上述的模式规则中,目标和依赖文件都是一系例的文件,那么我们如何书写一个命令来完成从不同的依赖文件生成相应的目标?因为在每一次的对模式规则的解析时,都会是不同的目标和依赖文件。 +在上述的模式规则中,目标和依赖文件都是一系例的文件,那么我们如何书写一个命令来完成从不同的依赖 +文件生成相应的目标?因为在每一次的对模式规则的解析时,都会是不同的目标和依赖文件。 -自动化变量就是完成这个功能的。在前面,我们已经对自动化变量有所提涉,相信你看到这里已对它有一个感性认识了。所谓自动化变量,就是这种变量会把模式中所定义的一系列的文件自动地挨个取出,直至所有的符合模式的文件都取完了。这种自动化变量只应出现在规则的命令中。 +自动化变量就是完成这个功能的。在前面,我们已经对自动化变量有所提涉,相信你看到这里已对它有一个 +感性认识了。所谓自动化变量,就是这种变量会把模式中所定义的一系列的文件自动地挨个取出,直至所有的 +符合模式的文件都取完了。这种自动化变量只应出现在规则的命令中。 下面是所有的自动化变量及其说明: -- ``$@`` : 表示规则中的目标文件集。在模式规则中,如果有多个目标,那么, ``$@`` 就是匹配于目标中模式定义的集合。 -- ``$%`` : 仅当目标是函数库文件中,表示规则中的目标成员名。例如,如果一个目标是 ``foo.a(bar.o)`` ,那么, ``$%`` 就是 ``bar.o`` , ``$@`` 就是 ``foo.a`` 。如果目标不是函数库文件(Unix下是 ``.a`` ,Windows下是 ``.lib`` ),那么,其值为空。 -- ``$<`` : 依赖目标中的第一个目标名字。如果依赖目标是以模式(即 ``%`` )定义的,那么 ``$<`` 将是符合模式的一系列的文件集。注意,其是一个一个取出来的。 +- ``$@`` : 表示规则中的目标文件集。在模式规则中,如果有多个目标,那么, ``$@`` 就是匹配于 + 目标中模式定义的集合。 +- ``$%`` : 仅当目标是函数库文件中,表示规则中的目标成员名。例如,如果一个目标是 ``foo.a(bar.o)`` , + 那么, ``$%`` 就是 ``bar.o`` , ``$@`` 就是 ``foo.a`` 。如果目标不是函数库文件 + (Unix下是 ``.a`` ,Windows下是 ``.lib`` ),那么,其值为空。 +- ``$<`` : 依赖目标中的第一个目标名字。如果依赖目标是以模式(即 ``%`` )定义的,那么 ``$<`` + 将是符合模式的一系列的文件集。注意,其是一个一个取出来的。 - ``$?`` : 所有比目标新的依赖目标的集合。以空格分隔。 -- ``$^`` : 所有的依赖目标的集合。以空格分隔。如果在依赖目标中有多个重复的,那个这个变量会去除重复的依赖目标,只保留一份。 +- ``$^`` : 所有的依赖目标的集合。以空格分隔。如果在依赖目标中有多个重复的,那个这个变量会去除 + 重复的依赖目标,只保留一份。 - ``$+`` : 这个变量很像 ``$^`` ,也是所有依赖目标的集合。只是它不去除重复的依赖目标。 -- ``$*`` : 这个变量表示目标模式中 ``%`` 及其之前的部分。如果目标是 ``dir/a.foo.b`` ,并且目标的模式是 ``a.%.b`` ,那么, ``$*`` 的值就是 ``dir/a.foo`` 。这个变量对于构造有关联的文件名是比较有较。如果目标中没有模式的定义,那么 ``$*`` 也就不能被推导出,但是,如果目标文件的后缀是make所识别的,那么 ``$*`` 就是除了后缀的那一部分。例如:如果目标是 ``foo.c`` ,因为 ``.c`` 是make所能识别的后缀名,所以, ``$*`` 的值就是 ``foo`` 。这个特性是GNU make的,很有可能不兼容于其它版本的make,所以,你应该尽量避免使用 ``$*`` ,除非是在隐含规则或是静态模式中。如果目标中的后缀是make所不能识别的,那么 ``$*`` 就是空值。 +- ``$*`` : 这个变量表示目标模式中 ``%`` 及其之前的部分。如果目标是 ``dir/a.foo.b`` ,并且 + 目标的模式是 ``a.%.b`` ,那么, ``$*`` 的值就是 ``dir/a.foo`` 。这个变量对于构造有关联的 + 文件名是比较有较。如果目标中没有模式的定义,那么 ``$*`` 也就不能被推导出,但是,如果目标文件的 + 后缀是make所识别的,那么 ``$*`` 就是除了后缀的那一部分。例如:如果目标是 ``foo.c`` ,因为 + ``.c`` 是make所能识别的后缀名,所以, ``$*`` 的值就是 ``foo`` 。这个特性是GNU make的, + 很有可能不兼容于其它版本的make,所以,你应该尽量避免使用 ``$*`` ,除非是在隐含规则或是静态 + 模式中。如果目标中的后缀是make所不能识别的,那么 ``$*`` 就是空值。 -当你希望只对更新过的依赖文件进行操作时, ``$?`` 在显式规则中很有用,例如,假设有一个函数库文件叫 ``lib`` ,其由其它几个object文件更新。那么把object文件打包的比较有效率的Makefile规则是: +当你希望只对更新过的依赖文件进行操作时, ``$?`` 在显式规则中很有用,例如,假设有一个函数库文件 +叫 ``lib`` ,其由其它几个object文件更新。那么把object文件打包的比较有效率的Makefile规则是: .. code-block:: makefile lib : foo.o bar.o lose.o win.o ar r lib $? -在上述所列出来的自动量变量中。四个变量( ``$@`` 、 ``$<`` 、 ``$%`` 、 ``$*`` )在扩展时只会有一个文件,而另三个的值是一个文件列表。这七个自动化变量还可以取得文件的目录名或是在当前目录下的符合模式的文件名,只需要搭配上 ``D`` 或 ``F`` 字样。这是GNU make中老版本的特性,在新版本中,我们使用函数 ``dir`` 或 ``notdir`` 就可以做到了。 ``D`` 的含义就是Directory,就是目录, ``F`` 的含义就是File,就是文件。 +在上述所列出来的自动量变量中。四个变量( ``$@`` 、 ``$<`` 、 ``$%`` 、 ``$*`` )在扩展时 +只会有一个文件,而另三个的值是一个文件列表。这七个自动化变量还可以取得文件的目录名或是在当前 +目录下的符合模式的文件名,只需要搭配上 ``D`` 或 ``F`` 字样。这是GNU make中老版本的特性, +在新版本中,我们使用函数 ``dir`` 或 ``notdir`` 就可以做到了。 ``D`` 的含义就是Directory, +就是目录, ``F`` 的含义就是File,就是文件。 下面是对于上面的七个变量分别加上 ``D`` 或是 ``F`` 的含义: ``$(@D)`` - 表示 ``$@`` 的目录部分(不以斜杠作为结尾),如果 ``$@`` 值是 ``dir/foo.o`` ,那么 ``$(@D)`` 就是 ``dir`` ,而如果 ``$@`` 中没有包含斜杠的话,其值就是 ``.`` (当前目录)。 + 表示 ``$@`` 的目录部分(不以斜杠作为结尾),如果 ``$@`` 值是 ``dir/foo.o`` ,那么 + ``$(@D)`` 就是 ``dir`` ,而如果 ``$@`` 中没有包含斜杠的话,其值就是 ``.`` (当前目录)。 ``$(@F)`` - 表示 ``$@`` 的文件部分,如果 ``$@`` 值是 ``dir/foo.o`` ,那么 ``$(@F)`` 就是 ``foo.o`` , ``$(@F)`` 相当于函数 ``$(notdir $@)`` 。 + 表示 ``$@`` 的文件部分,如果 ``$@`` 值是 ``dir/foo.o`` ,那么 ``$(@F)`` 就是 ``foo.o`` , + ``$(@F)`` 相当于函数 ``$(notdir $@)`` 。 ``$(*D)``, ``$(*F)`` - 和上面所述的同理,也是取文件的目录部分和文件部分。对于上面的那个例子, ``$(*D)`` 返回 ``dir`` ,而 ``$(*F)`` 返回 ``foo`` + 和上面所述的同理,也是取文件的目录部分和文件部分。对于上面的那个例子, ``$(*D)`` 返回 ``dir`` , + 而 ``$(*F)`` 返回 ``foo`` ``$(%D)``, ``$(%F)`` -分别表示了函数包文件成员的目录部分和文件部分。这对于形同 ``archive(member)`` 形式的目标中的 ``member`` 中包含了不同的目录很有用。 + 分别表示了函数包文件成员的目录部分和文件部分。这对于形同 ``archive(member)`` 形式的目标中的 + ``member`` 中包含了不同的目录很有用。 ``$(`` 可以用一个或多个空格隔开。举个例子,你有这样几个Makefile: ``a.mk`` 、 ``b.mk`` 、 ``c.mk`` ,还有一个文件叫 ``foo.make`` ,以及一个变量 ``$(bar)`` ,其包含了 ``e.mk`` 和 ``f.mk`` ,那么,下面的语句: +在 ``include`` 前面可以有一些空字符,但是绝不能是 ``Tab`` 键开始。 ``include`` 和 +```` 可以用一个或多个空格隔开。举个例子,你有这样几个Makefile: ``a.mk`` 、 +``b.mk`` 、 ``c.mk`` ,还有一个文件叫 ``foo.make`` ,以及一个变量 ``$(bar)`` ,其包含 +了 ``e.mk`` 和 ``f.mk`` ,那么,下面的语句: .. code-block:: makefile @@ -270,25 +341,38 @@ Makefile的文件名 include foo.make a.mk b.mk c.mk e.mk f.mk -make命令开始时,会找寻 ``include`` 所指出的其它Makefile,并把其内容安置在当前的位置。就好像C/C++的 ``#include`` 指令一样。如果文件都没有指定绝对路径或是相对路径的话,make会在当前目录下首先寻找,如果当前目录下没有找到,那么,make还会在下面的几个目录下找: +make命令开始时,会找寻 ``include`` 所指出的其它Makefile,并把其内容安置在当前的位置。就好 +像C/C++的 ``#include`` 指令一样。如果文件都没有指定绝对路径或是相对路径的话,make会在当前目 +录下首先寻找,如果当前目录下没有找到,那么,make还会在下面的几个目录下找: -#. 如果make执行时,有 ``-I`` 或 ``--include-dir`` 参数,那么make就会在这个参数所指定的目录下去寻找。 -#. 如果目录 ``/include`` (一般是: ``/usr/local/bin`` 或 ``/usr/include`` )存在的话,make也会去找。 +#. 如果make执行时,有 ``-I`` 或 ``--include-dir`` 参数,那么make就会在这个参数所指定的目 + 录下去寻找。 +#. 如果目录 ``/include`` (一般是: ``/usr/local/bin`` 或 + ``/usr/include`` )存在的话,make也会去找。 -如果有文件没有找到的话,make会生成一条警告信息,但不会马上出现致命错误。它会继续载入其它的文件,一旦完成makefile的读取,make会再重试这些没有找到,或是不能读取的文件,如果还是不行,make才会出现一条致命信息。如果你想让make不理那些无法读取的文件,而继续执行,你可以在include前加一个减号“-”。如: +如果有文件没有找到的话,make会生成一条警告信息,但不会马上出现致命错误。它会继续载入其它的 +文件,一旦完成makefile的读取,make会再重试这些没有找到,或是不能读取的文件,如果还是 +不行,make才会出现一条致命信息。如果你想让make不理那些无法读取的文件,而继续执行,你可以 +在include前加一个减号“-”。如: .. code-block:: makefile -include -其表示,无论include过程中出现什么错误,都不要报错继续执行。和其它版本make兼容的相关命令是sinclude,其作用和这一个是一样的。 +其表示,无论include过程中出现什么错误,都不要报错继续执行。和其它版本make兼容的相关命令 +是sinclude,其作用和这一个是一样的。 环境变量MAKEFILES ----------------- -如果你的当前环境中定义了环境变量 ``MAKEFILES`` ,那么,make会把这个变量中的值做一个类似于 ``include`` 的动作。这个变量中的值是其它的Makefile,用空格分隔。只是,它和 ``include`` 不同的是,从这个环境变量中引入的Makefile的“目标”不会起作用,如果环境变量中定义的文件发现错误,make也会不理。 +如果你的当前环境中定义了环境变量 ``MAKEFILES`` ,那么,make会把这个变量中的值做一个类似于 +``include`` 的动作。这个变量中的值是其它的Makefile,用空格分隔。只是,它和 ``include`` 不 +同的是,从这个环境变量中引入的Makefile的“目标”不会起作用,如果环境变量中定义的文件发现 +错误,make也会不理。 -但是在这里我还是建议不要使用这个环境变量,因为只要这个变量一被定义,那么当你使用make时,所有的Makefile都会受到它的影响,这绝不是你想看到的。在这里提这个事,只是为了告诉大家,也许有时候你的Makefile出现了怪事,那么你可以看看当前环境中有没有定义这个变量。 +但是在这里我还是建议不要使用这个环境变量,因为只要这个变量一被定义,那么当你使用make时, +所有的Makefile都会受到它的影响,这绝不是你想看到的。在这里提这个事,只是为了告诉大家,也许 +有时候你的Makefile出现了怪事,那么你可以看看当前环境中有没有定义这个变量。 make的工作方式 -------------- @@ -303,6 +387,9 @@ GNU的make工作时的执行步骤如下:(想来其它的make也是类似) #. 根据依赖关系,决定哪些目标要重新生成。 #. 执行生成命令。 -1-5步为第一个阶段,6-7为第二个阶段。第一个阶段中,如果定义的变量被使用了,那么,make会把其展开在使用的位置。但make并不会完全马上展开,make使用的是拖延战术,如果变量出现在依赖关系的规则中,那么仅当这条依赖被决定要使用了,变量才会在其内部展开。 +1-5步为第一个阶段,6-7为第二个阶段。第一个阶段中,如果定义的变量被使用了,那么,make会把其展 +开在使用的位置。但make并不会完全马上展开,make使用的是拖延战术,如果变量出现在依赖关系的规则 +中,那么仅当这条依赖被决定要使用了,变量才会在其内部展开。 -当然,这个工作方式你不一定要清楚,但是知道这个方式你也会对make更为熟悉。有了这个基础,后续部分也就容易看懂了。 +当然,这个工作方式你不一定要清楚,但是知道这个方式你也会对make更为熟悉。有了这个基础,后续部分 +也就容易看懂了。 diff --git a/source/invoke.rst b/source/invoke.rst index 38def2f..0a5baff 100644 --- a/source/invoke.rst +++ b/source/invoke.rst @@ -1,7 +1,9 @@ make 的运行 =========== -一般来说,最简单的就是直接在命令行下输入make命令,make命令会找当前目录的makefile来执行,一切都是自动的。但也有时你也许只想让make重编译某些文件,而不是整个工程,而又有的时候你有几套编译规则,你想在不同的时候使用不同的编译规则,等等。本章节就是讲述如何使用make命令的。 +一般来说,最简单的就是直接在命令行下输入make命令,make命令会找当前目录的makefile来执行,一切 +都是自动的。但也有时你也许只想让make重编译某些文件,而不是整个工程,而又有的时候你有几套编译规 +则,你想在不同的时候使用不同的编译规则,等等。本章节就是讲述如何使用make命令的。 make的退出码 ------------ @@ -19,28 +21,39 @@ make命令执行后有三个退出码: Make的相关参数我们会在后续章节中讲述。 - 指定Makefile ------------ -前面我们说过,GNU make找寻默认的Makefile的规则是在当前目录下依次找三个文件——“GNUmakefile”、“makefile”和“Makefile”。其按顺序找这三个文件,一旦找到,就开始读取这个文件并执行。 +前面我们说过,GNU make找寻默认的Makefile的规则是在当前目录下依次找三个文件——“GNUmakefile” +、“makefile”和“Makefile”。其按顺序找这三个文件,一旦找到,就开始读取这个文件并执行。 -当前,我们也可以给make命令指定一个特殊名字的Makefile。要达到这个功能,我们要使用make的 ``-f`` 或是 ``--file`` 参数( ``--makefile`` 参数也行)。例如,我们有个makefile的名字是“hchen.mk”,那么,我们可以这样来让make来执行这个文件: +当前,我们也可以给make命令指定一个特殊名字的Makefile。要达到这个功能,我们要使用make的 +``-f`` 或是 ``--file`` 参数( ``--makefile`` 参数也行)。例如,我们有个makefile的名字 +是“hchen.mk”,那么,我们可以这样来让make来执行这个文件: :: make –f hchen.mk -如果在make的命令行是,你不只一次地使用了 ``-f`` 参数,那么,所有指定的makefile将会被连在一起传递给make执行。 +如果在make的命令行是,你不只一次地使用了 ``-f`` 参数,那么,所有指定的makefile将会被连在 +一起传递给make执行。 指定目标 -------- -一般来说,make的最终目标是makefile中的第一个目标,而其它目标一般是由这个目标连带出来的。这是make的默认行为。当然,一般来说,你的makefile中的第一个目标是由许多个目标组成,你可以指示make,让其完成你所指定的目标。要达到这一目的很简单,需在make命令后直接跟目标的名字就可以完成(如前面提到的“make clean”形式) +一般来说,make的最终目标是makefile中的第一个目标,而其它目标一般是由这个目标连带出来的。这 +是make的默认行为。当然,一般来说,你的makefile中的第一个目标是由许多个目标组成,你可以指 +示make,让其完成你所指定的目标。要达到这一目的很简单,需在make命令后直接跟目标的名字就可以完成 +(如前面提到的“make clean”形式) -任何在makefile中的目标都可以被指定成终极目标,但是除了以 ``-`` 打头,或是包含了 ``=`` 的目标,因为有这些字符的目标,会被解析成命令行参数或是变量。甚至没有被我们明确写出来的目标也可以成为make的终极目标,也就是说,只要make可以找到其隐含规则推导规则,那么这个隐含目标同样可以被指定成终极目标。 +任何在makefile中的目标都可以被指定成终极目标,但是除了以 ``-`` 打头,或是包含了 ``=`` 的 +目标,因为有这些字符的目标,会被解析成命令行参数或是变量。甚至没有被我们明确写出来的目标也可以 +成为make的终极目标,也就是说,只要make可以找到其隐含规则推导规则,那么这个隐含目标同样可以被指 +定成终极目标。 -有一个make的环境变量叫 ``MAKECMDGOALS`` ,这个变量中会存放你所指定的终极目标的列表,如果在命令行上,你没有指定目标,那么,这个变量是空值。这个变量可以让你使用在一些比较特殊的情形下。比如下面的例子: +有一个make的环境变量叫 ``MAKECMDGOALS`` ,这个变量中会存放你所指定的终极目标的列表,如果在 +命令行上,你没有指定目标,那么,这个变量是空值。这个变量可以让你使用在一些比较特殊的情形下。比 +如下面的例子: .. code-block:: makefile @@ -49,7 +62,8 @@ Make的相关参数我们会在后续章节中讲述。 include $(sources:.c=.d) endif -基于上面的这个例子,只要我们输入的命令不是“make clean”,那么makefile会自动包含“foo.d”和“bar.d”这两个makefile。 +基于上面的这个例子,只要我们输入的命令不是“make clean”,那么makefile会自动包含“foo.d” +和“bar.d”这两个makefile。 使用指定终极目标的方法可以很方便地让我们编译我们的程序,例如下面这个例子: @@ -58,9 +72,14 @@ Make的相关参数我们会在后续章节中讲述。 .PHONY: all all: prog1 prog2 prog3 prog4 -从这个例子中,我们可以看到,这个makefile中有四个需要编译的程序——“prog1”, “prog2”, “prog3”和 “prog4”,我们可以使用“make all”命令来编译所有的目标(如果把all置成第一个目标,那么只需执行“make”),我们也可以使用 “make prog2”来单独编译目标“prog2”。 +从这个例子中,我们可以看到,这个makefile中有四个需要编译的程序——“prog1”, “prog2”, +“prog3”和 “prog4”,我们可以使用“make all”命令来编译所有的目标(如果把all置成第一个目标, +那么只需执行“make”),我们也可以使用 “make prog2”来单独编译目标“prog2”。 -即然make可以指定所有makefile中的目标,那么也包括“伪目标”,于是我们可以根据这种性质来让我们的makefile根据指定的不同的目标来完成不同的事。在Unix世界中,软件发布时,特别是GNU这种开源软件的发布时,其makefile都包含了编译、安装、打包等功能。我们可以参照这种规则来书写我们的makefile中的目标。 +即然make可以指定所有makefile中的目标,那么也包括“伪目标”,于是我们可以根据这种性质来让我们 +的makefile根据指定的不同的目标来完成不同的事。在Unix世界中,软件发布时,特别是GNU这种开源软 +件的发布时,其makefile都包含了编译、安装、打包等功能。我们可以参照这种规则来书写我们 +的makefile中的目标。 - all:这个伪目标是所有目标的目标,其功能一般是编译所有的目标。 - clean:这个伪目标功能是删除所有被make创建的文件。 @@ -71,24 +90,33 @@ Make的相关参数我们会在后续章节中讲述。 - TAGS:这个伪目标功能是更新所有的目标,以备完整地重编译使用。 - check和test:这两个伪目标一般用来测试makefile的流程。 -当然一个项目的makefile中也不一定要书写这样的目标,这些东西都是GNU的东西,但是我想,GNU搞出这些东西一定有其可取之处(等你的 UNIX下的程序文件一多时你就会发现这些功能很有用了),这里只不过是说明了,如果你要书写这种功能,最好使用这种名字命名你的目标,这样规范一些,规范的好处就是——不用解释,大家都明白。而且如果你的makefile中有这些功能,一是很实用,二是可以显得你的makefile很专业(不是那种初学者的作品)。 +当然一个项目的makefile中也不一定要书写这样的目标,这些东西都是GNU的东西,但是我想,GNU搞出这 +些东西一定有其可取之处(等你的 UNIX下的程序文件一多时你就会发现这些功能很有用了),这里只不过 +是说明了,如果你要书写这种功能,最好使用这种名字命名你的目标,这样规范一些,规范的好处就是——不 +用解释,大家都明白。而且如果你的makefile中有这些功能,一是很实用,二是可以显得你的makefile很 +专业(不是那种初学者的作品)。 检查规则 -------- -有时候,我们不想让我们的makefile中的规则执行起来,我们只想检查一下我们的命令,或是执行的序列。于是我们可以使用make命令的下述参数: +有时候,我们不想让我们的makefile中的规则执行起来,我们只想检查一下我们的命令,或是执行的序列。 +于是我们可以使用make命令的下述参数: ``-n``, ``--just-print``, ``--dry-run``, ``--recon`` - 不执行参数,这些参数只是打印命令,不管目标是否更新,把规则和连带规则下的命令打印出来,但不执行,这些参数对于我们调试makefile很有用处。 + 不执行参数,这些参数只是打印命令,不管目标是否更新,把规则和连带规则下的命令打印出来,但不 + 执行,这些参数对于我们调试makefile很有用处。 ``-t``, ``--touch`` - 这个参数的意思就是把目标文件的时间更新,但不更改目标文件。也就是说,make假装编译目标,但不是真正的编译目标,只是把目标变成已编译过的状态。 + 这个参数的意思就是把目标文件的时间更新,但不更改目标文件。也就是说,make假装编译目标,但不 + 是真正的编译目标,只是把目标变成已编译过的状态。 ``-q``, ``--question`` - 这个参数的行为是找目标的意思,也就是说,如果目标存在,那么其什么也不会输出,当然也不会执行编译,如果目标不存在,其会打印出一条出错信息。 + 这个参数的行为是找目标的意思,也就是说,如果目标存在,那么其什么也不会输出,当然也不会执行 + 编译,如果目标不存在,其会打印出一条出错信息。 ``-W ``, ``--what-if=``, ``--assume-new=``, ``--new-file=`` - 这个参数需要指定一个文件。一般是是源文件(或依赖文件),Make会根据规则推导来运行依赖于这个文件的命令,一般来说,可以和“-n”参数一同使用,来查看这个依赖文件所发生的规则命令。 + 这个参数需要指定一个文件。一般是是源文件(或依赖文件),Make会根据规则推导来运行依赖于这个 + 文件的命令,一般来说,可以和“-n”参数一同使用,来查看这个依赖文件所发生的规则命令。 另外一个很有意思的用法是结合 ``-p`` 和 ``-v`` 来输出makefile被执行时的信息(这个将在后面讲述)。 @@ -104,14 +132,18 @@ make的参数 认为所有的目标都需要更新(重编译)。 ``-C`` **, ``--directory``\ =\ ** - 指定读取makefile的目录。如果有多个“-C”参数,make的解释是后面的路径以前面的作为相对路径,并以最后的目录作为被指定目录。如:“make -C ~hchen/test -C prog”等价于“make -C ~hchen/test/prog”。 + 指定读取makefile的目录。如果有多个“-C”参数,make的解释是后面的路径以前面的作为相对路径 + ,并以最后的目录作为被指定目录。如:“make -C ~hchen/test -C prog”等价于 + “make -C ~hchen/test/prog”。 ``-debug``\ [=\ **] - 输出make的调试信息。它有几种不同的级别可供选择,如果没有参数,那就是输出最简单的调试信息。下面是的取值: + 输出make的调试信息。它有几种不同的级别可供选择,如果没有参数,那就是输出最简单的调试信息。 + 下面是的取值: - a: 也就是all,输出所有的调试信息。(会非常的多) - b: 也就是basic,只输出简单的调试信息。即输出不需要重编译的目标。 - - v: 也就是verbose,在b选项的级别之上。输出的信息包括哪个makefile被解析,不需要被重编译的依赖文件(或是依赖目标)等。 + - v: 也就是verbose,在b选项的级别之上。输出的信息包括哪个makefile被解析,不需要被重编 + 译的依赖文件(或是依赖目标)等。 - i: 也就是implicit,输出所以的隐含规则。 - j: 也就是jobs,输出执行规则中命令的详细信息,如命令的PID、返回码等。 - m: 也就是makefile,输出make读取makefile,更新makefile,执行makefile的信息。 @@ -150,10 +182,15 @@ make的参数 不重新生成的指定的,即使这个目标的依赖文件新于它。 ``-p``, ``--print-data-base`` - 输出makefile中的所有数据,包括所有的规则和变量。这个参数会让一个简单的makefile都会输出一堆信息。如果你只是想输出信息而不想执行makefile,你可以使用“make -qp”命令。如果你想查看执行makefile前的预设变量和规则,你可以使用 “make –p –f /dev/null”。这个参数输出的信息会包含着你的makefile文件的文件名和行号,所以,用这个参数来调试你的 makefile会是很有用的,特别是当你的环境变量很复杂的时候。 + 输出makefile中的所有数据,包括所有的规则和变量。这个参数会让一个简单的makefile都会输出 + 一堆信息。如果你只是想输出信息而不想执行makefile,你可以使用“make -qp”命令。如果你想查 + 看执行makefile前的预设变量和规则,你可以使用 “make –p –f /dev/null”。这个参数输出的 + 信息会包含着你的makefile文件的文件名和行号,所以,用这个参数来调试你的 makefile会是很有 + 用的,特别是当你的环境变量很复杂的时候。 ``-q``, ``--question`` - 不运行命令,也不输出。仅仅是检查所指定的目标是否需要更新。如果是0则说明要更新,如果是2则说明有错误发生。 + 不运行命令,也不输出。仅仅是检查所指定的目标是否需要更新。如果是0则说明要更新,如果是2则说 + 明有错误发生。 ``-r``, ``--no-builtin-rules`` 禁止make使用任何隐含规则。 @@ -165,7 +202,8 @@ make的参数 在命令运行时不输出命令的输出。 ``-S``, ``--no-keep-going``, ``--stop`` - 取消“-k”选项的作用。因为有些时候,make的选项是从环境变量“MAKEFLAGS”中继承下来的。所以你可以在命令行中使用这个参数来让环境变量中的“-k”选项失效。 + 取消“-k”选项的作用。因为有些时候,make的选项是从环境变量“MAKEFLAGS”中继承下来的。所以你 + 可以在命令行中使用这个参数来让环境变量中的“-k”选项失效。 ``-t``, ``--touch`` 相当于UNIX的touch命令,只是把目标的修改日期变成最新的,也就是阻止生成目标的命令运行。 @@ -180,7 +218,8 @@ make的参数 禁止“-w”选项。 ``-W`` **, ``--what-if``\ =\ **, ``--new-file``\ =\ **, ``--assume-file``\ =\ ** - 假定目标;需要更新,如果和“-n”选项使用,那么这个参数会输出该目标更新时的运行动作。如果没有“-n”那么就像运行UNIX的“touch”命令一样,使得;的修改时间为当前时间。 + 假定目标;需要更新,如果和“-n”选项使用,那么这个参数会输出该目标更新时的运行动作。 + 如果没有“-n”那么就像运行UNIX的“touch”命令一样,使得;的修改时间为当前时间。 ``--warn-undefined-variables`` 只要make发现有未定义的变量,那么就输出警告信息。 diff --git a/source/overview.rst b/source/overview.rst index 6e9631b..0583c42 100644 --- a/source/overview.rst +++ b/source/overview.rst @@ -1,25 +1,53 @@ 概述 ==== -什么是makefile?或许很多Winodws的程序员都不知道这个东西,因为那些Windows的集成开发环境(integrated development environment,IDE)都为你做了这个工作,但我觉得要作一个好的和专业的程序员,makefile还是要懂。这就好像现在有这么多的HTML编辑器,但如果你想成为一个专业人士,你还是要了解HTML的标签的含义。特别在Unix下的软件编译,你就不能不自己写makefile了,会不会写makefile,从一个侧面说明了一个人是否具备完成大型工程的能力。 +什么是makefile?或许很多Winodws的程序员都不知道这个东西,因为那些Windows的集成开发环境 +(integrated development environment,IDE)都为你做了这个工作,但我觉得要作一个好的和专 +业的程序员,makefile还是要懂。这就好像现在有这么多的HTML编辑器,但如果你想成为一个专业人士, +你还是要了解HTML的标签的含义。特别在Unix下的软件编译,你就不能不自己写makefile了,会不会 +写makefile,从一个侧面说明了一个人是否具备完成大型工程的能力。 -因为,makefile关系到了整个工程的编译规则。一个工程中的源文件不计其数,并且按类型、功能、模块分别放在若干个目录中,makefile定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作,因为makefile就像一个Shell脚本一样,其中也可以执行操作系统的命令。 +因为,makefile关系到了整个工程的编译规则。一个工程中的源文件不计其数,并且按类型、功能、模块分 +别放在若干个目录中,makefile定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译, +哪些文件需要重新编译,甚至于进行更复杂的功能操作,因为makefile就像一个Shell脚本一样,其中也可 +以执行操作系统的命令。 -makefile带来的好处就是——“自动化编译”,一旦写好,只需要一个make命令,整个工程完全自动编译,极大的提高了软件开发的效率。 make是一个命令工具,是一个解释makefile中指令的命令工具,一般来说,大多数的IDE都有这个命令,比如:Delphi的make,Visual C++的nmake,Linux下GNU的make。可见,makefile都成为了一种在工程方面的编译方法。 +makefile带来的好处就是——“自动化编译”,一旦写好,只需要一个make命令,整个工程完全自动编译,极 +大的提高了软件开发的效率。 make是一个命令工具,是一个解释makefile中指令的命令工具,一般来说, +大多数的IDE都有这个命令,比如:Delphi的make,Visual C++的nmake,Linux下GNU的make。可见 +,makefile都成为了一种在工程方面的编译方法。 -现在讲述如何写makefile的文章比较少,这是我想写这篇文章的原因。当然,不同产商的make各不相同,也有不同的语法,但其本质都是在 “文件依赖性”上做文章,这里,我仅对GNU的make进行讲述,我的环境是RedHat Linux 8.0,make的版本是3.80。毕竟,这个make是应用最为广泛的,也是用得最多的。而且其还是最遵循于IEEE 1003.2-1992标准的(POSIX.2)。 +现在讲述如何写makefile的文章比较少,这是我想写这篇文章的原因。当然,不同产商的make各不相同, +也有不同的语法,但其本质都是在 “文件依赖性”上做文章,这里,我仅对GNU的make进行讲述,我的环境 +是RedHat Linux 8.0,make的版本是3.80。毕竟,这个make是应用最为广泛的,也是用得最多的。而且 +其还是最遵循于IEEE 1003.2-1992标准的(POSIX.2)。 -在这篇文档中,将以C/C++的源码作为基础,所以必然涉及一些关于C/C++的编译的知识。关于这方面的内容,还请各位查看相关的编译器的文档。这里所默认的编译器是UNIX下的GCC和CC。 +在这篇文档中,将以C/C++的源码作为基础,所以必然涉及一些关于C/C++的编译的知识。关于这方面的内 +容,还请各位查看相关的编译器的文档。这里所默认的编译器是UNIX下的GCC和CC。 关于程序的编译和链接 -------------------- -在此,我想多说关于程序编译的一些规范和方法。一般来说,无论是C还是C++,首先要把源文件编译成中间代码文件,在Windows下也就是 ``.obj`` 文件,UNIX下是 ``.o`` 文件,即Object File,这个动作叫做编译(compile)。然后再把大量的Object File合成执行文件,这个动作叫作链接(link)。 +在此,我想多说关于程序编译的一些规范和方法。一般来说,无论是C还是C++,首先要把源文件编译成中间 +代码文件,在Windows下也就是 ``.obj`` 文件,UNIX下是 ``.o`` 文件,即Object File,这个动 +作叫做编译(compile)。然后再把大量的Object File合成执行文件,这个动作叫作链接(link)。 -编译时,编译器需要的是语法的正确,函数与变量的声明的正确。对于后者,通常是你需要告诉编译器头文件的所在位置(头文件中应该只是声明,而定义应该放在C/C++文件中),只要所有的语法正确,编译器就可以编译出中间目标文件。一般来说,每个源文件都应该对应于一个中间目标文件( ``.o`` 文件或 ``.obj`` 文件)。 +编译时,编译器需要的是语法的正确,函数与变量的声明的正确。对于后者,通常是你需要告诉编译器头文 +件的所在位置(头文件中应该只是声明,而定义应该放在C/C++文件中),只要所有的语法正确,编译器就 +可以编译出中间目标文件。一般来说,每个源文件都应该对应于一个中间目标文件( ``.o`` 文件或 +``.obj`` 文件)。 -链接时,主要是链接函数和全局变量。所以,我们可以使用这些中间目标文件( ``.o`` 文件或 ``.obj`` 文件)来链接我们的应用程序。链接器并不管函数所在的源文件,只管函数的中间目标文件(Object File),在大多数时候,由于源文件太多,编译生成的中间目标文件太多,而在链接时需要明显地指出中间目标文件名,这对于编译很不方便。所以,我们要给中间目标文件打个包,在Windows下这种包叫“库文件”(Library File),也就是 ``.lib`` 文件,在UNIX下,是Archive File,也就是 ``.a`` 文件。 +链接时,主要是链接函数和全局变量。所以,我们可以使用这些中间目标文件( ``.o`` 文件或 +``.obj`` 文件)来链接我们的应用程序。链接器并不管函数所在的源文件,只管函数的中间目标文件 +(Object File),在大多数时候,由于源文件太多,编译生成的中间目标文件太多,而在链接时需要明显 +地指出中间目标文件名,这对于编译很不方便。所以,我们要给中间目标文件打个包,在Windows下这种包 +叫“库文件”(Library File),也就是 ``.lib`` 文件,在UNIX下,是Archive File,也就是 +``.a`` 文件。 -总结一下,源文件首先会生成中间目标文件,再由中间目标文件生成执行文件。在编译时,编译器只检测程序语法和函数、变量是否被声明。如果函数未被声明,编译器会给出一个警告,但可以生成Object File。而在链接程序时,链接器会在所有的Object File中找寻函数的实现,如果找不到,那到就会报链接错误码(Linker Error),在VC下,这种错误一般是: ``Link 2001错误`` ,意思说是说,链接器未能找到函数的实现。你需要指定函数的Object File。 +总结一下,源文件首先会生成中间目标文件,再由中间目标文件生成执行文件。在编译时,编译器只检测程 +序语法和函数、变量是否被声明。如果函数未被声明,编译器会给出一个警告,但可以生成Object File。 +而在链接程序时,链接器会在所有的Object File中找寻函数的实现,如果找不到,那到就会报链接错误码 +(Linker Error),在VC下,这种错误一般是: ``Link 2001错误`` ,意思说是说,链接器未能找到 +函数的实现。你需要指定函数的Object File。 好,言归正传,gnu的make有许多的内容,闲言少叙。 diff --git a/source/postscript.rst b/source/postscript.rst index 7ea2e38..f153725 100644 --- a/source/postscript.rst +++ b/source/postscript.rst @@ -1,34 +1,76 @@ 后序 ==== -终于到写结束语的时候了,以上基本上就是GNU make的Makefile的所有细节了。其它的厂商的make基本上也就是这样的,无论什么样的make,都是以文件的依赖性为基础的,其基本是都是遵循一个标准的。这篇文档中80%的技术细节都适用于任何的make,我猜测“函数”那一章的内容可能不是其它make所支持的,而隐含规则方面,我想不同的make会有不同的实现,我没有精力来查看GNU的make和VC的nmake、BCB的 make,或是别的UNIX下的make有些什么样的差别,一是时间精力不够,二是因为我基本上都是在Unix下使用make,以前在SCO Unix和 IBM的AIX,现在在Linux、Solaris、HP-UX、AIX和Alpha下使用,Linux和Solaris下更多一点。不过,我可以肯定的是,在Unix下的make,无论是哪种平台,几乎都使用了Richard Stallman开发的make和cc/gcc的编译器,而且,基本上都是 GNU的make(公司里所有的UNIX机器上都被装上了GNU的东西,所以,使用GNU的程序也就多了一些)。GNU的东西还是很不错的,特别是使用得深了以后,越来越觉得GNU的软件的强大,也越来越觉得GNU的在操作系统中(主要是Unix,甚至Windows)“杀伤力”。 +终于到写结束语的时候了,以上基本上就是GNU make的Makefile的所有细节了。其它的厂商的make基本 +上也就是这样的,无论什么样的make,都是以文件的依赖性为基础的,其基本是都是遵循一个标准的。这篇 +文档中80%的技术细节都适用于任何的make,我猜测“函数”那一章的内容可能不是其它make所支持的,而隐 +含规则方面,我想不同的make会有不同的实现,我没有精力来查看GNU的make和VC的nmake、BCB的 make +,或是别的UNIX下的make有些什么样的差别,一是时间精力不够,二是因为我基本上都是在Unix下使 +用make,以前在SCO Unix和 IBM的AIX,现在在Linux、Solaris、HP-UX、AIX和Alpha下使用 +,Linux和Solaris下更多一点。不过,我可以肯定的是,在Unix下的make,无论是哪种平台,几乎都使 +用了Richard Stallman开发的make和cc/gcc的编译器,而且,基本上都是 GNU的make(公司里所有 +的UNIX机器上都被装上了GNU的东西,所以,使用GNU的程序也就多了一些)。GNU的东西还是很不错的,特 +别是使用得深了以后,越来越觉得GNU的软件的强大,也越来越觉得GNU的在操作系统中(主要是Unix,甚 +至Windows)“杀伤力”。 -对于上述所有的make的细节,我们不但可以利用make这个工具来编译我们的程序,还可以利用make来完成其它的工作,因为规则中的命令可以是任何Shell之下的命令,所以,在Unix下,你不一定只是使用程序语言的编译器,你还可以在Makefile中书写其它的命令,如:tar、 awk、mail、sed、cvs、compress、ls、rm、yacc、rpm、ftp等等,等等,来完成诸如“程序打包”、“程序备份”、“制作程序安装包”、“提交代码”、“使用程序模板”、“合并文件”等等五花八门的功能,文件操作,文件管理,编程开发设计,或是其它一些异想天开的东西。比如,以前在书写银行交易程序时,由于银行的交易程序基本一样,就见到有人书写了一些交易的通用程序模板,在该模板中把一些网络通讯、数据库操作的、业务操作共性的东西写在一个文件中,在这些文件中用些诸如“@@@N、###N”奇怪字串标注一些位置,然后书写交易时,只需按照一种特定的规则书写特定的处理,最后在make时,使用awk和sed,把模板中的“@@@N、###N”等字串替代成特定的程序,形成C文件,然后再编译。这个动作很像数据库的“扩展C”语言(即在C语言中用“EXEC SQL”的样子执行SQL语句,在用cc/gcc编译之前,需要使用“扩展C”的翻译程序,如cpre,把其翻译成标准C)。如果你在使用make时有一些更为绝妙的方法,请记得告诉我啊。 +对于上述所有的make的细节,我们不但可以利用make这个工具来编译我们的程序,还可以利用make来完成 +其它的工作,因为规则中的命令可以是任何Shell之下的命令,所以,在Unix下,你不一定只是使用程序语 +言的编译器,你还可以在Makefile中书写其它的命令,如:tar、 awk、mail、sed、cvs、compress +、ls、rm、yacc、rpm、ftp等等,等等,来完成诸如“程序打包”、“程序备份”、“制作程序安装包”、“提 +交代码”、“使用程序模板”、“合并文件”等等五花八门的功能,文件操作,文件管理,编程开发设计,或是 +其它一些异想天开的东西。比如,以前在书写银行交易程序时,由于银行的交易程序基本一样,就见到有人 +书写了一些交易的通用程序模板,在该模板中把一些网络通讯、数据库操作的、业务操作共性的东西写在一 +个文件中,在这些文件中用些诸如“@@@N、###N”奇怪字串标注一些位置,然后书写交易时,只需按照一种 +特定的规则书写特定的处理,最后在make时,使用awk和sed,把模板中的“@@@N、###N”等字串替代成特 +定的程序,形成C文件,然后再编译。这个动作很像数据库的“扩展C”语言(即在C语言中用“EXEC SQL”的 +样子执行SQL语句,在用cc/gcc编译之前,需要使用“扩展C”的翻译程序,如cpre,把其翻译成标准C)。 +如果你在使用make时有一些更为绝妙的方法,请记得告诉我啊。 -回头看看整篇文档,不觉记起几年前刚刚开始在Unix下做开发的时候,有人问我会不会写Makefile时,我两眼发直,根本不知道在说什么。一开始看到别人在vi中写完程序后输入“!make”时,还以为是vi的功能,后来才知道有一个Makefile在作怪,于是上网查啊查,那时又不愿意看英文,发现就根本没有中文的文档介绍Makefile,只得看别人写的Makefile,自己瞎碰瞎搞才积累了一点知识,但在很多地方完全是知其然不知所以然。后来开始从事UNIX下产品软件的开发,看到一个400人,近200万行代码的大工程,发现要编译这样一个庞然大物,如果没有Makefile,那会是多么恐怖的一样事啊。于是横下心来,狠命地读了一堆英文文档,才觉得对其掌握了。但发现目前网上对Makefile介绍的文章还是少得那么的可怜,所以想写这样一篇文章,共享给大家,希望能对各位有所帮助。 +回头看看整篇文档,不觉记起几年前刚刚开始在Unix下做开发的时候,有人问我会不会写Makefile时,我 +两眼发直,根本不知道在说什么。一开始看到别人在vi中写完程序后输入“!make”时,还以为是vi的功能, +后来才知道有一个Makefile在作怪,于是上网查啊查,那时又不愿意看英文,发现就根本没有中文的文档介 +绍Makefile,只得看别人写的Makefile,自己瞎碰瞎搞才积累了一点知识,但在很多地方完全是知其然不 +知所以然。后来开始从事UNIX下产品软件的开发,看到一个400人,近200万行代码的大工程,发现要编译 +这样一个庞然大物,如果没有Makefile,那会是多么恐怖的一样事啊。于是横下心来,狠命地读了一堆英文 +文档,才觉得对其掌握了。但发现目前网上对Makefile介绍的文章还是少得那么的可怜,所以想写这样一篇 +文章,共享给大家,希望能对各位有所帮助。 -现在我终于写完了,看了看文件的创建时间,这篇技术文档也写了两个多月了。发现,自己知道是一回事,要写下来,跟别人讲述又是另外一回事,而且,现在越来越没有时间钻研技术细节,所以在写作时,发现在阐述一些细节问题时很难做到严谨和精炼,而且对先讲什么后讲什么不是很清楚,所以,还是参考了一些国外站点上的资料和提纲,以及一些技术书籍的语言风格,才得以完成。整篇文档的提纲是基于GNU的Makefile技术手册的提纲来书写的,并结合了自己的工作经验,以及自己的学习历程。因为从来没有写过这么长,这么细的文档,所以一定会有很多地方存在表达问题,语言歧义或是错误。因些,我迫切地等待各位给我指正和建议,以及任何的反馈。 +现在我终于写完了,看了看文件的创建时间,这篇技术文档也写了两个多月了。发现,自己知道是一回事, +要写下来,跟别人讲述又是另外一回事,而且,现在越来越没有时间钻研技术细节,所以在写作时,发现在 +阐述一些细节问题时很难做到严谨和精炼,而且对先讲什么后讲什么不是很清楚,所以,还是参考了一些国 +外站点上的资料和提纲,以及一些技术书籍的语言风格,才得以完成。整篇文档的提纲是基于GNU +的Makefile技术手册的提纲来书写的,并结合了自己的工作经验,以及自己的学习历程。因为从来没有写过 +这么长,这么细的文档,所以一定会有很多地方存在表达问题,语言歧义或是错误。因些,我迫切地等待各 +位给我指正和建议,以及任何的反馈。 -最后,还是利用这个后序,介绍一下自己。我目前从事于所有Unix平台下的软件研发,主要是做分布式计算/网格计算方面的系统产品软件,并且我对于下一代的计算机革命——网格计算非常地感兴趣,对于分布式计算、P2P、Web Service、J2EE技术方向也很感兴趣,同时,对于项目实施、团队管理、项目管理也小有心得,希望同样和我战斗在“技术和管理并重”的阵线上的年轻一代,能够和我多多地交流。我的MSN是: haoel@hotmail.com(常用),QQ是:753640(不常用)。(注:请勿给我MSN的邮箱发信,由于hotmail的垃圾邮件导致我拒收这个邮箱的所有来信) +最后,还是利用这个后序,介绍一下自己。我目前从事于所有Unix平台下的软件研发,主要是做分布式计 +算/网格计算方面的系统产品软件,并且我对于下一代的计算机革命——网格计算非常地感兴趣,对于分布式计 +算、P2P、Web Service、J2EE技术方向也很感兴趣,同时,对于项目实施、团队管理、项目管理也小有心 +得,希望同样和我战斗在“技术和管理并重”的阵线上的年轻一代,能够和我多多地交流。我的MSN是: +haoel@hotmail.com(常用),QQ是:753640(不常用)。(注:请勿给我MSN的邮箱发信,由 +于hotmail的垃圾邮件导致我拒收这个邮箱的所有来信) -我欢迎任何形式的交流,无论是讨论技术还是管理,或是其它海阔天空的东西。除了政治和娱乐新闻我不关心,其它只要积极向上的东西我都欢迎! +我欢迎任何形式的交流,无论是讨论技术还是管理,或是其它海阔天空的东西。除了政治和娱乐新闻我不关 +心,其它只要积极向上的东西我都欢迎! 最最后,我还想介绍一下make程序的设计开发者。 -首当其冲的是:Richard Stallman +首当其冲的是:Richard Stallman -开源软件的领袖和先驱,从来没有领过一天工资,从来没有使用过Windows操作系统。对于他的事迹和他的软件以及他的思想,我无需说过多的话,相信大家对这个人并不比我陌生,这是他的主页:http://www.stallman.org/ 。这里只贴上一张他的近照: +开源软件的领袖和先驱,从来没有领过一天工资,从来没有使用过Windows操作系统。对于他的事迹和他的 +软件以及他的思想,我无需说过多的话,相信大家对这个人并不比我陌生,这是他的主页 +:http://www.stallman.org/ 。这里只贴上一张他的近照: http://bbs.chinaunix.net/attachments/rms.jpg -第二位是:Roland McGrath +第二位是:Roland McGrath 个人主页是:http://www.frob.com/~roland/ ,下面是他的一些事迹: #. 合作编写了并维护GNU make。 #. 和Thomas Bushnell一同编写了GNU Hurd。 -#. 编写并维护着GNU C library。 -#. 合作编写并维护着部分的GNU Emacs。 +#. 编写并维护着GNU C library。 +#. 合作编写并维护着部分的GNU Emacs。 在此,向这两位开源项目的斗士致以最真切的敬意。 diff --git a/source/recipes.rst b/source/recipes.rst index 2453a68..aee2867 100644 --- a/source/recipes.rst +++ b/source/recipes.rst @@ -1,30 +1,41 @@ 书写命令 ======== -每条规则中的命令和操作系统Shell的命令行是一致的。make会一按顺序一条一条的执行命令,每条命令的开头必须以 ``Tab`` 键开头,除非,命令是紧跟在依赖规则后面的分号后的。在命令行之间中的空格或是空行会被忽略,但是如果该空格或空行是以Tab键开头的,那么make会认为其是一个空命令。 +每条规则中的命令和操作系统Shell的命令行是一致的。make会一按顺序一条一条的执行命令,每条命令的 +开头必须以 ``Tab`` 键开头,除非,命令是紧跟在依赖规则后面的分号后的。在命令行之间中的空格或是 +空行会被忽略,但是如果该空格或空行是以Tab键开头的,那么make会认为其是一个空命令。 -我们在UNIX下可能会使用不同的Shell,但是make的命令默认是被 ``/bin/sh`` ——UNIX的标准Shell解释执行的。除非你特别指定一个其它的Shell。Makefile中, ``#`` 是注释符,很像C/C++中的 ``//`` ,其后的本行字符都被注释。 +我们在UNIX下可能会使用不同的Shell,但是make的命令默认是被 ``/bin/sh`` ——UNIX的标准Shell +解释执行的。除非你特别指定一个其它的Shell。Makefile中, ``#`` 是注释符,很像C/C++中的 +``//`` ,其后的本行字符都被注释。 显示命令 -------- -通常,make会把其要执行的命令行在命令执行前输出到屏幕上。当我们用 ``@`` 字符在命令行前,那么,这个命令将不被make显示出来,最具代表性的例子是,我们用这个功能来向屏幕显示一些信息。如:: +通常,make会把其要执行的命令行在命令执行前输出到屏幕上。当我们用 ``@`` 字符在命令行前,那么, +这个命令将不被make显示出来,最具代表性的例子是,我们用这个功能来向屏幕显示一些信息。如:: @echo 正在编译XXX模块...... -当make执行时,会输出“正在编译XXX模块......”字串,但不会输出命令,如果没有“@”,那么,make将输出:: +当make执行时,会输出“正在编译XXX模块......”字串,但不会输出命令,如果没有“@”,那么,make将 +输出:: echo 正在编译XXX模块...... 正在编译XXX模块...... -如果make执行时,带入make参数 ``-n`` 或 ``--just-print`` ,那么其只是显示命令,但不会执行命令,这个功能很有利于我们调试我们的Makefile,看看我们书写的命令是执行起来是什么样子的或是什么顺序的。 +如果make执行时,带入make参数 ``-n`` 或 ``--just-print`` ,那么其只是显示命令,但不会执行 +命令,这个功能很有利于我们调试我们的Makefile,看看我们书写的命令是执行起来是什么样子的或是什么 +顺序的。 而make参数 ``-s`` 或 ``--silent`` 或 ``--quiet`` 则是全面禁止命令的显示。 命令执行 -------- -当依赖目标新于目标时,也就是当规则的目标需要被更新时,make会一条一条的执行其后的命令。需要注意的是,如果你要让上一条命令的结果应用在下一条命令时,你应该使用分号分隔这两条命令。比如你的第一条命令是cd命令,你希望第二条命令得在cd之后的基础上运行,那么你就不能把这两条命令写在两行上,而应该把这两条命令写在一行上,用分号分隔。如: +当依赖目标新于目标时,也就是当规则的目标需要被更新时,make会一条一条的执行其后的命令。需要注意 +的是,如果你要让上一条命令的结果应用在下一条命令时,你应该使用分号分隔这两条命令。比如你的第一 +条命令是cd命令,你希望第二条命令得在cd之后的基础上运行,那么你就不能把这两条命令写在两行上,而 +应该把这两条命令写在一行上,用分号分隔。如: - 示例一: @@ -41,34 +52,52 @@ exec: cd /home/hchen; pwd -当我们执行 ``make exec`` 时,第一个例子中的cd没有作用,pwd会打印出当前的Makefile目录,而第二个例子中,cd就起作用了,pwd会打印出“/home/hchen”。 +当我们执行 ``make exec`` 时,第一个例子中的cd没有作用,pwd会打印出当前的Makefile目录,而第 +二个例子中,cd就起作用了,pwd会打印出“/home/hchen”。 -make一般是使用环境变量SHELL中所定义的系统Shell来执行命令,默认情况下使用UNIX的标准Shell——/bin/sh来执行命令。但在MS-DOS下有点特殊,因为MS-DOS下没有SHELL环境变量,当然你也可以指定。如果你指定了UNIX风格的目录形式,首先,make会在SHELL所指定的路径中找寻命令解释器,如果找不到,其会在当前盘符中的当前目录中寻找,如果再找不到,其会在PATH环境变量中所定义的所有路径中寻找。MS-DOS中,如果你定义的命令解释器没有找到,其会给你的命令解释器加上诸如 ``.exe`` 、 ``.com`` 、 ``.bat`` 、 ``.sh`` 等后缀。 +make一般是使用环境变量SHELL中所定义的系统Shell来执行命令,默认情况下使用UNIX的标 +准Shell——/bin/sh来执行命令。但在MS-DOS下有点特殊,因为MS-DOS下没有SHELL环境变量,当然你也 +可以指定。如果你指定了UNIX风格的目录形式,首先,make会在SHELL所指定的路径中找寻命令解释器,如 +果找不到,其会在当前盘符中的当前目录中寻找,如果再找不到,其会在PATH环境变量中所定义的所有路径 +中寻找。MS-DOS中,如果你定义的命令解释器没有找到,其会给你的命令解释器加上诸如 ``.exe`` 、 +``.com`` 、 ``.bat`` 、 ``.sh`` 等后缀。 命令出错 -------- -每当命令运行完后,make会检测每个命令的返回码,如果命令返回成功,那么make会执行下一条命令,当规则中所有的命令成功返回后,这个规则就算是成功完成了。如果一个规则中的某个命令出错了(命令退出码非零),那么make就会终止执行当前规则,这将有可能终止所有规则的执行。 +每当命令运行完后,make会检测每个命令的返回码,如果命令返回成功,那么make会执行下一条命令,当规 +则中所有的命令成功返回后,这个规则就算是成功完成了。如果一个规则中的某个命令出错了(命令退出码 +非零),那么make就会终止执行当前规则,这将有可能终止所有规则的执行。 -有些时候,命令的出错并不表示就是错误的。例如mkdir命令,我们一定需要建立一个目录,如果目录不存在,那么mkdir就成功执行,万事大吉,如果目录存在,那么就出错了。我们之所以使用mkdir的意思就是一定要有这样的一个目录,于是我们就不希望mkdir出错而终止规则的运行。 +有些时候,命令的出错并不表示就是错误的。例如mkdir命令,我们一定需要建立一个目录,如果目录不存 +在,那么mkdir就成功执行,万事大吉,如果目录存在,那么就出错了。我们之所以使用mkdir的意思就是 +一定要有这样的一个目录,于是我们就不希望mkdir出错而终止规则的运行。 -为了做到这一点,忽略命令的出错,我们可以在Makefile的命令行前加一个减号 ``-`` (在Tab键之后),标记为不管命令出不出错都认为是成功的。如: +为了做到这一点,忽略命令的出错,我们可以在Makefile的命令行前加一个减号 ``-`` (在Tab键之后) +,标记为不管命令出不出错都认为是成功的。如: .. code-block:: bash clean: -rm -f *.o -还有一个全局的办法是,给make加上 ``-i`` 或是 ``--ignore-errors`` 参数,那么,Makefile中所有命令都会忽略错误。而如果一个规则是以 ``.IGNORE`` 作为目标的,那么这个规则中的所有命令将会忽略错误。这些是不同级别的防止命令出错的方法,你可以根据你的不同喜欢设置。 +还有一个全局的办法是,给make加上 ``-i`` 或是 ``--ignore-errors`` 参数,那么,Makefile中 +所有命令都会忽略错误。而如果一个规则是以 ``.IGNORE`` 作为目标的,那么这个规则中的所有命令将会 +忽略错误。这些是不同级别的防止命令出错的方法,你可以根据你的不同喜欢设置。 -还有一个要提一下的make的参数的是 ``-k`` 或是 ``--keep-going`` ,这个参数的意思是,如果某规则中的命令出错了,那么就终止该规则的执行,但继续执行其它规则。 +还有一个要提一下的make的参数的是 ``-k`` 或是 ``--keep-going`` ,这个参数的意思是,如果某 +规则中的命令出错了,那么就终止该规则的执行,但继续执行其它规则。 嵌套执行make ------------ -在一些大的工程中,我们会把我们不同模块或是不同功能的源文件放在不同的目录中,我们可以在每个目录中都书写一个该目录的Makefile,这有利于让我们的Makefile变得更加地简洁,而不至于把所有的东西全部写在一个Makefile中,这样会很难维护我们的Makefile,这个技术对于我们模块编译和分段编译有着非常大的好处。 +在一些大的工程中,我们会把我们不同模块或是不同功能的源文件放在不同的目录中,我们可以在每个目录 +中都书写一个该目录的Makefile,这有利于让我们的Makefile变得更加地简洁,而不至于把所有的东西全 +部写在一个Makefile中,这样会很难维护我们的Makefile,这个技术对于我们模块编译和分段编译有着非 +常大的好处。 -例如,我们有一个子目录叫subdir,这个目录下有个Makefile文件,来指明了这个目录下文件的编译规则。那么我们总控的Makefile可以这样书写: +例如,我们有一个子目录叫subdir,这个目录下有个Makefile文件,来指明了这个目录下文件的编译规则 +。那么我们总控的Makefile可以这样书写: .. code-block:: makefile @@ -82,9 +111,11 @@ make一般是使用环境变量SHELL中所定义的系统Shell来执行命令, subsystem: $(MAKE) -C subdir -定义$(MAKE)宏变量的意思是,也许我们的make需要一些参数,所以定义成一个变量比较利于维护。这两个例子的意思都是先进入“subdir”目录,然后执行make命令。 +定义$(MAKE)宏变量的意思是,也许我们的make需要一些参数,所以定义成一个变量比较利于维护。这两个 +例子的意思都是先进入“subdir”目录,然后执行make命令。 -我们把这个Makefile叫做“总控Makefile”,总控Makefile的变量可以传递到下级的Makefile中(如果你显示的声明),但是不会覆盖下层的Makefile中所定义的变量,除非指定了 ``-e`` 参数。 +我们把这个Makefile叫做“总控Makefile”,总控Makefile的变量可以传递到下级的Makefile中(如果 +你显示的声明),但是不会覆盖下层的Makefile中所定义的变量,除非指定了 ``-e`` 参数。 如果你要传递变量到下级Makefile中,那么你可以使用这样的声明:: @@ -137,18 +168,26 @@ make一般是使用环境变量SHELL中所定义的系统Shell来执行命令, 如果你要传递所有的变量,那么,只要一个export就行了。后面什么也不用跟,表示传递所有的变量。 -需要注意的是,有两个变量,一个是 ``SHELL`` ,一个是 ``MAKEFLAGS`` ,这两个变量不管你是否export,其总是要传递到下层 Makefile中,特别是 ``MAKEFLAGS`` 变量,其中包含了make的参数信息,如果我们执行“总控Makefile”时有make参数或是在上层 Makefile中定义了这个变量,那么 ``MAKEFLAGS`` 变量将会是这些参数,并会传递到下层Makefile中,这是一个系统级的环境变量。 +需要注意的是,有两个变量,一个是 ``SHELL`` ,一个是 ``MAKEFLAGS`` ,这两个变量不管你是 +否export,其总是要传递到下层 Makefile中,特别是 ``MAKEFLAGS`` 变量,其中包含了make的参数 +信息,如果我们执行“总控Makefile”时有make参数或是在上层 Makefile中定义了这个变量,那么 +``MAKEFLAGS`` 变量将会是这些参数,并会传递到下层Makefile中,这是一个系统级的环境变量。 -但是make命令中的有几个参数并不往下传递,它们是 ``-C`` , ``-f`` , ``-h``, ``-o`` 和 ``-W`` (有关Makefile参数的细节将在后面说明),如果你不想往下层传递参数,那么,你可以这样来: +但是make命令中的有几个参数并不往下传递,它们是 ``-C`` , ``-f`` , ``-h``, ``-o`` 和 +``-W`` (有关Makefile参数的细节将在后面说明),如果你不想往下层传递参数,那么,你可以这样来: .. code-block:: makefile subsystem: cd subdir && $(MAKE) MAKEFLAGS= -如果你定义了环境变量 ``MAKEFLAGS`` ,那么你得确信其中的选项是大家都会用到的,如果其中有 ``-t`` , ``-n`` 和 ``-q`` 参数,那么将会有让你意想不到的结果,或许会让你异常地恐慌。 +如果你定义了环境变量 ``MAKEFLAGS`` ,那么你得确信其中的选项是大家都会用到的,如果其中有 +``-t`` , ``-n`` 和 ``-q`` 参数,那么将会有让你意想不到的结果,或许会让你异常地恐慌。 -还有一个在“嵌套执行”中比较有用的参数, ``-w`` 或是 ``--print-directory`` 会在make的过程中输出一些信息,让你看到目前的工作目录。比如,如果我们的下级make目录是“/home/hchen/gnu/make”,如果我们使用 ``make -w`` 来执行,那么当进入该目录时,我们会看到:: +还有一个在“嵌套执行”中比较有用的参数, ``-w`` 或是 ``--print-directory`` 会在make的过程 +中输出一些信息,让你看到目前的工作目录。比如,如果我们的下级make目录 +是“/home/hchen/gnu/make”,如果我们使用 ``make -w`` 来执行,那么当进入该目录时,我们会看 +到:: make: Entering directory `/home/hchen/gnu/make'. @@ -156,23 +195,30 @@ make一般是使用环境变量SHELL中所定义的系统Shell来执行命令, make: Leaving directory `/home/hchen/gnu/make' -当你使用 ``-C`` 参数来指定make下层Makefile时, ``-w`` 会被自动打开的。如果参数中有 ``-s`` ( ``--slient`` )或是 ``--no-print-directory`` ,那么, ``-w`` 总是失效的。 +当你使用 ``-C`` 参数来指定make下层Makefile时, ``-w`` 会被自动打开的。如果参数中有 +``-s`` ( ``--slient`` )或是 ``--no-print-directory`` ,那么, ``-w`` 总是失效的。 定义命令包 ---------- -如果Makefile中出现一些相同命令序列,那么我们可以为这些相同的命令序列定义一个变量。定义这种命令序列的语法以 ``define`` 开始,以 ``endef`` 结束,如:: +如果Makefile中出现一些相同命令序列,那么我们可以为这些相同的命令序列定义一个变量。定义这种命令 +序列的语法以 ``define`` 开始,以 ``endef`` 结束,如:: define run-yacc yacc $(firstword $^) mv y.tab.c $@ endef -这里,“run-yacc”是这个命令包的名字,其不要和Makefile中的变量重名。在 ``define`` 和 ``endef`` 中的两行就是命令序列。这个命令包中的第一个命令是运行Yacc程序,因为Yacc程序总是生成“y.tab.c”的文件,所以第二行的命令就是把这个文件改改名字。还是把这个命令包放到一个示例中来看看吧。 +这里,“run-yacc”是这个命令包的名字,其不要和Makefile中的变量重名。在 ``define`` 和 +``endef`` 中的两行就是命令序列。这个命令包中的第一个命令是运行Yacc程序,因为Yacc程序总是生 +成“y.tab.c”的文件,所以第二行的命令就是把这个文件改改名字。还是把这个命令包放到一个示例中来看 +看吧。 .. code-block:: makefile foo.c : foo.y $(run-yacc) -我们可以看见,要使用这个命令包,我们就好像使用变量一样。在这个命令包的使用中,命令包“run-yacc”中的 ``$^`` 就是 ``foo.y`` , ``$@`` 就是 ``foo.c`` (有关这种以 ``$`` 开头的特殊变量,我们会在后面介绍),make在执行命令包时,命令包中的每个命令会被依次独立执行。 +我们可以看见,要使用这个命令包,我们就好像使用变量一样。在这个命令包的使用中,命令 +包“run-yacc”中的 ``$^`` 就是 ``foo.y`` , ``$@`` 就是 ``foo.c`` (有关这种以 ``$`` +开头的特殊变量,我们会在后面介绍),make在执行命令包时,命令包中的每个命令会被依次独立执行。 diff --git a/source/rules.rst b/source/rules.rst index b143547..c040e10 100644 --- a/source/rules.rst +++ b/source/rules.rst @@ -3,7 +3,10 @@ 规则包含两个部分,一个是依赖关系,一个是生成目标的方法。 -在Makefile中,规则的顺序是很重要的,因为,Makefile中只应该有一个最终目标,其它的目标都是被这个目标所连带出来的,所以一定要让make知道你的最终目标是什么。一般来说,定义在Makefile中的目标可能会有很多,但是第一条规则中的目标将被确立为最终的目标。如果第一条规则中的目标有很多个,那么,第一个目标会成为最终的目标。make所完成的也就是这个目标。 +在Makefile中,规则的顺序是很重要的,因为,Makefile中只应该有一个最终目标,其它的目标都是被这 +个目标所连带出来的,所以一定要让make知道你的最终目标是什么。一般来说,定义在Makefile中的目标 +可能会有很多,但是第一条规则中的目标将被确立为最终的目标。如果第一条规则中的目标有很多个,那么 +,第一个目标会成为最终的目标。make所完成的也就是这个目标。 好了,还是让我们来看一看如何书写规则。 @@ -15,10 +18,15 @@ foo.o: foo.c defs.h # foo模块 cc -c -g foo.c -看到这个例子,各位应该不是很陌生了,前面也已说过, ``foo.o`` 是我们的目标, ``foo.c`` 和 ``defs.h`` 是目标所依赖的源文件,而只有一个命令 ``cc -c -g foo.c`` (以Tab键开头)。这个规则告诉我们两件事: +看到这个例子,各位应该不是很陌生了,前面也已说过, ``foo.o`` 是我们的目标, ``foo.c`` 和 +``defs.h`` 是目标所依赖的源文件,而只有一个命令 ``cc -c -g foo.c`` (以Tab键开头)。这个 +规则告诉我们两件事: -#. 文件的依赖关系, ``foo.o`` 依赖于 ``foo.c`` 和 ``defs.h`` 的文件,如果 ``foo.c`` 和 ``defs.h`` 的文件日期要比 ``foo.o`` 文件日期要新,或是 ``foo.o`` 不存在,那么依赖关系发生。 -#. 生成或更新 ``foo.o`` 文件,就是那个cc命令。它说明了如何生成 ``foo.o`` 这个文件。(当然,foo.c文件include了defs.h文件) +#. 文件的依赖关系, ``foo.o`` 依赖于 ``foo.c`` 和 ``defs.h`` 的文件,如果 ``foo.c`` + 和 ``defs.h`` 的文件日期要比 ``foo.o`` 文件日期要新,或是 ``foo.o`` 不存在,那么依赖 + 关系发生。 +#. 生成或更新 ``foo.o`` 文件,就是那个cc命令。它说明了如何生成 ``foo.o`` 这个文件。 + (当然,foo.c文件include了defs.h文件) 规则的语法 ---------- @@ -37,24 +45,34 @@ command ... -targets是文件名,以空格分开,可以使用通配符。一般来说,我们的目标基本上是一个文件,但也有可能是多个文件。 +targets是文件名,以空格分开,可以使用通配符。一般来说,我们的目标基本上是一个文件,但也有可能 +是多个文件。 -command是命令行,如果其不与“target:prerequisites”在一行,那么,必须以 ``Tab`` 键开头,如果和prerequisites在一行,那么可以用分号做为分隔。(见上) +command是命令行,如果其不与“target:prerequisites”在一行,那么,必须以 ``Tab`` 键开头,如 +果和prerequisites在一行,那么可以用分号做为分隔。(见上) -prerequisites也就是目标所依赖的文件(或依赖目标)。如果其中的某个文件要比目标文件要新,那么,目标就被认为是“过时的”,被认为是需要重生成的。这个在前面已经讲过了。 +prerequisites也就是目标所依赖的文件(或依赖目标)。如果其中的某个文件要比目标文件要新,那么, +目标就被认为是“过时的”,被认为是需要重生成的。这个在前面已经讲过了。 -如果命令太长,你可以使用反斜杠( ``\`` )作为换行符。make对一行上有多少个字符没有限制。规则告诉make两件事,文件的依赖关系和如何生成目标文件。 +如果命令太长,你可以使用反斜杠( ``\`` )作为换行符。make对一行上有多少个字符没有限制。规则告 +诉make两件事,文件的依赖关系和如何生成目标文件。 一般来说,make会以UNIX的标准Shell,也就是 ``/bin/sh`` 来执行命令。 在规则中使用通配符 ------------------ -如果我们想定义一系列比较类似的文件,我们很自然地就想起使用通配符。make支持三个通配符: ``*`` , ``?`` 和 ``~`` 。这是和Unix的B-Shell是相同的。 +如果我们想定义一系列比较类似的文件,我们很自然地就想起使用通配符。make支持三个通配符: +``*`` , ``?`` 和 ``~`` 。这是和Unix的B-Shell是相同的。 -波浪号( ``~`` )字符在文件名中也有比较特殊的用途。如果是 ``~/test`` ,这就表示当前用户的$HOME目录下的test目录。而 ``~hchen/test`` 则表示用户hchen的宿主目录下的test目录。(这些都是Unix下的小知识了,make也支持)而在Windows或是 MS-DOS下,用户没有宿主目录,那么波浪号所指的目录则根据环境变量“HOME”而定。 +波浪号( ``~`` )字符在文件名中也有比较特殊的用途。如果是 ``~/test`` ,这就表示当前用户 +的 ``$HOME`` 目录下的test目录。而 ``~hchen/test`` 则表示用户hchen的宿主目录下的test +目录。(这些都是Unix下的小知识了,make也支持)而在Windows或是 MS-DOS下,用户没有宿主目录, +那么波浪号所指的目录则根据环境变量“HOME”而定。 -通配符代替了你一系列的文件,如 ``*.c`` 表示所有后缀为c的文件。一个需要我们注意的是,如果我们的文件名中有通配符,如: ``*`` ,那么可以用转义字符 ``\`` ,如 ``\*`` 来表示真实的 ``*`` 字符,而不是任意长度的字符串。 +通配符代替了你一系列的文件,如 ``*.c`` 表示所有后缀为c的文件。一个需要我们注意的是,如果我们 +的文件名中有通配符,如: ``*`` ,那么可以用转义字符 ``\`` ,如 ``\*`` 来表示真实的 ``*`` +字符,而不是任意长度的字符串。 好吧,还是先来看几个例子吧: @@ -63,7 +81,8 @@ prerequisites也就是目标所依赖的文件(或依赖目标)。如果其 clean: rm -f *.o -其实在这个clean:后面可以加上你想做的一些事情,如果你想看到在编译完后看看main.c的源代码,你可以在加上cat这个命令,例子如下: +其实在这个clean:后面可以加上你想做的一些事情,如果你想看到在编译完后看看main.c的源代码,你 +可以在加上cat这个命令,例子如下: .. code-block:: makefile @@ -71,7 +90,8 @@ prerequisites也就是目标所依赖的文件(或依赖目标)。如果其 cat main.c rm -f *.o -其结果你试一下就知道的。 上面这个例子我不不多说了,这是操作系统Shell所支持的通配符。这是在命令中的通配符。 +其结果你试一下就知道的。 上面这个例子我不不多说了,这是操作系统Shell所支持的通配符。这是在命令 +中的通配符。 .. code-block:: makefile @@ -79,13 +99,16 @@ prerequisites也就是目标所依赖的文件(或依赖目标)。如果其 lpr -p $? touch print -上面这个例子说明了通配符也可以在我们的规则中,目标print依赖于所有的 ``.c`` 文件。其中的 ``$?`` 是一个自动化变量,我会在后面给你讲述。 +上面这个例子说明了通配符也可以在我们的规则中,目标print依赖于所有的 ``.c`` 文件。其中的 +``$?`` 是一个自动化变量,我会在后面给你讲述。 .. code-block:: makefile objects = *.o -上面这个例子,表示了通配符同样可以用在变量中。并不是说 ``*.o`` 会展开,不!objects的值就是 ``*.o`` 。Makefile中的变量其实就是C/C++中的宏。如果你要让通配符在变量中展开,也就是让objects的值是所有 ``.o`` 的文件名的集合,那么,你可以这样: +上面这个例子,表示了通配符同样可以用在变量中。并不是说 ``*.o`` 会展开,不!objects的值就是 +``*.o`` 。Makefile中的变量其实就是C/C++中的宏。如果你要让通配符在变量中展开,也就是 +让objects的值是所有 ``.o`` 的文件名的集合,那么,你可以这样: .. code-block:: makefile @@ -116,17 +139,24 @@ prerequisites也就是目标所依赖的文件(或依赖目标)。如果其 文件搜寻 -------- -在一些大的工程中,有大量的源文件,我们通常的做法是把这许多的源文件分类,并存放在不同的目录中。所以,当make需要去找寻文件的依赖关系时,你可以在文件前加上路径,但最好的方法是把一个路径告诉make,让make在自动去找。 +在一些大的工程中,有大量的源文件,我们通常的做法是把这许多的源文件分类,并存放在不同的目录中。 +所以,当make需要去找寻文件的依赖关系时,你可以在文件前加上路径,但最好的方法是把一个路径告 +诉make,让make在自动去找。 -Makefile文件中的特殊变量 ``VPATH`` 就是完成这个功能的,如果没有指明这个变量,make只会在当前的目录中去找寻依赖文件和目标文件。如果定义了这个变量,那么,make就会在当当前目录找不到的情况下,到所指定的目录中去找寻文件了。 +Makefile文件中的特殊变量 ``VPATH`` 就是完成这个功能的,如果没有指明这个变量,make只会在当前 +的目录中去找寻依赖文件和目标文件。如果定义了这个变量,那么,make就会在当当前目录找不到的情况下 +,到所指定的目录中去找寻文件了。 .. code-block:: makefile VPATH = src:../headers -上面的的定义指定两个目录,“src”和“../headers”,make会按照这个顺序进行搜索。目录由“冒号”分隔。(当然,当前目录永远是最高优先搜索的地方) +上面的的定义指定两个目录,“src”和“../headers”,make会按照这个顺序进行搜索。目录由“冒号”分隔 +。(当然,当前目录永远是最高优先搜索的地方) -另一个设置文件搜索路径的方法是使用make的“vpath”关键字(注意,它是全小写的),这不是变量,这是一个make的关键字,这和上面提到的那个VPATH变量很类似,但是它更为灵活。它可以指定不同的文件在不同的搜索目录中。这是一个很灵活的功能。它的使用方法有三种: +另一个设置文件搜索路径的方法是使用make的“vpath”关键字(注意,它是全小写的),这不是变量,这是 +一个make的关键字,这和上面提到的那个VPATH变量很类似,但是它更为灵活。它可以指定不同的文件在不 +同的搜索目录中。这是一个很灵活的功能。它的使用方法有三种: ``vpath `` 为符合模式的文件指定搜索目录。 @@ -137,15 +167,19 @@ Makefile文件中的特殊变量 ``VPATH`` 就是完成这个功能的,如果 ``vpath`` 清除所有已被设置好了的文件搜索目录。 -vapth使用方法中的需要包含 ``%`` 字符。 ``%`` 的意思是匹配零或若干字符,(需引用 ``%`` ,使用 ``\`` )例如, ``%.h`` 表示所有以 ``.h`` 结尾的文件。指定了要搜索的文件集,而则指定了< pattern>的文件集的搜索的目录。例如: +vapth使用方法中的需要包含 ``%`` 字符。 ``%`` 的意思是匹配零或若干字符,(需引用 +``%`` ,使用 ``\`` )例如, ``%.h`` 表示所有以 ``.h`` 结尾的文件。指定了要搜索 +的文件集,而则指定了< pattern>的文件集的搜索的目录。例如: .. code-block:: makefile vpath %.h ../headers -该语句表示,要求make在“../headers”目录下搜索所有以 ``.h`` 结尾的文件。(如果某文件在当前目录没有找到的话) +该语句表示,要求make在“../headers”目录下搜索所有以 ``.h`` 结尾的文件。(如果某文件在当前目 +录没有找到的话) -我们可以连续地使用vpath语句,以指定不同搜索策略。如果连续的vpath语句中出现了相同的,或是被重复了的,那么,make会按照vpath语句的先后顺序来执行搜索。如: +我们可以连续地使用vpath语句,以指定不同搜索策略。如果连续的vpath语句中出现了相同的 +,或是被重复了的,那么,make会按照vpath语句的先后顺序来执行搜索。如: .. code-block:: makefile @@ -172,17 +206,22 @@ vapth使用方法中的需要包含 ``%`` 字符。 ``%`` 的意思是 clean: rm *.o temp -正像我们前面例子中的“clean”一样,既然我们生成了许多文件编译文件,我们也应该提供一个清除它们的“目标”以备完整地重编译而用。 (以“make clean”来使用该目标) +正像我们前面例子中的“clean”一样,既然我们生成了许多文件编译文件,我们也应该提供一个清除它们的“ +目标”以备完整地重编译而用。 (以“make clean”来使用该目标) -因为,我们并不生成“clean”这个文件。“伪目标”并不是一个文件,只是一个标签,由于“伪目标”不是文件,所以make无法生成它的依赖关系和决定它是否要执行。我们只有通过显式地指明这个“目标”才能让其生效。当然,“伪目标”的取名不能和文件名重名,不然其就失去了“伪目标”的意义了。 +因为,我们并不生成“clean”这个文件。“伪目标”并不是一个文件,只是一个标签,由于“伪目标”不是 +文件,所以make无法生成它的依赖关系和决定它是否要执行。我们只有通过显式地指明这个“目标”才能让其 +生效。当然,“伪目标”的取名不能和文件名重名,不然其就失去了“伪目标”的意义了。 -当然,为了避免和文件重名的这种情况,我们可以使用一个特殊的标记“.PHONY”来显式地指明一个目标是“伪目标”,向make说明,不管是否有这个文件,这个目标就是“伪目标”。 +当然,为了避免和文件重名的这种情况,我们可以使用一个特殊的标记“.PHONY”来显式地指明一个目标是“ +伪目标”,向make说明,不管是否有这个文件,这个目标就是“伪目标”。 .. code-block:: makefile .PHONY : clean -只要有这个声明,不管是否有“clean”文件,要运行“clean”这个目标,只有“make clean”这样。于是整个过程可以这样写: +只要有这个声明,不管是否有“clean”文件,要运行“clean”这个目标,只有“make clean”这样。于是整 +个过程可以这样写: .. code-block:: makefile @@ -190,7 +229,10 @@ vapth使用方法中的需要包含 ``%`` 字符。 ``%`` 的意思是 clean : rm *.o temp -伪目标一般没有依赖的文件。但是,我们也可以为伪目标指定所依赖的文件。伪目标同样可以作为“默认目标”,只要将其放在第一个。一个示例就是,如果你的Makefile需要一口气生成若干个可执行文件,但你只想简单地敲一个make完事,并且,所有的目标文件都写在一个Makefile中,那么你可以使用“伪目标”这个特性: +伪目标一般没有依赖的文件。但是,我们也可以为伪目标指定所依赖的文件。伪目标同样可以作为“默认目 +标”,只要将其放在第一个。一个示例就是,如果你的Makefile需要一口气生成若干个可执行文件,但你只 +想简单地敲一个make完事,并且,所有的目标文件都写在一个Makefile中,那么你可以使用“伪目标”这个 +特性: .. code-block:: makefile @@ -206,9 +248,15 @@ vapth使用方法中的需要包含 ``%`` 字符。 ``%`` 的意思是 prog3 : prog3.o sort.o utils.o cc -o prog3 prog3.o sort.o utils.o -我们知道,Makefile中的第一个目标会被作为其默认目标。我们声明了一个“all”的伪目标,其依赖于其它三个目标。由于默认目标的特性是,总是被执行的,但由于“all”又是一个伪目标,伪目标只是一个标签不会生成文件,所以不会有“all”文件产生。于是,其它三个目标的规则总是会被决议。也就达到了我们一口气生成多个目标的目的。 ``.PHONY : all`` 声明了“all”这个目标为“伪目标”。(注:这里的显式 “.PHONY : all” 不写的话一般情况也可以正确的执行,这样make可通过隐式规则推导出, “all” 是一个伪目标,执行make不会生成“all”文件,而执行后面的多个目标。建议:显式写出是一个好习惯。) +我们知道,Makefile中的第一个目标会被作为其默认目标。我们声明了一个“all”的伪目标,其依赖于其它 +三个目标。由于默认目标的特性是,总是被执行的,但由于“all”又是一个伪目标,伪目标只是一个标签不 +会生成文件,所以不会有“all”文件产生。于是,其它三个目标的规则总是会被决议。也就达到了我们一口 +气生成多个目标的目的。 ``.PHONY : all`` 声明了“all”这个目标为“伪目标”。(注:这里的显式 +“.PHONY : all” 不写的话一般情况也可以正确的执行,这样make可通过隐式规则推导出, “all” 是一 +个伪目标,执行make不会生成“all”文件,而执行后面的多个目标。建议:显式写出是一个好习惯。) -随便提一句,从上面的例子我们可以看出,目标也可以成为依赖。所以,伪目标同样也可成为依赖。看下面的例子: +随便提一句,从上面的例子我们可以看出,目标也可以成为依赖。所以,伪目标同样也可成为依赖。看下面 +的例子: .. code-block:: makefile @@ -223,12 +271,17 @@ vapth使用方法中的需要包含 ``%`` 字符。 ``%`` 的意思是 cleandiff : rm *.diff -“make cleanall”将清除所有要被清除的文件。“cleanobj”和“cleandiff”这两个伪目标有点像“子程序”的意思。我们可以输入“make cleanall”和“make cleanobj”和“make cleandiff”命令来达到清除不同种类文件的目的。 +“make cleanall”将清除所有要被清除的文件。“cleanobj”和“cleandiff”这两个伪目标有点像“子程 +序”的意思。我们可以输入“make cleanall”和“make cleanobj”和“make cleandiff”命令来达到清 +除不同种类文件的目的。 多目标 ------ -Makefile的规则中的目标可以不止一个,其支持多目标,有可能我们的多个目标同时依赖于一个文件,并且其生成的命令大体类似。于是我们就能把其合并起来。当然,多个目标的生成规则的执行命令不是同一个,这可能会给我们带来麻烦,不过好在我们可以使用一个自动化变量 ``$@`` (关于自动化变量,将在后面讲述),这个变量表示着目前规则中所有的目标的集合,这样说可能很抽象,还是看一个例子吧。 +Makefile的规则中的目标可以不止一个,其支持多目标,有可能我们的多个目标同时依赖于一个文件,并且 +其生成的命令大体类似。于是我们就能把其合并起来。当然,多个目标的生成规则的执行命令不是同一个, +这可能会给我们带来麻烦,不过好在我们可以使用一个自动化变量 ``$@`` (关于自动化变量,将在后面讲 +述),这个变量表示着目前规则中所有的目标的集合,这样说可能很抽象,还是看一个例子吧。 .. code-block:: makefile @@ -244,12 +297,15 @@ Makefile的规则中的目标可以不止一个,其支持多目标,有可能 littleoutput : text.g generate text.g -little > littleoutput -其中, ``-$(subst output,,$@)`` 中的 ``$`` 表示执行一个Makefile的函数,函数名为subst,后面的为参数。关于函数,将在后面讲述。这里的这个函数是替换字符串的意思, ``$@`` 表示目标的集合,就像一个数组, ``$@`` 依次取出目标,并执于命令。 +其中, ``-$(subst output,,$@)`` 中的 ``$`` 表示执行一个Makefile的函数,函数名为subst, +后面的为参数。关于函数,将在后面讲述。这里的这个函数是替换字符串的意思, ``$@`` 表示目标的 +集合,就像一个数组, ``$@`` 依次取出目标,并执于命令。 静态模式 -------- -静态模式可以更加容易地定义多目标的规则,可以让我们的规则变得更加的有弹性和灵活。我们还是先来看一下语法: +静态模式可以更加容易地定义多目标的规则,可以让我们的规则变得更加的有弹性和灵活。我们还是先来 +看一下语法: .. code-block:: makefile @@ -263,9 +319,14 @@ target-parrtern是指明了targets的模式,也就是的目标集模式。 prereq-parrterns是目标的依赖模式,它对target-parrtern形成的模式再进行一次依赖目标的定义。 -这样描述这三个东西,可能还是没有说清楚,还是举个例子来说明一下吧。如果我们的定义成 ``%.o`` ,意思是我们的;集合中都是以 ``.o`` 结尾的,而如果我们的定义成 ``%.c`` ,意思是对所形成的目标集进行二次定义,其计算方法是,取模式中的 ``%`` (也就是去掉了 ``.o`` 这个结尾),并为其加上 ``.c`` 这个结尾,形成的新集合。 +这样描述这三个东西,可能还是没有说清楚,还是举个例子来说明一下吧。如果我们 +的定义成 ``%.o`` ,意思是我们的;集合中都是以 ``.o`` 结尾的,而 +如果我们的定义成 ``%.c`` ,意思是对所形成的目标集进 +行二次定义,其计算方法是,取模式中的 ``%`` (也就是去掉了 ``.o`` 这个结 +尾),并为其加上 ``.c`` 这个结尾,形成的新集合。 -所以,我们的“目标模式”或是“依赖模式”中都应该有 ``%`` 这个字符,如果你的文件名中有 ``%`` 那么你可以使用反斜杠 ``\`` 进行转义,来标明真实的 ``%`` 字符。 +所以,我们的“目标模式”或是“依赖模式”中都应该有 ``%`` 这个字符,如果你的文件名中有 ``%`` 那么 +你可以使用反斜杠 ``\`` 进行转义,来标明真实的 ``%`` 字符。 看一个例子: @@ -278,7 +339,12 @@ prereq-parrterns是目标的依赖模式,它对target-parrtern形成的模式 $(objects): %.o: %.c $(CC) -c $(CFLAGS) $< -o $@ -上面的例子中,指明了我们的目标从$object中获取, ``%.o`` 表明要所有以 ``.o`` 结尾的目标,也就是 ``foo.o bar.o`` ,也就是变量 ``$object`` 集合的模式,而依赖模式 ``%.c`` 则取模式 ``%.o`` 的 ``%`` ,也就是 ``foo bar`` ,并为其加下 ``.c`` 的后缀,于是,我们的依赖目标就是 ``foo.c bar.c`` 。而命令中的 ``$<`` 和 ``$@`` 则是自动化变量, ``$<`` 表示所有的依赖目标集(也就是 ``foo.c bar.c`` ), ``$@`` 表示目标集(也就是“foo.o bar.o”)。于是,上面的规则展开后等价于下面的规则: +上面的例子中,指明了我们的目标从$object中获取, ``%.o`` 表明要所有以 ``.o`` 结尾的目标,也 +就是 ``foo.o bar.o`` ,也就是变量 ``$object`` 集合的模式,而依赖模式 ``%.c`` 则取模式 +``%.o`` 的 ``%`` ,也就是 ``foo bar`` ,并为其加下 ``.c`` 的后缀,于是,我们的依赖目标就 +是 ``foo.c bar.c`` 。而命令中的 ``$<`` 和 ``$@`` 则是自动化变量, ``$<`` 表示所有的依赖 +目标集(也就是 ``foo.c bar.c`` ), ``$@`` 表示目标集(也就是“foo.o bar.o”)。于是,上面 +的规则展开后等价于下面的规则: .. code-block:: makefile @@ -287,7 +353,9 @@ prereq-parrterns是目标的依赖模式,它对target-parrtern形成的模式 bar.o : bar.c $(CC) -c $(CFLAGS) bar.c -o bar.o -试想,如果我们的 ``%.o`` 有几百个,那么我们只要用这种很简单的“静态模式规则”就可以写完一堆规则,实在是太有效率了。“静态模式规则”的用法很灵活,如果用得好,那会是一个很强大的功能。再看一个例子: +试想,如果我们的 ``%.o`` 有几百个,那么我们只要用这种很简单的“静态模式规则”就可以写完一堆 +规则,实在是太有效率了。“静态模式规则”的用法很灵活,如果用得好,那会是一个很强大的功能。再看一 +个例子: .. code-block:: makefile @@ -298,18 +366,23 @@ prereq-parrterns是目标的依赖模式,它对target-parrtern形成的模式 $(filter %.elc,$(files)): %.elc: %.el emacs -f batch-byte-compile $< -$(filter %.o,$(files))表示调用Makefile的filter函数,过滤“$files”集,只要其中模式为“%.o”的内容。其它的内容,我就不用多说了吧。这个例子展示了Makefile中更大的弹性。 +$(filter %.o,$(files))表示调用Makefile的filter函数,过滤“$files”集,只要其中模式 +为“%.o”的内容。其它的内容,我就不用多说了吧。这个例子展示了Makefile中更大的弹性。 自动生成依赖性 -------------- -在Makefile中,我们的依赖关系可能会需要包含一系列的头文件,比如,如果我们的main.c中有一句 ``#include "defs.h"`` ,那么我们的依赖关系应该是: +在Makefile中,我们的依赖关系可能会需要包含一系列的头文件,比如,如果我们的main.c中有一句 +``#include "defs.h"`` ,那么我们的依赖关系应该是: .. code-block:: makefile main.o : main.c defs.h -但是,如果是一个比较大型的工程,你必需清楚哪些C文件包含了哪些头文件,并且,你在加入或删除头文件时,也需要小心地修改Makefile,这是一个很没有维护性的工作。为了避免这种繁重而又容易出错的事情,我们可以使用C/C++编译的一个功能。大多数的C/C++编译器都支持一个“-M”的选项,即自动找寻源文件中包含的头文件,并生成一个依赖关系。例如,如果我们执行下面的命令:: +但是,如果是一个比较大型的工程,你必需清楚哪些C文件包含了哪些头文件,并且,你在加入或删除头文件 +时,也需要小心地修改Makefile,这是一个很没有维护性的工作。为了避免这种繁重而又容易出错的事情, +我们可以使用C/C++编译的一个功能。大多数的C/C++编译器都支持一个“-M”的选项,即自动找寻源文件中 +包含的头文件,并生成一个依赖关系。例如,如果我们执行下面的命令:: cc -M main.c @@ -319,7 +392,9 @@ $(filter %.o,$(files))表示调用Makefile的filter函数,过滤“$files” main.o : main.c defs.h -于是由编译器自动生成的依赖关系,这样一来,你就不必再手动书写若干文件的依赖关系,而由编译器自动生成了。需要提醒一句的是,如果你使用GNU的C/C++编译器,你得用 ``-MM`` 参数,不然, ``-M`` 参数会把一些标准库的头文件也包含进来。 +于是由编译器自动生成的依赖关系,这样一来,你就不必再手动书写若干文件的依赖关系,而由编译器自动 +生成了。需要提醒一句的是,如果你使用GNU的C/C++编译器,你得用 ``-MM`` 参数,不然, ``-M`` +参数会把一些标准库的头文件也包含进来。 gcc -M main.c的输出是:: @@ -337,9 +412,14 @@ gcc -MM main.c的输出则是:: main.o: main.c defs.h -那么,编译器的这个功能如何与我们的Makefile联系在一起呢。因为这样一来,我们的Makefile也要根据这些源文件重新生成,让 Makefile自已依赖于源文件?这个功能并不现实,不过我们可以有其它手段来迂回地实现这一功能。GNU组织建议把编译器为每一个源文件的自动生成的依赖关系放到一个文件中,为每一个 ``name.c`` 的文件都生成一个 ``name.d`` 的Makefile文件, ``.d`` 文件中就存放对应 ``.c`` 文件的依赖关系。 +那么,编译器的这个功能如何与我们的Makefile联系在一起呢。因为这样一来,我们的Makefile也要根据 +这些源文件重新生成,让 Makefile自已依赖于源文件?这个功能并不现实,不过我们可以有其它手段来迂 +回地实现这一功能。GNU组织建议把编译器为每一个源文件的自动生成的依赖关系放到一个文件中,为每一 +个 ``name.c`` 的文件都生成一个 ``name.d`` 的Makefile文件, ``.d`` 文件中就存放对应 +``.c`` 文件的依赖关系。 -于是,我们可以写出 ``.c`` 文件和 ``.d`` 文件的依赖关系,并让make自动更新或生成 ``.d`` 文件,并把其包含在我们的主Makefile中,这样,我们就可以自动化地生成每个文件的依赖关系了。 +于是,我们可以写出 ``.c`` 文件和 ``.d`` 文件的依赖关系,并让make自动更新或生成 ``.d`` +文件,并把其包含在我们的主Makefile中,这样,我们就可以自动化地生成每个文件的依赖关系了。 这里,我们给出了一个模式规则来产生 ``.d`` 文件: @@ -352,7 +432,11 @@ gcc -MM main.c的输出则是:: rm -f $@.$$$$ -这个规则的意思是,所有的 ``.d`` 文件依赖于 ``.c`` 文件, ``rm -f $@`` 的意思是删除所有的目标,也就是 ``.d`` 文件,第二行的意思是,为每个依赖文件 ``$<`` ,也就是 ``.c`` 文件生成依赖文件, ``$@`` 表示模式 ``%.d`` 文件,如果有一个C文件是name.c,那么 ``%`` 就是 ``name`` , ``$$$$`` 意为一个随机编号,第二行生成的文件有可能是“name.d.12345”,第三行使用sed命令做了一个替换,关于sed命令的用法请参看相关的使用文档。第四行就是删除临时文件。 +这个规则的意思是,所有的 ``.d`` 文件依赖于 ``.c`` 文件, ``rm -f $@`` 的意思是删除所有的 +目标,也就是 ``.d`` 文件,第二行的意思是,为每个依赖文件 ``$<`` ,也就是 ``.c`` 文件生成依 +赖文件, ``$@`` 表示模式 ``%.d`` 文件,如果有一个C文件是name.c,那么 ``%`` 就是 +``name`` , ``$$$$`` 意为一个随机编号,第二行生成的文件有可能是“name.d.12345”,第三行使 +用sed命令做了一个替换,关于sed命令的用法请参看相关的使用文档。第四行就是删除临时文件。 总而言之,这个模式要做的事就是在编译器生成的依赖关系中加入 ``.d`` 文件的依赖,即把依赖关系: @@ -366,7 +450,10 @@ gcc -MM main.c的输出则是:: main.o main.d : main.c defs.h -于是,我们的 ``.d`` 文件也会自动更新了,并会自动生成了,当然,你还可以在这个 ``.d`` 文件中加入的不只是依赖关系,包括生成的命令也可一并加入,让每个 ``.d`` 文件都包含一个完赖的规则。一旦我们完成这个工作,接下来,我们就要把这些自动生成的规则放进我们的主Makefile中。我们可以使用Makefile的“include”命令,来引入别的Makefile文件(前面讲过),例如: +于是,我们的 ``.d`` 文件也会自动更新了,并会自动生成了,当然,你还可以在这个 ``.d`` 文件中 +加入的不只是依赖关系,包括生成的命令也可一并加入,让每个 ``.d`` 文件都包含一个完赖的规则。一旦 +我们完成这个工作,接下来,我们就要把这些自动生成的规则放进我们的主Makefile中。我们可以使 +用Makefile的“include”命令,来引入别的Makefile文件(前面讲过),例如: .. code-block:: makefile @@ -374,4 +461,7 @@ gcc -MM main.c的输出则是:: include $(sources:.c=.d) -上述语句中的 ``$(sources:.c=.d)`` 中的 ``.c=.d`` 的意思是做一个替换,把变量 ``$(sources)`` 所有 ``.c`` 的字串都替换成 ``.d`` ,关于这个“替换”的内容,在后面我会有更为详细的讲述。当然,你得注意次序,因为include是按次序来载入文件,最先载入的 ``.d`` 文件中的目标会成为默认目标。 +上述语句中的 ``$(sources:.c=.d)`` 中的 ``.c=.d`` 的意思是做一个替换,把变量 +``$(sources)`` 所有 ``.c`` 的字串都替换成 ``.d`` ,关于这个“替换”的内容,在后面我会有更为 +详细的讲述。当然,你得注意次序,因为include是按次序来载入文件,最先载入的 ``.d`` 文件中的目 +标会成为默认目标。 diff --git a/source/variables.rst b/source/variables.rst index e8612a2..21685b0 100644 --- a/source/variables.rst +++ b/source/variables.rst @@ -1,16 +1,23 @@ 使用变量 ======== -在Makefile中的定义的变量,就像是C/C++语言中的宏一样,他代表了一个文本字串,在Makefile中执行的时候其会自动原模原样地展开在所使用的地方。其与C/C++所不同的是,你可以在Makefile中改变其值。在Makefile中,变量可以使用在“目标”,“依赖目标”, “命令”或是Makefile的其它部分中。 +在Makefile中的定义的变量,就像是C/C++语言中的宏一样,他代表了一个文本字串,在Makefile中 +执行的时候其会自动原模原样地展开在所使用的地方。其与C/C++所不同的是,你可以在Makefile中改变其 +值。在Makefile中,变量可以使用在“目标”,“依赖目标”, “命令”或是Makefile的其它部分中。 -变量的命名字可以包含字符、数字,下划线(可以是数字开头),但不应该含有 ``:`` 、 ``#`` 、 ``=`` 或是空字符(空格、回车等)。变量是大小写敏感的,“foo”、“Foo”和“FOO”是三个不同的变量名。传统的Makefile的变量名是全大写的命名方式,但我推荐使用大小写搭配的变量名,如: MakeFlags。这样可以避免和系统的变量冲突,而发生意外的事情。 +变量的命名字可以包含字符、数字,下划线(可以是数字开头),但不应该含有 ``:`` 、 ``#`` 、 +``=`` 或是空字符(空格、回车等)。变量是大小写敏感的,“foo”、“Foo”和“FOO”是三个不同的 +变量名。传统的Makefile的变量名是全大写的命名方式,但我推荐使用大小写搭配的变量名,如: +MakeFlags。这样可以避免和系统的变量冲突,而发生意外的事情。 有一些变量是很奇怪字串,如 ``$<`` 、 ``$@`` 等,这些是自动化变量,我会在后面介绍。 变量的基础 ---------- -变量在声明时需要给予初值,而在使用时,需要给在变量名前加上 ``$`` 符号,但最好用小括号 ``()`` 或是大括号 ``{}`` 把变量给包括起来。如果你要使用真实的 ``$`` 字符,那么你需要用 ``$$`` 来表示。 +变量在声明时需要给予初值,而在使用时,需要给在变量名前加上 ``$`` 符号,但最好用小括号 +``()`` 或是大括号 ``{}`` 把变量给包括起来。如果你要使用真实的 ``$`` 字符,那么你需要用 +``$$`` 来表示。 变量可以使用在许多地方,如规则中的“目标”、“依赖”、“命令”以及新的变量中。先看一个例子: @@ -37,16 +44,20 @@ prog.o : prog.c cc -c prog.c -当然,千万不要在你的Makefile中这样干,这里只是举个例子来表明Makefile中的变量在使用处展开的真实样子。可见其就是一个“替代”的原理。 +当然,千万不要在你的Makefile中这样干,这里只是举个例子来表明Makefile中的变量在使用处展开的 +真实样子。可见其就是一个“替代”的原理。 -另外,给变量加上括号完全是为了更加安全地使用这个变量,在上面的例子中,如果你不想给变量加上括号,那也可以,但我还是强烈建议你给变量加上括号。 +另外,给变量加上括号完全是为了更加安全地使用这个变量,在上面的例子中,如果你不想给变量加上 +括号,那也可以,但我还是强烈建议你给变量加上括号。 变量中的变量 ------------ -在定义变量的值时,我们可以使用其它变量来构造变量的值,在Makefile中有两种方式来在用变量定义变量的值。 +在定义变量的值时,我们可以使用其它变量来构造变量的值,在Makefile中有两种方式来在用变量定义 +变量的值。 -先看第一种方式,也就是简单的使用 ``=`` 号,在 ``=`` 左侧是变量,右侧是变量的值,右侧变量的值可以定义在文件的任何一处,也就是说,右侧中的变量不一定非要是已定义好的值,其也可以使用后面定义的值。如: +先看第一种方式,也就是简单的使用 ``=`` 号,在 ``=`` 左侧是变量,右侧是变量的值,右侧变量的值 +可以定义在文件的任何一处,也就是说,右侧中的变量不一定非要是已定义好的值,其也可以使用后面定义的值。如: .. code-block:: makefile @@ -57,7 +68,9 @@ all: echo $(foo) -我们执行“make all”将会打出变量 ``$(foo)`` 的值是 ``Huh?`` ( ``$(foo)`` 的值是 ``$(bar)`` , ``$(bar)`` 的值是 ``$(ugh)`` , ``$(ugh)`` 的值是 ``Huh?`` )可见,变量是可以使用后面的变量来定义的。 +我们执行“make all”将会打出变量 ``$(foo)`` 的值是 ``Huh?`` ( ``$(foo)`` 的值是 +``$(bar)`` , ``$(bar)`` 的值是 ``$(ugh)`` , ``$(ugh)`` 的值是 ``Huh?`` )可见,变 +量是可以使用后面的变量来定义的。 这个功能有好的地方,也有不好的地方,好的地方是,我们可以把变量的真实值推到后面来定义,如: @@ -66,7 +79,8 @@ CFLAGS = $(include_dirs) -O include_dirs = -Ifoo -Ibar -当 ``CFLAGS`` 在命令中被展开时,会是 ``-Ifoo -Ibar -O`` 。但这种形式也有不好的地方,那就是递归定义,如: +当 ``CFLAGS`` 在命令中被展开时,会是 ``-Ifoo -Ibar -O`` 。但这种形式也有不好的地方,那就 +是递归定义,如: .. code-block:: makefile @@ -79,7 +93,9 @@ A = $(B) B = $(A) -这会让make陷入无限的变量展开过程中去,当然,我们的make是有能力检测这样的定义,并会报错。还有就是如果在变量中使用函数,那么,这种方式会让我们的make运行时非常慢,更糟糕的是,他会使用得两个make的函数“wildcard”和“shell”发生不可预知的错误。因为你不会知道这两个函数会被调用多少次。 +这会让make陷入无限的变量展开过程中去,当然,我们的make是有能力检测这样的定义,并会报错。还有就 +是如果在变量中使用函数,那么,这种方式会让我们的make运行时非常慢,更糟糕的是,他会使用得两 +个make的函数“wildcard”和“shell”发生不可预知的错误。因为你不会知道这两个函数会被调用多少次。 为了避免上面的这种方法,我们可以使用make中的另一种用变量来定义变量的方法。这种方法使用的是 ``:=`` 操作符,如: @@ -105,7 +121,8 @@ 那么,y的值是“bar”,而不是“foo bar”。 -上面都是一些比较简单的变量使用了,让我们来看一个复杂的例子,其中包括了make的函数、条件表达式和一个系统变量“MAKELEVEL”的使用: +上面都是一些比较简单的变量使用了,让我们来看一个复杂的例子,其中包括了make的函数、条件表达式和 +一个系统变量“MAKELEVEL”的使用: .. code-block:: makefile @@ -116,22 +133,29 @@ MAKE := ${MAKE} host-type=${host-type} whoami=${whoami} endif -关于条件表达式和函数,我们在后面再说,对于系统变量“MAKELEVEL”,其意思是,如果我们的make有一个嵌套执行的动作(参见前面的“嵌套使用make”),那么,这个变量会记录了我们的当前Makefile的调用层数。 +关于条件表达式和函数,我们在后面再说,对于系统变量“MAKELEVEL”,其意思是,如果我们的make有一 +个嵌套执行的动作(参见前面的“嵌套使用make”),那么,这个变量会记录了我们的当前Makefile的调用 +层数。 -下面再介绍两个定义变量时我们需要知道的,请先看一个例子,如果我们要定义一个变量,其值是一个空格,那么我们可以这样来: +下面再介绍两个定义变量时我们需要知道的,请先看一个例子,如果我们要定义一个变量,其值是一个 +空格,那么我们可以这样来: .. code-block:: makefile nullstring := space := $(nullstring) # end of the line -nullstring是一个Empty变量,其中什么也没有,而我们的space的值是一个空格。因为在操作符的右边是很难描述一个空格的,这里采用的技术很管用,先用一个Empty变量来标明变量的值开始了,而后面采用“#”注释符来表示变量定义的终止,这样,我们可以定义出其值是一个空格的变量。请注意这里关于“#”的使用,注释符“#”的这种特性值得我们注意,如果我们这样定义一个变量: +nullstring是一个Empty变量,其中什么也没有,而我们的space的值是一个空格。因为在操作符的右边 +是很难描述一个空格的,这里采用的技术很管用,先用一个Empty变量来标明变量的值开始了,而后面采 +用“#”注释符来表示变量定义的终止,这样,我们可以定义出其值是一个空格的变量。请注意这里关于“#”的 +使用,注释符“#”的这种特性值得我们注意,如果我们这样定义一个变量: .. code-block:: makefile dir := /foo/bar # directory to put the frobs in -dir这个变量的值是“/foo/bar”,后面还跟了4个空格,如果我们这样使用这样变量来指定别的目录——“$(dir)/file”那么就完蛋了。 +dir这个变量的值是“/foo/bar”,后面还跟了4个空格,如果我们这样使用这样变量来指定别的目 +录——“$(dir)/file”那么就完蛋了。 还有一个比较有用的操作符是 ``?=`` ,先看示例: @@ -139,7 +163,8 @@ dir这个变量的值是“/foo/bar”,后面还跟了4个空格,如果我 FOO ?= bar -其含义是,如果FOO没有被定义过,那么变量FOO的值就是“bar”,如果FOO先前被定义过,那么这条语将什么也不做,其等价于: +其含义是,如果FOO没有被定义过,那么变量FOO的值就是“bar”,如果FOO先前被定义过,那么这条语将 +什么也不做,其等价于: .. code-block:: makefile @@ -152,7 +177,8 @@ dir这个变量的值是“/foo/bar”,后面还跟了4个空格,如果我 这里介绍两种变量的高级使用方法,第一种是变量值的替换。 -我们可以替换变量中的共有的部分,其格式是 ``$(var:a=b)`` 或是 ``${var:a=b}`` ,其意思是,把变量“var”中所有以“a”字串“结尾”的“a”替换成“b”字串。这里的“结尾”意思是“空格”或是“结束符”。 +我们可以替换变量中的共有的部分,其格式是 ``$(var:a=b)`` 或是 ``${var:a=b}`` ,其意思是, +把变量“var”中所有以“a”字串“结尾”的“a”替换成“b”字串。这里的“结尾”意思是“空格”或是“结束符”。 还是看一个示例吧: @@ -161,7 +187,8 @@ dir这个变量的值是“/foo/bar”,后面还跟了4个空格,如果我 foo := a.o b.o c.o bar := $(foo:.o=.c) -这个示例中,我们先定义了一个 ``$(foo)`` 变量,而第二行的意思是把 ``$(foo)`` 中所有以 ``.o`` 字串“结尾”全部替换成 ``.c`` ,所以我们的 ``$(bar)`` 的值就是“a.c b.c c.c”。 +这个示例中,我们先定义了一个 ``$(foo)`` 变量,而第二行的意思是把 ``$(foo)`` 中所有以 +``.o`` 字串“结尾”全部替换成 ``.c`` ,所以我们的 ``$(bar)`` 的值就是“a.c b.c c.c”。 另外一种变量替换的技术是以“静态模式”(参见前面章节)定义的,如: @@ -170,7 +197,8 @@ dir这个变量的值是“/foo/bar”,后面还跟了4个空格,如果我 foo := a.o b.o c.o bar := $(foo:%.o=%.c) -这依赖于被替换字串中的有相同的模式,模式中必须包含一个 ``%`` 字符,这个例子同样让 ``$(bar)`` 变量的值为“a.c b.c c.c”。 +这依赖于被替换字串中的有相同的模式,模式中必须包含一个 ``%`` 字符,这个例子同样让 +``$(bar)`` 变量的值为“a.c b.c c.c”。 第二种高级用法是——“把变量的值再当成变量”。先看一个例子: @@ -180,7 +208,8 @@ dir这个变量的值是“/foo/bar”,后面还跟了4个空格,如果我 y = z a := $($(x)) -在这个例子中,$(x)的值是“y”,所以$($(x))就是$(y),于是$(a)的值就是“z”。(注意,是“x=y”,而不是“x=$(y)”) +在这个例子中,$(x)的值是“y”,所以$($(x))就是$(y),于是$(a)的值就是“z”。(注意,是“x=y”, +而不是“x=$(y)”) 我们还可以使用更多的层次: @@ -202,7 +231,8 @@ dir这个变量的值是“/foo/bar”,后面还跟了4个空格,如果我 z = Hello a := $($(x)) -这里的 ``$($(x))`` 被替换成了 ``$($(y))`` ,因为 ``$(y)`` 值是“z”,所以,最终结果是: ``a:=$(z)`` ,也就是“Hello”。 +这里的 ``$($(x))`` 被替换成了 ``$($(y))`` ,因为 ``$(y)`` 值是“z”,所以,最终结果是: +``a:=$(z)`` ,也就是“Hello”。 再复杂一点,我们再加上函数: @@ -214,7 +244,10 @@ dir这个变量的值是“/foo/bar”,后面还跟了4个空格,如果我 z = y a := $($($(z))) -这个例子中, ``$($($(z)))`` 扩展为 ``$($(y))`` ,而其再次被扩展为 ``$($(subst 1,2,$(x)))`` 。 ``$(x)`` 的值是“variable1”,subst函数把“variable1”中的所有“1”字串替换成“2”字串,于是,“variable1”变成 “variable2”,再取其值,所以,最终, ``$(a)`` 的值就是 ``$(variable2)`` 的值——“Hello”。(喔,好不容易) +这个例子中, ``$($($(z)))`` 扩展为 ``$($(y))`` ,而其再次被扩展为 +``$($(subst 1,2,$(x)))`` 。 ``$(x)`` 的值是“variable1”,subst函数把“variable1”中的 +所有“1”字串替换成“2”字串,于是,“variable1”变成 “variable2”,再取其值,所以,最终, +``$(a)`` 的值就是 ``$(variable2)`` 的值——“Hello”。(喔,好不容易) 在这种方式中,或要可以使用多个变量来组成一个变量的名字,然后再取其值: @@ -236,7 +269,8 @@ dir这个变量的值是“/foo/bar”,后面还跟了4个空格,如果我 sources := $($(a1)_objects:.o=.c) -这个例子中,如果 ``$(a1)`` 的值是“a”的话,那么, ``$(sources)`` 的值就是“a.c b.c c.c”;如果 ``$(a1)`` 的值是“1”,那么 ``$(sources)`` 的值是“1.c 2.c 3.c”。 +这个例子中,如果 ``$(a1)`` 的值是“a”的话,那么, ``$(sources)`` 的值就是“a.c b.c c.c”; +如果 ``$(a1)`` 的值是“1”,那么 ``$(sources)`` 的值是“1.c 2.c 3.c”。 再来看一个这种技术和“函数”与“条件语句”一同使用的例子: @@ -252,7 +286,9 @@ dir这个变量的值是“/foo/bar”,后面还跟了4个空格,如果我 foo := $($(func) $(bar)) -这个示例中,如果定义了“do_sort”,那么: ``foo := $(sort a d b g q c)`` ,于是 ``$(foo)`` 的值就是 “a b c d g q”,而如果没有定义“do_sort”,那么: ``foo := $(strip a d b g q c)`` ,调用的就是strip函数。 +这个示例中,如果定义了“do_sort”,那么: ``foo := $(sort a d b g q c)`` ,于是 +``$(foo)`` 的值就是 “a b c d g q”,而如果没有定义“do_sort”,那么: +``foo := $(strip a d b g q c)`` ,调用的就是strip函数。 当然,“把变量的值再当成变量”这种技术,同样可以用在操作符的左边:: @@ -285,7 +321,8 @@ dir这个变量的值是“/foo/bar”,后面还跟了4个空格,如果我 所不同的是,用 ``+=`` 更为简洁。 -如果变量之前没有定义过,那么, ``+=`` 会自动变成 ``=`` ,如果前面有变量定义,那么 ``+=`` 会继承于前次操作的赋值符。如果前一次的是 ``:=`` ,那么 ``+=`` 会以 ``:=`` 作为其赋值符,如: +如果变量之前没有定义过,那么, ``+=`` 会自动变成 ``=`` ,如果前面有变量定义,那么 ``+=`` 会 +继承于前次操作的赋值符。如果前一次的是 ``:=`` ,那么 ``+=`` 会以 ``:=`` 作为其赋值符,如: .. code-block:: makefile @@ -306,12 +343,14 @@ dir这个变量的值是“/foo/bar”,后面还跟了4个空格,如果我 variable = value variable += more -由于前次的赋值符是 ``=`` ,所以 ``+=`` 也会以 ``=`` 来做为赋值,那么岂不会发生变量的递补归定义,这是很不好的,所以make会自动为我们解决这个问题,我们不必担心这个问题。 +由于前次的赋值符是 ``=`` ,所以 ``+=`` 也会以 ``=`` 来做为赋值,那么岂不会发生变量的递补归 +定义,这是很不好的,所以make会自动为我们解决这个问题,我们不必担心这个问题。 override 指示符 --------------- -如果有变量是通常make的命令行参数设置的,那么Makefile中对这个变量的赋值会被忽略。如果你想在Makefile中设置这类参数的值,那么,你可以使用“override”指示符。其语法是:: +如果有变量是通常make的命令行参数设置的,那么Makefile中对这个变量的赋值会被忽略。如果你想 +在Makefile中设置这类参数的值,那么,你可以使用“override”指示符。其语法是:: override ; = ; @@ -321,7 +360,8 @@ override 指示符 override ; += ; -对于多行的变量定义,我们用define指示符,在define指示符前,也同样可以使用override指示符,如:: +对于多行的变量定义,我们用define指示符,在define指示符前,也同样可以使用override指示符, +如:: override define foo bar @@ -330,9 +370,12 @@ override 指示符 多行变量 -------- -还有一种设置变量值的方法是使用define关键字。使用define关键字设置变量的值可以有换行,这有利于定义一系列的命令(前面我们讲过“命令包”的技术就是利用这个关键字)。 +还有一种设置变量值的方法是使用define关键字。使用define关键字设置变量的值可以有换行,这有利于 +定义一系列的命令(前面我们讲过“命令包”的技术就是利用这个关键字)。 -define指示符后面跟的是变量的名字,而重起一行定义变量的值,定义是以endef 关键字结束。其工作方式和“=”操作符一样。变量的值可以包含函数、命令、文字,或是其它变量。因为命令需要以[Tab]键开头,所以如果你用define定义的命令变量中没有以 ``Tab`` 键开头,那么make 就不会把其认为是命令。 +define指示符后面跟的是变量的名字,而重起一行定义变量的值,定义是以endef 关键字结束。其工作方 +式和“=”操作符一样。变量的值可以包含函数、命令、文字,或是其它变量。因为命令需要以[Tab]键开头, +所以如果你用define定义的命令变量中没有以 ``Tab`` 键开头,那么make 就不会把其认为是命令。 下面的这个示例展示了define的用法:: @@ -344,20 +387,32 @@ define指示符后面跟的是变量的名字,而重起一行定义变量的 环境变量 -------- -make运行时的系统环境变量可以在make开始运行时被载入到Makefile文件中,但是如果Makefile中已定义了这个变量,或是这个变量由make命令行带入,那么系统的环境变量的值将被覆盖。(如果make指定了“-e”参数,那么,系统环境变量将覆盖Makefile中定义的变量) +make运行时的系统环境变量可以在make开始运行时被载入到Makefile文件中,但是如果Makefile中已 +定义了这个变量,或是这个变量由make命令行带入,那么系统的环境变量的值将被覆盖。(如果make指定 +了“-e”参数,那么,系统环境变量将覆盖Makefile中定义的变量) -因此,如果我们在环境变量中设置了 ``CFLAGS`` 环境变量,那么我们就可以在所有的Makefile中使用这个变量了。这对于我们使用统一的编译参数有比较大的好处。如果Makefile中定义了CFLAGS,那么则会使用Makefile中的这个变量,如果没有定义则使用系统环境变量的值,一个共性和个性的统一,很像“全局变量”和“局部变量”的特性。 +因此,如果我们在环境变量中设置了 ``CFLAGS`` 环境变量,那么我们就可以在所有的Makefile中使用 +这个变量了。这对于我们使用统一的编译参数有比较大的好处。如果Makefile中定义了CFLAGS,那么则会 +使用Makefile中的这个变量,如果没有定义则使用系统环境变量的值,一个共性和个性的统一,很像“全局 +变量”和“局部变量”的特性。 -当make嵌套调用时(参见前面的“嵌套调用”章节),上层Makefile中定义的变量会以系统环境变量的方式传递到下层的Makefile 中。当然,默认情况下,只有通过命令行设置的变量会被传递。而定义在文件中的变量,如果要向下层Makefile传递,则需要使用exprot关键字来声明。(参见前面章节) +当make嵌套调用时(参见前面的“嵌套调用”章节),上层Makefile中定义的变量会以系统环境变量的方式 +传递到下层的Makefile 中。当然,默认情况下,只有通过命令行设置的变量会被传递。而定义在文件中的 +变量,如果要向下层Makefile传递,则需要使用exprot关键字来声明。(参见前面章节) -当然,我并不推荐把许多的变量都定义在系统环境中,这样,在我们执行不用的Makefile时,拥有的是同一套系统变量,这可能会带来更多的麻烦。 +当然,我并不推荐把许多的变量都定义在系统环境中,这样,在我们执行不用的Makefile时,拥有的是同一 +套系统变量,这可能会带来更多的麻烦。 目标变量 -------- -前面我们所讲的在Makefile中定义的变量都是“全局变量”,在整个文件,我们都可以访问这些变量。当然,“自动化变量”除外,如 ``$<`` 等这种类量的自动化变量就属于“规则型变量”,这种变量的值依赖于规则的目标和依赖目标的定义。 +前面我们所讲的在Makefile中定义的变量都是“全局变量”,在整个文件,我们都可以访问这些变量。 +当然,“自动化变量”除外,如 ``$<`` 等这种类量的自动化变量就属于“规则型变量”,这种变量的值依赖 +于规则的目标和依赖目标的定义。 -当然,我也同样可以为某个目标设置局部变量,这种变量被称为“Target-specific Variable”,它可以和“全局变量”同名,因为它的作用范围只在这条规则以及连带规则中,所以其值也只在作用范围内有效。而不会影响规则链以外的全局变量的值。 +当然,我也同样可以为某个目标设置局部变量,这种变量被称为“Target-specific Variable”,它可以 +和“全局变量”同名,因为它的作用范围只在这条规则以及连带规则中,所以其值也只在作用范围内有效。而 +不会影响规则链以外的全局变量的值。 其语法是: @@ -367,9 +422,11 @@ make运行时的系统环境变量可以在make开始运行时被载入到Makefi : overide -;可以是前面讲过的各种赋值表达式,如 ``=`` 、 ``:=`` 、 ``+= ``或是 ``?=`` 。第二个语法是针对于make命令行带入的变量,或是系统环境变量。 +;可以是前面讲过的各种赋值表达式,如 ``=`` 、 ``:=`` 、 ``+= `` +或是 ``?=`` 。第二个语法是针对于make命令行带入的变量,或是系统环境变量。 -这个特性非常的有用,当我们设置了这样一个变量,这个变量会作用到由这个目标所引发的所有的规则中去。如: +这个特性非常的有用,当我们设置了这样一个变量,这个变量会作用到由这个目标所引发的所有的规则中 +去。如: .. code-block:: makefile @@ -386,14 +443,18 @@ make运行时的系统环境变量可以在make开始运行时被载入到Makefi bar.o : bar.c $(CC) $(CFLAGS) bar.c -在这个示例中,不管全局的 ``$(CFLAGS)`` 的值是什么,在prog目标,以及其所引发的所有规则中(prog.o foo.o bar.o的规则), ``$(CFLAGS)`` 的值都是 ``-g`` +在这个示例中,不管全局的 ``$(CFLAGS)`` 的值是什么,在prog目标,以及其所引发的所有规则 +中(prog.o foo.o bar.o的规则), ``$(CFLAGS)`` 的值都是 ``-g`` 模式变量 -------- -在GNU的make中,还支持模式变量(Pattern-specific Variable),通过上面的目标变量中,我们知道,变量可以定义在某个目标上。模式变量的好处就是,我们可以给定一种“模式”,可以把变量定义在符合这种模式的所有目标上。 +在GNU的make中,还支持模式变量(Pattern-specific Variable),通过上面的目标变量中,我们 +知道,变量可以定义在某个目标上。模式变量的好处就是,我们可以给定一种“模式”,可以把变量定义在符 +合这种模式的所有目标上。 -我们知道,make的“模式”一般是至少含有一个 ``%`` 的,所以,我们可以以如下方式给所有以 ``.o`` 结尾的目标定义目标变量: +我们知道,make的“模式”一般是至少含有一个 ``%`` 的,所以,我们可以以如下方式给所有以 ``.o`` +结尾的目标定义目标变量: .. code-block:: makefile