Makefile 进阶笔记

1. 项目结构

├── LICENSE
├── Makefile
├── README.md
├── bin
├── build
│   └── liblcthw.a
├── src
│   └── dbg.h
└── tests
    └── runtests.sh

1. Makefile概览

整体的一个Makefile 如下

CFLAGS=-g -O2 -Wall -Wextra -Isrc -rdynamic -DNDEBUG $(OPTFLAGS)
LIBS=-ldl $(OPTLIBS)
PREFIX?=/usr/local

SOURCES=$(wildcard src/**/*.c src/*.c)
OBJECTS=$(patsubst %.c,%.o, $(SOURCES))

TEST_SRC=$(wildcard tests/*_tests.c)
TESTS=$(patsubst %.c,%,$(TEST_SRC))

TARGET=build/liblcthw.a
SO_TARGET=$(patsubst %.a,%.so,$(TARGET))

# The Target Build
all: $(TARGET) $(SO_TARGET) tests

dev: CFLAGS=-g -Wall -Isrc -Wall -Wextra $(OPTFLAGS)
dev: all

$(TARGET): CFLAGS += -fPIC
$(TARGET): build $(OBJECTS)
    ar rcs $@ $(OBJECTS)
    ranlib $@

$(SO_TARGET): $(TARGET) $(OBJECTS)
    $(CC) -shared -o $@ $(OBJECTS)

build:
    @mkdir -p build
    @mkdir -p bin

# The Unit Tests
.PHONY: tests
tests: LDLIBS += $(TARGET)
#tests: LDLIBS += -lm -L./build -llcthw
tests: $(TESTS)
    sh ./tests/runtests.sh

valgrind:
    VALGRIND="valgrind --log-file=/tmp/valgrind-%p.log" $(MAKE)

# The Cleaner
clean:
    rm -rf build $(OBJECTS) $(TESTS)
    rm -f tests/test.log
    find . -name "*.gc*" -exec rm {} \;
    rm -rf `find . -name "*.dSYM" -print`

# The Install
install: all
    install -d $(DESTDIR)/$(PREFIX)/lib/
    install $(TARGET) $(DESTDIR)/$(PREFIX)/lib/


# The checker
BADFUNCS='[^_.>a-zA-Z0-9](str(n?cpy|n?cat|xfrm|n?dup|str|pbrk|tok|_)|stpn?cpy|a?sn?printf|byte_)'
check:
    @echo Files with potentially dangerous functions
    @egrep $(BADFUNCS) $(SOURCES) || true

2. Makefile头部解析

CFLAGS=-g -O2 -Wall -Wextra -Isrc -rdynamic -DNDEBUG $(OPTFLAGS)

OPTFLAGS 可以让使用者按需扩建构建选项

-Wall : 打开所有编译警告

LIBS=-ldl $(OPTLIBS)

用于链接库的选项, 同样也允许其它人使用 OPTFLAGS 变量扩展链接选项。

PREFIX?=/usr/local

设置一个叫做PREFIX的可选变量, 它只在没有PREFIX设置的平台上运行Makefile时有效, 这就是?=的作用

SOURCES=$(wildcard src/**/*.c src/*.c)

通过执行wildcard搜索在src/中所有*.c文件来动态创建SOURCES变量. 需要提供src/**/*.csrc/*.c 以便GNU make能够包含src目录及其子目录的所有此类文件

OBJECTS=$(patsubst %.c,%.o, $(SOURCES))

一旦创建了源文件列表, 可以使用patsubst命令获取*.c文件的SOURCES来创建目标文件的新列表. patsubst把所有%.c扩展为%.o,并将它们赋给OBJECTS

TEST_SRC=$(wildcard tests/*_tests.c)

类似上述的含义,提取测试源文件

TESTS=$(patsubst %.c,%,$(TEST_SRC))

只是将 .c 文件的后缀都去掉了

TARGET=build/liblcthw.a
SO_TARGET=$(patsubst %.a,%.so,$(TARGET))

设置目标

2.1 扩展参数的使用示例

make PREFIX=/tmp install
make OPTFLAGS=-pthread

3. Makefile构建目标

在没有提供目标时make会默认运行第一个目标. 这里它叫做all

all: $(TARGET) $(SO_TARGET) tests

可以看出, 这里先构建出库文件,再构建出单元测试(tests)

dev: CFLAGS=-g -Wall -Isrc -Wall -Wextra $(OPTFLAGS)
dev: all

这个是用来执行"开发者构建"的目标,可以为单一目标修改选项, 例如 CFLAGS 包含类似 Wextra 这样的选项

$(TARGET): CFLAGS += -fPIC

构建 TARGET 库,向目标提供选项来为当前目标修改它们, PIC 表示position-independent code 这样生成出来的代码和绝对地址没有关系,便于加载如内存

$(TARGET): build $(OBJECTS)
    ar rcs $@ $(OBJECTS)
    ranlib $@
    
build:
    @mkdir -p build
    @mkdir -p bin
  1. 构建 build ,再编译所有的 OBJECTS
  2. 运行实际创建 TARGETar 的命令. $@ $(OBJECTS)语法的意思是, 将当前目标的名称放在这里, 并把 OBJECTS 的内容放在后面. 这里 $@ 的值为20行
    $(TARGET) ,它实际上为 build/liblcthw.a. 看起来在这一重定向中它做了很多跟踪工作,它也有这个功能,并且你可以通过修改顶部的 TARGET ,来构建一个全新的库
  3. TARGET 上运行 ranlib 来构建这个库

命令完成后的文件结构如下:

build
├── liblcthw.a
└── liblcthw.so

4. 单元测试

.PHONY: tests

用来标记不是一个真实的目标,只是有个目录/文件叫这个名字,以便 make 忽略

tests: LDLIBS += $(TARGET)
tests: $(TESTS)
    sh ./tests/runtests.sh

构建 TESTS 之后,运行一个 shell 脚本

# runtests.sh
echo "Running unit tests:"

for i in tests/*_tests
do
    if test -f $i
    then
        if $VALGRIND ./$i 2>> tests/tests.log
        then
            echo $i PASS
        else
            echo "ERROR in test $i: here's tests/test.log"
            echo "-----"
            tail tests/tests.log
            exit 1
        fi
    fi
done

echo ""
valgrind:
    VALGRIND="valgrind --log-file=/tmp/valgrind-%p.log" $(MAKE)

上面的 shell 脚本会用到 VALGRIND

5. 清理工具

clean:
    rm -rf build $(OBJECTS) $(TESTS)
    rm -f tests/test.log
    find . -name "*.gc*" -exec rm {} \;
    rm -rf `find . -name "*.dSYM" -print`

6. 安装

install: all
    install -d $(DESTDIR)/$(PREFIX)/lib/
    install $(TARGET) $(DESTDIR)/$(PREFIX)/lib/

上面的2个参数 PREFIX ,DESTDIR 是为了给使用者更多方便以指定路径

7. 检查工具

BADFUNCS='[^_.>a-zA-Z0-9](str(n?cpy|n?cat|xfrm|n?dup|str|pbrk|tok|_)|stpn?cpy|a?sn?printf|byte_)'
check:
    @echo Files with potentially dangerous functions
    @egrep $(BADFUNCS) $(SOURCES) || true

@echo 命令告诉 make 不要打印命令,之需要打印输出

ref: https://learncodethehardway.org/c/ #28

你可能感兴趣的:(Makefile 进阶笔记)