快速入门Makefile、make、Cmake。学会Makefile、CMakeLists.txt的基本写法,学会基本用法。
简单快速编译指令
首先,我们先编写一个简单的C程序,如下所示:
//main.c
#include <stdio.h>
int main() {
printf("Hello, World!\n");
return 0;
}
然后我们使用简单的编译指令进行编译:
gcc main.c -o main
这样我们就可以得到一个可执行文件main。
直接运行./main
即可。
root@crzax:~/test# ./main
Hello, World!
引入Makefile
现在,我们封装打印函数
//hello.h
void hello();
//hello.c
#include <stdio.h>
void hello() {
printf("欢迎来到Crzax的博客!\n");
}
//main.c
#include <stdio.h>
#include "hello.h"
int main() {
//printf("Hello, World!\n");
hello();
return 0;
}
为了编译这些文件,我们接下来编写 Makefile 文件, Makefile 虽然是为C语言项目设计的,但其实他是一个通用的构建工具,可以构建任何类型的项目,但是目前还是主要用来构建C/C++项目。
编写Makefile文件
Makefile的规则为:
target : dependencies
action
...
基于此,我们构建如下Makefile文件:
main: main.c hello.c
gcc main.c hello.c -o main
由于Makefile的规则,命令前面只能是tab
键,不能是空格键,否则Make命令会报错。
这个规则的意思是,我们生成一个main的可执行文件,依赖于main.c
,log.c
,如果二者更新就执行下方的命令。
执行make
,
root@crzax:~/test# make
gcc main.c hello.c -o main
执行./main
root@crzax:~/test# ./main
欢迎来到Crzax的博客!
再次执行make
root@crzax:~/test# make
make: 'main' is up to date.
说明只有在依赖文件更新的时候才会执行。时间戳检查,依赖文件时间戳比目标文件新才会更新。
把编译和链接分开写的Makefile
main: main.o hello.o
gcc main.o hello.o -o main
main.o: main.c
gcc -c main.c
hello.o: hello.c
gcc -c hello.c
这样写的目的就是当更新一个源文件的时候只要重新编译那个源文件就可以了
伪目标
有的时候,我们可能需要进行清理文件、打包等不需要生成文件的操作,这个时候就需要伪目标了。
伪目标就是不生成文件的目标,她只是一个标签,用来执行一些操作。
main: main.o hello.o
gcc main.o hello.o -o main
main.o: main.c
gcc -c main.c
hello.o: hello.c
gcc -c hello.c
clean:
rm -rf *.o main
这个就是添加了clean伪目标。
执行make clean
。
root@crzax:~/test# make clean
rm -rf *.o main
显示指定伪目标
如果当前目录有clean文件,则make会把make clean
当作文件处理。
比如:
root@crzax:~/test# touch clean
root@crzax:~/test# make clean
make: 'clean' is up to date.
为了避免这种情况,我们可以在Makefile文件前面添加
.PHONY: clean
main: main.o hello.o
gcc main.o hello.o -o main
main.o: main.c
gcc -c main.c
hello.o: hello.c
gcc -c hello.c
clean:
rm -rf *.o main
用于显式告诉Make
这个clean
是一个伪目标。
再执行make clean
,即可
root@crzax:~/test# make clean
rm -rf *.o main
另一个常用的伪目标是all
。
默认来说,当我们执行make
的时候,Make会默认执行第一个规则。但是当我们想要生成的目标文件不止一个的话,就可以使用all这个伪目标。
.PHONY: clean all
all: main niam
echo "all done"
main: main.o hello.o
gcc main.o hello.o -o main
niam: main.o hello.o
gcc main.o hello.o -o niam
main.o: main.c
gcc -c main.c
hello.o: hello.c
gcc -c hello.c
clean:
rm -rf *.o main niam
root@crzax:~/test# make
gcc -c main.c
gcc -c hello.c
gcc main.o hello.o -o main
gcc main.o hello.o -o niam
echo "all done"
all done
这样我们就生成了main和niam两个可执行文件。
或者将all设置为默认目标,比如我们把它放在第一行。
root@crzax:~/test# make
echo "all done"
all done
只编译单独文件
很多时候,我们只想编译某一个文件,而不是整个项目或者工程。
比如说我只想编译main,那么执行
root@crzax:~/test# make main
gcc -c main.c
gcc -c hello.c
gcc main.o hello.o -o main
自动变量的使用
我们看到main和niam两个依赖的文件是一样的,所以我们可以放到一行写。
.PHONY: clean all
all: main niam
echo "all done"
main niam: main.o hello.o
gcc main.o hello.o -o $@
main.o: main.c
gcc -c main.c
hello.o: hello.c
gcc -c hello.c
clean:
rm -rf *.o main niam
执行make
root@crzax:~/test# make
gcc -c main.c
gcc -c hello.c
gcc main.o hello.o -o main
gcc main.o hello.o -o niam
echo "all done"
all done
如果我们不想看到某个过程的语句的话,可以在前面加上@,比如:
.PHONY: clean all
all: main niam
@echo "all done"
main niam: main.o hello.o
gcc main.o hello.o -o $@
main.o: main.c
gcc -c main.c
hello.o: hello.c
gcc -c hello.c
clean:
rm -rf *.o main niam
再执行make,那么就有:
root@crzax:~/test# make
gcc -c main.c
gcc -c hello.c
gcc main.o hello.o -o main
gcc main.o hello.o -o niam
all done
在Makefile中定义变量
我们一般会把一些编译选项定义成一个变量,这样后面编译命令就可以直接使用它。
比如定义一个CFLAGS
变量
.PHONY: clean all
CFLAGS = -Wall -g -O2
all: main niam
@echo "all done"
main niam: main.o hello.o
gcc $(CFLAGS) main.o hello.o -o $@
main.o: main.c
gcc $(CFLAGS) -c main.c
hello.o: hello.c
gcc $(CFLAGS) -c hello.c
clean:
rm -rf *.o main niam
一般在正式的工程文件中我们也会把目标文件、源文件、中间文件等等都定义成变量,比如:
.PHONY: clean all
CFLAGS = -Wall -g -O2
targets = main niam
sources = main.c hello.c
objects = main.o hello.o
all: $(targets)
@echo "all done"
$(targets): $(objects)
gcc $(CFLAGS) $(objects) -o $@
main.o: main.c
gcc $(CFLAGS) -c main.c
hello.o: hello.c
gcc $(CFLAGS) -c hello.c
clean:
rm -rf *.o main niam
再执行make
root@crzax:~/test# make
gcc -Wall -g -O2 -c main.c
gcc -Wall -g -O2 -c hello.c
gcc -Wall -g -O2 main.o hello.o -o main
gcc -Wall -g -O2 main.o hello.o -o niam
all done
自动变量的详细介绍
make在执行命令中自动设置的变量叫做自动变量。一般是一个美元符号加上一个特殊符号表示。
比如$@
表示目标文件,$<
表示第一个依赖文件,$^
表示所有的依赖文件等等。这些变量可以直接在Makefile中使用。
.PHONY: clean all
CFLAGS = -Wall -g -O2
targets = main niam
sources = main.c hello.c
objects = main.o hello.o
all: $(targets)
@echo "all done"
$(targets): $(objects)
gcc $(CFLAGS) $(objects) -o $@
main.o: main.c
gcc $(CFLAGS) -c $< -o $@
hello.o: hello.c
gcc $(CFLAGS) -c $< -o $@
clean:
rm -rf *.o main niam
再执行make
root@crzax:~/test# make
gcc -Wall -g -O2 -c main.c -o main.o
gcc -Wall -g -O2 -c hello.c -o hello.o
gcc -Wall -g -O2 main.o hello.o -o main
gcc -Wall -g -O2 main.o hello.o -o niam
all done
这个也是没有问题的。
我们也可以使用通配符,比如
main.o: main.c
gcc $(CFLAGS) -c $< -o $@
hello.o: hello.c
gcc $(CFLAGS) -c $< -o $@
除了目标文件和依赖文件的文件名部分不一样外,其他是一样的,那我们只需要:
%.o: %.c
gcc $(CFLAGS) -c $< -o $@
就可以用通配符简化了这两个规则,表示所有的.o文件都是由.c文件生成的。
再执行make
root@crzax:~/test# make
gcc -Wall -g -O2 -c main.c -o main.o
gcc -Wall -g -O2 -c hello.c -o hello.o
gcc -Wall -g -O2 main.o hello.o -o main
gcc -Wall -g -O2 main.o hello.o -o niam
all done
没有问题。
Make的选项参数
make
命令后面是可以加参数,比如Makefile文件名字是固定的,不能是其他文件,默认情况下make会自动识别makefile这个文件,我们现在想要指定用main.mk文件,不用makefile,就可以用-f参数。
比如:
root@crzax:~/test# make -f main.mk
gcc -Wall -g -O2 -c main.c -o main.o
gcc -Wall -g -O2 -c hello.c -o hello.o
gcc -Wall -g -O2 main.o hello.o -o main
gcc -Wall -g -O2 main.o hello.o -o niam
all done
root@crzax:~/test# make -f main.mk clean
rm -rf *.o main niam
其他参数:
-n 不执行命令,只是显示要执行的命令,一般在调试的时候使用。
-C 指定目录,比如make -C /home/crzax/test
,表示到/home/crzax/test
目录下执行make。当项目的工程下有多个不同的子模块,每个子模块都有自己的Makefile文件,那么可以在父目录下写一个汇总的Makefile文件,去调用子模块的Makefile文件。
Makefile的分支,循环,函数
Makefile也是支持分支、循环、函数等等的。这里略过。
CMake的用法
CMake是一个跨平台的构建工具,可以用来构建C、C++、Fortran等等项目。CMake是一个比较高级的跨平台构建工具,它可以根据不同的平台和编译工具,生成对应的构建文件。比如,可以生成Makefile文件,也可以生成Visual Studio、Xcode等IDE的工程文件。
CMake的配置文件
CMake的配置文件是CMakeLists.txt,这个文件是CMake的配置文件,用来配置项目的构建规则。
cmake_minimum_required(VERSION 3.10)
这个表示CMake的最低版本是3.10,如果低于这个版本,CMake会报错。
project(main)
这个表示项目的名字是main。
set(SOURCES main.c hello.c)
这个用set命令定义了一个变量SOURCES,这个变量包含了main.c和hello.c两个源文件。
add_executable(main ${SOURCES})
这个表示生成一个可执行文件main,依赖于SOURCES变量。
我们在目录的根目录下创建一个build目录,然后进入build目录,执行cmake命令,指定上一级目录的CMakeLists.txt文件。
root@crzax:~/test/build# cmake ..
-- The C compiler identification is GNU 11.4.0
-- The CXX compiler identification is GNU 11.4.0
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: /usr/bin/cc - skipped
-- Detecting C compile features
-- Detecting C compile features - done
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /usr/bin/c++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done
-- Generating done
-- Build files have been written to: /root/test/build
这个命令的意思是在当前目录下生成构建文件,但是CMakeLists文件不在当前目录下,而是在上一级目录里。当前目录下就生成了一个Makefile
文件
再执行make
root@crzax:~/test/build# make
[ 33%] Building C object CMakeFiles/main.dir/main.c.o
[ 66%] Building C object CMakeFiles/main.dir/hello.c.o
[100%] Linking C executable main
[100%] Built target main
再执行./main
root@crzax:~/test/build# ./main
欢迎来到Crzax的博客!
正常执行。
暂无评论内容