专业的编程技术博客社区

网站首页 > 博客文章 正文

「Linux」Makefile以及cmake(linux make makefile)

baijin 2024-10-12 02:13:01 博客文章 17 ℃ 0 评论
私信我或关注微信号:狮范课,回复:学习,获取免费学习资源包。

make通常被视为一种软件构建工具,主要经由读取一种名为makefile 或 Makefile的文件来实现软件的自动化建构。它会通过一种被称之为target概念来检查相关文件之间的依赖关系,这种依赖关系的检查系统非常简单,主要通过对比文件的修改时间来实现。在大多数情况下,我们主要用它来编译源代码,生成结果代码,然后把结果代码连接起来生成可执行文件或者库文件。


特别是在类Unix系统下的软件编译,会不会写makefile,从一个侧面说明了一个人是否具备完成大型工程的能力。因为,makefile关系到了整个工程的编译规则。一个工程中的源文件不计数,其按类型、功能、模块分别放在若干个目录中,makefile定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作,因为makefile就像一个Shell脚本一样,其中也可以执行操作系统的命令。makefile带来的好处就是——“自动化编译”,一旦写好,只需要一个make命令,整个工程完全自动编译,极大的提高了软件开发的效率。make是一个命令工具,是一个解释makefile中指令的命令工具,一般来说,大多数的IDE都有这个命令,比如:Delphi的makeVisual C++的nmakeLinux下的GNU make

Makefile基本格式如下

1
2
3
4
5
6
7
target ... : prerequisites ...
 command
 ......
 ......
###target - 目标文件, 可以是 Object File, 也可以是可执行文件
###prerequisites - 生成 target 所需要的文件或者目标
###command - make需要执行的命令 (任意的shell命令), 必须以[tab]开头

Makefile基本规则

1
2
3
4
5
显示规则 :: 说明如何生成一个或多个目标文件(包括 生成的文件, 文件的依赖文件, 生成的命令)
隐晦规则 :: make的自动推导功能所执行的规则
变量定义 :: Makefile中定义的变量
文件指示 :: Makefile中引用其他Makefile; 指定Makefile中有效部分; 定义一个多行命令
注释 :: Makefile只有行注释 "#", 如果要使用或者输出"#"字符, 需要进行转义, "\#"

make基本的工作方式

1
2
3
4
5
6
7
1.读入主Makefile (主Makefile中可以引用其他Makefile)
2.读入被include的其他Makefile
3.初始化文件中的变量
4.推导隐晦规则, 并分析所有规则
5.为所有的目标文件创建依赖关系链
6.根据依赖关系, 决定哪些目标要重新生成
7.执行生成命令

示例

[free@hurd]$ vim foo1.c
#include <stdio.h>
void foo1()
{
 printf("This is foo1!\n");
}
[free@hurd]$ vim foo2.c
#include <stdio.h>
void foo2()
{
 printf("This is foo2!\n");
}
[free@hurd]$ vim main.c
#include <stdio.h>
int main(void)
{
 foo1();
 foo2();
 printf("This is main!\n");
 return 0;
}
[free@hurd]$ vim Makefile
main: foo1.c foo2.c main.c
	gcc foo1.c foo2.c main.c -o main
###这是一个最简单的Makefile
###生成目标main依赖foo1.c foo2.c main.c
###执行命令gcc foo1.c foo2.c main.c -o main;完成目标
[free@hurd]$ make
gcc foo1.c foo2.c main.c -o main
[free@hurd]$ ls
foo1.c foo2.c main main.c Makefile
[free@hurd]$ ./main
This is foo1!
This is foo2!
This is main!
###成功执行,现在改进Makefile
[free@hurd]$ vim Makefile
main: main.o foo1.o foo2.o
	gcc main.o foo1.o foo2.o -o main
main.o: main.c
	gcc -c main.c
foo1.o: foo1.c
	gcc -c foo1.c
