2025-01-16【Linux】看懂makefile + CMake

内容分享24小时前发布
0 0 0

本质就是看懂bash编程,bash是shell的一种

all        : str2str
str2str    : str2str.o stream.o rtkcmn.o solution.o sbas.o geoid.o
str2str    : rcvraw.o novatel.o ublox.o ss2.o crescent.o skytraq.o gw10.o javad.o
str2str    : nvs.o binex.o rt17.o rtcm.o rtcm2.o rtcm3.o rtcm3e.o preceph.o streamsvr.o

1 这是什么

在Makefile中,all 是一个伪目标(phony target)。伪目标是一种特殊的目标,它们并不表明实际的文件,而是用于执行一组命令或依赖关系。

在这个Makefile中,all 是一个用于构建所有目标的伪目标。它告知Make执行 str2str 目标,从而构建 str2str 可执行文件。一般,all 被用作默认目标,如果你直接运行 make 而没有指定目标,Make会执行 all 目标。

这种写法的目的是让用户只需运行 make 就能构建整个项目,而不必指定特定的目标。如果你在命令行中运行 make,它将默认执行 all 目标,从而构建 str2str 可执行文件。

2 依赖

在软件开发中,依赖(dependencies)指的是一个实体(一般是模块、文件、库或其他组件)依赖于另一个实体的情况。这种依赖关系表明一个实体的正确功能或构建取决于另一个实体的存在、状态或接口。

在编程中,依赖一般包括以下几种类型:

  1. 源代码级的依赖: 一个源代码文件依赖于其他源代码文件,由于它引用了其他文件中定义的变量、函数或类。

  2. 模块级的依赖: 一个软件模块依赖于其他模块,由于它使用了其他模块提供的功能或接口。

  3. 库级的依赖: 一个程序或模块依赖于一个库,由于它使用了库中封装的功能。

  4. 构建级的依赖: 在构建过程中,一个目标文件或可执行文件依赖于其他目标文件、源文件或库文件,由于它们是构建该目标所必需的。

在软件开发中,理解和管理依赖关系是超级重大的。合理的依赖管理可以确保系统的可维护性和可扩展性,并有助于避免潜在的问题,例如构建错误、运行时错误或不稳定的行为。
在Makefile中,每一行的依赖关系定义了目标文件(str2str)所依赖的源文件或其他目标文件。一般,每一行的依赖关系应该表明一个逻辑上的单元或模块,使得构建规则更加清晰和可维护。

在这个特定的Makefile中,str2str 可能是由不同模块(str2str.ostream.ortkcmn.o 等等)组成的,而这些模块可能分别负责不同的功能。通过将依赖关系分为多行,可以更容易地理解每个模块的作用,并更灵活地组织和管理代码。

这种做法还有助于使Makefile更易读,特别是当有许多依赖关系时。分成多行有助于提高代码的可读性和维护性。同时,如果其中一个模块的依赖关系发生变化,只需修改与该模块相关的那一行,而不会影响到其他模块的规则。

总的来说,这是一种组织代码的方式,目的是提高代码的可读性和维护性。
这样的行是为了指定每个目标文件的依赖关系,即告知 make 工具每个目标文件依赖于哪些头文件或其他源文件。在这里,str2str.o 文件依赖于 $(SRC)/rtklib.h 头文件,而 stream.o 文件也依赖于一样的头文件。

str2str.o  : $(SRC)/rtklib.h
stream.o   : $(SRC)/rtklib.h
streamsvr.o: $(SRC)/rtklib.h
rtkcmn.o   : $(SRC)/rtklib.h

这样的规则一般用于确保在修改了特定的头文件时,只重新编译依赖于该头文件的源文件,而不必重新编译整个项目。这有助于提高构建的效率,由于不需要重新编译那些未受影响的源文件。
这意味着如果$(SRC)/rtklib.h文件被修改,或者不存在,那么str2str.o就需要重新生成

make 中,这种规则的语法是:

target: dependencies
    commands

  • target: 目标文件或目标规则的名称。
  • dependencies: 目标文件所依赖的文件列表(需要存在或更新的文件,也叫前置条件¥)。
  • commands: 用于生成目标文件的命令。

在这里,str2str.ostream.o 都是目标文件,它们分别依赖于 $(SRC)/rtklib.h 头文件。

3 示例

下面是一个简单的C程序示例,包含两个源文件 main.cfunctions.c,以及一个头文件 functions.hmain.c 包含主函数,而 functions.c 包含一些辅助函数。functions.h 包含了函数的声明。

// main.c
#include <stdio.h>
#include "functions.h"

