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