Makefile与bash

本文探讨了在Linux中使用bash编译大型工程时的不便,介绍了通过bash脚本和Makefile进行编译的解决方案。Makefile通过依赖管理和时间戳判断,实现高效编译更新,尤其是在内核编译等场景中。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

先导问题

在Linux系统上,如果用bash编译程序,就需要手动敲入命令,并且每次编译都需要重新敲一遍命令,对于大型工程,比如内核编译,就显得非常麻烦。如何解决这个问题?

说明

以下代码均在Linux环境下执行,涉及文件都在同一个文件夹下。

解决方案

有2种解决方案,第一种是把需要编写的命令行写入一个文件中,也就是bash文件,后缀是.sh;第二种是Makefile文件。

bash文件

将要编写的命令放在一个文件中,之后在命令行运行该文件。
比如下面这些命令,操作是编译一个动态库并软链接到/usr/lib/中:

[fish@localhost 文档]$ ls
CPP_program  java_program
[fish@localhost 文档]$ cd CPP_program
[fish@localhost CPP_program]$ ls
libtest.so  main.c  test.c  test.h
[fish@localhost CPP_program]$ gcc test.c -fPIC -shared -o libtest.so
[fish@localhost CPP_program]$ sudo link libtest.so /usr/lib/libtest.so

写入一个文件,名为showtime.sh

ls
cd CPP_program
ls
gcc test.c -fPIC -shared -o libtest.so
link libtest.so /usr/lib/libtest.so

文件截图如下:
在这里插入图片描述
在命令行中输入bash showtime.sh即可,因为要进行软链接的那个目录需要管理员权限,所以在前面加上sudo。执行时相当于后台重新打开一个bash窗口,并把输出打印在当前窗口。
在这里插入图片描述

bash的问题

bash用于指令是没有问题的,但是用于编译却有一个很严重的问题,假设一种场景,一个大工程,比如编译Linux内核,这个内核经过2个小时的编译,编译结束之后,突然发现有一个小错误,及时修改了这一处错误,接下来怎么做?重新编译吗?再经过2个小时?这种效率太低了,这就是bash的问题。于是Makefile登场了。

Makefile

构建场景

假设一个工程中,文件F依赖于文件C文件E文件C依赖于文件A文件B文件E依赖于文件D,可以得到下面这个关系图。

A
C
B
D
E
F

假设这个工程写好了,编译结束。然后开始代码审查和测试,发现文件E出现问题,里面有一个小BUG,开发人员修改了这处BUG,也就是修改文件E,接下来要重新编译,这个时候只用编译文件E文件F即可,因为只有这两个文件受修改的影响。如何做到呢?
Makefile有2个机制,一个是依赖,另一个是时间戳。

依赖

在写Makefile文件的时候,会写如下的语句:

target : source

冒号前是目标,冒号后是源,目标依赖于源,这样就可以实现只编译修改的对象和依赖于修改的对象。
那么如何确定哪个文件被修改了?

时间戳

通过对比时间戳可以判断那些文件是在上次做Makefile时改变的还是没有改变,以此来判断是否重新编译。

Makefile应用举例

构建场景

文件名:mytest.cpp

#include <iostream>
#include "test1.hpp"
#include "test2.hpp"

using namespace std;

int main()
{
	cout << "testfunc1: " << testfunc(6) << endl;
	testfunc2();
	return 0;
}

文件名:test1.cpp

#include <iostream>
#include "test1.hpp"

using namespace std;

int testfunc1(int n)
{
	int savenum = 5 + n;
	cout << "test1" << endl;
	return savenum;
}

文件名:test1.hpp

#ifndef TEST1
#define TEST1
int testfunc1(int n);
#endif

文件名:test2.cpp

#include <iostream>
#include "test2.hpp"

using namespace std;

void testfunc2(void)
{
	cout << "test2" << endl;
}

文件名:test2.hpp

#ifndef TEST2
#define TEST2
void testfunc2(void);
#endif

文件依赖图如下:

test1
mytest
test2

我想要实现制作两个动态库,分别为test1test2mytest依赖于这两个动态库。
有关动态库和静态库可以参考头文件+源文件、动态库和静态库、宏定义

Makefile语法

文件名:Makefile