int main() {
    printf("Sum: %d
", add(3, 4));
    printf("Product: %d
", multiply(5, 6));
    return 0;
}

// functions.c
#include "functions.h"

int add(int a, int b) {
    return a + b;
}

int multiply(int a, int b) {
    return a * b;
}

// functions.h
#ifndef FUNCTIONS_H
#define FUNCTIONS_H

int add(int a, int b);
int multiply(int a, int b);

#endif

然后,创建一个名为 Makefile 的文件,定义编译规则和依赖关系:

# Makefile

CC = gcc
CFLAGS = -Wall -Wextra

# 目标文件
TARGET = myprogram

# 源文件
SRCS = main.c functions.c

# 生成的目标文件
OBJS = $(SRCS:.c=.o)  #将SRC中的.c替换为.o  源文件映射目标文件技巧

# 默认目标
all: $(TARGET)

# 链接目标文件生成可执行文件
$(TARGET): $(OBJS)
    $(CC) $(OBJS) -o $(TARGET)

# 编译每个源文件生成目标文件
%.o: %.c
    $(CC) $(CFLAGS) -c $< -o $@   #%<表明一个前置(依赖)对一个目标,%^是全部前置,包括重复文件对一个目标    $@是目标集

# 清理生成的文件
clean:
    rm -f $(OBJS) $(TARGET)

在这个 Makefile 中,我们定义了以下内容:

  • CC: 编译器的名称。
  • CFLAGS: 编译选项,包括 -Wall-Wextra
  • TARGET: 最终生成的可执行文件的名称。
  • SRCS: 源文件列表。
  • OBJS: 生成的目标文件列表。
  • all: 默认目标,生成可执行文件。
  • $(TARGET): 链接目标文件生成可执行文件。
  • %.o: %.c: 编译每个源文件生成目标文件。
  • clean: 清理生成的文件。

4 特别说明

  • makefile具有默认编译器变量CC/CXX
    在linux平台,CC会指向cc,再指向gccCXX会指向g++
    在Windows平台则没有这种指向,所以需要在makefile首行规定编译器,添加CC = gccCXX = g++

  • makefile具有默认编译选项变量CFLAGS 会被识别并自动加入编译指令

  • make工具已经超级智能,当规定了目标和依赖关系后,即便不设置编译指令,它会按照C语言的编译规则自动使用编译指令

案例1

CC = gcc
CFLAGS = -Wall -Wextra
# target file
TARGET = main
#source file
#generate file
OBJS = main.c add.c multiply.c
$(TARGET) : $(OBJS)
clean :
    del -f $(OBJS) $(TARGET).exe

执行指令

gcc -Wall -Wextra    main.c add.c multiply.c   -o main


案例2

CC = gcc
CFLAGS = -Wall -Wextra
# target file
TARGET = main
#source file
#generate file
OBJS = main.o add.o multiply.o
$(TARGET) : $(OBJS)
clean :
    del -f $(OBJS) $(TARGET).exe

执行指令

gcc -Wall -Wextra   -c -o main.o main.c
gcc -Wall -Wextra   -c -o add.o add.c
gcc -Wall -Wextra   -c -o multiply.o multiply.c
gcc   main.o add.o multiply.o   -o main

在不提供.c源文件的情况下,make工具甚至根据已有的.o文件自动填写与.c文件有关的指令。


案例3

CC = gcc
CFLAGS = -Wall -Wextra

# target file
TARGET = main

#source file
SRCS   = main.c add.c multiply.c

#generate file
OBJS = $(SRCS:.c=.o)

$(TARGET) : $(OBJS)
%.o: %.c
    $(CC) $(CFLAGS) -c $< -o $@
clean :
    del -f $(OBJS) $(TARGET).exe

执行指令

gcc -Wall -Wextra -c main.c -o main.o
gcc -Wall -Wextra -c add.c -o add.o
gcc -Wall -Wextra -c multiply.c -o multiply.o
gcc   main.o add.o multiply.o   -o main

案例3才是正规写法,使用

%.o: %.c
    $(CC) $(CFLAGS) -c $< -o $@

只规定.c到.o之间的编译,随后make工具会根据目标和依赖关系

$(TARGET) : $(OBJS)

自动执行

gcc   main.o add.o multiply.o   -o main

这样的指令比较符合C语言编译直觉,即便案例1,2也没大的问题。


5 全面转向CMake

CMake作为跨平台的C/C++编译工具,使得开发者只需专注于与平台无关的CMakeLists.txt文件的编写,此后CMake会自动生成对应Makefile。

5.1 CMake语法规则

Cmake 语法与实战入门 – 知乎 (zhihu.com)

© 版权声明

相关文章

暂无评论

none
暂无评论...