Makefile系列文章目录
提示:写完文章后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
以新手视野看待Makefile
提示: 作为一个从业者者,在课堂外第一次自己动手写makefile,参考了不少网上资料结合之前在工作中遇到的Makefile,这里写一篇文章进行学习记录。
一、Makefile是什么?
在我们学校stm32使用keil或者codeblock中,我们一般只需要在project中add 文件就行,其会自动编译成可执行文件,但是在一般开发过程中并不是,我们一般需要撇开ide的编译器去编译,并且在不同厂商提供的交叉编译器去使用编译代码,编译器就显得冗余(编写代码除外(vi大神再除外))
在嵌入式linux开发中,一般用arm-linux-gexxxxxx-gcc等编译器去编辑,对于c初学者可能只有一个main.c的话可能只需要gcc -o main main.c 即可,但是对于一些小型demo就不方便了,得一个个编译成.o以后再进行编译,这时候Makefie就起到作用了,脚本型makefile能够一本万利,书写一次即可,对于经常改动代码调参也方便
二、学习步骤
1 学习之前先看看其gcc参数
-E 预处理 去宏 注释空白行等 输出x.i文件
-S 编译 检查语法规范 输出x.s文件
-c 只编译不链接得到二进制文件 产生.o文件,就是obj文件,不产生执行文件
-o指定输出文件名 不指定的话默认 a.out 并非最终执行文件
-D 向程序中“动态”注册宏定义 gcc -o ddd main.c add.c minus.c -D DDDDDDD=444
-Wall 显示所有警告信息
-w 关闭编译时的警告,数据转换以及可能未使用变量
-O3 意思是开启编译优化,等级为三。
-I 指定头文件所在目录位置
-g 编译时添加调试文件,用于 gdb 调试
-l 指定动态库库名
-L 指定动态库路径
-shared 如果想创建一个动态链接库,可以使用 gcc的-shared选项。输入文件可以是源文件、汇编文件或者目标文件。
-fPIC 选项作用于编译阶段,告诉编译器产生与位置无关代码 使用相对地址。
1.认识简单的gcc
demo代码很简单,但是对于库文件代码记得添加容错处理
int add(int param1,int param2)
{
return param1+param2;
}
#include<stdio.h>
int add(int param1,int param2);
int minus(int param1,int param2)
{
return param1-param2;
}
#include<stdio.h>
int minus(int param1,int param2);
#include<stdio.h>
int main(int argv ,char **argc)
{
int a=0;
int b=1;
int c = add(a,b);
printf("add %d \n",c);
int d = minus(a,b);
printf("minus %d \n",d);
}
1.最简单的
gcc -o main main.c add.c minus.c
2.使用静态库
gcc -c add.c
gcc -c minus.c
ar -r libaddminus.a add.o minus.o
gcc -o main main.c libaddminus.a -D DDDDDDD=1
3.使用动态库
gcc -shared -fpic -o libmyhello.so add.o minus.o
gcc main.c -o main -lmyhello -L./
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$pwd
2.书写简单的Makefile
首先我们先了解Makefile的书写规则
目标文件 : 依赖文件1 依赖文件2 依赖文件3
执行指令1
执行指令2
etc.....
目标文件 : 通常为.o或者可执行文件
依赖文件:即目标文件由哪些文件生成
执行指令:这个后面有讲(注意:执行指令前面有个tab)
目标文件被设置为第一个目标,并且依赖文件1 依赖文件2 依赖文件3 被列为目标文件的依赖。当你在命令行中运行make时,make命令会自动寻找准备好的依赖文件1 依赖文件2 依赖文件3 并执行目标文件编译命令
接下来试试最简单的,我们在终端输入
mkdir makestudy //创建·一个学历makefile目录
cd makestudy/ // 进入此目录
以上命令为我们创建一个文件夹
然后又我们创建"Makefile"并写入以下命令
main:main.c
gcc -o main main.c
然后又我们创建"main.c"并写入以下命令
#include<stdio.h>
int main(void)
{
printf(" tong hiden 666\n");
return 0;
}
之后make并可以得到第三个执行文件。此文件可以直接"./main"让他执行
如果此时我们不修改main就会被提示此文件未被更新
make: ‘main’ is up to date.
2.认识标识符
以下几个麻烦一直记着!!!!!!!!因为用多了就就会了,不用麻烦了
$^ 表示所有的依赖文件
$@ 表示生成的目标文件
$< 代表第一个依赖文件
我们先把我们刚刚的Makefike改成以下内容
SRC = $(wildcard *.c)
OBJ = $(patsubst %.c, %.o, $(SRC))
ALL: main1
main1: $(OBJ)
@echo "action 2 "
@echo "object file is " $(OBJ)
@echo "some dependent file is " $<
@echo "object file is " $@
gcc $< -o $@
%.o: %.c
@echo "action 1 "
@echo "all_dependent file is " $^
@echo "some dependent file is " $<
@echo "object file is " $@
gcc -c $< -o $@
clean:
rm main.o
看到这里是不是很懵逼,为啥一大堆符合出现了的时候突然就可以编译成新的可执行文件“main1”了,我的main.c呢??这就是Makefile的魅力!!!
SRC = $(wildcard *.c) ,这句话是加载当前路径的所有.c文件
@echo $(OBJ) 打印某个变量
OBJ = $(patsubst %.c, %.o, $(SRC)) 这句话是加载当前SRC的所包含有.c文件,并不改源文件情况下重命名.c文件成.o并赋值给OBJ,"不改源文件情况下重命名.c"啥意思呢。相当于如下图c语言中的变量dd,至于我们为啥要拥有.o。看看编译流程
$^ 表示所有的依赖文件
$@ 表示生成的目标文件
$< 代表第一个依赖文件
然后我们回头看此章节中的那几个符号,根据我们打印的就会发现,
在action 1中。因为我们只有一个源文件,那么 $^ 与 $<
都是main.c,$@是main.o。
在action 2中。(忽略第一行打印,备注 错的 )因为我们只有一个源文件就是 main.o,那么 $<
都是main.0,$@是main1。
那么我们会问,啥时候给这几个符号赋值的呢?为啥他的顺序不从上到下?-------其实是执行make的时候并不是立马从上到下执行,他先会遍历一遍,相当于我们编译过程中的预处理–去掉宏,然后给对应的数据赋值,根据编译规则,他会发现编译main1的依赖文件是
OBJ = $(patsubst %.c, %.o, $(SRC)) 展开后的 mian.o,而在gcc $< -o $@中 $< 是依赖文件mian.o,所以赋值,而后mian.o没有。其会去索引%,去寻找需要在action 2中的依赖文件mian.o,纵使action 2是编译最终可执行文件的命令
刚刚说到了wildcard 和 patsubst ,就解释下我常用的几个函数
$(wildcard .source) :加载当前路径的所有.source文件
$(patsubst %.from ,%.to, $(/sourcedir)) :模式替换函数 并不改源文件情况下从/sourcedir中重命名.from 文件成.to 并返回
(
s
u
b
s
t
.
f
r
o
m
,
.
t
o
,
(subst .from, .to ,
(subst.from,.to,(/sourcedir)) :字符串替换函数并不改源文件情况下从/sourcedir中重命名.from 文件成.to 并返回
上面两个是不是感觉很像,subst 是 遇到匹配的字符就会 比如奇葩文件是 aaa.from.from,那么他都会aaa .to .to ,但是putsubst的话则会aaa.from.to
(
f
i
l
t
e
r
(filter %.c .d,
(filter(source))函数名称:过滤函数过滤掉其他的只用.c .d后缀的。
(
f
o
r
e
a
c
h
i
t
e
m
,
(foreach item ,
(foreachitem,(sorcename), -I
(
i
t
e
m
)
)
f
o
r
e
a
c
h
遍历函数读取
s
o
r
c
e
n
a
m
e
里面用空格分隔的成员重新定义成
−
I
成员名并返回等同于
(item)) foreach 遍历函数读取sorcename里面用空格分隔的成员重新定义成 -I成员名并返回 等同于
(item))foreach遍历函数读取sorcename里面用空格分隔的成员重新定义成−I成员名并返回等同于(sorcename:%=-I%)
$(notdir $(source)) 将source中的文件的路径名字去掉
$(basename $(source)) 将source中的基础名字的后缀去掉
lib_srcs:=$(filter-out src/main.c, $(wildcard src/*.c))
lib_objs := $(patsubst src/%.c, obj/%.o,$(lib_srcs))
include_path := $(wildcard src/*.h)
include_path:=$(include_path:%=-I%)
lib_path:=./lib
link_libs:=mymath
lib_link_l:=$(link_libs:%=-l%)
lib_link_L:=$(lib_path:%=-L%)
compile_flags:= -g -O3 $(include_path)
link_flags:= $(lib_link_L) $(lib_link_l)
debug:
@echo $(link_flags)
obj/%.o:src/%.c
mkdir -p $(dir $@)
gcc -c $^ -o $@ $(compile_flags)
lib/libmymath.a:$(lib_objs)
mkdir -p $(dir $@)
ar -r $@ $^
makelib:lib/libmymath.a
obj/main.o:main.c
mkdir -p $(dir $@)
gcc -c $^ -o $@
exec:obj/main.o
gcc -o $@ $^ $(compile_flags) $(link_flags)
execmake :exec
.PHONY:debug
lib_src:=$(wildcard src/*.c)
lib_obj:=$(patsubst src/%.c,obj/%.o,$(lib_src))
lib_include:= $(wildcard src/*.h)
lib_include:= $(lib_include:%=-I%)
lib_path:=./lib
lib_name :=mymath
link_name:=$(lib_name:%=-l%)
link_path:=$(lib_path:%=-L%)
run_link:=$(lib_path:%=-Wl,-rapth=%)
link_flag:=$(link_path) $(link_name) $(run_link)
compile_flag:= -g -O3 -w -fPIC $(lib_include)
debug:
echo $(link_flag)
obj/%.o:src/%.c
mkdir -p $(dir $@)
gcc -c $^ -o $@ $(compile_flag)
obj/%.o:src/%.c
mkdir -p $(dir $@)
gcc -c $^ -o $@ $(compile_flags)
makeo:$(lib_obj)
lib/libmymath.so:$(lib_obj)
mkdir -p $(dir $@)
gcc -shared $^ -o $@
makelib:lib/libmymath.so
main:main.c
gcc $^ -o $@ $(compile_flags) $(link_flag)
看不懂函数符号都给我去看这个笔者的博客,对于Makefile的函数比较详细