makefile管理编译工程 自动生成.h头文件依赖
makefile管理编译工程 自动生成.h头文件依赖
工程目录结构
每一个功能模块建立一个文件夹,然后该文件夹下建立include
,lib
,src
文件夹。include
存放对外接口,lib
存放编译好的静态库,src
存放源码.c和.h文件。
每个功能模块有单独的makefile进行编译管理。
顶层路径下建立一个管理所有功能模块的makefile。这个makefile负责主函数的编译链接工作。
这里给出一个用我写的Makefile框架管理工程的例子。可对比后面完整项目工程框架,实际使用中只需要进行简单配置即可。
Makfile实现
先写一个最简单的,能把整个工程编译出来。再考虑后续makefile的简洁,易配置等特性。
cJSON
build = ../build/cJSON
CFLAGS = $(CFLAGS_ENV)
INCLUDE = -I./include -I./src
src = $(wildcard ./src/*.c)
obj = $(src:.c=.o)
lib/libcJSON.a:$(addprefix $(build)/, $(obj))
ar -rc $@ $^
$(addprefix $(build)/, %.o):%.c
$(CC) -c $(INCLUDE) $(CFLAGS) $< -o $@
.PHONY: clean
clean:
-rm $(build)/src/*.o ./lib/*.a
gsoap
build = ../build/gsoap
INCLUDE = -I../openssl/include -I./include -I./src
CFLAGS = -g -fPIC -DWITH_DOM -DWITH_OPENSSL -DWITH_NONAMESPACE -DDBUG -DWITH_NO_C_LOCALE $(CFLAGS_ENV)
src = $(wildcard ./src/*.c)
obj = $(src:.c=.o)
lib/libgsoap.a:$(addprefix $(build)/, $(obj))
ar -rc $@ $^
$(addprefix $(build)/, %.o):%.c
$(CC) -c $(CFLAGS) $(INCLUDE) $< -o $@
.PHONY: clean
clean:
-rm $(build)/src/*.o ./lib/*.a
onvif
build = ../build/onvif
INCLUDE = -I/usr/local/ssl/include -I./include -I./src -I../gsoap/include -I../cJSON/include
CFLAGS = -g -fPIC -DWITH_DOM -DWITH_OPENSSL -DWITH_NONAMESPACE -DDBUG -DWITH_NO_C_LOCALE $(CFLAGS_ENV)
src = $(wildcard ./src/*.c)
obj = $(src:.c=.o)
lib/libonvif.a:$(addprefix $(build)/, $(obj))
ar -rc $@ $^
$(addprefix $(build)/, %.o):%.c
$(CC) -c $(CFLAGS) $(INCLUDE) $< -o $@
.PHONY: clean
clean:
-rm $(build)/src/*.o ./lib/*.a
主函数makefile
LDLAGS = -lpthread -ldl -lm
main: gsoap/lib/libgsoap.a onvif/lib/libonvif.a openssl/lib/libssl.a openssl/lib/libcrypto.a cJSON/lib/libcJSON.a
$(CC) -o $@ -Xlinker "-(" $^ -Xlinker "-)" $(LDLAGS)
gsoap/lib/libgsoap.a:gsoap/src/*.c
cd gsoap && make && cd ..
onvif/lib/libonvif.a:onvif/src/*.c
cd onvif && make && cd ..
cJSON/lib/libcJSON.a:cJSON/src/*.c
cd cJSON && make && cd ..
.PHONY: clean
clean:
cd gsoap && make clean && cd ../onvif && make clean && cd ../cJSON && make clean
环境变量设置
build/
目录为空,无法添加到远程仓库。通过这是环境变量来实现选择编译器和创建build/
目录。
#!/bin/bash
#export CC=arm-himix100-linux-gcc
export CC=gcc
echo ">> [toolchain] $CC"
export CFLAGS_ENV="-ffunction-sections"
# -Wall -O2
export obj_dir=$(ls -l | grep "^d" | awk '{print $9}' | sed 's/build//g')
for i in $obj_dir;
do
if [ ! -d "./build/$i/src" ]; then
mkdir -p ./build/$i/src
fi
done
if [ $CC == "arm-himix100-linux-gcc" ]; then
cp ./openssl/lib_arm_gcc/*.a ./openssl/lib
elif [ $CC == "gcc" ]; then
cp ./openssl/lib_gcc/*.a ./openssl/lib
fi
修改优化makefile
上面的makefile文件已经能把工程编译出来了,修改.c文件,会重新编译对应的.c。但是如果我们修改头文件,运行make是不会有任何更新。所以需要在makefile里面增加.h的依赖关系。
鉴于前面都是所有文件编译都是自动识别的,考虑到如果工程很大,手动输入文件名称是一件很笨的事,需要用makefile来自动生成头文件的依赖关系。
原理:通过gcc -MM main.c命令,可以得到main.c这个文件所依赖的其它文件。如:
#include <stdio.h>
#include "test.h"
#include "main.h"
那么,生成main.o所依赖的文件就包括main.c
,test.h
,main.h
。格式如下:
main.o: main.c test.h main.h
显然这是我们需要的依赖关系,而且这个依赖关系很完美。那么makefile是如何做的呢?
makefile通过这个命令得到依赖关系,然后把这个依赖关系保存到对应的.d文件中,在需要的地方进行导入。在保存之前,由于后面会生成.d文件,.d文件也应该随着.c导入头文件的变化而变化。所以.d是依赖于.c的,因此,makefile会对命令得到的依赖关系进行简单的修改:
main.o main.d : main.c test.h main.h
这个依赖关系等价于:
main.o: main.c test.h main.h
main.d: main.c test.h main.h
这里对于.o和.d目标的生成,运行makefile的匹配符号来完成。我看网上的教程都是不带前缀的。不带前缀会使我们将.c和.d和.o分开存放时造成错误。当对obj和dep添加前缀的话,又会导致匹配失败。所以还是在匹配的时候,通过addprefix
来处理前缀问题。
至于.d文件的作用,前面已经讲了,在.c修改导入头文件后,依赖关系改变了,这个时候就是通过这个.d文件来动态的表示改变的依赖关系。否则,如果依赖关系一直不变,那么我在main.c中又导入了fun.h,那么我修改fun.h的话,这个时候运行make,那么将不会有任何更新。
通过这个方法,对上面的makefile进行修改。
cJSON
target := libcJSON.a
build := ../build/cJSON
outdir := lib
srcdir := src
INCLUDE := -I./include -I./src
CFLAGS := $(CFLAGS_ENV)
src := $(wildcard $(srcdir)/*.c)
obj := $(src:.c=.o)
dep := $(src:.c=.d)
$(outdir)/$(target):$(addprefix $(build)/, $(obj))
ar -rc $@ $^
$(addprefix $(build)/, %.o):%.c
$(CC) -c $(INCLUDE) $(CFLAGS) $< -o $@
$(addprefix $(build)/, %.d): %.c
@set -e; rm -f $@; \
$(CC) -MM $(CPPFLAGS) $(INCLUDE) $< > $@.$$$$; \
sed 's,\($(*F)\)\.o[ :]*,$(build)/$(<D)/\1.o $@ : ,g' < $@.$$$$ > $@; \
rm -f $@.$$$$
include $(addprefix $(build)/, $(dep))
.PHONY: clean
clean:
-rm $(addprefix $(build)/, $(dep)) $(addprefix $(outdir)/, $(target)) $(addprefix $(build)/, $(obj))
gsoap
target := libgsoap.a
build := ../build/gsoap
outdir := lib
srcdir := src
INCLUDE := -I../openssl/include -I./include -I./src
CFLAGS := -g -fPIC -DWITH_DOM -DWITH_OPENSSL -DWITH_NONAMESPACE -DDBUG -DWITH_NO_C_LOCALE $(CFLAGS_ENV)
src := $(wildcard $(srcdir)/*.c)
obj := $(src:.c=.o)
dep := $(src:.c=.d)
$(outdir)/$(target):$(addprefix $(build)/, $(obj))
ar -rc $@ $^
$(addprefix $(build)/, %.o):%.c
$(CC) -c $(CFLAGS) $(INCLUDE) $< -o $@
$(addprefix $(build)/, %.d): %.c
@set -e; rm -f $@; \
$(CC) -MM $(CPPFLAGS) $(INCLUDE) $< > $@.$$$$; \
sed 's,\($(*F)\)\.o[ :]*,$(build)/$(<D)/\1.o $@ : ,g' < $@.$$$$ > $@; \
rm -f $@.$$$$
include $(addprefix $(build)/, $(dep))
.PHONY: clean
clean:
-rm $(addprefix $(build)/, $(dep)) $(addprefix $(outdir)/, $(target)) $(addprefix $(build)/, $(obj))
onvif
target := libonvif.a
build := ../build/onvif
outdir := lib
srcdir := src
INCLUDE := -I/usr/local/ssl/include -I./include -I./src -I../gsoap/include -I../cJSON/include
CFLAGS := -g -fPIC -DWITH_DOM -DWITH_OPENSSL -DWITH_NONAMESPACE -DDBUG -DWITH_NO_C_LOCALE $(CFLAGS_ENV)
src := $(wildcard $(srcdir)/*.c)
obj := $(src:.c=.o)
dep := $(src:.c=.d)
$(outdir)/$(target):$(addprefix $(build)/, $(obj))
ar -rc $@ $^
$(addprefix $(build)/, %.o):%.c
$(CC) -c $(CFLAGS) $(INCLUDE) $< -o $@
$(addprefix $(build)/, %.d): %.c
@set -e; rm -f $@; \
$(CC) -MM $(CPPFLAGS) $(INCLUDE) $< > $@.$$$$; \
sed 's,\($(*F)\)\.o[ :]*,$(build)/$(<D)/\1.o $@ : ,g' < $@.$$$$ > $@; \
rm -f $@.$$$$
include $(addprefix $(build)/, $(dep))
.PHONY: clean
clean:
-rm $(addprefix $(build)/, $(dep)) $(addprefix $(outdir)/, $(target)) $(addprefix $(build)/, $(obj))
上面修改后的makefile支持我们修改.h文件后对工程对应的编译更新。
并且我用了很多变量。运用变量编写makefile非常的有利于我们对makefile的修改维护。
并且会让我们makefile看起来更加的清晰。
由于用了很多变量,这3个makefile的很大一部分内容都是一样。这使我们可以用makefile的include
。
将相同的内容写到另外一个makefile中,然后通过include
将那个makefile导入。这个运用和C语言的#define宏定义类似。会对文本原样导入。
这样就得到最终的三个看起来很简洁的4个makefile。
cJSON
target := libcJSON.a
build := ../build/cJSON
outdir := lib
srcdir := src
make_run_mk_dir := ..
INCLUDE := -I./include -I./src
CFLAGS := $(CFLAGS_ENV)
include $(make_run_mk_dir)/make_run.mk
gsoap
target := libgsoap.a
build := ../build/gsoap
outdir := lib
srcdir := src
make_run_mk_dir := ..
INCLUDE := -I../openssl/include -I./include -I./src
CFLAGS := -g -fPIC -DWITH_DOM -DWITH_OPENSSL -DWITH_NONAMESPACE -DDBUG -DWITH_NO_C_LOCALE $(CFLAGS_ENV)
include $(make_run_mk_dir)/make_run.mk
onvif
target := libonvif.a
build := ../build/onvif
outdir := lib
srcdir := src
make_run_mk_dir := ..
INCLUDE := -I/usr/local/ssl/include -I./include -I./src -I../gsoap/include -I../cJSON/include
CFLAGS := -g -fPIC -DWITH_DOM -DWITH_OPENSSL -DWITH_NONAMESPACE -DDBUG -DWITH_NO_C_LOCALE $(CFLAGS_ENV)
include $(make_run_mk_dir)/make_run.mk
make_run.mk
src := $(wildcard $(srcdir)/*.c)
obj := $(src:.c=.o)
dep := $(src:.c=.d)
$(outdir)/$(target):$(addprefix $(build)/, $(obj))
ar -rc $@ $^
$(addprefix $(build)/, %.o):%.c
$(CC) -c $(CFLAGS) $(INCLUDE) $< -o $@
$(addprefix $(build)/, %.d): %.c
@set -e; rm -f $@; \
$(CC) -MM $(CPPFLAGS) $(INCLUDE) $< > $@.$$$$; \
sed 's,\($(*F)\)\.o[ :]*,$(build)/$(<D)/\1.o $@ : ,g' < $@.$$$$ > $@; \
rm -f $@.$$$$
include $(addprefix $(build)/, $(dep))
.PHONY: clean
clean:
-rm $(addprefix $(build)/, $(dep)) $(addprefix $(outdir)/, $(target)) $(addprefix $(build)/, $(obj))
这样就非常的简单明了了。我们只需要修改target
、build
、CFLAGS
和INCLUDE
即可。剩下的问题makefile会帮我自动处理。
让Makefile变得更好
通过上面的内容,可以自己来定义的Makefile的结构。接下来我定义了一下我的工程的Makefile的结构,是makefile使用起来更加的容易。
通过前面Makefile的编写,整个Makefile功能已经很完善了,能够处理我们一般的问题。
现在,通过修改工程的结构,让我们来更简单的使用Makefile。并且在配置Makefile的时候能够更加集中的处理。将需要配置的内容放到Makefile的最顶层和最底层。最顶层的内容存放:通用的配置和各个需要编译的模块内容。最底层存放:编译处理的具体实施。中间的Makefile只是做一个过度作用,连接顶层目录和Makefile和底层路径的Makefile。
这样做,整个Makefile结构更加的清晰明了,更易于配置。
我的工程放在github上了。
工程中的所有.c和.h已经删除,只保留了结构和Makefile,供参考。
这里展示部分makefile内容,整个工程makefile结构请参考上面的github链接。
顶层makefile
all:
@make -C $(MOD_DIR)
@make -C $(APP_DIR)
@echo "------------------------------"
@echo "make all done."
@echo "------------------------------"
.PHONY: clean gsoap_clean onvif_clean cJSON_clean clean_all
clean_all: clean
-@rm -rf $(BUILD_DIR)
clean:
-@make -C $(APP_DIR) clean
-@make -C $(MOD_DIR) clean
@echo "------------------------------"
@echo "clean all doen."
@echo "------------------------------"
gsoap_clean:
-@make -C $(MOD_DIR) gsoap_clean
onvif_clean:
-@make -C $(MOD_DIR) onvif_clean
cJSON_clean:
-@make -C $(MOD_DIR) cJSON_clean
生成程序makefile
#------------------------------
# 生成程序名称
#------------------------------
target := main
#------------------------------
# 生成程序输出路径
#------------------------------
outdir := $(ROOT_DIR)
#------------------------------
# .c源文件
#------------------------------
src := $(wildcard *.c)
#------------------------------
# 链接参数
#------------------------------
LDLAGS := -lpthread -ldl -lm
#------------------------------
# 链接时需要的头文件
#------------------------------
INCLUDE := -I$(MOD_DIR)/onvif/include
#------------------------------
# 模块路径
#------------------------------
MOD += cJSON
MOD += gsoap
MOD += onvif
MOD += openssl
#------------------------------
# 链接时需要的库
#------------------------------
LIB += $(foreach d, $(addsuffix /lib, $(addprefix $(MOD_DIR)/, $(MOD))), $(wildcard $(d)/*.a))
all: $(outdir)/$(target)
$(outdir)/$(target): $(LIB) $(src)
$(CC) -o $(outdir)/$(target) $(src) $(INCLUDE) -Xlinker "-(" $(LIB) -Xlinker "-)" $(LDLAGS)
.PHONY: clean
clean:
-@rm -f $(outdir)/$(target)
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 零经验选手,Compose 一天开发一款小游戏!
· 通过 API 将Deepseek响应流式内容输出到前端
· 因为Apifox不支持离线,我果断选择了Apipost!