foo2.o: foo2.c
	gcc -c foo2.c
###改写后各种依赖更清楚
###只要有一项更改,重新make只重新编译更新的部分,其他不变
[free@hurd]$ make
gcc -c main.c
gcc -c foo1.c
gcc -c foo2.c
gcc main.o foo1.o foo2.o -o main
[free@hurd]$ ./main
This is foo1!
This is foo2!
This is main!
###成功执行,继续改进Makefile
[free@hurd]$ vim Makefile
target= main
src= $(shell find . -name "*.c")
obj= $(src:%.c=%.o)
$(target): $(obj)
 gcc $^ -o $@
%.o: %.c
 gcc -c lt; -o $@
###这里引入了变量,其实和C语言中的宏类似
###$(shell ****)使用了shell命令,将搜索到的结果传给src
###$(src:%.c=%.o)是进行字符替换,将所有的*.c字符串替换成*.o字符串,传给obj
###$^:所有依赖目标的集合
###$@:目标集合
###lt;:第一个依赖目标. 如果依赖目标是多个, 逐个表示依赖目标
###改进后,更加精炼清晰,而且方便以后拓展构建
[free@hurd]$ make
gcc -c foo2.c -o foo2.o
gcc -c main.c -o main.o
gcc -c foo1.c -o foo1.o
gcc foo2.o main.o foo1.o -o main
[free@hurd]$ ./main
This is foo1!
This is foo2!
This is main!
###成功执行,进一步改进Makefile
[free@hurd]$ vim Makefile
CC = gcc
CFLAGS = -Wall
CXXFLAGS =
CPPFLAGS =
LDFLAGS =
target = main
src = $(shell find . -name "*.c")
obj = $(src:%.c=%.o)
$(target): $(obj)
 $(CC) $^ $(CFLAGS) -o $@
%.o : %.c
 $(CC) -c lt; $(CFLAGS) $(CPPFALGS) -o $@
.PHONY:clean
clean:
 -rm -rf $(obj) $(target)
install:
 mkdir target
 mv main target
test:
 @echo $(src)
 @echo $(obj)
###改进后基本看不到源文件的影子,慢慢走向通用化更易于拓展
###此处还添加了伪目标
###.PHONY意思表示clean是一个"伪目标";而在rm命令前面加了一个小减号的意思就是,也许某些文件出现问题,但不要管,继续做后面的事。当然,clean的规则不要放在文件的开头,不然,这就会变成make的默认目标,相信谁也不愿意这样。不成文的规矩是——"clean从来都是放在文件的最后"
###"伪目标"并不是一个文件,只是一个标签,由于“伪目标”不是文件,所以make无法生成它的依赖关系和决定它是否要执行。只有通过显示地指明这个“目标”才能让其生效。当然,“伪目标”的取名不能和文件名重名,不然其就失去了“伪目标”的意义了
###为了避免和文件重名的这种情况,可以使用一个特殊的标记".PHONY"来显示地指明一个目标是“伪目标”,向make说明,不管是否有这个文件,这个目标就是"伪目标"

Makefile 命令前缀

1
2
3
4
不用前缀 :: 输出执行的命令以及命令执行的结果, 出错的话停止执行
前缀 @ :: 只输出命令执行的结果, 出错的话停止执行
前缀 - :: 命令执行有错的话, 忽略错误, 继续执行
###例如上面的-rm、@echo

make 退出码

1
2
3
0 :: 表示成功执行
1 :: 表示make命令出现了错误
2 :: 使用了 "-q" 选项, 并且make使得一些目标不需要更新

make 常用参数

参数含义–debug[=]输出make的调试信息, options 可以是 a, b, v-j –jobs同时运行的命令的个数, 也就是多线程执行 Makefile,比如常用的make -j8-r –no-builtin-rules禁止使用任何隐含规则-R –no-builtin-variabes禁止使用任何作用于变量上的隐含规则-B –always-make假设所有目标都有更新, 即强制重编译

