美文网首页
makefile 日常使用

makefile 日常使用

作者: wayyyy | 来源:发表于2018-01-16 15:45 被阅读0次

通常一个大型程序由多个模块文件构成的,按照其功能划分,模块文件会分布在不同的目录中,模块文件之间有包含有头文件,调用函数的情况,他们之间存在依赖关系。

大多数情况下,我们只是修改了其中某些文件,而不是所有文件,按理说,我们只需要把那些修改过的文件,并且依赖于这些文件的相关文件重新编译即可,用不着重新编译全部文件。

所以问题来了,有没有办法自动对那些有改过的文件重新编译呢?这实际上要分为 2 个问题:

  • 目标文件依赖哪些文件?
  • 依赖的文件是否更新?

为了解决这2个问题,业界就有了 make 和 makefile。总的来说,它俩并不是用来编译程序,它们只负责找出哪些文件变化,并且根据依赖关系找出受影响的文件,然后执行事先在 makefile 中定义好的命令规则。只是该规则大多数情况是调用 gcc 或 nasm 进行编译。因此这也就完成了我们上面提到的 2 个问题。

makefile 基本语法

目标文件:依赖文件
[tab] 命令

eg:
a.o: a.c
  gcc -c a.c -o a.o
a.out: a.o
  gcc a.o -o a.out
  • 目标文件是指此规则中想要生成的文件,可以是 .o 结尾的目标文件,也可以是可执行文件,也可以是伪目标。
  • 依赖文件是指要生成此规则中的目标文件。通常不止一个,是一个依赖文件的列表。
  • 命令是指此规则中要执行的动作,各种 shell命令,一个命令单独占用一行,行首必须以 Tab 开头。

根据以上规则,我们已经知道了目标文件依赖哪些文件,但还需要解决第2个问题。怎么判断依赖的文件是否更新?

在 Linux 中,每个文件有3种时间:

  • atime
    access time 访问文件数据部分时间
  • ctime
    change time 文件属性部分或数据部分的改变时间
  • mtime
    modify time 文件数据部分的修改时间
1:2
    @echo "makefile test ok"

这个makefile文件的意义就是 " 如果文件2的 mtime 比文件1的 mtime 要新,则打印 "makefile test ok" "。

综上,依赖关系定义在文件 makefile 中,make 程序通过解析 makefile 文件,根据mtime标签自动找出 变更的文件 以及 依赖此变更文件的目标文件,然后对所有受影响的相关文件执行事先定义好的命令规则。

makefile 的文件名并不固定,可以用 -f 参数指定。默认是先寻找名为 GNUmakefile 的文件,若该文件不存在再去找名为 makefile 的文件,若 makefile 也不存在,最后去找名为 Makefile 的文件。

t1:1
  @echo "target1"
t2:1
  @echo "target2"

若采用make 目标名称,比如make t2,则会单独执行目标名称处的规则。
若采用make ,则会单独执行在 makefile 中第一个出现的目标。

变量

= := ?= 区别
目标变量

伪目标

有时我们有"并不关心是否产生真实的目标文件,我们只希望通过 make 不要考虑 mtime,而是总能去执行一些命令",比如

clean:
    rm *.o     # 清理文件

这时,make规定,当规则中不存在依赖文件时,这个目标文件名就称为 -- 伪目标。

  • .PHONY

  • 约定俗成的伪目标名称

伪目标名称 功能描述
all 通常用于完成所有模块的编译工作
clean 通常用于清空编译完成的所有目标文件
dist 将打包文件再压缩成gz 文件
Install 通常将编译好的程序复制到安装目录下
printf 通常用于打印已经发生改变的文件
tar 通常用于将文件打包成tar文件
test 测试makefile 流程

make: 递归式推导目标

test1.o : test1.c
  gcc -c test1.c -o test1.o

test2.o : test2.c
  gcc -c test2.c -o test2.o

test.bin : test1.o test2.o
  gcc test1.o test2.o -o test.bin

all : test.bin
  @echo "compile done"

当执行make all时,make 首先发现 all 依赖于 test.bin,寻找 test.bin 不存在,于是便去寻找 test.bin的依赖文件 test1.o test2.o,同样不存在,于是寻找能生成 test1.o 和 test2.o 规则。找到后执行相应规则,有了test1.o 和 test2.o 再返回生成 test.bin,最后在执行生成 all 的规则。

自定义变量与系统变量

makefile中定义变量基本格式:

变量名=值    # 多个值之间用空格分开,值仅仅支持字符串类型。即使数字也会当作字符串处理
eg:

objfiles = test1.o test2.o
test.bin: $(objfiles)

除了自定义的变量,make也定义了很多系统变量

变量名 描述
AS 汇编语言编译器,默认是 as
CC C语言编译器,默认是 gcc
CXX C++语言编译器,默认 g++
RM 删除命令,默认是 rm -f
ASFLAGS 汇编语言编译器参数,无默认值
CFLAGS C语言编译器参数,无默认值
CXXFLAGS C++编译器参数,无默认值
LDFLAGS 链接器参数,无默认值

隐含规则

对于一些使用频率非常高的规则,make 把它们当成是默认的,不需要显式地写出来,当用户未在 makefile 中显示定义规则时,将默认使用隐含规则进行推导。

