• Linux之Makefile


    Makefile文件的作用是指导make程序该如何工作。

    make的工作原理

    当我们只输入make命令的工作流程是:
    1. make会在当前目录下找名字叫“Makefile”或“makefile”的文件;
    2. 如果找到,它会找文件中的第一个目标文件(target),在上面的例子中,他会找到“main”这个文件,并把这个文件作为最终的目标文件;
    3. 如果main文件不存在,或是main所依赖的后面的 .o 文件的文件修改时间要比main这个文件新,那么,make会执行下面定义的命令来生成main文件;
    4. 如果main所依赖的.o文件也存在,那么make会在当前文件中找目标为.o文件的依赖性,如果找到再根据命令生成.o文件(这是一个递归的过程);


    如果在找寻的过程中,出现了被依赖的文件找不到的错误,那么make就会直接退出,并报错。

    如果在一条依赖链中,比如:A依赖B,B依赖C,C依赖D。那么当D更新后,make发现D比C新则会重新构建C,以此类推,最终A也会被更新。

    Makefile文件的语法组成

    基本的结构形式:

    1 target: prerequisites
    2     command
    3     command
    4     ...

    说明:
    target:可以是任何类型的文件,也可以是一个标签(Label),或叫作“伪目标”,这个我们一会儿再讲。
    prerequisites:就是要生成target所需要的文件、目标。
    command:当prerequisites比target要新,就会执行这里定义的动作(任意的Shell命令)


    其实就是一个文件的依赖关系处理,也就是说,target目标依赖于prerequisites中的文件,其生成规则定义在command中。说白一点就是说,prerequisites中如果有任何一个及以上的文件比target文件要新的话,command所定义的命令就会被执行。这就是Makefile的规则。也是Makefile中最核心的内容。

    Makefile中使用变量

    我们通过脚本实验来了解定义变量的几种形式:

     1 .PHONY: target
     2 
     3 VAR_0:=$(VAR)
     4 VAR_1=$(VAR)
     5 VAR="hello world"
     6 VAR2:=$(VAR)
     7 VAR="hello world2"
     8 VAR3="hello world3"
     9 VAR3?=$(VAR)
    10 VAR_0+="abc"
    11 VAR_1+="abc"
    12 
    13 target:
    14     @echo $(VAR_0)
    15     @echo $(VAR_1)
    16     @echo $(VAR)
    17     @echo $(VAR2)
    18     @echo $(VAR3)

    $ make
    abc
    hello world2 abc
    hello world2
    hello world
    hello world3

    =   直接赋值,比较直观。值得说的是当赋值的是变量时,如果引用的变量不存在,那么赋值的是空字符串。
    :=  延迟引用变量,也就是说只有当脚本中使用到VAR2变量时,make才会去找被引用的VAR变量的值。
    ?= 条件赋值,被称为条件赋值是因为:只有此变量在之前没有被赋值的情况下才会对这个变量进行赋值。
    += 追加赋值,上面的例子的输出很明显了。

    Makefile中也可以直接使用shell进程的环境变量,比如可以在Makefile中输出@echo $(PATH)等等。

    make自动推导
    首先让我在本地创建几个文件:

    $ vim lib1.h

    1 #ifndef __LIB1_H__
    2 #define __LIB1_H__
    3 void lib1();
    4 #endif

    $ vim lib1.c

    1 #include <stdio.h>
    2 void lib1()
    3 {
    4     printf("this is lib1
    ");
    5 }

    $ vim lib2.h

    1 #ifndef __LIB2_H__
    2 #define __LIB2_H__
    3 void lib2();
    4 #endif

    $ vim lib2.c

    1 #include <stdio.h>
    2 void lib2()
    3 {
    4     printf("this is lib2
    ");
    5 }

    $ vim main.c

    1 #include "lib1.h"
    2 #include "lib2.h"
    3 
    4 int main(int argc, char *argv[]) {
    5     lib1();
    6     lib2();
    7     return 0;
    8 }

    $ vim Makefile

     1 .PHONY: clean
     2 
     3 CC=gcc
     4 CFLAGS=-O3
     5 OBJS=main.o lib1.o lib2.o
     6 LIB=libtest.a
     7 BIN=main
     8 
     9 $(BIN): $(LIB) $(BIN).o 
    10     $(CC) $(CFLAGS) -o $@ $(BIN).o -L. -Wl,-Bstatic -ltest  -Wl,-Bdynamic
    11     echo $?
    12 
    13 %.o: %.c 
    14     $(CC) -c -o $*.o $*.c
    15 
    16 $(LIB): lib1.o lib2.o
    17     ar crv $@ $^
    18 
    19 clean:
    20     rm -rf $(BIN)
    21     rm -rf $(OBJS)
    22     rm -rf $(LIB)

    $ make

    大家可以自行改动代码进行测试!

    说一下.PHONY的作用,.PHONY后面写的是伪目标,也就是说这种目标只是占用一个符号一个名字而已,无论当前目录下是否有clean文件,不会对比是否最新,只要执行make clean,clean目标下面定义的命令永远都会执行!


    $@:表示目标文件名称
    $<:prerequisites依赖列表中的第一个依赖的名字
    $?:所有比目标新的依赖文件名称的集合,以空格分隔
    $^:当前目标中依赖的所有文件,它并不关心这些文件是不是比目标文件新。然而,重复的依赖文件名会被移除。这会在你需要将所有的依赖文件输出到屏幕时变得非常有用
    $+:很像$^,也是所有依赖文件的集合,但是它不去除重复的依赖
    $*:匹配目标模式中“%”之前的部分


    好方法和技巧:
    1. 引入外部Makefile
    2. 变量值替换
    从一个已有的宏创建一个新宏并非不可能。例如宏SRC代表一系列的源文件,你希望生成一个对应的目标文件宏OBJ。要这样做,你只需要指定OBJ = SRC,除了扩展名不同以外:OBJ = $(SRC:.c=.o)


    陷阱:
    1. 环境变量 MAKEFILES
    2. 万能通配符的陷阱
    3. 环境变量 VPATH

    本文会继续不断打磨、完善!

  • 相关阅读:
    Docker 基础 : 数据管理
    linux sudo 命令
    TeamCity : .NET Core 插件
    C# 文件下载之断点续传
    TeamCity : 配置 Build 过程
    TeamCity : Build 版本控制系统配置
    Git : SSH 协议服务器
    TeamCity : Build 基本配置
    C# 文件下载 : WinINet
    [翻译] TSMessages
  • 原文地址:https://www.cnblogs.com/yuyifeiyang/p/9455809.html
Copyright © 2020-2023  润新知