0%

脚本语言之Makefile

本节主要介绍makefile相关的基本概念。

Makefile基础

  • Makefile由若干条规则(Rule)构成,每一条规则指出一个目标文件(Target),若干依赖文件(prerequisites),以及生成目标文件的命令。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    #makefile的核心逻辑是:默认把目标名当成“要生成的文件名”

    #.PHONY: all clean 是告诉 make:all 和 clean 是“伪目标”,不是要生成的真实文件名。
    #这样,无论是否存在名为 all 或 clean 的文件,make 都会执行对应的规则命令,而不会因为文件存在而跳过执行。这对于像 clean 这样的目标特别有用,因为它通常用于删除生成的文件,而不是生成一个新的文件。
    .PHONY: all clean

    all: clean x.txt cd

    #makefile默认执行第一条规则
    #这里clean相当于一个目标名,它没有依赖文件
    #然而,在执行clean时,我们并没有创建一个名为clean的文件,所以,因为目标文件clean不存在,每次运行make clean,都会执行这个规则的命令。
    clean:
    rm -f x.txt m.txt

    #目标文件:依赖文件1 依赖文件2
    #tab开头的是命令,用来生成目标文件,这里使用cat命令将m.txt和c.txt的内容合并到x.txt中
    x.txt: m.txt c.txt
    cat m.txt c.txt > x.txt

    m.txt: a.txt b.txt
    cat a.txt b.txt > m.txt

    #执行多条命令
    cd:
    pwd;\
    cd ..;\
    pwd;
  • makefile默认执行第一条规则,上述代码中make == make all

  • makefile的核心逻辑是:默认把目标名当成“要生成的文件名”

    • 所以当执行 make clean 时,它会先看当前目录里有没有一个叫 clean 的文件:

      • clean 文件,且它看起来不需要更新(常见是 clean 没依赖)→ make 认为目标已完成,不跑命令。

      • 没有 clean 文件 → 才会执行 clean: 下的 rm -f ...

  • 加上 .PHONY: clean 后,语义变成:

    • clean 不是文件目标,只是一个动作标签;
      • 无论目录里有没有同名文件 clean,都执行 clean: 下面命令。

编译C程序

  • make最初就是为了编译C程序而设计的,为了免去重复创建编译.o文件的规则,make内置了隐式规则(Implicit Rule),即遇到一个xyz.o时,如果没有找到对应的规则,就自动应用一个隐式规则:

    1
    2
    xyz.o: xyz.c
    cc -c -o xyz.o xyz.c
  • Makefile中,经常可以看到$@$<这样的变量,这种变量称为自动变量(Automatic Variable),它们在一个规则中自动指向某个值。例如,$@表示目标文件,$^表示所有依赖文件,因此,我们可以这么写:

    1
    2
    world.out: hello.o main.o
    cc -o $@ $^
  • C程序的基础makefile:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    SRC_DIR = ./src
    BUILD_DIR = ./build
    TARGET = $(BUILD_DIR)/world.out

    CC = cc
    CFLAGS = -Wall #编译参数,-Wall 打开常见警告。

    # ./src/*.c
    SRCS = $(shell find $(SRC_DIR) -name '*.c')
    # ./src/*.c => ./build/*.o
    OBJS = $(patsubst $(SRC_DIR)/%.c,$(BUILD_DIR)/%.o,$(SRCS))
    # ./src/*.c => ./build/*.d
    DEPS = $(patsubst $(SRC_DIR)/%.c,$(BUILD_DIR)/%.d,$(SRCS))

    # 默认目标:
    all: $(TARGET)

    # build/xyz.d 的规则由 src/xyz.c 生成:
    $(BUILD_DIR)/%.d: $(SRC_DIR)/%.c
    @mkdir -p $(dir $@); \
    rm -f $@; \
    $(CC) -MM $< >$@.tmp; \
    sed 's,\($*\)\.o[ :]*,$(BUILD_DIR)/\1.o $@ : ,g' < $@.tmp > $@; \
    rm -f $@.tmp

    # build/xyz.o 的规则由 src/xyz.c 生成:
    $(BUILD_DIR)/%.o: $(SRC_DIR)/%.c
    @mkdir -p $(dir $@)
    $(CC) $(CFLAGS) -c -o $@ $<

    # 链接:
    $(TARGET): $(OBJS)
    @echo "building $@..."
    @mkdir -p $(dir $@)
    $(CC) -o $(TARGET) $(OBJS)

    # 清理 build 目录:
    clean:
    @echo "clean..."
    rm -rf $(BUILD_DIR)

    # 引入所有 .d 文件:
    -include $(DEPS) #包含依赖文件;前缀 - 表示即使 .d 不存在也不报错



    # #生成可执行文件world.out,依赖于hello.o和main.o
    # world.out: hello.o main.o
    # gcc -o world.out hello.o main.o

    # #注释掉之后makefile会隐藏式生成默认规则,编译hello.c生成hello.o,编译main.c生成main.o
    # #对于C程序来说,使用隐式规则有一个潜在问题,那就是无法跟踪.h文件的修改。
    # #如果我们修改了hello.h的定义,由于隐式规则main.o: main.c并不会跟踪hello.h的修改
    # #导致main.c不会被重新编译
    # # #编译hello.c生成hello.o
    # # hello.o: hello.c
    # # gcc -c hello.c

    # # #编译main.c生成main.o,依赖于hello.h
    # # main.o: main.c hello.h
    # # gcc -c main.c

    # clean:
    # rm -f *.o world.out

Reference

欢迎来到ssy的世界