隐含规则只限于那些编译过程中基本固定的依赖关系,比如C语言代码文件扩展名为.c,编译生成的目标文件扩展名是.o。并且若想通过隐含规则自动推导生成目标,存在于文件系统上的文件,除扩展名之外的文件名部分必须相同。

自动化变量

为了方便,make 还支持一种自动化变量:

自动化变量 描述
$@ 表示规则中目标文件名集合
$< 表示规则中依赖文件中的第1个文件
$^ 表示规则中所有依赖文件的集合
$? 表示规则中所有比目标文件mtime更新的依赖文件的集合
test1.o : test1.c
  gcc -c test1.c -o test1.o

test2.o : test2.c
  gcc -c test2.c -o test2.o

objfiles = test1.o test2.o
test.bin : $(objfiles)
  gcc $^ -o $@

all : test.bin
  @echo "compile done"

再方便的就是利用正则表达式匹配,%.o 表示多有以 .o 结尾的文件,make 会拿这个字符串模式去文件系统上查找文件,默认当前路径。

%.o : %.c  # test1.c test2.c 都在当前目录。
  gcc -c $^ -o $@

objfiles = test1.o test2.o
test.bin : $(objfiles)
  gcc $^ -o $@

all : test.bin
  @echo "compile done"

makefile 中执行shell命令

SVN_VERSION:=$(shell svnversion)
COMP_DATE:=$(shell date)

makefile中常用函数

  • foreach
  • wildcard
  • notdir
    去除目录信息,只保留文件名。
  • patsubst
    替换。
example
  • 项目文件
    a.h
    a.cpp
    b.h
    b.cpp
    c.h
    Makefile
    
  • Makefile
    SRC_PATH=./
    SRC_FILE=$(foreach SUB_DIR, $(SRC_PATH), $(wildcard $(SUB_DIR)*.cpp))
    ALL_FILE=$(notdir $(SRC_FILE))
    # patsubst把$(ALL_FILE)中的变量符合后缀是.cpp的全部替换成.o
    OBJ_FILE=$(patsubst %.cpp, %.o, $(ALL_FILE))
    
    all:
        $(info $(SRC_FILE))
        $(info $(ALL_FILE))
        $(info $(OBJ_FILE))
    
    输出:
    ./b.cpp ./a.cpp
    b.cpp a.cpp
    b.o a.o
    

一个简单的Makefile模板

  • 项目
    --Makefile
    --h
    --lib
    --src
    
  • Makefile
    VERSION = 1.0.0   # 程序版本号 ​ 
    
    SOURCE = $(wildcard ./src/*.c)    # 获取所有的.c文件 
    OBJ = $(patsubst %.c, %.o, $(SOURCE)) # 将.c文件转为.o文件 
    INCLUDES = -I./h  # 头文件路径 ​ 
    
    LIBS = -ldylib    # 库文件名字 
    LIB_PATH = -L./lib    # 库文件地址 ​ 
    DEBUG = -D_MACRO  # 宏定义 
    
    CFLAGS = -Wall -c # 编译标志位 ​ 
    
    TARGET = app 
    CC = gcc
    
    $(TARGET): $(OBJ) 
        @mkdir -p output/     
        $(CC) $(OBJ) $(LIB_PATH) $(LIBS) -o output/$(TARGET).$(VERSION) ​ 
    
    %.o: %.c 
        $(CC) $(INCLUDES) $(DEBUG) $(CFLAGS) $< -o $@ ​ 
    
    .PHONY: clean 
    clean: 
        rm -rf $(OBJ) output/ 
    

相关文章

  • makefile 日常使用

    通常一个大型程序由多个模块文件构成的,按照其功能划分,模块文件会分布在不同的目录中,模块文件之间有包含有头文件,调...

  • Linux Kernel Makefiles特殊符号

    Makefile基本规则: Makefile文件中可使用特殊的符号简化Makefile文件的书写。 1、$@:表示...

  • Makefile 学习

    Makefile 一、Makefile 简单使用 Makefile是Linux系统下的一种编译脚本,更快、更方便的...

  • Makefile

    在使用Makefile 编译多个c文件时,出现 Makefile:2:*** missing separator....

  • Makefile的基本使用

    Makefile的基本使用 首先需要有一个Makefile文件然后执行make即可完成所有的Makefile中定义...

  • Makefile简笔记

    一、Makefile 变量 #Makefile变量的使用 objects = main.o input.o cal...

  • Makefile使用

    Makefile 简介 一个工程中的源文件不计其数,按其类型、功能、模块分别放在若干个目录中。makefile定义...

  • Mac 下 makefile编译使用

    依据makeFile文,编译包使用make命令,makefile文件所在目录下 文章:https://www.cn...

  • GitLab CI 实现 Golang 自动构建为 Docker

    实现目标 提交代码自动构建,自动打包为docker镜像 前期准备 Makefile 学习并使用 MakeFile ...

  • 迅为i.MX6ULL开发板按键例程编译及运行

    我们使用命令“vi Makefile”进入Makefile。如图 1所示: 添加内容如下。 第2行TARGET修改...

网友评论

      本文标题:makefile 日常使用

      本文链接:https://www.haomeiwen.com/subject/cvzcoxtx.html