Makefile写法

makefile写法

蓝森林 http://www.lslnet.com 2001年3月22日 08:44
作 者: 许明彦

Abstract:

    在 Unix 上写程式的人大概都碰过 Makefile,尤其是用 C 来开发程式的人。用 make来开发和编译程式的确很方便,可是要写出一个 Makefile就不简单了。偏偏介绍 Makefile 的文件不多,GNU Make 那份印出来要几百页的文件,光看完 Overview 就快阵亡了,难怪许多
人闻 Unix 色变。

    本文将介绍如何利用 GNU Autoconf 及 Automake 这两套软体来协助我们『自动』产生 Makefile 档,并且让开发出来的软体可以像 Apache, MySQL 和常见的 GNU 软体一样,只要会 ``./configure'', ``make'', ``make install'' 就可以把程式安装到系统中。如果您有
心开发 Open Source 的软体,或只是想在 Unix 系统下写写程式。希望这份介绍文件能帮助您轻松地进入 Unix Programming 的殿堂。

1. 简介

    Makefile 基本上就是『目标』(target), 『关连』(dependencies) 和『动作』三者所组成的一连串规则。而 make 就会根据 Makefile 的规则来决定如何编译 (compile) 和连结 (link) 程式。实际上,make 可做的不只是编译和连结程式,例如 FreeBSD 的 port collect
ion 中, Makefile 还可以做到自动下载原始程式套件,解压缩 (extract) ,修补 (patch),设定,然後编译,安装至系统中。

    Makefile 基本构造虽然简单,但是妥善运用这些规则就也可以变出许多不同的花招。却也因此,许多刚开始学习写 Makefile 时会感到没有规范可循,每个人写出来的 Makefile 长得都不太一样,不知道从何下手,而且常常会受限於自己的开发环境,只要环境变数不同或路
径改一下,可能Makefile 就得跟着修改。虽然有 GNU Makefile Conventions (GNU Makefile 惯例) 订出一些使用 GNU 程式设计时撰写 Makefile 的一些标准和规范,但是内容很长而且很复杂, 并且经常做些调整,为了减轻程式设计师维护 Makefile 的负担,因此有了Automake。

    程式设计师只需写一些预先定义好的巨集 (macro),交给 Automake 处理後会产生一个可供Autoconf 使用的 Makefile.in 档。再配合利用Autoconf 产生的自动设定档 configure即可产生一份符合 GNU Makefile惯例的 Makeifle 了。

2. 上路之前

    在开始试着用 Automake 之前,请先确认你的系统已经安装以下的软体:
1. GNU Automake
2. GNU Autoconf
3. GNU m4
4. perl
5. GNU Libtool (如果你需要产生 shared library)

    我会建议你最好也使用 GNU C/C++ 编译器 、GNU Make 以及其它 GNU 的工具程式来做为开发的环境,这些工具都是属於 Open Source Software不仅免费而且功能强大。如果你是使用Red Hat Linux 可以找到所有上述软体的 rpm 档,FreeBSD 也有现成的 package 可以直
接安装,或着你也可以自行下载这些软体的原始档回来 DIY。以下的范例是在 Red Hat Linux 5.2 + CLE2 的环境下所完成的。

3. 一个简单的例子

    Automake 所产生的 Makefile 除了可以做到程式的编译和连结,也已经把如何产生程式文件(如 manual page, info 档及 dvi 档) 的动作,还有把原始程式包装起来以供散 的动作都考虑进去了,所以原始程式所存放的目录架构最好符合 GNU 的标准惯例,接下来我拿hello.c 来做为例子。

    在工作目录下建立一个新的子目录 ``devel'',再在 devel 下建立一个``hello'' 的子目录,这个目录将作为我们存放 hello 这个程式及其相关档案的地方:

% mkdir devel
% cd devel
% mkdir hello
% cd hello

用编辑器写个 hello.c 档,
#include stdio.h
int main(int argc, char** argv)
{
printf(``Hello, GNU! '');
return 0;
}

    接下来就要用 Autoconf 及 Automake 来帮我们产生 Makefile 档了,

1. 用 autoscan 产生一个 configure.in 的雏型,执行 autoscan 後会产生一个configure.scan 的档案,我们可以用它做为configure.in档的蓝本。

% autoscan
% ls
configure.scan hello.c