命令变量 和 命令参数变量

变量名含义RMrm -fARarCCccCXXg++ARFLAGSAR命令的参数CFLAGSC语言编译器的参数CXXFLAGSC++语言编译器的参数CPPFLAGS预处理阶段的参数LDFLAGS传递给连接器的参数LIBS链接库的参数

1
2
3
4
5
6
7
8
9
10
11
[free@hurd]$ vim Makefile
all:
 @echo $(RM)
 @echo $(AR)
 @echo $(CC)
 @echo $(CXX)
[free@hurd]$ make
rm -f
ar
cc
g++

自动变量

以上只是简单介绍了 Makefile,足以应付一些小项目,但当遇到一些工程级的大项目手写 Makefile 变得十分困难繁琐,而且不难以跨平台,我们需要其他的工具来生成 Makefile。

CMake是一个比make更高级的编译配置工具,它可以根据不同平台、不同的编译器,生成相应的Makefile或者vcproj项目。通过编写CMakeLists.txt,可以控制生成的Makefile,从而控制编译过程。CMake自动生成的Makefile不仅可以通过make命令构建项目生成目标文件,还支持安装(make install)、测试安装的程序是否能正确执行(make test,或者ctest)、生成当前平台的安装包(make package)、生成源码包(make package_source)、产生Dashboard显示数据并上传等高级功能,只要在CMakeLists.txt中简单配置,就可以完成很多复杂的功能,包括写测试用例。如果有嵌套目录,子目录下可以有自己的CMakeLists.txt。

在 linux 平台下使用 CMake 生成 Makefile 并编译的流程如下:

  1. 编写 CMake 配置文件 CMakeLists.txt
  2. 执行命令 cmake PATH 或者 ccmake PATH 生成 Makefile 。其中, PATH 是 CMakeLists.txt 所在的目录
  3. 使用 make 命令进行编译。

示例

