参考视频及博客
https://www.bilibili.com/video/BV1Xt4y1h7rH/?buvid=XU932919AEC08339E30CE57D39A2BABF6A44F&from_spmid=search.search-result.0.0&is_story_h5=false&mid=rSmqqQLB5PQj7nof4wjfpQ%3D%3D&p=4&plat_id=114&share_from=ugc&share_medium=android&share_plat=android&share_session_id=2a5b3942-3b2b-4fc4-9fd7-732da207d478&share_source=WEIXIN&share_tag=s_i&spmid=united.player-video-detail.0.0×tamp=1730082843&unique_k=UoT3HRP&up_id=438662048
https://www.cnblogs.com/paul-617/p/15501875.html
- 概述
想要掌握makefile,首先需要了解两个概念,⼀个是⽬标(target),另⼀个就是依赖(dependency)。⽬标就是指要⼲什么,或说运⾏ make 后⽣成什么,⽽依赖是告诉 make 如何去做以实现⽬标。在 Makefile 中,⽬标和依赖是通过规则(rule)来表达的。
Makefile文件名:
GNUmakefile
makefile
Makefile
//通常使用Makefile
Makefile中的注释使用:#
使用man make
可以查看帮助文档
- 基本使用
make //默认执行当前文件夹下边的Makefile文件
make name //执行文件名为name的makefile文件
$@ 自动变量
$^ 所有依赖文件
$< 第一个依赖文件
make -f filename 指定makefile的文件名
make -s 执行,但不显示
make -n 打印运行时输出,但是不显示
make -c 指定makefile运行的目录
可以使用通配符% 来简化
-
g++/gcc编译流程
3.1一次执行
gcc mian.c
3.2将上述过程拆分:
gcc -E main.c > main.i //预处理
gcc -S main.i //编译:得到main.S的汇编文件
gcc -c main.s //汇编:得到main.o的二进制文件
gcc main.o //链接:得到a.out的可执行文件
3.2.1预处理:
3.2.2编译
3.2.3汇编
3.2.4链接
程序运行结果:
- Makefiel中变量
使用 make -p查看
5.伪目标和模式匹配
5.1伪目标
比如执行makefile中的clean目标;如果执行目录中含有clean文件,这时候会出现执行错误;可以在代码中将clean定义为伪目标来解决这个问题。
.PHONY := clean
5.2模式匹配
%.o: %.cpp #.o依赖于对应的.cpp
wildcard的使用:
$(wildcard ./*.cpp) #获取当前目录下的所有.cpp文件
patsubst的使用:
($patsubst %.c, %.o, ./*.c) #获取当前文件夹中的.c文件,然后替换后,创建对应的.o文件
#比如根据 xxx.c 创建 xxx.o
6.Makefile运行流程
保证目标是用最新的依赖生成的;
第一次完全编译,后边是部分编译;
达到第一次编译时间久,后边编译时间短的效果。
7.Makefile中动态链接库
windows: .dll
linux: .so
采用动态链接库:
好处是程序和库文件分离,库文件可以共享;
动态是运行时加载,动态加载
链接:指的是库文件和二进制程序分离,用某种特殊手段维护二者关系。
g++ -shared -fPIC SoTestcpp -o libSoTest.so
g++ -lSoTest -L./ test.cpp test
8.Makefile中公共部分做头文件
就是将不同子文件夹中的Makefile写成一个公共部分,然后在子文件夹中的Makefile中引用该文件夹。
具体代码:
//001
//
//a.cpp
#include<iostream>
void func1(){
printf("func1-cpp\n");
}
//
//b.cpp
#include<iostream>
void func2(){
printf("func2-cpp\n");
}
//
//c.cpp
extern void func1();
extern void func2();
#include<iostream>
using namespace std;
int main(){
func1();
func2();
cout<<"std"<<endl;
return 0;
}
//002
//
//x.c
#include<stdio.h>
void func1(){
printf("func1-c\n");
}
//
//y.c
#include<stdio.h>
void func2(){
printf("func2-c\n");
}
//
//z.c
extern void func1();
extern void func2();
#include<stdio.h>
int main(){
func1();
func2();
return 0;
}
由子文件001和子文件002的代码可知,代码的结构非常像。都是第三个函数来调用第一个函数中的子函数。因此Makefile中的共性可以抽取出来。
Makefile
#文件夹1中Makefile
TARGET:=c
LDLIBS:=-lstdc++
include ../makefile-head#包含公共的Makefile
#文件夹2中Makefile
TARGET=z
include ../makefile-head
#公共Makefile
#公共
SOURCE=$(wildcard ./*.cpp ./*.c)#寻找文件夹中以.cpp和.c结尾的文件
OBJ=$(patsubst %.cpp,%.o,$(SOURCE))#将.cpp文件截取文件名,得到*.o
OBJ:=$(patsubst %.c,%.o,$(OBJ))#将.c文件截取文件名,得到*.o
.PHONY:clean
#如果在子文件夹中没有定义目标变量,这里定义为test
ifndef TARGET
TARGET:=test
endif
#如果在子文件夹中没有定义动态链接库文件,这里定义为空
#编译cpp文件会使用到,但是编译.c文件不会使用到
ifndef LDLIBS
LDLIBS:=
endif
$(TARGET):$(OBJ)
$(CXX) $(LDLIBS) $^ -o $@#编译,
#gcc $(LDLIBS) $^ -o $@
clean:
$(RM) $(TARGET) $(OBJ)#删除文件,$(RM) 代表rm -f
show:
echo $(SOURCE)
echo $(OBJ)
#总的Makefile
.PHONY:001 002 clean
DIR = 001 002
all:$(DIR)
$(DIR):
make -C $@#进入子文件夹中执行Makefile操作
clean:
echo $(shell for dir in $(DIR); do make -C $$dir clean;done)# $$表示展开shell中的变量#分别进入每一个子文件夹中执行删除操作
# -C 指定工作目录
all-v1:
$(MAKE) -C ./001 -f Makefile#进入文件夹执行Makefile
$(MAKE) -C ./002 -f Makefile
clean-v1:
$(MAKE) -C ./001 -f Makefile clean#进入文件夹执行删除操作
$(MAKE) -C ./002 -f Makefile clean
9.在Makefile中调用shell命令
例子:
A:= $(shell ls )
B:= $(shell pwd)
all:
@echo $(A)
@echo $(B)
结果
10.Makefile中嵌套调用
就是《8》中通过顶层目录中的Makefile来分别调用子文件夹001和002中的Makefile的过程,就叫嵌套调用,这里不再赘述。
11.Makefile中的条件判断
ifeq
ifneq
ifdef
ifndef
其他三个的用法与此类似。
12.Makefile中的循环
13.Makefile中自定义函数调用与实现
14.Makefile中install的实现
make 1
make install 2 3 4
make clean 5
实现完整的项目编译、安装过程
1.make 将源文件编译成二进制可执行文件(包括各种库文件)
2.创建目录,将可执行文件拷贝到指定目录(安装目录)
3.加全局可执行路径
4.加全局启停脚本
5.重置编译环境,删除无关文件
14.1源代码
//main.cpp
#include<iostream>
#include<unistd.h>
using namespace std;
int main(){
int i=0;
while (true){
i++;
cout<<"006-main-running-"<<i<<endl;
sleep(1);
}
return 0;
}
14.2Makefile
TARGET:=006_main
OBJ:=$(TARGET).o
CC:=g++
PATHS:=/tmp/006_main/
BIN:=/usr/local/bin/
START_SH:=$(TARGET)_start
STOP_SH:=$(TARGET)_stop
LOG:=$(PATHS)$(TARGET).log
$(TARGET):$(OBJ)
install:$(TARGET)
if [ -d $(PATHS) ];\
then echo $(PATHS) exist; \
else \
mkdir $(PATHS);\
cp $(TARGET) $(PATHS);\
ln -sv $(PATHS)$(TARGET) $(BIN);\
touch $(LOG);\
chmod a+rwx $(LOG);\
echo "$(TARGET)>$(LOG) & echo $(TARGET) running...">$(PATHS)$(START_SH);\
echo "killall $(TARGET)">$(PATHS)$(STOP_SH);\
chmod a+x $(PATHS)$(START_SH);\
chmod a+x $(PATHS)$(STOP_SH);\
ln -sv $(PATHS)$(START_SH) $(BIN);\
ln -sv $(PATHS)$(STOP_SH) $(BIN);\
fi;
clean:
$(RM) $(TARGET) $(OBJ) $(BIN)$(TARGET) $(BIN)$(START_SH) $(BIN)$(STOP_SH)
$(RM) -rf $(PATHS)
.PHONY:clean install
注意:
上边的Makefile中,PATHS不能用PATH表示,因为会和系统中的PATH冲突。
其次,采用/连接每一行的时候,/后边不能有空格 回车符这些的,不然会报错,而且这个错误比较难以找到。
TARGET:=006_main#定义目标文件
OBJ:=$(TARGET).o#定义依赖文件
CC:=g++#设置编译器为g++
PATHS:=/tmp/006_main/#定义拷贝文件的路径
BIN:=/usr/local/bin/#定义实现 006_main直接调用,存放的路径
START_SH:=$(TARGET)_start#开始脚本
STOP_SH:=$(TARGET)_stop#停止脚本
LOG:=$(PATHS)$(TARGET).log#存放程序的输出数据
$(TARGET):$(OBJ)#自动推导
install:$(TARGET)
if [ -d $(PATHS) ];\
then echo $(PATHS) exist; \
else \
mkdir $(PATHS);\#创建文件夹
cp $(TARGET) $(PATHS);\#将生成的006_main拷贝到路径中
ln -sv $(PATHS)$(TARGET) $(BIN);\#创建软链接
touch $(LOG);\#创建存放输出信息的文件
chmod a+rwx $(LOG);\#改变文件路径
echo "$(TARGET)>$(LOG) & echo $(TARGET) running...">$(PATHS)$(START_SH);\#创建开始脚本
echo "killall $(TARGET)">$(PATHS)$(STOP_SH);\#创建停止脚本
chmod a+x $(PATHS)$(START_SH);\#改变文件权限
chmod a+x $(PATHS)$(STOP_SH);\#改变文件权限
ln -sv $(PATHS)$(START_SH) $(BIN);\#创建软链接
ln -sv $(PATHS)$(STOP_SH) $(BIN);\#创建软链接
fi;
clean:
$(RM) $(TARGET) $(OBJ) $(BIN)$(TARGET) $(BIN)$(START_SH) $(BIN)$(STOP_SH)#删除不需要的文件
$(RM) -rf $(PATHS)#删除文件夹
.PHONY:clean install#定义为目标
注意:上述的Makefile文件需要再root 用户下运行。