2. 编辑 configure.scan 档,如下所示,并且把它的档名改成configure.in
dnl Process this file with autoconf to produce a con figure script.
AC_INIT(hello.c)
AM_INIT_AUTOMAKE(hello, 1.0)
dnl Checks for programs.
AC_PROG_CC
dnl Checks for libraries.
dnl Checks for header files.
dnl Checks for typedefs, structures, and compiler ch aracteristics.
dnl Checks for library functions.
AC_OUTPUT(Makefile)

3. 执行 aclocal 和 autoconf ,分别会产生 aclocal.m4 及 configure 两个档案
% aclocal
% autoconf
% ls
aclocal.m4 configure configure.in hello.c

4. 编辑 Makefile.am 档,内容如下
AUTOMAKE_OPTIONS= foreign
bin_PROGRAMS= hello
hello_SOURCES= hello.c

5. 执行 automake --add-missing ,Automake 会根据 Makefile.am 档产生一些档案,包含最重要的 Makefile.in
% automake --add-missing
automake: configure.in: installing `./install-sh'
automake: configure.in: installing `./mkinstalldirs'
automake: configure.in: installing `./missing'

6. 最後执行 ./configure ,
% ./configure
creating cache ./config.cache
checking for a BSD compatible install... /usr/bin/in stall -c
checking whether build environment is sane... yes
checking whether make sets ${MAKE}... yes
checking for working aclocal... found
checking for working autoconf... found
checking for working automake... found
checking for working autoheader... found
checking for working makeinfo... found
checking for gcc... gcc
checking whether the C compiler (gcc ) works... yes
checking whether the C compiler (gcc ) is a cross-co mpiler... no
checking whether we are using GNU C... yes
checking whether gcc accepts -g... yes
updating cache ./config.cache
creating ./config.status
creating Makefile

    现在你的目录下已经产生了一个 Makefile 档,下个 ``make'' 指令就可以开始编译 hello.c 成执行档,执行 ./hello 和 GNU 打声招呼吧!

% make
gcc -DPACKAGE="hello" -DVERSION="1.0" -I. -I. -g -O2 -c he llo.c
gcc -g -O2 -o hello hello.o
% ./hello
Hello! GNU!

    你还可以试试 ``make clean'',''make install'',''make dist'' 看看会有什麽结果。你也可以把产生出来的 Makefile 秀给你的老板,让他从此对你刮目相看 :-)

4. 一探究竟

    上述产生 Makefile 的过程和以往自行编写的方式非常不一样,舍弃传统自行定义 make 的规则,使用 Automake 只需用到一些已经定义好的巨集即可。我们把巨集及目标 (target)写在 Makefile.am 档内,Automake读入 Makefile.am 档後会把这一串已经定义好的巨集展
开并且产生对应的Makefile.in 档, 然後再由 configure 这个 shell script 根据Makefile.in 产生适合的 Makefile。
    在此范例中可藉由 Autoconf 及 Automake 工具所产生的档案有 configure.scan、aclocal.m4、configure、Makefile.in,需要我们加入设定者为 configure.in 及 Makefile.am。

4.1 编辑 configure.in 档

    Autoconf 是用来产生 'configure' 档的工具。'configure' 是一个shell script,它可以自动设定原始程式以符合各种不同平台上 Unix 系统的特性,并且根据系统叁数及环境产生合适的 Makefile 档或是C 的标头档 (header file),让原始程式可以很方便地在这些不同
的平台上被编译出来。Autoconf 会读取 configure.in 档然後产生 'configure' 这个shell script。

    configure.in 档的内容是一连串 GNU m4 的巨集,这些巨集经过autoconf 处理後会变成检查系统特徵的 shell script。configure.in 内巨集的顺序并没有特别的规定,但是每一个 configure.in 档必须在所有巨集前加入 AC_INIT 巨集,然後在所有巨集的最後面加上 AC
_OUTPUT 巨集。我们可先用 autoscan 扫描原始档以产生一个 configure.scan 档,再对 configure.scan 做些修改成 configure.in 档。在范例中所用到的巨集如下:

dnl
    这个巨集後面的字不会被处理,可视为注解。

AC_INIT(FILE)
    这个巨集用来检查原始码所在的路径,autoscan 会自动产生,我们不必修改它。

AM_INIT_AUTOMAKE(PACKAGE,VERSION)
    这是使用 Automake 所必备的巨集,PACKAGE 是我们所要产生软体套件的名称,VERSION 是版本编号。

AC_PROG_CC
    检查系统可用的 C 编译器,如果原始程式是用 C 写的就需要这个巨集。

AC_OUTPUT(FILE)
    设定 configure 所要产生的档案,如果是 Makefile 的话,configure 便会把它检查出来的结果带入 Makefile.in 档然後产生
合适的 Makefile。

    实际上,我们使用 Automake 时,还须要一些其它的巨集,这些额外的巨集我们用 aclocal来帮我们产生。执行 aclocal 会产生 aclocal.m4档,如果没有特别的用途,我们可以不必修改它,用 aclocal 所产生的巨集会告诉 Automake 怎麽做。

    有了 configure.in 及 aclocal.m4 两个档案後,便可以执行 autoconf来产生 configure档了。

4.2 编辑 Makefile.am 档

    接下来我们要编辑 Makefile.am 档,Automake 会根据 configure.in 中的巨集把Makefile.am 转成 Makefile.in 档。Makefile.am 档定义我们所要产的目标:

AUTOMAKE_OPTIONS
    设定 automake 的选项。Automake 主要是帮助开发 GNU 软体的人员维护软体套件,所以在执行 automake 时,会检查目录下是否存在标准 GNU 软体套件中应具备的文件档案,例如 'NEWS'、'AUTHOR'、'ChangeLog' 等文件档。设成 foreign 时,automake 会改用一般软
体套件的标准来检查。

bin_PROGRAMS
    定义我们所要产生的执行档档名。如果要产生多个执行档,每个档名用空白字元隔开。

hello_SOURCES
    定义 'hello' 这个执行档所需要的原始档。如果 'hello' 这个程式是由多个原始档所产生,必须把它所用到的原始档都列出来,以空白字元隔开。假设 'hello' 这个程式需要 'hello.c'、'main.c'、

'hello.h'
    三个档案的话,则定义
        hello_SOURCES= hello.c main.c hello.h
    如果我们定义多个执行档,则对每个执行档都要定义相对的filename_SOURCES。

    编辑好 Makefile.am 档,就可以用 automake --add-missing 来产生Makefile.in。加上 --add-missing 选项是告诉 automake 顺便帮我们加入包装一个软体套件所必备的档案。Automake 产生出来的 Makefile.in档是完全符合 GNU Makefile 的惯例,我们只要执行 confi
gure 这个shell script 便可以产生合适的 Makefile 档了。

4.3 使用 Makefile

    利用 configure 所产生的 Makefile 档有几个预设的目标可供使用,我们只拿其中几个简述如下:

make all
    产生我们设定的目标,即此范例中的执行档。只打 make 也可以,此时会开始编译原始码,然後连结,并且产生执行档。

make clean
    清除之前所编译的执行档及目的档 (object file, *.o)。

make distclean
    除了清除执行档和目的档外,也把 configure 所产生的 Makefile也清除掉。

make install
    将程式安装至系统中。如果原始码编译无误,且执行结果正确,便可以把程式安装至系统预设的执行档存放路径。如果我们用bin_PROGRAMS 巨集的话,程式会被安装至 /usr/local/bin 这个目录。

make dist
    将程式和相关的档案包装成一个压缩档以供散播 (distribution) 。执行完在目录下会产生一个以 PACKAGE-VERSION.tar.gz 为名称的档案。PACKAGE 和 VERSION 这两个变数是根据 configure.in 档中AM_INIT_AUTOMAKE(PACKAGE, VERSION) 的定义。在此范例中会产生
'hello-1.0.tar.gz' 的档案。

make distcheck
    和 make dist 类似,但是加入检查包装後的压缩档是否正常。这个目标除了把程式和相关档案包装成 tar.gz 档外,还会自动把这个压
缩档解开,执行 configure,并且进行 make all 的动作,确认编译无误後,会显示这个 tar.gz 档已经准备好可供散播了。这个检查非
常有用,检查过关的套件,基本上可以给任何一个具备 GNU 发展境的人去重新编译。就 hello-1.tar.gz 这个范例而言,除了在 Red
Hat Linux 上,在 FreeBSD 2.2.x 版也可以正确地重新编译。

    要注意的是,利用 Autoconf 及 Automake 所产生出来的软体套件是可以在没有安装 Autoconf 及 Automake 的环境上使用的,因为 configure 是一个 shell script,它己被设计可以在一般 Unix 的 sh 这个 shell 下执行。但是如果要修改 configure.in 及 Makefile.a
m 档再产生新的configure 及 Makefile.in 档时就一定要有 Autoconf 及 Automake 了。

5. 相关讯息

    Autoconf 和 Automake 功能十分强大,你可以从它们所附的 info 档找到详细的用法。你也可以从许多现存的 GNU 软体或 Open Source 软体中找到相关的 configure.in 或 Makefile.am 档,它们是学习 Autoconf 及Automake 更多技巧的最佳范例。

    这篇简介只用到了 Autoconf 及 Automake 的皮毛罢了,如果你有心加入Open Source 软体开发的行列,希望这篇文件能帮助你对产生 Makefile有个简单的依据。其它有关开发 GNU程式或 C 程式设计及 Makefile 的详细运用及技巧,我建议你从 GNU Coding Standards3
(GNU 编码标准规定) 读起,里面包含了 GNU Makefile 惯例,还有发展 GNU 软体套件的标准程序和惯例。这些 GNU 软体的线上说明文件可以在http://www.gnu.org/ 这个网站上找到。

6. 结语

    经由 Autoconf 及 Automake 的辅助,产生一个 Makefile 似乎不再像以前那麽困难了,而使用 Autoconf 也使得我们在不同平台上或各家 Unix之间散播及编译程式变得简单,这对於在 Unix 系统上开发程式的人员来说减轻了许多负担。妥善运用这些 GNU 的工具软体,可
以帮助我们更容易去发展程式,而且更容易维护原始程式码。

    一九九八年是 Open Source 运动风起云涌的一年,许多 Open Source 的软体普遍受到网路上大众的欢迎和使用。感谢所有为 Open Source 奉献的人们,也希望藉由本文能吸引更多的人加入『自由』、『开放』的 OpenSource 行列。

About this document ...

轻轻松松产生 Makefile1

This document was generated using the LaTeX2HTML translator Version 98.2 beta6 (August 14th, 1998) Copyright (C) 1993, 1994, 1995, 1996, Nikos Drakos, ComputerBased Learning Unit, University of Leeds.Copyright (C) 1997, 1998, Ross Moore, Mathematics Department,Macquarie University, Sydney.

The command line arguments were:
latex2html -split 0 -show_section_numbers automake.tex
The translation was initiated by on 1999-02-11

Footnotes
... itle1
本文件使用 ChiLaTeX 制作。
... CLE2
CLE (Chinese Linux Extension,Linux 中文延伸套件),
http://cle.linux.org.tw/

... Standards3
GNU Coding Standards, Richard Stallman.

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
作者:胡彦 2013-5-21 本文档可能有更新,更新版本请留意http://blog.csdn.net/huyansoft/article/details/8924624 一 目的:编写一个实际可用的makefile,能自动编译当前目录下所有.c源文件,并且任何.c、.h或依赖的源文件被修改后,能自动重编那些改动了的源文件,未改动的不编译。 二 要达到这个目的,用到的技术有: 1-使用wildcard函数来获得当前目录下所有.c文件的列表。 2-make的多目标规则。 3-make的模式规则。 4-用gcc -MM命令得到一个.c文件include了哪些文件。 5-用sed命令对gcc -MM命令的结果作修改。 6-用include命令包含依赖描述文件.d。 三 准备知识 (一)多目标 对makefile里下面2行,可看出多目标特征,执行make bigoutput或make littleoutput可看到结果: bigoutput littleoutput: defs.h pub.h @echo $@ $(subst output,OUTPUT,$@) $^ # $@指这个规则里所有目标的集合,$^指这个规则里所有依赖的集合。该行是把目标(bigoutput或littleoutput)里所有子串output替换成大写的OUTPUT (二)隐含规则 对makefile里下面4行,可看出make的隐含规则,执行foo可看到结果: 第3、4行表示由.c得到.o,第1、2行表示由.o得到可执行文件。 如果把第3、4行注释的话,效果一样。 即不写.o来自.c的规则,它会自动执行gcc -c -o foo.o foo.c这条命令,由.c编译出.o(其中-c表示只编译不链接),然后自动执行gcc -o foo foo.o链接为可执行文件。 foo:foo.o gcc -o foo foo.o; ./foo foo.o:foo.c #注释该行看效果 gcc -c foo.c -o foo.o #注释该行看效果 (三)定义模式规则 下面定义了一个模式规则,即如何由.c文件生成.d文件的规则。 foobar: foo.d bar.d @echo complete generate foo.d and bar.d %.d: %.c #make会对当前目录下每个.c文件,依次做一次里面的命令,从而由每个.c文件生成对应.d文件。 @echo from $< to $@ g++ -MM $ $@ 假定当前目录下有2个.c文件:foo.c和bar.c(文件内容随意)。 验证方法有2种,都可: 1-运行make foo.d(或make bar.d),表示想要生成foo.d这个目标。 根据规则%.d: %.c,这时%匹配foo,这样%.c等于foo.c,即foo.d这个目标依赖于foo.c。 此时会自动执行该规则里的命令gcc -MM foo.c > foo.d,来生成foo.d这个目标。 2-运行make foobar,因为foobar依赖于foo.d和bar.d这2个文件,即会一次性生成这2个文件。 四 下面详述如何自动生成依赖性,从而实现本例的makefile。 (一) 本例使用了makefile的模式规则,目的是对当前目录下每个.c文件,生成其对应的.d文件,例如由main.c生成的.d文件内容为: main.o : main.c command.h 这里指示了main.o目标依赖于哪几个源文件,我们只要把这一行的内容,通过make的include指令包含到makefile文件里,即可在其任意一个依赖文件被修改后,重新编译目标main.o。 下面详解如何生成这个.d文件。 (二) gcc/g++编译器有一个-MM选项,可以对某个.c/.cpp文件,分析其依赖的源文件,例如假定main.c的内容为: #include //标准头文件(以方式包含的),被-MM选项忽略,被-M选项收集 #include "stdlib.h"//标准头文件(以""方式包含的),被-MM选项忽略,被-M选项收集 #include "command.h" int main() { printf("##### Hello Makefile #####\n"); return 0; } 则执行gcc -MM main.c后,屏幕输出: main.o: main.c command.h 执行gcc -M main.c后,屏幕输出: main.o: main.c /usr/include/stdio.h /usr/include/features.h \ /usr/include/bits/predefs.h /usr/include/sys/cdefs.h \ /usr/include/bits/wordsize.h /usr/include/gnu/stubs.h \ /usr/include/gnu/stubs-64.h \ /usr/lib/gcc/x86_64-linux-gnu/4.4.3/include/stddef.h \ /usr/include/bits/types.h /usr/include/bits/typesizes.h \ /usr/include/libio.h /usr/include/_G_config.h /usr/include/wchar.h \ /usr/lib/gcc/x86_64-linux-gnu/4.4.3/include/stdarg.h \ /usr/include/bits/stdio_lim.h /usr/include/bits/sys_errlist.h \ /usr/include/stdlib.h /usr/include/sys/types.h /usr/include/time.h \ /usr/include/endian.h /usr/include/bits/endian.h \ /usr/include/bits/byteswap.h /usr/include/sys/select.h \ /usr/include/bits/select.h /usr/include/bits/sigset.h \ /usr/include/bits/time.h /usr/include/sys/sysmacros.h \ /usr/include/bits/pthreadtypes.h /usr/include/alloca.h command.h (三) 可见,只要把这些行挪到makefile里,就能自动定义main.c的依赖是哪些文件了,做法是把命令的输出重定向到.d文件里:gcc -MM main.c > main.d,再把这个.d文件include到makefile里。 如何include当前目录每个.c生成的.d文件: sources:=$(wildcard *.c) #使用$(wildcard *.cpp)来获取工作目录下的所有.c文件的列表。 dependence=$(sources:.c=.d) #这里,dependence是所有.d文件的列表.即把串sources串里的.c换成.d。 include $(dependence) #include后面可以跟若干个文件名,用空格分开,支持通配符,例如include foo.make *.mk。这里是把所有.d文件一次性全部include进来。注意该句要放在终极目标all的规则之后,否则.d文件里的规则会被误当作终极规则了。 (四) 现在main.c command.h这几个文件,任何一个改了都会重编main.o。但是这里还有一个问题,如果修改了command.h,在command.h中加入#include "pub.h",这时: 1-再make,由于command.h改了,这时会重编main.o,并且会使用新加的pub.h,看起来是正常的。 2-这时打开main.d查看,发现main.d中未加入pub.h,因为根据模式规则%.d: %.c中的定义,只有依赖的.c文件变了,才会重新生成.d,而刚才改的是command.h,不会重新生成main.d、及在main.d中加入对pub.h的依赖关系,这会导致问题。 3-修改新加的pub.h的内容,再make,果然问题出现了,make报告up to date,没有像期望那样重编译main.o。 现在问题在于,main.d里的某个.h文件改了,没有重新生成main.d。进一步说,main.d里给出的每个依赖文件,任何一个改了,都要重新生成这个main.d。 所以main.d也要作为一个目标来生成,它的依赖应该是main.d里的每个依赖文件,也就是说make里要有这样的定义: main.d: main.c command.h 这时我们发现,main.d与main.o的依赖是完全相同的,可以利用make的多目标规则,把main.d与main.o这两个目标的定义合并为一句: main.o main.d: main.c command.h 现在,main.o: main.c command.h这一句我们已经有了,如何进一步得到main.o main.d: main.c command.h呢? (五) 解决方法是行内字符串替换,对main.o,取出其中的子串main,加上.d后缀得到main.d,再插入到main.o后面。能实现这种替换功能的命令是sed。 实现的时候,先用gcc -MM命令生成临时文件main.d.temp,再用sed命令从该临时文件中读出内容(用输出到最终文件main.d。 命令可以这么写: g++ -MM main.c > main.d.temp sed 's,\(main\)\.o[ :]*,\1.o main.d : ,g' main.d 其中: sed 's,\(main\)\.o[ :]*,\1.o main.d : ,g',是sed命令。 main.d,把行内替换结果输出到最终文件main.d。 (六) 这条sed命令的结构是s/match/replace/g。有时为了清晰,可以把每个/写成逗号,即这里的格式s,match,replace,g。 该命令表示把源串内的match都替换成replace,s指示match可以是正则表达式。 g表示把每行内所有match都替换,如果去掉g,则只有每行的第1处match被替换(实际上不需要g,因为一个.d文件中,只会在开头有一个main.o:)。 这里match是正则式\(main\)\.o[ :]*,它分成3段: 第1段是\(main\),在sed命令里把main用\(和\)括起来,使接下来的replace中可以用\1引用main。 第2段是\.o,表示匹配main.o,(这里\不知何意,去掉也是可以的)。 第3段是正则式[ :]*,表示若干个空格或冒号,(其实一个.d里只会有一个冒号,如果这里写成[ ]*:,即匹配若干个空格后跟一个冒号,也是可以的)。 总体来说match用来匹配'main.o :'这样的串。 这里的replace是\1.o main.d :,其中\1会被替换为前面第1个\(和\)括起的内容,即main,这样replace值为main.o main.d : 这样该sed命令就实现了把main.o :替换为main.o main.d :的目的。 这两行实现了把临时文件main.d.temp的内容main.o : main.c command.h改为main.o main.d : main.c command.h,并存入main.d文件的功能。 (七) 进一步修改,采用自动化变量。使得当前目录下有多个.c文件时,make会依次对每个.c文件执行这段规则,生成对应的.d: gcc -MM $ [email protected]; sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' $@; (八) 现在来看上面2行的执行流程: 第一次make,假定这时从来没有make过,所有.d文件不存在,这时键入make: 1-include所有.d文件的命令无效果。 2-首次编译所有.c文件。每个.c文件中若#include了其它头文件,会由编译器自动读取。由于这次是完整编译,不存在什么依赖文件改了不会重编的问题。 3-对每个.c文件,会根据依赖规则%.d: %.c,生成其对应的.d文件,例如main.c生成的main.d文件为: main.o main.d: main.c command.h 第二次make,假定改了command.h、在command.h中加入#include "pub.h",这时再make: 1-include所有.d文件,例如include了main.d后,得到依赖规则: main.o main.d: main.c command.h 注意所有include命令是首先执行的,make会先把所有include进来,再生成依赖规则关系。 2-此时,根据依赖规则,由于command.h的文件戳改了,要重新生成main.o和main.d文件。 3-先调用gcc -c main.c -o main.o生成main.o, 再调用gcc -MM main.c > main.d重新生成main.d。 此时main.d的依赖文件里增加了pub.h: main.o main.d: main.c command.h pub.h 4-对其它依赖文件没改的.c(由其.d文件得到),不会重新编译.o和生成其.d。 5-最后会执行gcc $(objects) -o main生成最终可执行文件。 第三次make,假定改了pub.h,再make。由于第二遍中,已把pub.h加入了main.d的依赖,此时会重编main.c,重新生成main.o和main.d。 这样便实现了当前目录下任一源文件改了,自动编译涉及它的.c。 (九) 进一步修改,得到目前大家普遍使用的版本: set -e; rm -f $@; \ $(CC) -MM $(CPPFLAGS) $ $@.$$$$; \ sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' $@; \ rm -f $@.$$$$ 第一行,set -e表示,如果某个命令的返回参数非0,那么整个程序立刻退出。 rm -f用来删除上一次make时生成的.d文件,因为现在要重新生成这个.d,老的可以删除了(不删也可以)。 第二行:前面临时文件是用固定的.d.temp作为后缀,为了防止重名覆盖掉有用的文件,这里把temp换成一个随机数,该数可用$$得到,$$的值是当前进程号。 由于$是makefile特殊符号,一个$要用$$来转义,所以2个$要写成$$$$(你可以在makefile里用echo $$$$来显示进程号的值)。 第三行:sed命令的输入也改成该临时文件.$$。 每个shell命令的进程号通常是不同的,为了每次调用$$时得到的进程号相同,必须把这4行放在一条命令中,这里用分号把它们连接成一条命令(在书写时为了易读,用\拆成了多行),这样每次.$$便是同一个文件了。 你可以在makefile里用下面命令来比较: echo $$$$ echo $$$$; echo $$$$ 第四行:当make完后,每个临时文件.d.$$,已经不需要了,删除之。 但每个.d文件要在下一次make时被include进来,要保留。 (十) 综合前面的分析,得到我们的makefile文件: #使用$(wildcard *.c)来获取工作目录下的所有.c文件的列表 sources:=$(wildcard *.c) objects:=$(sources:.c=.o) #这里,dependence是所有.d文件的列表.即把串sources串里的.c换成.d dependence:=$(sources:.c=.d) #所用的编译工具 CC=gcc #当$(objects)列表里所有文件都生成后,便可调用这里的 $(CC) $^ -o $@ 命令生成最终目标all了 #把all定义成第1个规则,使得可以把make all命令简写成make all: $(objects) $(CC) $^ -o $@ #这段是make的模式规则,指示如何由.c文件生成.o,即对每个.c文件,调用gcc -c XX.c -o XX.o命令生成对应的.o文件。 #如果不写这段也可以,因为make的隐含规则可以起到同样的效果 %.o: %.c $(CC) -c $< -o $@ include $(dependence) #注意该句要放在终极目标all的规则之后,否则.d文件里的规则会被误当作终极规则了 %.d: %.c set -e; rm -f $@; \ $(CC) -MM $(CPPFLAGS) $ $@.$$$$; \ sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' $@; \ rm -f $@.$$$$ .PHONY: clean #之所以把clean定义成伪目标,是因为这个目标并不对应实际的文件 clean: rm -f all $(objects) $(dependence) #清除所有临时文件:所有.o和.d。.$$已在每次使用后立即删除。-f参数表示被删文件不存在时不报错 (十一) 上面这个makefile已经能正常工作了(编译C程序),但如果要用它编译C++,变量CC值要改成g++,每个.c都要改成.cpp,有点繁琐。 现在我们继续完善它,使其同时支持C和C++,并支持二者的混合编译。 #一个实用的makefile,能自动编译当前目录下所有.c/.cpp源文件,支持二者混合编译 #并且当某个.c/.cpp、.h或依赖的源文件被修改后,仅重编涉及到的源文件,未涉及的不编译 #详解文档:http://blog.csdn.net/huyansoft/article/details/8924624 #author:胡彦 2013-5-21 #---------------------------------------------------------- #编译工具用g++,以同时支持C和C++程序,以及二者的混合编译 CC=g++ #使用$(winldcard *.c)来获取工作目录下的所有.c文件的列表 #sources:=main.cpp command.c #变量sources得到当前目录下待编译的.c/.cpp文件的列表,两次调用winldcard、结果连在一起即可 sources:=$(wildcard *.c) $(wildcard *.cpp) #变量objects得到待生成的.o文件的列表,把sources中每个文件的扩展名换成.o即可。这里两次调用patsubst函数,第1次把sources中所有.cpp换成.o,第2次把第1次结果里所有.c换成.o objects:=$(patsubst %.c,%.o,$(patsubst %.cpp,%.o,$(sources))) #变量dependence得到待生成的.d文件的列表,把objects中每个扩展名.o换成.d即可。也可写成$(patsubst %.o,%.d,$(objects)) dependence:=$(objects:.o=.d) #---------------------------------------------------------- #当$(objects)列表里所有文件都生成后,便可调用这里的 $(CC) $^ -o $@ 命令生成最终目标all了 #把all定义成第1个规则,使得可以把make all命令简写成make all: $(objects) $(CC) $(CPPFLAGS) $^ -o $@ @./$@ #编译后立即执行 #这段使用make的模式规则,指示如何由.c文件生成.o,即对每个.c文件,调用gcc -c XX.c -o XX.o命令生成对应的.o文件 #如果不写这段也可以,因为make的隐含规则可以起到同样的效果 %.o: %.c $(CC) $(CPPFLAGS) -c $< -o $@ #同上,指示如何由.cpp生成.o,可省略 %.o: %.cpp $(CC) $(CPPFLAGS) -c $< -o $@ #---------------------------------------------------------- include $(dependence) #注意该句要放在终极目标all的规则之后,否则.d文件里的规则会被误当作终极规则了 #因为这4行命令要多次凋用,定义成命令包以简化书写 define gen_dep set -e; rm -f $@; \ $(CC) -MM $(CPPFLAGS) $ $@.$$$$; \ sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' $@; \ rm -f $@.$$$$ endef #指示如何由.c生成其依赖规则文件.d #这段使用make的模式规则,指示对每个.c文件,如何生成其依赖规则文件.d,调用上面的命令包即可 %.d: %.c $(gen_dep) #同上,指示对每个.cpp,如何生成其依赖规则文件.d %.d: %.cpp $(gen_dep) #---------------------------------------------------------- #清除所有临时文件(所有.o和.d)。之所以把clean定义成伪目标,是因为这个目标并不对应实际的文件 .PHONY: clean clean: #.$$已在每次使用后立即删除。-f参数表示被删文件不存在时不报错 rm -f all $(objects) $(dependence) echo: #调试时显示一些变量的值 @echo sources=$(sources) @echo objects=$(objects) @echo dependence=$(dependence) @echo CPPFLAGS=$(CPPFLAGS) #提醒:当混合编译.c/.cpp时,为了能够在C++程序里调用C函数,必须把每一个要调用的C函数,其声明都包括在extern "C"{}块里面,这样C++链接时才能成功链接它们。 五 makefile学习体会: 刚学过C语言的读者,可能会觉得makefile有点难,因为makefile不像C语言那样,一招一式都那么清晰明了。 在makefile里到处是“潜规则”,都是一些隐晦的东西,要弄明白只有搞清楚这些“潜规则”。 基本的规则无非是“一个依赖改了,去更新哪些目标”。 正因为隐晦动作较多,写成一个makefile才不需要那么多篇幅,毕竟项目代码才是主体。只要知道makefile的框架,往它的套路里填就行了。 较好的学习资料是《跟我一起写Makefile.pdf》这篇文档(下载包里已经附带了),比较详细,适合初学者。 我们学习的目的是,能够编写一个像本文这样的makefile,以满足简单项目的基本需求,这要求理解前面makefile几个关键点: 1-多目标 2-隐含规则 3-定义模式规则 4-自动生成依赖性 可惜的是,这篇文档虽然比较全面,却没有以一个完整的例子为引导,对几处要点没有突出指明,尤其是“定义模式规则”在最后不显眼的位置(第十一部分第五点),导致看了“自动生成依赖性”一节后还比较模糊。 所以,看了《跟我一起写Makefile.pdf》后,再结合本文针对性的讲解,会有更实际的收获。 另一个学习资料是《GNU make v3.80中文手册v1.5.pdf》,这个手册更详细,但较枯燥,不适合完整学习,通常是遇到问题再去查阅。 其它文章和代码请留意我的blog: http://blog.csdn.net/huyansoft [END]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值