target=mytest
source=mytest.cpp
lib=-L./lib/ -ltest1 -L./lib/ -ltest2
OBJ1=test1.cpp
OBJ2=test2.cpp
FUNCLIB1=./lib/libtest1.so
FUNCLIB2=./lib/libtest2.so
$(target): $(source) $(FUNCLIB1) $(FUNCLIB2)
	g++ $(source) -o $@ $(lib)
$(FUNCLIB1):$(OBJ1)
	g++ -fPIC -shared $^ -o $@
$(FUNCLIB2):$(OBJ2)
	g++ -fPIC -shared $^ -o $@
clean:
	rm -rf $(target) $(FUNCLIB1) $(FUNCLIB2)

Makefile中同样有变量的概念,就相当于将一长串字符串用一个变量代替,在运行Makefile文件时,会将变量所表示的字符串直接替换变量的位置。

target=mytest
source=mytest.cpp
lib=-L./lib/ -ltest1 -L./lib/ -ltest2
OBJ1=test1.cpp
OBJ2=test2.cpp
FUNCLIB1=./lib/libtest1.so
FUNCLIB2=./lib/libtest2.so

上面这一段的意思如下表所示:

变量赋值
targetmytest
sourcemytest.cpp
lib-L./lib/ -ltest1 -L./lib/ -ltest2
OBJ1test1.cpp
OBJ2test2.cpp
FUNCLIB1./lib/libtest1.so
FUNCLIB2./lib/libtest2.so

接下来两行正式开始执行命令:

$(target): $(source) $(FUNCLIB1) $(FUNCLIB2)
	g++ $(source) -o $@ $(lib)

第一行的意思是变量target依赖于三个变量:sourceFUNCLIB1FUNCLIB2,即可执行文件mytest依赖于三个文件:mytest.cpp./lib/libtest1.so./lib/libtest2.so。在语法上要注意依赖于依赖之间要用空格分隔变量前面有$并且需要将变量放在括号中,所以这一行等同于下面这一条语句:

mytest : mytest.cpp ./lib/libtest1.so ./lib/libtest2.so

第二行是一个命令,在Makefile中只有前面有tab的才算命令,在Makefile中有三个非常有用的变量,分别为$@$^$<,代表的意思为:$@为目标文件,$^为所有的依赖文件,$<为第一个依赖文件。
从参数表查找各个参数,并结合规则,第二行等同于:

	g++ mytest.cpp -o mytest -L./lib/ -ltest1 -L./lib/ -ltest2

如果通过依赖判断,需要执行命令,相当于在bash中执行了

g++ mytest.cpp -o mytest -L./lib/ -ltest1 -L./lib/ -ltest2

其余同理,读者可以自行尝试翻译,答案放在文末。

执行Makefile

bash窗口输入make即可执行以下代码:

target=mytest
source=mytest.cpp
lib=-L./lib/ -ltest1 -L./lib/ -ltest2
OBJ1=test1.cpp
OBJ2=test2.cpp
FUNCLIB1=./lib/libtest1.so
FUNCLIB2=./lib/libtest2.so
$(target): $(source) $(FUNCLIB1) $(FUNCLIB2)
	g++ $(source) -o $@ $(lib)
$(FUNCLIB1):$(OBJ1)
	g++ -fPIC -shared $^ -o $@
$(FUNCLIB2):$(OBJ2)
	g++ -fPIC -shared $^ -o $@

bash窗口输入clean即可执行以下代码:

clean:
	rm -rf $(target) $(FUNCLIB1) $(FUNCLIB2)

那么写一个简单的bash脚本来实现自动执行。
文件名:Testsolib.sh

make
wait
export LD_LIBRARY_PATH=./lib/
wait
./mytest

bash窗口输入bash Testsolib.sh即可执行bash文件。

答案

./lib/libtest1.so:test1.cpp
	g++ -fPIC -shared test1.cpp -o ./lib/libtest1.so
./lib/libtest2.so:test2.cpp
	g++ -fPIC -shared test2.cpp -o ./lib/libtest2.so
clean:
	rm -rf mytest ./lib/libtest1.so ./lib/libtest2.so
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

FishPotatoChen

谢谢您的支持,我会更努力的~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值