Makefile学习

在Linux(unix)环境下使用GNU的make工具能够比较容易的构建一个属于你自己的工程,整个工程的编译只需要一个命令就可以完成编译、连接以至于最后的执行。

make是一个命令工具,它解释Makefile中的指令(应该说是规则)。在Makefile文件中描述了整个工程所有文件的编译顺序、编译规则。Makefile有自己的书写格式、关键字、函数。像C语言有自己的格式、关键字和函数一样。而且在Makefile中可以使用系统shell所提供的任何命令来完成想要的工作。

Makefile(在其它的系统上可能是另外的文件名)在绝大多数的IDE开发环境中都在使用,已经成为一种工程的编译方法。

1 基础

  • 首先需要了解常见的编译器选项:以gcc编译器为例:

    • -c,仅编译不链接; -I,指定头文件路径(include);-L,指定依赖库路径;-l,指定链接库的名字,如libc.a这一名称的库可用-lc来引用;

    • -o,指定输出;-O,优化的4级别(0,1,2,3,常用-O2);-g,产生调试信息;-static,禁用动态库;-share,生成一个共享目标文件,他可以和其他目标文件连接产生可执行文件.只有部分系统支持该选项;

    • -D,定义比较重要的宏,如-DDEBUG=1,可配合程序中类似#if DEBUG语句使用;-w,关闭编译时警告;-Wall,编译后显示所有警告;

  • 简单规则:
    target ... : prerequisites ...
    #recipe前面一定要是一个Tab,不是空格
      recipe
    clean:
      recipe
  • 简单例子:main.cdefs.hfunc.c,其中两个.c文件都依赖defs.h

    #反斜线(用于换行)后不要加空格
    Name: main.o \
      func.o      #这里用空格就行
      gcc -o main.o func.o # 命令前是Tab
    main.o: main.c defs.h
      gcc -c main.c
    func.o: func.c defs.h
      gcc -c func.c
    clean:          #这个target没有依赖
      rm Name main.o func.o
  • 变量的使用

    objects= main.o func.o
    Name: $(objects)
      gcc -o ${objects}   #小括号和花括号都行,但必须要加
  • 一条默认规则: 一个N.o文件默认依赖N.c,可以不用写出,故上面例子简化成:

    Name: main.o \
      func.o      #这里用空格就行
      gcc -o main.o func.o # 命令前是Tab
    #书写建议:单目标、多依赖
    main.o: defs.h
    func.o: defs.h
    .PHONY:clean        #将clean声明为伪目标,避免存在名为clean的文件
    clean:
      -rm Name main.o func.o
    # '-':表示忽略rm的执行错误

2 进阶

  • include FILENAME: 暂停当前Makefile读取,完成指定文件读取后再继续

    # VPATH, 指定依赖文件的搜索路径
    VPATH=../src:../headers
    # 使用空格或冒号可将多个目录分开
    include ../src/Makefile
  • 通配符(如*.c)的使用,限于规则的目标依赖命令

  • 变量定义中应使用wildcard展开通配符

    # :=表示不能使用后面定义的变量
    OBJS:=$(wildcard *.o)
    # 高级用法,先获取所有.c文件,再将变量中的.c后缀替换为.o
    OBJS:=$(patsubst %.c,%.o,$(wildcard *.c))
  • ?=操作符,表示只有在之前没有赋值的情况下才会对此变量进行赋值

  • 定义变量时注释和定义要分行写,否则注释前的空格会被作为变量值的一部分

    dir := /foo/bar    # directory to ...
    # 变量dir的值是"/foo/bar    "(后面有四个空格)
  • 变量的替换引用

    # 表示替换foo中所有以.o结尾的字为.c结尾
    bar:=$(foo:.o=.c)
  • $^ 代表所有通过目录搜索得到的依赖文件的完整路径名(目录 + 一般文件名)列表;$@ 代表规则的目标名;$< 代表规则中通过目录搜索得到的依赖文件列表的第一个依赖文件

  • 链接静态库、共享库

    # 表示生成目标时需要链接libcurses.a或libcurses.so文件
    foo : foo.c -lcurses
      cc $^ -o $@
  • 命令回显:make执行命令时会默认将其输出到shell

    # 用@字符后不会回显命令,但仍会输出命令运行结果
    @echo 开始编译...
  • 命令的执行:每一行的命令执行相互独立,除非用\将一行分两行写

  • 命令前加-字符,表示即使命令执行失败,make仍继续执行

  • make中的条件语句

    ifeq ($(CC),gcc)
    libs=$(libs_for_gcc)
    else
    libs=$(normal_libs)
    endif
    foo: $(objects)
      $(CC) -o foo $(objects) $(libs)
  • make中的循环语句

    dirs:=a b c d
    files := $(foreach dir, $(dirs) , $(wildcard $(dir)/*))
    #等价于:
    files := $(wildcard a/* b/* c/* d/*)
  • shell函数的使用

    files:=$(shell echo *.c)
    # 变量“ files”赋值为当前目录下所有.c文件的列表(文件名之间使用空格分割),等效于$(wildcard *.c)

3 深入

  • 自动找寻源文件中包含的头文件,并生成文件的依赖关系

    gcc -M main.c
    # 不考虑标准库头文件时,用-MM参数
  • 自动生成每一个.c文件对应的.d文件(包含相应依赖文件描述)

    %.d: %.cpp
    # 1. 使用编译器自自动生成依赖文件($<)的头文件的依赖关系,并输出成为一个临时文件
    # 2. 使用sed处理第二行已产生的那个临时文件并生成此规则的目标文件。(将.d加入到规则的目标中)
    # 3. 删除临时文件
      $(CXX) -MM $(CXXFLAGS) $< > $@.$$$$; \
      sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.$$$$ > $@; \
      rm -f $@.$$$$
  • .d文件的处理(接着上面一条)

    SRCS = $(wildcard ./*.cpp)
    dependence:=$(SRCS:.cpp=.d)
    sinclude $(dependence)
    # sinclude相当于 -include,(和其它版本make兼容),表示无论include过程中出现什么错误,都不要报错继续执行。
  • order-only依赖使用

    LIBS=libtest.a
    foo: foo.c|$(LIB)
      $(CC) $(CFLAGS) $< -o $@ $(LIBS)
    # 如果foo已存在(前提),若libtest.a被修改,将不会重建foo
  • filter分类函数使用

    files = foo.elc bar.o lose.o
    $(filter %.o,$(files)): %.o: %.c
      $(CC) -c $(CFLAGS) $< -o $@
    $(filter %.elc,$(files)): %.elc: %.el
      emacs -f batch-byte-compile $<
  • 没卵用的小技巧

    var := one$\
         word
    # 上面的内容等价于:
    var := one$ word
    # '$ '($后面有一个空格): a variable named ' '
    # 又等价于:
    var := oneword
  • 一个例子

    #一个实用的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)
Author: zcp
Reprint policy: All articles in this blog are used except for special statements CC BY 4.0 reprint polocy. If reproduced, please indicate source zcp !
评论
 Previous
NAS可以外网访问咯
1 前言虽然这篇post的主题是NAS(Network Attached Storage),但因为懒还是放在了VPS(virtual private server)目录里。上班后发现家里的黑群晖处于长期闲置状态,虽然一直开着机但在
Next 
新的开始
好久没更新点东西了,4月份一直在写论文,5-6月在改论文、准备答辩(顺便沉迷了下流放者柯南这个游戏),6月底毕业收拾行李准备工作的时候才忽然发现、自己的学生时代似乎已经结束了(虽然N年后可能考虑去读个博) 前些日子称体重发现这3个月自己胖了
  TOC