文件准备

hello.c
1
2
3
4
5
6
7
#include <stdio.h>
#include "message.h"

int main(){
sayHello();
return 0;
}
message.h
1
void sayHello();
message.c
1
2
3
4
5
6
#include "message.h"
#include <stdio.h>

void sayHello(){
printf("Hello world lnpbqc");
}

传统方法

1
2
gcc hello.c message.c -o hello
./hello

使用构建工具

只是一些简单的项目和文件结构当然可以这样写,但要是复杂起来就不是那么友好了。

这个时候就需要启用makecmake 了。

make的简单使用

make 是一个构建自动化工具,广泛应用于软件开发中,尤其在 Unix 和 Linux 环境下。
它通过读取名为 Makefile(当然可以读取其他文件,后续介绍) 的文件,根据其中定义的规则和依赖关系来编译和链接程序

make: 默认情况下,make 会执行 Makefile 中第一个目标(通常是 all 或主目标)。
make target: 只执行特定目标的规则。例如 make clean 只会执行 clean 规则。
make -j [N]: 并行构建,N 指定并行的任务数,可以加速编译。
使用make -f 指定文件名字 来根据其他Makefile规则编译
使用make -n 只打印命令不执行
使用make -C 目录 指定makefile工作目录

基本的结构:

1
2
生成目标:依赖文件
命令

由此即可编写基本make文件:

makefile
1
2
hello: main.c message.c
gcc main.c message.c -o hello

这样,只要main.c或者message.c发生改变,使用make命令即可完成编译。如果没有改变文件,则不会重新编译。

部分依赖编译

那么如果文件过多,有更多的依赖需要编译,但你只是改动了其中部分文件,不想全部重新编译,也可以分开编译中间文件,最后链接。

makefile
1
2
3
4
5
6
7
8
9
10
11
12
13
14
.PHONY: clean all
//.PHONY 表示后面是一个命令,而不会作为一个文件被make识别

hello: main.o message.o
gcc main.o message.o -o hello

main.o: main.c
gcc -c main.c

message.o: message.c
gcc -c message.c

clean:
rm -f *.o hello

这样一来,只是改动部分文件,而不需要全部重新编译。

有多个目标程序生成

可以使用按照make的特性,把目标程序作为依赖,来实现多个目标同时编译

Makefile
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
.PHONY: clean all

all: hello world
@echo "Done"
// 在命令前面添加 @ 只会显示执行结果,而不会显示命令本身,@echo "Done"

hello: main.o message.o
gcc main.o message.o -o hello

world: main.o message.o
gcc main.o message.o -o world

main.o: main.c
gcc -c main.c

message.o: message.c
gcc -c message.c

clean:
rm -f *.o hello world

而进一步地,相同生成规则可以写在一起。

makefile
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
.PHONY: clean all

all: hello world
@echo "Done"

hello world:main.o message.o
gcc main.o message.o -o $@

main.o: main.c
gcc -c main.c

message.o: message.c
gcc -c message.c

clean:
rm -f *.o hello world

定义变量

对应重复使用的值,可以通过变量一起管理,方便后续更改。

自动变量

  • $@ 表示目标文件
  • $< 表示第一个依赖
  • $^ 表示所有依赖
makefile
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
.PHONY: clean all

CFLAGS = -Wall -g -O2
targets = hello world
sources = main.c message.c
objects = main.o message.o

all: $(targets)
@echo "Done"

$(targets):$(objects)
gcc $(CFLAGS) $(objects) -o $@

main.o: main.c
gcc $(CFLAGS) -c main.c

message.o: message.c
gcc $(CFLAGS) -c message.c

clean:
rm -f *.o hello world

对于同样的文件后缀有着同样处理逻辑的,可以进步这样省略。
使用通配符简化

makefile
1
2
%.o: %.c
gcc $(CFLAGS) -c $< -o $@

完整示例

一个完整的Makefile 如下所示。

makefile
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 定义编译器和编译标志
CC = gcc
CFLAGS = -Wall -g

# 目标可执行文件
TARGET = HelloWorld

# 目标规则
$(TARGET): main.o message.o
$(CC) $(CFLAGS) -o $(TARGET) main.o message.o

# 编译目标文件
main.o: main.c
$(CC) $(CFLAGS) -c main.c

message.o: message.c
$(CC) $(CFLAGS) -c message.c

# 清理构建文件
clean:
rm -f $(TARGET) *.o


Cmake的初级使用

CMake 是一个跨平台的构建系统生成工具,广泛用于软件开发中,特别是在需要生成项目的构建系统配置文件(如 Makefile 或 Visual Studio 项目文件)时。
CMake 的主要功能是通过一个简单的脚本语言(CMakeLists.txt)来描述项目结构、依赖关系、编译和链接规则,然后为不同的平台生成相应的构建文件。

一个简单的CmakeLists.txt 示例如下。

1
2
3
4
5
6
7
8
9
cmake_minimum_required(VERSION 3.10)

project(HelloWorld)

# 定义变量
set(C_SOURCES hello.c message.c)
message("C_SOURCES: ${C_SOURCES}")

add_executable(HelloWorld ${C_SOURCES})

使用方法

  • 使用 cmake -S CmakeLists.txt所在目录 -B 生成的编译目录
  • 使用 cmake –build 编译生成目录

当你修改源文件后,你可以直接使用以下命令进行重新编译:
cmake --build build
不需要重新运行 cmake -S . -B build,因为构建系统已经生成好了。
如果你只是在编辑 .c 文件或其他源文件,直接运行 cmake –build build 就会重新编译受影响的部分,并生成更新后的可执行文件。

什么时候需要重新运行 cmake -S . -B build?
你只需要重新运行 cmake -S . -B build 或者执行类似步骤的情况包括:

修改了 CMakeLists.txt 文件:例如添加新的源文件、更改编译选项、添加新的库依赖等。
移动或删除了源文件:比如你更改了文件路径或文件名。
更换了编译工具链:例如从 make 切换到 ninja,或者从 gcc 切换到 clang。
在这些情况下,CMake 需要重新配置构建系统,所以你需要重新执行 cmake -S . -B build