###这是一个简单的C快速排序程序
[free@hurd]$ vim test.c
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
void swap(int *x, int *y)
{
 int tmp;
 tmp = *x;
 *x = *y;
 *y = tmp;
 return ;
}
void display_arr(int *arr, int len)
{
 int i;
 for(i = 0; i < len; i++)
 {
 printf("%d\t", arr[i]);
 if((i + 1) % 10 == 0)
 printf("\n");
 }
 printf("\n");
 return ;
}
void genereate(int *arr, int len, int maxnum)
{
 int i;
 srand(time(NULL));
 
 for(i = 0; i < len; i++)
 {
 arr[i] = rand()%maxnum;
 }
 return ;
}
void sort(int *arr, int left, int right)
{
 if(left < right)
 {
 int i, j;
 i = left + 1;
 j = right;
 while(i < j)
 {
 if(arr[i] > arr[left])
 {
 swap(&arr[i], &arr[j]);
 j--;
 }
 else
 i++;
 }
 if(arr[i] >= arr[left])
 i--;
 swap(&arr[left], &arr[i]);
 sort(arr, left, i);
 sort(arr, j, right);
 }
}
int main(int argc, char *argv[])
{
 if(argc != 3)
 {
 fprintf(stderr,"\e[1;31mUsage: ./a.out len maxnum\e[0m");
 exit(1);
 }
 int len, maxnum;
 len = atoi(argv[1]);
 maxnum = atoi(argv[2]);
 if(len <= 0)
 {
 fprintf(stderr,"\e[1;31mThe array of length error\e[0m\n");
 exit(1);
 }
 if(maxnum <= 0)
 {
 fprintf(stderr, "\e[1;31mThe maxnum is error\e[0m\n");
 exit(1);
 }
 int arr[len];
 printf("\e[1;32m排序前的随机数组: \e[0m\n");
 genereate(arr, len, maxnum);
 display_arr(arr, len);
 printf("\e[1;32m排序后的有序数组: \e[0m\n");
 sort(arr, 0, len - 1);
 display_arr(arr, len);
 exit(0);
}
###编写CMakeLists.txt
[free@hurd]$ vim CMakeLists.txt
# CMake 最低版本号要求
cmake_minimum_required (VERSION 2.8)
# 项目信息
project (cmake_test)
# 指定生成目标
add_executable(test test.c)
###CMakeLists.txt 的语法比较简单,由命令、注释和空格组成,其中命令是不区分大小写的。符号 # 后面的内容被认为是注释。命令由命令名称、小括号和参数组成,参数之间使用空格进行间隔
###cmake_minimum_required:指定运行此配置文件所需的 CMake 的最低版本;
###project:参数值是 cmake_test,该命令表示项目的名称是 cmake_test
###add_executable:将名为 test.c 的源文件编译成一个名称为 test 的可执行文件
[free@hurd]$ cmake .
-- The C compiler identification is GNU 4.8.4
-- The CXX compiler identification is GNU 4.8.4
-- Check for working C compiler: /usr/bin/cc
-- Check for working C compiler: /usr/bin/cc -- works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working CXX compiler: /usr/bin/c++
-- Check for working CXX compiler: /usr/bin/c++ -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Configuring done
-- Generating done
-- Build files have been written to: /home/free/cmake
###得到生成的Makefile,并执行make
[free@hurd]$ make
Scanning dependencies of target test
[100%] Building C object CMakeFiles/test.dir/test.c.o
Linking C executable test
[100%] Built target test
[free@hurd]$ ./test 20 255
排序前的随机数组:
71	130	200	244	31	149	206	241	4	34	
127	11	251	167	233	68	250	231	247	14	
排序后的有序数组:
4	11	14	31	34	68	71	127	130	149	
167	200	206	231	233	241	244	247	250	251	
###下面对test.c进行拆分,将单独的函数抽取出来
[free@hurd]$ ls
CMakeCache.txt CMakeFiles cmake_install.cmake CMakeLists.txt display_arr.c genereate.c Makefile sort.c swap.c test test.c
[free@hurd]$ vim CMakeLists.txt
# CMake 最低版本号要求
cmake_minimum_required (VERSION 2.8)
# 项目信息
project (cmake_test)
# 指定生成目标
add_executable(test test.c display_arr.c genereate.c sort.c swap.c)
###唯一的改动只是在 add_executable 命令中增加了其他的 .c源文件
###如果源文件很多,把所有源文件的名字都加进去将是一件烦人的工作;更省事的方法是使用 aux_source_directory 命令,该命令会查找指定目录下的所有源文件,然后将结果存进指定变量名
###可以做如下修改
[free@hurd]$ vim CMakeLists.txt
# CMake 最低版本号要求
cmake_minimum_required (VERSION 2.8)
# 项目信息
project (cmake_test)
# 查找当前目录下的所有源文件
# 并将名称保存到 DIR_SRCS 变量
aux_source_directory(. DIR_SRCS)
# 指定生成目标
add_executable(test ${DIR_SRCS})
###CMake 会将当前目录所有源文件的文件名赋值给变量 DIR_SRCS ,再指示变量 DIR_SRCS 中的源文件需要编译成一个名称为 test 的可执行文件
[free@hurd]$ cmake .
-- Configuring done
-- Generating done
-- Build files have been written to: /home/free/cmake
[free@hurd]$ make
Scanning dependencies of target test
[ 20%] Building C object CMakeFiles/test.dir/display_arr.c.o
[ 40%] Building C object CMakeFiles/test.dir/test.c.o
[ 60%] Building C object CMakeFiles/test.dir/swap.c.o
[ 80%] Building C object CMakeFiles/test.dir/genereate.c.o
[100%] Building C object CMakeFiles/test.dir/sort.c.o
Linking C executable test
[100%] Built target test

来源网络,侵权联系删除

私信我或关注微信号:狮范课,回复:学习,获取免费学习资源包。

Tags:

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表