鹤啸九天 自律更自由,平凡不平庸 Less is More

Makefile

2021-12-21
鹤啸九天
阅读量

Notes(温馨提示):

  1. ★ 首次阅读建议浏览:导航指南
  2. 右上角工具条搜索文章,右下角二维码关注微信公众号(鹤啸九天),底栏分享、赞赏、评论
  3. ★ 转载请注明文章来源,知识点积累起来不容易,水滴石穿,绳锯木断,谢谢理解
  4. ★ 如有疑问,邮件讨论,欢迎贡献优质资料


makefile

1 概述

make工具:

  • Delphi-make
  • Visual C++ nmake
  • GNU make

文章主要针对GNU的make

GCC指令

参考链接: GCC;GCC online documentation;GCC C++ Command Options;GCC参数详解

GCC 编译步骤

参考链接: gcc程序的编译过程和链接原理;

GCC编译分为4步;

  • 编译流程图
    1. gcc -E:预处理阶段(preprocessing),对包含的头文件(#include)和宏定义(#define,#ifdef等)进行处理。在上述的代码处理过程中,编译器将包含的头文件stdio.h编译进来,并且让用户使用选项”-E“ 进行查看,该选项的作用是让gcc在预处理结束后停止编译过程。”.i“文件为已经过预处理的c程序。一下列出部分;处理操作: gcc -E a.c -o gcc_a_e.i -std=c99
    2. gcc -S:编译操作(compilation),将C/C++或者.i文件编译成为汇编代码。处理操作: gcc -S gcc_a_e.i -o gcc_a_e.s -std=c99
    3. gcc -c:汇编操作(assembly),将.c或者.s文件转化为对应的二进制文件.o或者.obj;处理操作:gcc -c gcc_a_e_s.s -o gcc_a.o;
    4. gcc -o a a.o:链接操作(link);链接不同的.o文件,生成可执行的文件。

常用选项表

选项 含义
-v 查看gcc编译器的版本,显示gcc执行时的详细过程
-o <file> Place the output into ;指定输出文件名为file,这个名称不能跟源文件名同名
-E Preprocess only; do not compile, assemble or link;只预处理,不会编译、汇编、链接
-S Compile only; do not assemble or link;只编译,不会汇编、链接
-c Compile and assemble, but do not link; 编译和汇编,不会链接
-On 编译优化选项,默认等级-O1,-O2告诉GCC除了完成-O1级别的优化之外,同时还要进行一些额外的调整工作,如处理器指令调度等,选项-O3则除了完成-O2级别的优化之外,还包括循环展开和其他一些与处理器特性相关的优化工作
-l -lLIBRARY 连接时搜索指定的函数库LIBRARY
-I Include 添加include头文件
-Ldir 制定编译的时候,搜索库的路径。比如你自己的库,可以用它制定目录,不然编译器将只在标准库的目录找。这个dir就是目录的名称。
-w/Wall 不生成/生成所有警告信息

1 make 的使用

用法:make [选项] [目标] ...
选项:
  -b, -m                      忽略兼容性。
  -B, --always-make           无条件 make 所有目标。
  -C DIRECTORY, --directory=DIRECTORY
                              在执行前先切换到 DIRECTORY 目录。
  -d                          打印大量调试信息。
  --debug[=FLAGS]             打印各种调试信息。
  -e, --environment-overrides
                              环境变量覆盖 makefile 中的变量。
  --eval=STRING               Evaluate STRING as a makefile statement.
  -f FILE, --file=FILE, --makefile=FILE
                              从 FILE 中读入 makefile。
  -h, --help                  打印该消息并退出。
  -i, --ignore-errors         Ignore errors from recipes.
  -I DIRECTORY, --include-dir=DIRECTORY
                              在 DIRECTORY 中搜索被包含的 makefile。
  -j [N], --jobs[=N]          同时允许 N 个任务;无参数表明允许无限个任务。
  -k, --keep-going            当某些目标无法创建时仍然继续。
  -l [N], --load-average[=N], --max-load[=N]
                              在系统负载高于 N 时不启动多任务。
  -L, --check-symlink-times   使用软链接及软链接目标中修改时间较晚的一个。
  -n, --just-print, --dry-run, --recon
                              Don not actually run any recipe; just print them.
  -o FILE, --old-file=FILE, --assume-old=FILE
                              将 FILE 当做很旧,不必重新生成。
  -O[TYPE], --output-sync[=TYPE]
                              Synchronize output of parallel jobs by TYPE.
  -p, --print-data-base       打印 make 的内部数据库。
  -q, --question              Run no recipe; exit status says if up to date.
  -r, --no-builtin-rules      禁用内置隐含规则。
  -R, --no-builtin-variables   禁用内置变量设置。
  -s, --silent, --quiet       Don not echo recipes.
  -S, --no-keep-going, --stop
                              关闭 -k-t, --touch                 touch 目标而不是重新创建它们。
  --trace                     Print tracing information.
  -v, --version               打印 make 的版本号并退出。
  -w, --print-directory       打印当前目录。
  --no-print-directory        关闭 -w,即使 -w 默认开启。
  -W FILE, --what-if=FILE, --new-file=FILE, --assume-new=FILE
                              将 FILE 当做最新。
  --warn-undefined-variables  当引用未定义变量的时候发出警告。

该程序为 x86_64-pc-linux-gnu 编译
报告错误到 <bug-make@gnu.org>

2 关于程序的编译与链接

具体的过程可以参照:C/C++ 的一个符号操作问题

  • 编译时(.c->.o):编译器需要的是语法正确,函数与变量的声明的正确。
  • 链接时(.o->exe):主要是链接函数和全局变量。

Makefile快速入门

Makefile快速入门

总结

【2020-12-28】九张图记住makefile

  • 解释编译和链接
    • 以C/C++代码为例,我们编写完代码后变成可执行文件需要经过编译和链接阶段。这个过程如果在Windows下是由IDE内置的编译器和链接器完成的。但是在Unix系统我们需要自己处理这个过程。以图解表示如下
  • Make的工作流程
    • 在 Makefile 中,规则的顺序是很重要的,因为,Makefile 中只应该有一个最终目标,其它的目标都是被这个目标所连带出来的,所以一定要让 make 知道你的最终目标是什么。一般来说,定义在 Makefile 中的目标可能会有很多,但是第一条规则中的目标将被确立为最终的目标。如果第一条规则中的目标有很多个,那么,第一个目标会成为最终的目标。 make所完成的也就是这个目标
  • 书写依赖规则
    • make实际上只是检查Makefile里面的依赖关系(规则),然后决定哪一条规则下面的命令会被执行。命令执行成功与否make并不管
  • 书写命令
    • 当make检查到某一条规则里,依赖文件比目标文件更新的情况时,就会执行该规则下的命令
  • 使用变量
    • 使用得最多最频繁的,可能是自动化变量了
    • Makefile中的自动化变量
  • 使用函数
  • 字符串处理函数
  • 文件名操作函数

【2021-5-12】5分钟理解make/makefile/cmake/nmake gcc是GNU Compiler Collection(就是GNU编译器套件),可以简单认为是编译器,编译很多种编程语言(括C、C++、Objective-C、Fortran、Java等等)。当程序只有一个源文件时,直接就可以用gcc命令编译它。可是,程序包含很多个源文件时,怎么办,总不能挨个编译吧?

  • make 工具可以看成是一个智能的批处理工具,它本身并没有编译和链接的功能,而是用类似于批处理的方式—通过调用makefile文件中用户指定的命令来进行编译和链接的。
  • makefile 就像一首歌的乐谱,make工具就像指挥家,指挥家根据乐谱指挥整个乐团怎么样演奏,make工具就根据makefile中的命令进行编译和链接的。makefile命令中就包含了调用gcc(也可以是别的编译器)去编译某个源文件的命令。
    • 当工程非常大时,手写makefile非常麻烦的,换了个平台makefile又要重新修改,这时候就出现了Cmake工具。
  • nmake是Microsoft Visual Studio中的附带命令,需要安装VS,实际上可以说相当于linux的make
  • cmake 可以更加简单的生成makefile文件。还可以跨平台生成对应平台能用的makefile,不用再自己去修改了。
    • 几种 Make 工具,例如 GNU Make ,QT 的 qmake ,微软的 MS nmake,BSD Make(pmake),Makepp,等等。这些 Make 工具遵循着不同的规范和标准,所执行的 Makefile 格式也千差万别。这样就带来了一个严峻的问题:如果软件想跨平台,必须要保证能够在不同平台编译。而如果使用上面的 Make 工具,就得为每一种标准写一次 Makefile ,这将是一件让人抓狂的工作。
    • CMake可以跨平台生成makefile的工具:它首先允许开发者编写一种平台无关的 CMakeList.txt 文件来定制整个编译流程,然后再根据目标用户的平台进一步生成所需的本地化 Makefile 和工程文件,如 Unix 的 Makefile 或 Windows 的 Visual Studio 工程。从而做到“Write once, run everywhere”。显然,CMake 是一个比上述几种 make 更高级的编译配置工具。一些使用 CMake 作为项目架构系统的知名开源项目有 VTK、ITK、KDE、OpenCV、OSG 等
    • CMakeList.txt:cmake根据一个叫CMakeLists.txt文件(学名:组态档)去生成makefile。
    • 执行命令 cmake PATH 或者 ccmake PATH 生成 Makefile(ccmake 和 cmake 的区别在于前者提供了一个交互式的界面)

【2022-1-20】总结:

  • (1)gcc/g++编译器:单个文件编译没问题,但多个源文件时,难以应付
    • 主要流程: *.c -> *.o -> *.exe/bin文件
  • (2)make工具:智能批处理工具,通过“曲谱”(makefile文件)里的过程来安排编译; 工程量大时,手写makefile非常麻烦,且换平台时需要重新编写
    • make:指挥家,根据makefile里的命令进行编译、链接
    • makefile:曲谱,包含了具体的编译命令
  • (3)百花齐放:各个平台独有的make工具,语法各不相同,难以实现跨平台
    • make(GNU Make)、qmake(QT)、nmake(微软)、pmake(BSD)
  • (4)CMake:跨平台/开源的make工具,Write once, run everywhere
    • CMakeList.txt 文件(组态档)制定编译流程
    • 根据具体平台生成对应的 本地化 makefile 文件 + 工程文件

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

  • 编写 CMake 配置文件 CMakeLists.txt 。
  • 执行命令 cmake PATH 或者 ccmake PATH 生成 Makefile, 其中, PATH 是 CMakeLists.txt 所在的目录。 ccmake 和 cmake 的区别在于前者提供了一个交互式的界面。
  • 使用 make 命令进行编译。

cmake 语法

CMake

  • 【2021-5-12】CMakeList语法知识
  • 实际项目中的C/C++文件不计其数、文件放置的位置也不同,Makefile定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作。实现自动化的编译。
  • CMake是一种跨平台编译工具,比make更高级,使用更方便。CMake主要是编写CMakeLists.txt文件,通过cmake命令将CMakeLists.txt文件转化为make所需要的Makefile文件,最后用make命令编译源码生成可执行程序或者库文件

make与cmake对比

  • make与cmake对比
flowchart LR %% 节点颜色 classDef red fill:#f02; classDef green fill:#5CF77B; classDef blue fill:#6BE0F7; classDef orange fill:#F7CF6B; classDef grass fill:#C8D64B; %%节点关系定义 A(源代码):::orange -->|手工编写| M(Makefile文件):::grass -->|make命令| E(可执行程序):::red A-->|自动生成| C(CMakeLists.txt):::green-->|make命令|M

cmake目录结构

  • cmake 指向CMakeLists.txt所在的目录,例如
    • cmake .. 表示CMakeLists.txt在当前目录的上一级目录。
    • cmake后会生成很多编译的中间文件以及makefile文件,所以一般建议新建一个新的目录,专门用来编译

CMake方式编译生成库文件

CMake的语法, 创建一个 test 项目,项目结构如下:

├── test目录 # 测试?
│ ├── CMakeLists.txt 
│ ├── include目录 # 头文件
│ │ ├── myprint.h
│ ├── src 目录 # 源码文件
│ │ ├── myprint.cpp
│ ├── lib目录 # lib库
│ ├── biuld目录 # 构建项目

myprint.h

#include<stdio.h>
#include<stdlib.h>

void myprint(char* str);

myprint.cpp

#include "/usr/demo/test5/include/myprint.h"

void myprint(char* str) {
    printf("%s",str);
}

cmake常用变量

  • PROJECT_SOURCE_DIR:工程的根目录
  • PROJECT_BINARY_DIR:运行cmake命令的目录,通常为${PROJECT_SOURCE_DIR}/build
  • PROJECT_NAME:返回通过 project 命令定义的项目名称
  • CMAKE_CURRENT_SOURCE_DIR:当前处理的 CMakeLists.txt 所在的路径
  • CMAKE_CURRENT_BINARY_DIR:target 编译目录
  • CMAKE_CURRENT_LIST_DIR:CMakeLists.txt 的完整路径
  • EXECUTABLE_OUTPUT_PATH:重新定义目标二进制可执行文件的存放位置
  • LIBRARY_OUTPUT_PATH:重新定义目标链接库文件的存放位置

CMakeLists.txt文件

  • CMakeLists.txt 的语法比较简单,由命令、注释和空格组成,其中命令是不区分大小写的。
  • 符号#后面的内容被认为是注释。
  • 命令由命令名称、小括号和参数组成,参数之间使用空格进行间隔。
# 添加本项目版本号:当前的项目的主版本号和副版本号
set (Demo_VERSION_MAJOR 1) # 主版本
set (Demo_VERSION_MINOR 0) # 副版本

# 指定CMake编译最低要求版本;使用高版本cmake 特有的一些命令时需要加上这行,提醒用户升级到该版本之后再执行 cmake。
CMAKE_MINIMUM_REQUIRED(VERSION 3.14)

# 指定项目的名称,一般和项目的文件夹名称对应
PROJECT(MYPRINT) # 或 project,这个命令非必须,但最好都加上。
# 引入两个变量 demo_BINARY_DIR 和 demo_SOURCE_DIR,同时,cmake自动定义了两个等价的变量 PROJECT_BINARY_DIR 和 PROJECT_SOURCE_DIR。

# 指定源文件目录,自动获取目录下所有源文件到变量名DIR_SRCS,免得一个个添加源文件;
AUX_SOURCE_DIRECTORY(src DIR_SRCS)
# 收集c/c++文件并赋值给变量SRC_LIST_CPP  ${PROJECT_SOURCE_DIR}代表区当前项目录
FILE(GLOB SRC_LIST_CPP ${PROJECT_SOURCE_DIR}/src/*.cpp)
FILE(GLOB SRC_LIST_C ${PROJECT_SOURCE_DIR}/src/*.c)

# -------- 添加子目录(嵌套包含cmakelists.txt) --------- 
add_subdirectory(math) # 子目录math下也包含源码
# 子目录中的 CMakeLists.txt
# 查找当前目录下的所有源文件
# 并将名称保存到 DIR_LIB_SRCS 变量
aux_source_directory(. DIR_LIB_SRCS)
# 生成链接库
add_library (MathFunctions ${DIR_LIB_SRCS})
# ---------------------------

# 指定头文件目录
INCLUDE_DIRECTORIES(${PROJECT_SOURCE_DIR}/include)
# 指定生成库文件的目录
SET(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib)

# ==== 设置编译类型 =====
# 去变量SRC_LIST_CPP 与SRC_LIST_C 指定生成libmyprint 动态库,默认生成静态库,SHARED指定生成库类型为动态库
# Linux 下  :demo libcommon.a libcommon.so
# Windows 下:demo.exe common.lib common.dll
ADD_LIBRARY(myprint SHARED ${SRC_LIST_CPP} ${SRC_LIST_C})
add_library(common STATIC util.cpp) # 生成静态库(默认)  libcommon.a(linux)/common.lib(windows)
add_library(common SHARED util.cpp) # 生成动态库或共享库  libcommon.so(linux)/common.dll(windows)
# 制定包含的源文件
add_library(demo demo.cpp test.cpp util.cpp) # 指定demo包含的源文件

# ===== 自定义搜索规则 =====
file(GLOB SRC_LIST "*.cpp" "protocol/*.cpp") # 将当前目录和protocol目录下的cpp文件找出来,存放到SRC_LIST变量中
add_library(demo ${SRC_LIST}) # demo依赖以上源文件
# 或者
file(GLOB SRC_LIST "*.cpp")
file(GLOB SRC_PROTOCOL_LIST "protocol/*.cpp")
add_library(demo ${SRC_LIST} ${SRC_PROTOCOL_LIST})
# 或者
aux_source_directory(. SRC_LIST)
aux_source_directory(protocol SRC_PROTOCOL_LIST)
add_library(demo ${SRC_LIST} ${SRC_PROTOCOL_LIST})
# 查找库文件
find_library(VAR name path) # 查找到指定的预编译库,并将它的路径存储在变量中
# 类似的命令还有 find_file()、find_path()、find_program()、find_package()。

aux_source_directory(. SRC_LIST) # 搜索当前目录下的所有.cpp文件, 并保存在变量SRC_LIST中
add_library(demo ${SRC_LIST})

# 生成可执行文件
add_executable(demo demo.cpp) # 生成可执行文件 demo/demo.exe

# 添加链接库
target_link_libraries(Demo MathFunctions) # 可执行文件 main 需要连接一个名为 MathFunctions 的链接库

# ------- 测试 ---------
# 启用测试
enable_testing()
# 测试程序是否成功运行
add_test (test_run Demo 5 2)
# 测试帮助信息是否可以正常提示
add_test (test_usage Demo)
set_tests_properties (test_usage
  PROPERTIES PASS_REGULAR_EXPRESSION "Usage: .* base exponent")

# 定义一个宏,用来简化测试工作
macro (do_test arg1 arg2 result)
  add_test (test_${arg1}_${arg2} Demo ${arg1} ${arg2})
  set_tests_properties (test_${arg1}_${arg2}
    PROPERTIES PASS_REGULAR_EXPRESSION ${result})
endmacro (do_test)
# 使用该宏进行一系列的数据测试
do_test (5 2 "is 25")
do_test (10 5 "is 100000")

# ------ gdb调试 ------
set(CMAKE_BUILD_TYPE "Debug")
set(CMAKE_CXX_FLAGS_DEBUG "$ENV{CXXFLAGS} -O0 -Wall -g -ggdb")
set(CMAKE_CXX_FLAGS_RELEASE "$ENV{CXXFLAGS} -O3 -Wall")

# ------- 安装包 -------
# 构建一个 CPack 安装包
include (InstallRequiredSystemLibraries) # 导入 InstallRequiredSystemLibraries 模
set (CPACK_RESOURCE_FILE_LICENSE # 设置一些 CPack 相关变量,包括版权信息和版本信息,
  "${CMAKE_CURRENT_SOURCE_DIR}/License.txt")
set (CPACK_PACKAGE_VERSION_MAJOR "${Demo_VERSION_MAJOR}")
set (CPACK_PACKAGE_VERSION_MINOR "${Demo_VERSION_MINOR}")
include (CPack) # 导入 CPack 模块
# 生成二进制安装包:
cpack -C CPackConfig.cmake
# 生成源码安装包
cpack -C CPackSourceConfig.cmake

cd到项目biuld目录执行cmake命令, 将会在biuld目录下生成Makefile文件,执行make命令项目就会开始编译,在项目lib目录下生成libmyprint.so文件

mkdir build
cd build
cmake .. # cmakelists文件目录
cmake -H. -Bbuild # 或指定输出目录
ccmake .. # 可交互方式进行,字符界面

# 生成makefile文件
make # 开始编译
# 生成libmyprint.dylib文件

使用库文件

#include <stdio.h>
#include "/usr/demo/test5/include/myprint.h"

int main() {
        myprint("hello World\n");
        return 0;
}

重新编写CMakeLists.txt

CMAKE_MINIMUM_REQUIRED(VERSION 3.14)
#指定项目名称
PROJECT(HELLO)
#将hello.cpp 赋值给SOURCE 变量
SET(SOURCE ${PROJECT_SOURCE_DIR}/src/hello.cpp)
#指定头文件目录
INCLUDE_DIRECTORIES(${PROJECT_SOURCE_DIR}/include)
#指定链接库文件目录
LINK_DIRECTORIES(${PROJECT_SOURCE_DIR}/lib)
#将hello.cpp生成可执行文件hello 
ADD_EXECUTABLE(hello ${SOURCE})
#指定hello 链接库myprint
TARGET_LINK_LIBRARIES(hello myprint)

build目录下,重新执行一遍cmake及make,生成二进制文件 ./hello

CMakeLists.txt文件规则

(1)条件控制

逻辑判断和比较:

  • if (expression):expression 不为空(0,N,NO,OFF,FALSE,NOTFOUND)时为真
  • if (not exp):与上面相反
  • if (var1 AND var2)
  • if (var1 OR var2)
  • if (COMMAND cmd):如果 cmd 确实是命令并可调用为真
  • if (EXISTS dir) if (EXISTS file):如果目录或文件存在为真
  • if (file1 IS_NEWER_THAN file2):当 file1 比 file2 新,或 file1/file2 中有一个不存在时为真,文件名需使用全路径
  • if (IS_DIRECTORY dir):当 dir 是目录时为真
  • if (DEFINED var):如果变量被定义为真
  • if (var MATCHES regex):给定的变量或者字符串能够匹配正则表达式 regex 时为真,此处 var 可以用 var 名,也可以用 ${var}
  • if (string MATCHES regex)

数字比较:

  • if (variable LESS number):LESS 小于
  • if (string LESS number)
  • if (variable GREATER number):GREATER 大于
  • if (string GREATER number)
  • if (variable EQUAL number):EQUAL 等于
  • if (string EQUAL number)

字母表顺序比较:

  • if (variable STRLESS string)
  • if (string STRLESS string)
  • if (variable STRGREATER string)
  • if (string STRGREATER string)
  • if (variable STREQUAL string)
  • if (string STREQUAL string)

示例


if(MSVC)
    set(LINK_LIBS common)
else()
    set(boost_thread boost_log.a boost_system.a)
endif()
target_link_libraries(demo ${LINK_LIBS})
# 或者
if(UNIX)
    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -fpermissive -g")
else()
    add_definitions(-D_SCL_SECURE_NO_WARNINGS
    D_CRT_SECURE_NO_WARNINGS
    -D_WIN32_WINNT=0x601
    -D_WINSOCK_DEPRECATED_NO_WARNINGS)
endif()
 
if(${CMAKE_BUILD_TYPE} MATCHES "debug")
    ...
else()
    ...

(2)循环

# ----- while ------
while(condition)
    ...
endwhile()
# ----- for ------
foreach(loop_var RANGE start stop [step])
    ...
endforeach(loop_var)

foreach(i RANGE 1 9 2)
    message(${i})
endforeach(i)
# 输出:13579

(3) 打印信息

message(${PROJECT_SOURCE_DIR}) message(“build with debug mode”) message(WARNING “this is warnning message”) message(FATAL_ERROR “this build has many error”) # FATAL_ERROR 会导致编译失败

(4)文件包含

include(./common.cmake) # 指定包含文件的全路径
include(def) # 在搜索路径中搜索def.cmake文件
set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake) # 设置include的搜索路径

(5) 预定义变量

  • PROJECT_SOURCE_DIR:工程的根目录
  • PROJECT_BINARY_DIR:运行 cmake 命令的目录,通常是 ${PROJECT_SOURCE_DIR}/build
  • PROJECT_NAME:返回通过 project 命令定义的项目名称
  • CMAKE_CURRENT_SOURCE_DIR:当前处理的 CMakeLists.txt 所在的路径
  • CMAKE_CURRENT_BINARY_DIR:target 编译目录
  • CMAKE_CURRENT_LIST_DIR:CMakeLists.txt 的完整路径
  • CMAKE_CURRENT_LIST_LINE:当前所在的行
  • CMAKE_MODULE_PATH:定义自己的 cmake 模块所在的路径,SET(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake),然后可以用INCLUDE命令来调用自己的模块
  • EXECUTABLE_OUTPUT_PATH:重新定义目标二进制可执行文件的存放位置
  • LIBRARY_OUTPUT_PATH:重新定义目标链接库文件的存放位置

(6)变量

系统信息 ­- CMAKE_MAJOR_VERSION:cmake 主版本号,比如 3.4.1 中的 3

  • ­CMAKE_MINOR_VERSION:cmake 次版本号,比如 3.4.1 中的 4 ­- CMAKE_PATCH_VERSION:cmake 补丁等级,比如 3.4.1 中的 1 ­- CMAKE_SYSTEM:系统名称,比如 Linux-­2.6.22 ­- CMAKE_SYSTEM_NAME:不包含版本的系统名,比如 Linux ­- CMAKE_SYSTEM_VERSION:系统版本,比如 2.6.22 ­- CMAKE_SYSTEM_PROCESSOR:处理器名称,比如 i686 ­- UNIX:在所有的类 UNIX 平台下该值为 TRUE,包括 OS X 和 cygwin ­- WIN32:在所有的 win32 平台下该值为 TRUE,包括 cygwin
set(ENV{Name} value) # 设置环境变量,这里没有“$”符号
$ENV{Name} # 使用环境变量

开关选项

  • BUILD_SHARED_LIBS:这个开关用来控制默认的库编译方式,如果不进行设置,使用 add_library 又没有指定库类型的情况下,默认编译生成的库都是静态库。如果 set(BUILD_SHARED_LIBS ON) 后,默认生成的为动态库
  • CMAKE_C_FLAGS:设置 C 编译选项,也可以通过指令 add_definitions() 添加
  • CMAKE_CXX_FLAGS:设置 C++ 编译选项,也可以通过指令 add_definitions() 添加

add_definitions(-DENABLE_DEBUG -DABC) # 参数之间用空格分隔

参考:CMakeLists.txt 语法介绍与实例演练

介绍

  • Makefile教程(绝对经典,所有问题看这一篇足够了)
  • make命令会在当前目录下按顺序找寻文件名为“GNUmakefile”、“makefile”、“Makefile”的文件,找到了解释这个文件
  • 指定某个makefile文件:make -f Make.Linux 或 make –file Make.AIX。
  • 引用makefile:用include关键字可以把别的Makefile包含进来,这很像C语言的#include,被包含的文件会原模原样的放在当前文件的包含位置。
    • include foo.make *.mk $(bar)
    • 在include前面可以有一些空字符,但是绝不能是[Tab]键开始。include和可以用一个或多个空格隔开。

Makefile里主要包含了五个东西:显式规则、隐晦规则、变量定义、文件指示和注释。

  • 显式规则。显式规则说明了,如何生成一个或多的的目标文件。这是由Makefile的书写者明显指出,要生成的文件,文件的依赖文件,生成的命令。
  • 隐晦规则。由于我们的make有自动推导的功能,所以隐晦的规则可以让我们比较粗糙地简略地书写Makefile,这是由make所支持的。
  • 变量的定义。在Makefile中我们要定义一系列的变量,变量一般都是字符串,这个有点你C语言中的宏,当Makefile被执行时,其中的变量都会被扩展到相应的引用位置上。
  • 文件指示。其包括了三个部分,一个是在一个Makefile中引用另一个Makefile,就像C语言中的include一样;另一个是指根据某些情况指定Makefile中的有效部分,就像C语言中的预编译#if一样;还有就是定义一个多行的命令。有关这一部分的内容,我会在后续的部分中讲述。
  • 注释。Makefile中只有行注释,和UNIX的Shell脚本一样,其注释是用“#”字符,这个就像C/C++中的“//”一样。如果你要在你的Makefile中使用“#”字符,可以用反斜框进行转义,如:“#”。
  • 最后,还值得一提的是,在Makefile中的命令,必须要以[Tab]键开始。

makefile执行顺序

GNU的make工作时的执行步骤入下:(想来其它的make也是类似)

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

  • 变量
    • 自动变量
      • $< : 规则中的第一个依赖目标. 如果依赖目标是多个, 逐个表示依赖目标
      • $@:规则中的目标集合
      • $^: 规则中所有的依赖, 所有依赖目标的集合, 会去除重复的依赖目标
      • $% : 当目标是函数库文件时, 表示其中的目标文件名
      • $? : 比目标新的依赖目标的集合
      • $+ : 所有依赖目标的集合, 不会去除重复的依赖目标
      • $* : 这个是GNU make特有的, 其它的make不一定支持
    • 默认变量
      • CC:cc(即gcc)
      • CXX: g++
      • AR: ar
      • APPFLAGS:预处理使用的选项
      • CFLAGS:编译的时候使用的选项,语言编译器的参数
      • LDFLAGS:链接库使用的选项
      • CXXFLAGS: C++语言编译器的参数
      • ARFLAGS: AR命令的参数
      • RM: rm -f
  • 函数
    • wildcard 查找当前目录下所有.c文件,返回值给src
      • src=$(wildcard ./*.c)
    • patsubst 替换所有.c文件为.o文件
      • obj=$(patsubst ./%.c, ./%.o, $(src))
  • 条件控制

示例

  • 编译:makefile–参数传递、条件判断、include (五)
    • make 或 make hello
    • make -f Makefile.rule # 执行指定的makefile文件
    • make HOST_CFLAGS=tmp.cpp # 传参指定编译哪个文件,HOST_CFLAGS变量将会替换相应Makefile中的HOST_CFLAGS
    • make FILE=test.cpp # 传参
    • 清理现场:make clean
# 目录变量
SRC = tmp.cpp # 源码
SRC = $(HOST_CFLAGS) # 从外部参数指定源文件
DIR = ./out # 二进制文件目录

ifeq ($(DEBUG_SYMBOLS), TRUE)
	CFLAGS += -g -Wall -Werror -O0
else
	CFLAGS += -Wall -Werror -O2
endif 

ifdef FILE
	SRC = $(FILE)
else
	SRC = tmp.cpp
endif

#SRC = test.cpp
# 调用shell获取当前目录地址
CUR_DIR = $(shell pwd -L .) # 调用shell命令

FILE_NAME = $(basename $(SRC)) # 提取路径文件
# include $(SRC_BASE)/Makefile.rule # 包含别的makefile文件
# 自动寻找本目录源码文件 wildcard表示文件匹配
#OBJS = $(patsubst %.cpp, %.o, $(wildcard *.cpp))
# 指定源码文件 patsubst表示模式替换
OBJS = $(patsubst %.cpp, %.o, $(SRC))
# 公共变量
CC = g++ # 编译器
CFLAGS = -c -Wall # 配置信息
LFLAGS = -Wall
 
# 目标 hello
hello: $(OBJS)
	$(CC) $(LFLAGS) $^ -o $(DIR)/$@
# 伪目标
.PHONY : clean
# 目标clean,清理冗余文件
clean :
	rm -rf *.o hello
# 帮助文件
help:
	@echo "编译$(SRC)..."
	@echo "make HOST_CFLAGS=test.cpp # 编译指定文件"
	@echo "make -f makefile # 编译指定makefile"
	@echo "make clean # 清理现场"

需求介绍

首先,假设我们有如下几个代码文件:

— functions.h —

// functions.h
void print_hello();
int factorial(int n);

— function1.cpp —

// function1.cpp
#include "functions.h"
int factorial(int n){
    if (n!=1){
        return n*factorial(n-1);
    else return 1;
}

— function2.cpp —

//function2.cpp
#include<iostream>
#include<>

— main.cpp —

//main.cpp
# include<iostream>
# include "functions.h"
 
int main(){
    print_hello();
    std::cout << "this is main" << std::endl;
    std::cout << "The factorial of 5 is " << factorial(5) << std::endl;
    return 0;
}

不用 makefile 如何编译?

如果不用 makefile,则需要按照下面的方式编译上述代码:

g++ -c function1.cpp
g++ -c function2.cpp
g++ -c main.cpp
g++ -o hello main.o function1.o function2.o

其中,g++ -c function1.cpp 会将源码编译成名为 function1.o 对象文件。如果不想采用默认的命名,也可以自定义文件名,例如:g++ -c function1.cpp -o fun1.o

也可以用一行命令整合编译、链接的步骤:

g++ -o hello main.cpp function1.cpp function2.cpp

这种方式有很多弊端,例如:

    1. 每次编译、链接都需要手动敲的很多命令。
    1. 当工程量很大时,编译整个工程需要花很久。而我们往往并不是每次都修改了所有源文件,因此希望程序自动编译那些被修改的源码,而没被修改的部分不要浪费时间重新编译。

为了解决上述第一个问题,我们可以把所有编译需要的命令保存到文件中,编译时一键执行。针对第二个问题,我们希望有一个软件,自动检测哪些源文件被修改过,然后自动把它们挑出来选择性地编译。而 make 命令通过检测代码文件的时间戳,决定是否编译它。

第一版 Makefile

首先需要确定 Makefile 的名字,需要设置成 Makefile 或者 makefile,而不能是其它版本(MakeFile, Make_file, makeFile,... )。其次,需要注意的是 Makefile 是缩进敏感的,在行首一定不能随便打空格。下面我们看一下第一版 Makefile。

# Makefile (井号为注释)
all:
    g++ -o hello main.cpp function1.cpp function2.cpp
clean:
    rm -rf *.o hello

(注意上面代码片段的缩进,是一个而不是4个或者8个空格。)

  • 其中 allclean的术语为 target,我也可以随意指定一个名字,例如 abc,真正执行编译的是它下面缩进行的命令。我们可以看到,这个命令和我们在命令行中手动敲的没有任何区别。因此,通过这个简单的 Makefile,就可以省去了每次手动敲命令的痛苦:只需要在命令行敲下 make 回车,即可完成编译。
  • clean 表示清除编译结果,它下方就是普通的命令行删除文件命令。命令行输入 make 将默认执行第一个 target (即 all)下方的命令;如要执行清理操作,则需要输入 make clean,指定执行 clean 这个 target 下方的命令。
  • 这个 Makefile 虽然可以省去敲命令的痛苦,却无法选择性编译源码。因为我们把所有源文件都一股脑塞进了一条命令,每次都要编译整个工程,很浪费时间。第二版 Makefile 将解决这个问题。

第二版 Makefile

既然我们希望能够选择性地编译源文件,就不能像上一节那样把所有源文件放在一条命令里编译了,而是要分开写:

all: hello
hello: main.o function1.o function2.o
    g++ main.o function1.o function2.o -o hello
main.o: main.cpp
    g++ -c main.cpp
function1.o: function1.cpp
    g++ -c function1.cpp
function2.o: function2.cpp
    g++ -c function2.cpp
clean:
    rm -rf *.o hello

上面的 Makefile 包含了一条重要的语法::。即,目标:目标依赖的文件。

顺着代码捋一下逻辑:

    1. 命令行输入 make ,将默认执行 all 这个 target;
    1. 而 all 这个 target 依赖于 hello,hello 在当前目录下并不存在,于是程序开始往下读取命令..……终于找到了 hello 这个 target;
    1. 正待执行 hello 这个 target 的时候,却发现它依赖于 main.o,function1.o,function2.o 这三个文件,而它们在当前目录下都不存在,于是程序继续向下执行;
    1. 遇到 main.o target,它依赖于 main.cpp。而 main.cpp 是当前目录下存在的文件,终于可以编译了,生成 main.o 对象文件。后面两个函数以此类推,都编译好之后,再回到 hello target,连接各种二进制文件,生成 hello 文件。

第一次编译的时候,命令行会输出:

g++ -c main.cpp
g++ -c function1.cpp
g++ -c function2.cpp
g++ main.o function1.o function2.o -o hello

证明所有的源码都被编译了一遍。假如我们对 main.cpp 做一点修改,再重新 make(重新 make 前不要 make clean),则命令行只会显示:

g++ -c main.cpp
g++ main.o function1.o function2.o -o hello

这样,我们就发挥出 Makefile 选择性编译的功能了。下面,将介绍如何在 Makefile 中声明变量(declare variable)。

第三版 Makefile

我们希望将需要反复输入的命令整合成变量,用到它们时直接用对应的变量替代,这样如果将来需要修改这些命令,则在定义它的位置改一行代码即可。

CC = g++
CFLAGS = -c -Wall
LFLAGS = -Wall
 
all: hello
hello: main.o function1.o function2.o
    $(CC) $(LFLAGS) main.o function1.o function2.o -o hello
main.o: main.cpp
    $(CC) $(CFLAGS) main.cpp
function1.o: function1.cpp
    $(CC) $(CFLAGS) function1.cpp
function2.o: function2.cpp
    $(CC) $(CFLAGS) function2.cpp
clean:
    rm -rf *.o hello

上面的 Makefile 中,开头定义了三个变量:CC,CFLAGS,和 LFLAGS。其中 CC 表示选择的编译器(也可以改成 gcc);CFLAGS 表示编译选项,-c 即 g++ 中的 -c,-Wall 表示显示编译过程中遇到的所有 warning;LFLAGS 表示链接选项,它就不加 -c 了。这些名字都是自定义的,真正起作用的是它们保存的内容,因此只要后面的代码正确引用,将它们定义成阿猫阿狗都没问题。容易看出,引用变量名时需要用 $() 将其括起来,表示这是一个变量名。

第四版 Makefile

第三版的 Makefile 还是不够简洁,例如我们的 dependencies 中的内容,往往和 g++ 命令中的内容重复:

hello: main.o function1.o function2.o
    $(CC) $(LFLAGS) main.o function1.o function2.o -o hello

我们不想敲那么多字,能不能善用 : 中的内容呢?这就需要引入下面几个特殊符号了(也正是这些特殊符号,把 Makefile 搞得像是天书,吓退了很多初学者):$@ ,$<,$^。

例如我们有 target: dependencies 对:all: library.cpp main.cpp

    • $@ 指代 all ,即 target
    • $< 指代 library.cpp, 即第一个 dependency
    • $^ 指代 library.cpp 和 main.cpp,即所有的 dependencies

因此,本节开头的 Makefile 片段可以改为:

hello: main.o function1.o function2.o
    $(CC) $(LFLAGS) $^ -o $@

而第四版 Makefile 就是这样的:

CC = g++
CFLAGS = -c -Wall
LFLAGS = -Wall
 
all: hello
hello: main.o function1.o function2.o
    $(CC) $(LFLAGS) $^ -o $@
main.o: main.cpp
    $(CC) $(CFLAGS) $<
function1.o: function1.cpp
    $(CC) $(CFLAGS) $<
function2.o: function2.cpp
    $(CC) $(CFLAGS) $<
clean:
    rm -rf *.o hello

但是手动敲文件名还是有点麻烦,能不能自动检测目录下所有的 cpp 文件呢?此外 main.cpp 和 main.o 只差一个后缀,能不能自动生成对象文件的名字,将其设置为源文件名字后缀换成 .o 的形式?

第五版 Makefile

想要实现自动检测 cpp 文件,并且自动替换文件名后缀,需要引入两个新的命令:patsubstwildcard

5.1 wildcard

wildcard 用于获取符合特定规则的文件名,例如下面的代码:

SOURCE_DIR = . # 如果是当前目录,也可以不指定
SOURCE\_FILE = $(wildcard $(SOURCE\_DIR)/*.cpp)
target:
    @echo $(SOURCE_FILE)

make 后发现,输出的为当前目录下所有的 .cpp 文件:

./function1.cpp ./function2.cpp ./main.cpp

其中 @echo 前加 @是为了避免命令回显,上文中 make clean 调用了 rm -rf 会在 terminal 中输出这行命令,如果在 rm 前加了 @ 则不会输出了。

5.2 patsubst

patsubst 应该是 pattern substitution 的缩写。用它可以方便地将 .cpp 文件的后缀换成 .o。它的基本语法是:$(patsubst 原模式,目标模式,文件列表)。运行下面的示例:

SOURCES = main.cpp function1.cpp function2.cpp
OBJS = $(patsubst %.cpp, %.o, $(SOURCES))
target:
    @echo $(SOURCES)
    @echo $(OBJS)

输出的结果为:

main.cpp function1.cpp function2.cpp
main.o function1.o function2.o

5.3 综合两个命令

综合上述两个命令,我们可以升级到第五版 Makefile:

OBJS = $(patsubst %.cpp, %.o, $(wildcard *.cpp))
CC = g++
CFLAGS = -c -Wall
LFLAGS = -Wall
 
all: hello
hello: $(OBJS)
    $(CC) $(LFLAGS) $^ -o $@
main.o: main.cpp
    $(CC) $(CFLAGS) $< -o $@
function1.o: function1.cpp
    $(CC) $(CFLAGS) $< -o $@
function2.o: function2.cpp
    $(CC) $(CFLAGS) $< -o $@
 
clean:
    rm -rf *.o hello

然而这一版的 Makefile 还有提升空间,它的 main.o,function1.o,function2.o 使用的都是同一套模板,不过换了个名字而已。第六版的 Makefile 将处理这个问题。

第六版 Makefile

  • 这里要用到 Static Pattern Rule,其语法为:
    • targets: target-pattern: prereq-patterns
  • 其中 targets 不再是一个目标文件了,而是一组目标文件。而 target-pattern 则表示目标文件的特征。例如目标文件都是 .o 结尾的,那么就将其表示为 %.o,prereq-patterns (prerequisites) 表示依赖文件的特征,例如依赖文件都是 .cpp 结尾的,那么就将其表示为 %.cpp
  • 通过上面的方式,可以对 targets 列表中任何一个元素,找到它对应的依赖文件,例如通过 targets 中的 main.o,可以锁定到 main.cpp。

下面是第六版的 Makefile

OBJS = $(patsubst %.cpp, %.o, $(wildcard *.cpp))
CC = g++
CFLAGS = -c -Wall
LFLAGS = -Wall
 
all: hello
hello: $(OBJS)
    $(CC) $(LFLAGS) $^ -o $@
$(OBJS):%.o:%.cpp
    $(CC) $(CFLAGS) $< -o $@
 
clean:
    rm -rf *.o hello

看到有的 Makefile 设置了 -lm 的 flag,查阅资料发现表示连街 math 库,因为代码中可能 #include<math.h>

例如 g++ -o out fun.cpp -lm

CC = g++
LIBS = -lm
out: fun.cpp
    $(CC) -o $@ $^ $(LIBS)

总结

本文介绍了如何写 Makefile,主要的知识点有:

  • 在 Makefile 中定义变量并引用
  • $^,$@,$< 的含义
  • wildcard,patsubst 的用法
  • static pattern rule:targets: target-pattern: prereq-patterns

3 Makefile介绍

为了方便使用gcc等编译器进行编译和链接,对复杂项目使用Makefile文件来,记录编译指令的顺序,相当于另外的针对gcc等编译器的shell脚本。使用make命令来执行相关的命令。

3.1 Makefile的规则

基本指令格式:

target... : prerequisites ...
    command
    ...
#或者
targets : prerequisites ; command
    command
...
  • target:编译目标文件;可以使用ObjectFile、label或者可执行文件
  • prerequisites 生成所需要的文件或者目标
  • command 就是make需要执行的命令(任意的shell命令)

注意这里的command如果不是和target在同一行,则必须以[Tab]开头。命令行可以使用\作为续行符。

简单示例:

edit : main.o kbd.o command.o display.o \
    insert.o search.o files.o utils.o
    cc -o edit main.o kbd.o command.o display.o \
    insert.o search.o files.o utils.o
main.o : main.c defs.h
    cc -c main.c
kbd.o : kbd.c defs.h command.h
    cc -c kbd.c
command.o : command.c defs.h command.h
    cc -c command.c
display.o : display.c defs.h buffer.h
    cc -c display.c
insert.o : insert.c defs.h buffer.h
    cc -c insert.c
search.o : search.c defs.h buffer.h
    cc -c search.c
files.o : files.c defs.h buffer.h command.h
    cc -c files.c
utils.o : utils.c defs.h
    cc -c utils.c
clean :
    rm edit main.o kbd.o command.o display.o \
    insert.o search.o files.o utils.o

注意这里的clean是一个label相当于一个记录动作的变量

3.3 make工作流程

  • 查找当前目录下的“Makefile”或者“makefile”文件。
  • 找到文件,查找文件中的第一个目标文件(target)。并将其设置为最终的目标文件
  • 如果文件不存在,或者相关依赖需要其它的动作生成,或者依赖文件的文件修改时间要比edit这个文件新,则执行后面的命令生成相关目标文件。
  • 一层层的查找相关依赖,进行递归,执行程序。

3.4 makefile中使用变量

可以直接使用=来进行变量赋值。如下:

objects = main.o kbd.o command.o display.o \
    insert.o search.o files.o utils.o
edit : $(objects)
    cc -o edit $(objects)
main.o : main.c defs.h
    cc -c main.c
kbd.o : kbd.c defs.h command.h
    cc -c kbd.c
command.o : command.c defs.h command.h

    cc -c command.c
display.o : display.c defs.h buffer.h
    cc -c display.c
insert.o : insert.c defs.h buffer.h
    cc -c insert.c
search.o : search.c defs.h buffer.h
    cc -c search.c
files.o : files.c defs.h buffer.h command.h
    cc -c files.c
utils.o : utils.c defs.h
    cc -c utils.c
clean :
    rm edit $(objects)

可以使用$(变量名)来对变量进行使用。

3.5 makefile 的自动推导

makefile可以自动推导文件,以及文件依赖关系后面的命令,可以使用makefile中的自动推导(看到xxx.o就会将xxx.c文件添加在依赖关系中)。

objects = main.o kbd.o command.o display.o \
    insert.o search.o files.o utils.o
edit : $(objects)
    cc -o edit $(objects)
main.o : defs.h
kbd.o : defs.h command.h
command.o : defs.h command.h
display.o : defs.h buffer.h
insert.o : defs.h buffer.h
search.o : defs.h buffer.h
files.o : defs.h buffer.h command.h
utils.o : defs.h
#别名
.PHONY : clean
clean :
    rm edit $(objects)

#也可以这样写

objects = main.o kbd.o command.o display.o \
    insert.o search.o files.o utils.o
edit : $(objects)
    cc -o edit $(objects)
$(objects) : defs.h
  kbd.o command.o files.o : command.h
  display.o insert.o search.o files.o : buffer.h

.PHONY : clean

clean :
    rm edit $(objects)

4 Makefile总述

Makefile主要包括内容

  • 显式规则:直接明文书写的规则
  • 隐式规则:自动推导出来的隐晦规则
  • 变量的定义:一系列字符串变量的定义
  • 文件指示:根据条件的外部makefile的引用导入
  • 注释:脚本注释文件

make查找当前目录下的文件顺序是:

  • GNUmakefile
  • makefile
  • Makefile 推荐使用Makefile,最好不要使用GNUMakefile因为这个只能被GNU的make识别。 可以使用make -f file_name来指定makefile文件,例如make -f Make.Linux

makefile中可以使用include引入其它makefile文件 注意:include前不能加入[Tab]键,可以使用空格

bar=a.mk b.mk c.mk e.mk 
include foo.make *.mk $(bar)

include的查找路径如下:

  • 存在-I或者--include-dir参数会在这个参数指定的目录下继续寻找
  • 存在<prefix>/include存在,make也会去寻找。

注意:当没有找到时,make会生成一条警告,并在最终时再次尝试寻找,如果还是没找到就是error。可以使用-include告诉make 忽略这个的读取错误,直接继续执行。当然不建议这样做。

环境变量

在当前的环境变量中定义了,那么所有的make都会使用,因此建议不要使用。

make的工作方式

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

5.3 在规则中使用通配符

make与shell相同支持三种通配符号:

  • *:通用匹配符号,真实的’*‘可以使用\*来表示
  • ?:判断符号:
  • [...]:
  • $@:表示规则中的目标文件集。
    • 在模式规则中,如果有多个目标,那么,”$@”就是匹配于目标中模式定义的集合。
  • $%:仅当目标是函数库文件中,表示规则中的目标成员名。
    • 例如,如果一个目标是”foo.a (bar.o)”,那么,”$%”就是”bar.o”,”$@”就是”foo.a”。如果目标不是函数库文件(Unix下是[.a],Windows下是 [.lib]),那么,其值为空。
  • $<:依赖目标中的第一个目标名字。
    • 如果依赖目标是以模式(即”%”)定义的,那么”$<”将是符合模式的一系列的文件集。注意,其是一个一个取出来的。
  • $?:所有比目标新的依赖目标的集合。以空格分隔。
  • $^:所有的依赖目标的集合。以空格分隔。
    • 如果在依赖目标中有多个重复的,那个这个变量会去除重复的依赖目标,只保留一份。
  • $+:这个变量很像”$^”,也是所有依赖目标的集合。只是它不去除重复的依赖目标。
  • $*:这个变量表示目标模式中”%”及其之前的部分。
    • 如果目标是”dir/a.foo.b”,并且目标的模式是”a.%.b”,那么,”$“的值就是”dir /a.foo”。这个变量对于构造有关联的文件名是比较有较。如果目标中没有模式的定义,那么”$“也就不能被推导出,但是,如果目标文件的后缀是 make所识别的,那么”$“就是除了后缀的那一部分。例如:如果目标是”foo.c”,因为”.c”是make所能识别的后缀名,所以,”$“的值 就是”foo”。这个特性是GNU make的,很有可能不兼容于其它版本的make,所以,你应该尽量避免使用”$“,除非是在隐含规则或是静态模式中。如果目标中的后缀是make所不 能识别的,那么”$“就是空值。 例如:
print:*.c
    lpr -p $?
    touch print
  • $(@D):表示$@的目录部分,如果 “$@” 值是 “dir/foo.o” ,那么 “$(@D)” 就是 “dir” ,而如果 “$@” 中没有包含斜杠的话,其值就是 “.” (当前目录)。
  • $(@F):表示$@的文件部分。 剩下的$(操作符D)和$(操作符F)功能一次类推。

5.4 文件查询

makefile中可以使用VPATH来指定文件查找目录。使用方法如下:

  • 1.vpath :为符合模式的文件指定搜索目录
  • 2.vpath :清除符合模式的文件的搜索目录。
  • vpath:清除所有已经被设置好了的文件搜索目录
#指定文件搜索在../headers和当前目录下,查找所有以`.h`结尾的文件
vpath %.h ../headers
#这里是先在foo中搜索,然后在blish,最后是bar
vpath %.c foo:bar
vpath % blish

5.5 伪目标

makefile中可以使用.PHONY:xxx的方式来设置伪目标,类似于shell中的alias操作别名 伪目标一般没有依赖文件,主要是相关操作。也可以使用依赖来化简操作。

all:prog1 prog2 prog3
.PHONY:all
prog1:prog1.o utils.o
        cc -o prog1 prog1.o utils.o
prog2:...
#也可以使用伪目标作为依赖,实现操作如下
cleanall : cleanobj cleandiff
    rm program
cleanobj:
    rm *.o
cleandiff:
    rm *.diff

因为makefile会将第一个目标作为默认目标,因此可以使用伪目标依赖,来实现makefile的多目标依赖。

5.7 静态模式

使用静态模式可以更加容易地定义多目标的规则。使用语法如下

<targets ...>: <target-pattern>: <prereq-patterns ...>
    <commands>
    ...

target:定义了一系列目标,可以有通配符,是目标的集合 target-pattern:指明了target的模式,也是目标集模式 prereq-patterns:是目标依赖模式对target-pattern的再次依赖定义

使用示例:

    objects = foo.o bar.o
    all: $(objects)
#将后缀中的.o变为.c
$(objects): %.o: %.c
    $(CC) -c $(CFLAGS) $< -o $@
    # $(CC) -c $(CFLAGS) foo.c bar.c -o foo.o bar.o

5.8 自动生成依赖性

gcc中可以使用-M编译选项开启自动依赖查找。

6 书写命令

使用@在命令前,这个命令将不会被make显示出来。例如:

@echo 正在编译XXX模块
#输出:正在编译XXX模块
echo 正在编译XXX模块
#输出:
#    echo 正在编译XXX模块
#   正在编译XXX模块

可以在make后面带入参数-n或者--just-print,只执行显示命令,但是不会执行命令。方便对Makefile进行调试。

使用-s或者--slient则是全面禁止命令的显示。

6.2 命令执行

makefile中的命令会,一条一条的执行其后面的的命令,当需要上一命令结果应用在下一命令时,应该使用分号,分割相关命令。当命令相关联时,应该写在一行上。

exec:
    cd /home/
    pwd
# 打印执行文件夹路径,cd无作用。
exec:
    cd /home/;pwd
# 打印 /home/ cd 有作用

6.3 命令错误

make中命令错误会立即停止执行,因此需要,使用make clean进行重新的make; 可以在命令前使用-或者添加make的-i/ignore-errors参数实现忽略错误。可以使用-k/--keep-going保证make行为不停止。

6.4 make的嵌套执行

可以使用shell的脚本访问,执行子目录中的make,对与方便第三方依赖的单独编译。

subsystem:
    cd subdir && $(MAKE)
#等价于
subsystem:
    $(MAKE) -C subdir

使用export 和unexport来进行变量的下级传递和使用。

注意:

  • SHELL和MAKEFLAGS是一定会传递到下一层Makefile中的。
  • 可以使用-w/--print-directory;输出信息,让你看到当前的工作目录。

6.5 定义命令包

Makefile中可以使用相同命令序列来定义一个变量。以define开始,endef结束。例如:

define run-yacc
yacc $(firstword $^)
mv y.tab.c $@
endef

7 使用变量

makefile中的变量,可以是数字开头,但不应该含有:#=或是空字符(空格、回车等)。并且变量的大小写敏感。

7.1 变量基础

可以定义变量,并用${变量名}/$(变量名)来使用变量,可以精确使用和展开,字符串之间的链接等。

foo=c
prog.o:prog.$(foo)
    $(foo)$(foo) -$(foo) prog.$(foo)
#翻译结果
prog.o : prog.c
    cc -c prog.c

7.2 变量中的变量

makefile中的变量,不像c++需要预先声明,可以直接使用,只要make能在后面找到就行了,但是很容易引起嵌套的循环使用。

变量的赋值

操作 含义
= 变量的直接赋值,可以使用后面定义的变量
:= 赋值操作,前面的变量不能使用后面的变量,只能使用前面已经定义好了的变量
?= 如百年来那个没有被定义过,则进行赋值,如果事先被定义过,就什么都不做
+= 追加变量,给变量+=操作,可以追加新的变量

注意:谨慎使用#注释,在行后直接注释无\n换行符时,会引起变量中存在多余的空格;例如dir := /foo/bar # directory to put the frobs in中dir会多4个空格。

7.3 变量的高级用法

  • 变量的替换:替换变量中的共有的部分,格式是$(var:a=b)或者${var:a=b}把变量var中所有以子没有a结尾(空格或者结束符)的a换成b。这样就可以执行简单的变量替换。
foo:=a.o b.o c.o
bar:=$(foo:.o=.c)
#bar是a.c b.c c.c

也可以使用静态模式进行替换。

  • 将变量值作为变量:将变量结果作为字符串使用,获取新变量的名称。
x=y
y=z
a:=$($(x))
#最终a的结果是z

也可以进行多层次的嵌套。

7.5 override 指示符号

如果变量是通过make的命令行参数进行设置的,那么Makefile中对这个变量的赋值将会被忽略。可以使用override指示符,在Makefile中设置这类参数的值,进行重载

override <variable>=<value>
override <variable>:=<value>
override <variable>+=<value>

7.6 多行变量

使用define关键字设置变量的值可以换行,只要define没有使用[Tab]开头,那么make就不会把其认为是命令

define two-lines
echo foo
echo ${bar}
endef

7.7 环境变量

make运行时,系统环境变量可被载入到Makefile文件中,但是如果Makefile已经定义,则会被覆盖,可以使用-e参数方式系统环境变量被覆盖。

嵌套调用时,上层变量会以系统环境变量的方式传递到下层Makefile。默认情况只有通过命令行设置的变量会被传递。定义在文件中的变量,需要使用export关键字来声明。

7.8 目标变量

可以通过伪目标标签,设置目标局部变量,这个变量可以额全局变量同名,因为它的作用范围只在这条规则以及连带规则中。语法如下:

<target ...>:<variable-assignment>
<target ...>:overide <variable-assignment>

使用示例:

all: tran test test2
my_test:=/usr/binb
my_test:=${my_test:inb=a} /usr/include/boost 
tran:
    @echo "hello word"
test2:my_test=test2
test2:
    echo ${my_test}
test:
    echo "test";echo $(SHELL) ;echo $(MAKEFLAGS) echo ${my_test}

#result
hello word
echo "test";echo /bin/sh ;echo  echo /usr/ba /usr/include/boost 
test
/bin/sh
echo /usr/ba /usr/include/boost
echo test2
test2

7.9 模式变量

在GNU的make中还支持模式变量,使用一种模式,在模式中定义变量,使得变量可以根据模式进行判断赋值。使用格式和目标变量相同,例如:

%.o:CFLAGS = -O

8 使用条件判断

使用条件判断根据不同的分枝情况选择执行分支。

下面是根据编译器选择相关编译参数

libs_for_gcc=-lgnu
normal_libs=
foo:$(objects)
#判断编译器类型

ifeq($(CC),gcc)
    $(CC) -o foo $(objects) $(libs_for_gcc)
else
    $(CC) -o foo $(object) $(normal_libs)
endif

判断语句的基本语法

<conditional-directive>
<text-if-true>
endif
#或者
<conditional-directive>
<text-if-true>
else
<text-if-false>
endif

条件判断是ifeq或者ifneq、ifdef和ifndef

ifeq(<arg1>,<arg2>)
ifeq '<arg1>' '<arg2>'
ifeq "<arg1>" "<arg2>"
ifeq "<arg1>" '<arg2>'
ifeq '<arg1>' "<arg2>"
#还可以这样判断
ifeq ($(strip $(foo)),)
<text-if-empty>
endif

ifneq:

ifneq (<arg1>, <arg2>)
ifneq '<arg1>' '<arg2>'
ifneq "<arg1>" "<arg2>"
ifneq "<arg1>" '<arg2>'
ifneq '<arg1>' "<arg2>"

ifdef和ifndef

ifdef <variable-name>
endif

ifndef <variable-name>
endif

9 使用函数

Makefile可以使用函数来处理变量,变量以$进行标识,其基本语法如下:

$(<function> <arguments>)
#或者
${<function> <arguments>}

中间参数以,进行分割。 为了风格统一,建议都使用()的方式;如:$(subst a,b,$(x))

comma:=,
empty:=
space:=$(empty) $(empty)
foo:=a b c
bar:=$(subst $(space),$(comma),$(foo))

上面的函数是指,将$(foo)中的$(space)全部替换成为$(comma)。 最终$(bar)的结果是”a,b,c”。

9.2 字符串处理函数

函数名称 函数 功能 返回 示例 注意
subst $(subst <from>,<to>,<text>) 把字串 中的 字符串替换成 函数返回被替换过后的字符串 $(subst ee,EE,feet on the street)  
patsubst $(patsubst <pattern>,<replacement>,<text>) 查找其中的匹配字符串并替换 替换过后的字符串 $(patsubst %.c,%.o,x.c.c bar.c)  
strip $(strip <string>) 去掉字符串中开头和结尾的空字符串 返回去除的值 $(strip a b c ) 示例中去除了结尾的一个空格
findstring $(findstring <find>,<in>) 在字串 中查找 字串。 如果找到,那么返回 ,否则返回空字符串。 $(findstring a,a b c) 示例返回”a”字符串
filter $(filter <pattern...>,<text>) 模式过滤 字符串中的单词,保留符合模式 的单词。可以有多个模式。 返回符合模式 的字串。 $(filter %.c %.s,foo.c bar.c baz.s ugh.h) 示例返回值是foo.c bar.c baz.s
filter-out $(filter-out <pattern...>,<text>) 模式过滤 字符串中的单词,去除符合模式 的单词。可以有多个模式。 返回不符合模式 的字串。 $(filter-out main1.o main2.o,main1.o foo.o main2.o bar.o) 返回值是foo.o bar.o
sort $(sort <list>) 给字符串 中的单词排序(升序)。 返回排序后的字符串。 $(sort foo bar lose)返回bar foo lose sort 函数会去掉 中相同的单词。
word $(word <n>,<text>) 取字符串 中第 个单词。(从一开始) 返回字符串 中第 个单词。如果 中的单词数要大,那么返回空字符串。 $(word 2, foo bar baz) 示例返回值是”bar”
wordlist $(wordlist <s>,<e>,<text>) 字符串 中取从 开始到 的单词串。 是一个数字 返回字符串 中从 的单词字串。如果 中的单词数要大,那么返回空字符串。如果 大于 的单词数,那么返回从 开始,到 结束的单词串 $(wordlist 2, 3, foo bar baz) 返回值是bar baz
words $(words <text>) 统计 中字符串中的单词个数。 返回 中的单词数 $(words, foo bar baz)返回值为”3” 如果我们要取 中最后的一个单词,我们可以这样: $(word $(words ),) 。
firstword $(firstword <text>) 取字符串 中的第一个单词。 返回字符串 的第一个单词 $(firstword foo bar)返回”foo”  

9.3 文件名操作函数

  • dir: $(dir <name...>)
    • 功能:从文件序列中取出目录部分(最后一个“/”之前的部分)。
    • 返回:返回文件名序列 的目录部分。
    • 示例:$(dir src/foo.c hacks)返回值是src/ ./
  • notdir:$(notdir <names...>)
    • 功能:从文件名序列 中取出非目录部分。非目录部分是指最后一个反斜杠(“ / ”)之后的部分。
    • 返回:返回文件名序列 的非目录部分。
    • 示例:$(notdir src/foo.c hacks),返回值是”foo.c hacks”。
  • suffix:$(suffix <names...>)
    • 功能:从文件名序列 中取出各个文件名的后缀。
    • 返回:返回文件名序列 的后缀序列,如果文件没有后缀,则返回空字串。
    • 示例:$(suffix src/foo.c src-1.0/bar.c hacks);返回值是”.c .c”。
  • basename:$(basename <names...>)
    • 功能:从文件名序列 中取出各个文件名的前缀部分。
    • 返回:返回文件名序列 的前缀序列,如果文件没有前缀,则返回空字串。
    • 示例:$(basename src/foo.c src-1.0/bar.c hacks);返回值是”src/foo src-1.0/bar hacks”。
  • addsuffix:$(addsuffix <suffix>,<names...>)
    • 功能:把后缀 加到 中的每个单词后面。
    • 返回:返回加过后缀的文件名序列。
    • $(addsuffix .c,foo bar),返回值是”foo.c bar.c”。
  • addprefix:$(addprefix <prefix>,<names...>)
    • 功能:把前缀 加到 中的每个单词后面。
    • 返回:返回加过前缀的文件名序列。
    • 示例:$(addprefix src/,foo bar);返回值是”src/foo src/bar”
  • join:$(join <list1>,<list2>)
    • 功能:把 中的单词对应地加到 的单词后面。如果 的单词个数要比 的多,那么, 中的多出来的单词将保持原样。如果 的单词个数要比 多,那么, 多出来的单词将被复制到 中。
    • 返回:返回连接过后的字符串。
    • 示例:$(join aaa bbb , 111 222 333);返回值是aaa111 bbb222 333

9.4 foreach函数

  • 语法:$(foreach <var>,<list>,<text>)
  • 功能:将list中的单词逐一去除,放到var指定的变量中,然后再执行所包含表达式。
  • 示例:
names:=a b c d
files:=$(foreach n,$(names),$(n).o)

#files返回值是 a.o b.o c.o d.o

9.6 call函数

call函数是唯一一个可以用来创建新的参数化的函数,可以使用call向一个复杂函数中传递参数。其语法是: $(call <expression>,<parm1>,<parm2>,<parm3>...)

执行时,后面的参数将函数中的变量进行取代。

reverse=$(1) $(2)

foo=$(call reverse,a,b)
#最后foo的结果是“a b”

9.7 origin 函数

这个函数返回变量的来源;语法:$(origin <variable>);来源类型如下:

  • undefined:从来就没有定义过
  • default:默认定义变量,如CC
  • environment:环境变量(注意make 没有“-e”参数)
  • file: 这个变量定义在Makefile中
  • command line :这个变量是被命令行定义的。
  • override :是被override重新定义的。
  • automatic: 是命令中定义的自动化变量。

9.8 shell函数

执行shell命令,它和”`“是一样的功能。将执行操作系统命令后的输出作为函数返回。使用示例:

contents:=$(shell cat foo)
files:=$(shell echo *.c)

注意这个函数会新生成一个shell程序来执行命令。不应该被大量使用。

9.9 控制make的函数

$(error ): 输出错误,并退出 $(warning ):输出一段警告信息,而make继续执行。

10 make的运行

10.1 make的退出码

  • 0:表示成功执行
  • 1:运行时产生错误,返回1。
  • 2:如果使用了“-q”参数,并且make使得一些目标不需要更新,那么返回2。

使用-f可以指定make file文件,也可以在make后面添加指定的伪目标。

一般伪目标函数

  • all:这个伪目标是所有目标的目标,其功能一般是编译所有的目标。
  • clean:这个伪目标功能是删除所有被 make 创建的文件。
  • install:这个伪目标功能是安装已编译好的程序,其实就是把目标执行文件拷贝到指定的目标中去。
  • print:这个伪目标的功能是例出改变过的源文件。
  • tar:这个伪目标功能是把源程序打包备份。也就是一个 tar 文件。
  • dist:这个伪目标功能是创建一个压缩文件,一般是把 tar 文件压成 Z 文件。或是 gz 文件。
  • TAGS:这个伪目标功能是更新所有的目标,以备完整地重编译使用。
  • check:check ” 和 “test”这两个伪目标一般用来测试 makefile 的流程。

10.4 检查规则

我们不想makefile中的规则执行起来,只想检查一下命令,可以使用一下相关参数:

  • - n
  • --just-print
  • --dry-run
  • --recon:不执行参数,这些参数只是打印命令,不管目标是否更新,把规则和连带规则下的命令打印出来,但 不执行,这些参数对于我们调试 makefile 很有用处。
  • - t --touch:这个参数的意思就是把目标文件的时间更新,但不更改目标文件。也就是说, make 假装编译目标,但不是真正的编译目标,只是把目标变成已编译过的状态。
  • - q --question这个参数的行为是找目标的意思,也就是说,如果目标存在,那么其什么也不会输出,当然也不会执行编译,如果目标不存在,其会打印出一条出错信息。
  • -W <file>
  • --what-if=<file>`
  • --assume-new=<file>
  • --new-file=<file> 这个参数需要指定一个文件。一般是是源文件(或依赖文件), Make 会根据规则推导来运行依赖于这 个文件的命令,一般来说,可以和“ -n ”参数一同使用,来查看这个依赖文件所发生的规则命令。 另外一个很有意思的用法是结合“ -p ”和“ -v ”来输出 makefile 被执行时的信息(这个将在后面讲述)。

10.5 make参数请查看相关文档。

11 隐含规则

隐含规则主要是make的自动推导。 会为.o自动配置生成依赖生成项。 注意

  • 因为隐藏规则的存在,当执行混合编译的时候,可能存在隐藏规则先后性的问题,例如foo:foo.p如果文件夹下存在foo.c隐含规则就会找到foo.c执行过后就不再向下找了。
  • 可以使用-r/--no-builtin-rules取消所有隐含参数设置。
  • 编译c时,.o会自动添加目标依赖推导.c,并生成指令`$(CC) –c $(CPPFLAGS) $(CFLAGS)`
  • 编译c++时,会自动推导为.cc或者.C,生成命令`$(CXX) $(CPPFLAGS) $(CFLAGS)`
  • 编译Pascal时,自动推导为.p,生成指令为`$(PC) –c $(PFLAGS)`;
  • 编译Fortran/Ratfor时,自动推导为 “.r” 或 “.F” 或 “.f”,其生成命令是:
".f" "$(FC) –c $(FFLAGS)"
".F" "$(FC) –c $(FFLAGS) $(CPPFLAGS)"
".f" "$(FC) –c $(FFLAGS) $(RFLAGS)"
  • 汇编和汇编预处理:自动推导为.s默认使用编译器“as”,生成命令:`$(AS) $(ASFLAGS)`。
  • 链接Object文件:运行C的编译器来运行链接程序(一般是“ld”),生成命令:$(CC) $(LDFLAGS) <n>.o $(LOADLIBES) $(LDLIBS)

11.3 隐含规则使用的变量

make中会预先定义一些执行的变量

  • AR:函数库打包程序。默认命令是“ ar ”
  • AS:汇编语言编译程序。默认命令是“ as ”。
  • CC:C语言编译程序,默认命令是“cc”。
  • CXX:C++ 语言编译程序。默认命令是“ g++ ”。
  • CO:从 RCS 文件中扩展文件程序。默认命令是“ co ”。
  • CPP:C 程序的预处理器(输出是标准输出设备)。默认命令是“ $(CC) – E ”。
  • FC:Fortran 和 Ratfor 的编译器和预处理程序。默认命令是“ f77 ”。
  • GET:从 SCCS 文件中扩展文件的程序。默认命令是“ get ”。
  • LEX:Lex 方法分析器程序(针对于 C 或 Ratfor )。默认命令是“ lex ”。
  • PC:Pascal 语言编译程序。默认命令是“ pc ”。
  • YACC:Yacc 文法分析器(针对于 C 程序)。默认命令是“ yacc ”。
  • YACCR:Yacc 文法分析器(针对于 Ratfor 程序)。默认命令是“ yacc – r ”。
  • MAKEINFO:转换 Texinfo 源文件( .texi )到 Info 文件程序。默认命令是“ makeinfo ”。
  • TEX:从 TeX 源文件创建 TeX DVI 文件的程序。默认命令是“ tex ”。
  • TEXI2DVI:从 Texinfo 源文件创建军 TeX DVI 文件的程序。默认命令是“ texi2dvi ”。
  • WEAVE:转换 Web 到 TeX 的程序。默认命令是“ weave ”。
  • CWEAVE:转换 C Web 到 TeX 的程序。默认命令是“ cweave ”。
  • TANGLE:转换 Web 到 Pascal 语言的程序。默认命令是“ tangle ”。
  • CTANGLE:转换 C Web 到 C 。默认命令是“ ctangle ”。
  • RM:删除文件命令。默认命令是“ rm – f ”。

对应编译器参数变量: 基本都是编译器名+FLAGS;如:CXXFLAGS

注意隐含规则链:

  • 当存在编译器的相互准话你,例如一个.o文件的生成,可能是先将.y文件生成.c再由C的编译器生成。make会进自己最大的努力进行编译转换和推导。为了安全起见,需要使用伪目标“.INTERMEDIATE”来强制声明中间件。
  • 禁止同一个目标出现两次或者以上。防止自动推导时出现无限递归的情况。
  • make会对简单的中间产物进行优化,可能不会产生中间文件。

11.5 定义模式规则

模式规则中一般包含“%”,主要代表任意性的推导。例如

%.o : %.c
    $(CC) -c $(CFLAGS) $(CPPFLAGS) $< -o $@
#将所有.c文件编译成为.o文件
# $<表示了所有依赖目标的挨个值,$@表示所有的目标的挨个值

%.tab.c %.tab.h: %.y
    bison -d $<

# 将所有的[.y]文件都以bison -d $<执行。生成对应的<n>.tab.c和<n>.tab.h文件

11.5.3 自动化变量

5.3节中已有描述,不再赘述。

可以通过在隐含规则和后面不添加命令,来覆盖隐含规则。或者添加命令,重建隐含规则。

make注意使用后缀来进行推导,因此可以更改.SUFFIXES来进行默认后缀的更改。make 的参数 “-r” 或 “-no-builtin-rules” 也会使用得默认的后缀列表为空。

11.7 隐含规则搜索算法

对于目标T的搜索算法如下所示:

  • 把 T 的目录部分分离出来。叫 D ,而剩余部分叫 N 。(如:如果 T 是 “src/foo.o” ,那么, D 就是 “src/” ,N 就是 “foo.o” )
  • 创建所有匹配于 T 或是 N 的模式规则列表。
  • 如果在模式规则列表中有匹配所有文件的模式,如 “%” ,那么从列表中移除其它的模式。
  • 移除列表中没有命令的规则。
  • 对于第一个在列表中的模式规则:
    • 推导其 “ 茎 “S , S 应该是 T 或是 N 匹配于模式中 “%” 非空的部分。
    • 计算依赖文件。把依赖文件中的 “%” 都替换成 “ 茎 “S 。如果目标模式中没有包含斜框字符,而把 D 加在第一个依赖文件的开头。
    • 测试是否所有的依赖文件都存在或是理当存在。(如果有一个文件被定义成另外一个规则的目标文件,或者是一个显式规则的依赖文件,那么这个文件就叫 “ 理当存在 “ )
    • 如果所有的依赖文件存在或是理当存在,或是就没有依赖文件。那么这条规则将被采用,退出该算法。
  • 如果经过第 5 步,没有模式规则被找到,那么就做更进一步的搜索。对于存在于列表中的第一个模式规则:
    • 如果规则是终止规则,那就忽略它,继续下一条模式规则。
    • 计算依赖文件。(同第 5 步)
    • 测试所有的依赖文件是否存在或是理当存在。
    • 对于不存在的依赖文件,递归调用这个算法查找他是否可以被隐含规则找到。
    • 如果所有的依赖文件存在或是理当存在,或是就根本没有依赖文件。那么这条规则被采用,退出该算法。
  • 如果没有隐含规则可以使用,查看 “.DEFAULT” 规则,如果有,采用,把 “.DEFAULT” 的命令给 T 使用。

当规则被找到,就会执行其相当的命令,此时自动化变量才会生成。

12 使用 make 更新函数库文件

函数库文件也就是对 Object 文件(程序编译的中间文件)的打包文件。在 Unix 下,一般是由命令 “ar” 来完成打包工作。

12.1 函数库文件的成员

一个函数库文件由多个文件组成。你可以以如下格式指定函数库文件及其组成: archive(member)

这个主要是对目标和依赖的定义。主要是为了“ar”来进行服务。如:

foolib(hack.o) : hack.o
    ar cr foolib hack.o

12.2 函数库成员的隐含规则

当目标是”a(m)”,形式时,其会把目标转换为(m),例如”make foo.a(bar.o)”的形式调用Makefile时,隐含规则会去找”bar.o”的规则,如果没有定义相关规则,那么内建隐含规则生效。make会去找对应的文件来生成目标,找到则成功执行。

注意: “$%”是专属函数库文件的自动化变量。

12.3 函数库文件的后缀规则

使用后缀规则和隐含规则来生成函数库打包文件,如:

.c.a:
    $(CC) $(CFLAGS) $(CPPFLAGS) -c $< -o $*.o
    $(AR) r $@ $*.o
    $(RM) $*.o

等价于:

(%.o) : %.c
    $(CC) $(CFLAGS) $(CPPFLAGS) -c $< -o $*.o
    $(AR) r $@ $*.o
    $(RM) $*.o

注意:

  • 进行函数库打包文件生成时,请小心使用 make 的并行机制( “-j” 参数)。如果多个 ar 命令在同一时间运行在同一个函数库打包文件上,就很有可以损坏这个函数库文件。

结束


支付宝打赏 微信打赏

~ 海内存知已,天涯若比邻 ~

Share

Related Posts

下一篇 人物:柳传志

标题:提示学习 Prompt learning

摘要:NLP新范式:Prompt(提示学习)

标题:人物:柳传志

摘要:联想发家史

Comments

--disqus--

    Content
    My Moment ( 微信公众号 )
    欢迎关注鹤啸九天