1)将单个源码文件编译为可执行文件
2)切换生成器
3)构建和连接静态库与动态库
4)用条件语句控制编译
5)向用户显示选项
6)指定编译器
7)切换构建类型
8)设置编译器选项
9)为语言设定标准
10)使用控制流进行构造
#include
#include
char *say_hello() { return "Hello, CMake world!"; }
int main() {
printf("%s\n", say_hello());
return EXIT_SUCCESS;
}
备注:将该文件与源文件 hello-world.cpp 放在相同的目录中。记住,它只能被命名为 CMakeLists.txt 。
# set minimum cmake version
//设置CMake所需的最低版本。如果使用的CMake版本低于该版本,则会发出致命错误:
cmake_minimum_required(VERSION 3.5 FATAL_ERROR)
# project name and language
//声明了项目的名称( recipe-01 )和支持的编程语言(CXX代表C++,C代表C):
project(recipe-01 LANGUAGES C)
//指示CMake创建一个新目标:可执行文件 hello-world 。
//这个可执行文件是通过编译和链接源文件 hello-world.cpp 生成的。
add_executable(hello-world hello-world.c)
1. $ mkdir -p build
2. $ cd build
3. $ cmake ..
4.
5. -- The CXX compiler identification is GNU 8.1.0
6. -- Check for working CXX compiler: /usr/bin/c++
7. -- Check for working CXX compiler: /usr/bin/c++ -- works
8. -- Detecting CXX compiler ABI info
9. -- Detecting CXX compiler ABI info - done
10. -- Detecting CXX compile features
11. -- Detecting CXX compile features - done
12. -- Configuring done
13. -- Generating done
14.
-- Build files have been written to: /home/user/cmake-cookbook/chapter01/recipe-01/cxx-example/build
①Makefile : make 将运行指令来构建项目。
②CMakefile :包含临时文件的目录,CMake用于检测操作系统、编译器等。此外,根据所选的生成器,它还包含特定的文件。
③cmake_install.cmake :处理安装规则的CMake脚本,在项目安装时使用。
④CMakeCache.txt :如文件名所示,CMake缓存。CMake在重新运行配置时使用这个文件。
③命令实现编译
$ cmake -H. -Bbuild
该命令是跨平台的,使用了 -H 和 -B 为CLI选项。 -H 表示当前目录中搜索
根 CMakeLists.txt 文件。 -Bbuild 告诉CMake在一个名为 build 的目录中生成所有的文件。
④CMake不强制指定构建目录执行名称或位置,我们完全可以把它放在项目路径之外。这样做同样有效:
1. $ mkdir -p /tmp/someplace
2. $ cd /tmp/someplace
3. $ cmake /path/to/source
4. $ cmake --build .
⑤查看cmake帮助
1. $ cmake --build . --target help
2.
3. The following are some of the valid targets for this Makefile:
4. ... all (the default if no target is provided)
5. ... clean
6. ... depend
7. ... rebuild_cache
8. ... hello-world
9. ... edit_cache
10. ... hello-world.o
11. ... hello-world.i
12. ... hello-world.s
定义
CMake是一个构建系统生成器,可以使用单个CMakeLists.txt为不同平台上的不同工具集配置项目。您可以在CMakeLists.txt中描述构建系统必须运行的操作,以配置并编译代码。基于这些指令,CMake将为所选的构建系统(Unix Makefile、Ninja、Visual Studio等等)生成相应的指令
解释
我们将重用前一节示例中的 hello-world.cpp 和 CMakeLists.txt 。惟一的区别在使用CMake时,因为现在必须显式地使用命令行方式,用 -G 切换生成器。
举例
---------------------配置项目
1. $ mkdir -p build
2. $ cd build
3. $ cmake -G Ninja ..
4.
5. -- The CXX compiler identification is GNU 8.1.0
6. -- Check for working CXX compiler: /usr/bin/c++
7. -- Check for working CXX compiler: /usr/bin/c++ -- works
8. -- Detecting CXX compiler ABI info
9. -- Detecting CXX compiler ABI info - done
10. -- Detecting CXX compile features
11. -- Detecting CXX compile features - done
12. -- Configuring done
13. -- Generating done
14.
-- Build files have been written to: /home/user/cmake-cookbook/chapter01/recipe-02/cxx-exampl
---------------------构建项目
1. $ cmake --build .
2.
3. [2/2] Linking CXX executable hello-world
①build.ninja 和 rules.ninja :包含Ninja的所有的构建语句和构建规则。
②CMakeCache.txt :CMake会在这个文件中进行缓存,与生成器无关。
③CMakeFiles :包含由CMake在配置期间生成的临时文件。
④cmake_install.cmake :CMake脚本处理安装规则,并在安装时使用。
# set minimum cmake version
cmake_minimum_required(VERSION 3.5 FATAL_ERROR)
# 1、创建项目recipe-03 ,规定源码为C++
# project name and language
project(recipe-03 LANGUAGES CXX)
# 2、生成静态库叫message
# generate a library from sources
add_library(message
STATIC
Message.hpp
Message.cpp
)
# 3、生成执行文件叫hello-world
add_executable(hello-world hello-world.cpp)
# 4、链接库message
target_link_libraries(hello-world message)
#include "Message.hpp"
#include
#include
std::ostream &Message::printObject(std::ostream &os) {
os << "This is my very nice message: " << std::endl;
os << message_;
return os;
}
2)Message.hpp
#pragma once
#include
#include
class Message {
public:
Message(const std::string &m) : message_(m) {}
friend std::ostream &operator<<(std::ostream &os, Message &obj) {
return obj.printObject(os);
}
private:
std::string message_;
std::ostream &printObject(std::ostream &os);
};
3)hello-world.cpp
#include "Message.hpp"
#include
#include
int main() {
Message say_hello("Hello, CMake World!");
std::cout << say_hello << std::endl;
Message say_goodbye("Goodbye, CMake World");
std::cout << say_goodbye << std::endl;
return EXIT_SUCCESS;
}
add_library
的第二个参数的有效值,我们来看下本书会用到的值:⑥展示OBJECT库的使用
,如下
1. cmake_minimum_required(VERSION 3.5 FATAL_ERROR)
2. project(recipe-03 LANGUAGES CXX)
3.
4. add_library(message-objs
5. OBJECT
6. Message.hpp
7. Message.cpp
8. )
9.
10. # this is only needed for older compilers
11. # but doesn't hurt either to have it
12. set_target_properties(message-objs
13. PROPERTIES
14. POSITION_INDEPENDENT_CODE 1
15. )
16.
17. add_library(message-shared
18. SHARED
19. $
20. )
21.
22. add_library(message-static
23. STATIC
24. $
25. )
26.
27. add_executable(hello-world hello-world.cpp)
28.
29. target_link_libraries(hello-world message-static)
message-objs
目标的相应属性来实现。POSITION_INDEPENDENT_CODE
属性。④是否可以让CMake生成同名的两个库?换句话说,它们都可以被称为 message ,而不是 static 和 message-share d吗?我们需要修改这两个目标的属性:
1. add_library(message-shared
2. SHARED
3. $
4. )
5.
6. set_target_properties(message-shared
7. PROPERTIES
8. OUTPUT_NAME "message"
9. )
10.
11. add_library(message-static
12. STATIC
13. $
14. )
15.
16. set_target_properties(message-static
17. PROPERTIES
18. OUTPUT_NAME "message"
19. )
注意点
:这让库同一个名不具备跨平台性!
我们可以链接到DSO吗?这取决于操作系统和编译器:
目的(从与上一个示例的的源代码开始,我们希望能够在不同的两种行为之间进行切换:)
1) 将 Message.hpp 和 Message.cpp 构建成一个库(静态或动态),然后将生成库链接到 helloworld 可执行文件中。
2)将 Message.hpp , Message.cpp 和 hello-world.cpp 构建成一个可执行文件,但不生成任何一个库。
cmake和解释
# 1、首先,定义最低CMake版本、项目名称和支持的语言:
# set minimum cmake version
cmake_minimum_required(VERSION 3.5 FATAL_ERROR)
# project name and language
project(recipe-04 LANGUAGES CXX)
# 2、我们引入了一个新变量 USE_LIBRARY ,这是一个逻辑变量,值为 OFF 。我们还打印了它的
值:
# introduce a toggle for using a library
set(USE_LIBRARY OFF)
message(STATUS "Compile sources into a library? ${USE_LIBRARY}")
# 3、CMake中定义 BUILD_SHARED_LIBS 全局变量,并设置为 OFF 。调用 add_library 并省略
第二个参数,将构建一个静态库:
# BUILD_SHARED_LIBS is a global flag offered by CMake
# to toggle the behavior of add_library
set(BUILD_SHARED_LIBS OFF)
# 4、然后,引入一个变量 _sources ,包括 Message.hpp 和 Message.cpp :
# list sources
list(APPEND _sources Message.hpp Message.cpp)
# 5、引入一个基于 USE_LIBRARY 值的 if-else 语句。如果逻辑为真,
则 Message.hpp 和 Message.cpp 将打包成一个库:
if(USE_LIBRARY)
# add_library will create a static library
# since BUILD_SHARED_LIBS is OFF
add_library(message ${_sources}) # 打包库
add_executable(hello-world hello-world.cpp)
target_link_libraries(hello-world message)
else()
add_executable(hello-world hello-world.cpp ${_sources})
endif()
# 6、补充:我们可以再次使用相同的命令集进行构建。由于 USE_LIBRARY 为 OFF , hello-world 可
执行文件将使用所有源文件来编译。可以通过在GNU/Linux上,运行 objdump -x 命令进行验
证。
BUILD_SHARED_LIBS
是CMake的一个全局标志。因为CMake内部要查询 BUILD_SHARED_LIBS 全局变量,所以 add_library 命令可以在不传递 STATIC/SHARED/OBJECT 参数的情况下调用;如果为 false 或未定义,将生成一个静态库if(XXX) + else() + endif()
也可以用 if(USE_LIBRARY)…else(USE_LIBRARY)… endif(USE_LIBIRAY)
,这个格式并不唯一,可以根据个人喜好来决定使用哪种格式。_sources
变量是一个局部变量,不应该在当前范围之外使用,可以在名称前加下划线# set minimum cmake version
cmake_minimum_required(VERSION 3.5 FATAL_ERROR)
# project name and language
project(recipe-05 LANGUAGES CXX)
# expose options to the user
option(USE_LIBRARY "Compile sources into a library" OFF)
message(STATUS "Compile sources into a library? ${USE_LIBRARY}")
include(CMakeDependentOption)
# second option depends on the value of the first
cmake_dependent_option(
MAKE_STATIC_LIBRARY "Compile sources into a static library" OFF
"USE_LIBRARY" ON
)
# third option depends on the value of the first
cmake_dependent_option(
MAKE_SHARED_LIBRARY "Compile sources into a shared library" ON
"USE_LIBRARY" ON
)
set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)
# list sources
list(APPEND _sources Message.hpp Message.cpp)
if(USE_LIBRARY)
message(STATUS "Compile sources into a STATIC library? ${MAKE_STATIC_LIBRARY}")
message(STATUS "Compile sources into a SHARED library? ${MAKE_SHARED_LIBRARY}")
if(MAKE_SHARED_LIBRARY)
add_library(message SHARED ${_sources})
add_executable(hello-world hello-world.cpp)
target_link_libraries(hello-world message)
endif()
if(MAKE_STATIC_LIBRARY)
add_library(message STATIC ${_sources})
add_executable(hello-world hello-world.cpp)
target_link_libraries(hello-world message)
endif()
else()
add_executable(hello-world hello-world.cpp ${_sources})
endif()
1. option(USE_LIBRARY "Compile sources into a library" OFF)
②现在,可以通过CMake的 -D CLI选项,将信息传递给CMake来切换库的行为:
1. $ mkdir -p build
2. $ cd build
3. $ cmake -D USE_LIBRARY=ON ..
4.
5. -- ...
6. -- Compile sources into a library? ON
7. -- ...
8.
9. $ cmake --build .
10.
11. Scanning dependencies of target message
12. [ 25%] Building CXX object CMakeFiles/message.dir/Message.cpp.o
13. [ 50%] Linking CXX static library libmessage.a
14. [ 50%] Built target message
15. Scanning dependencies of target hello-world
16. [ 75%] Building CXX object CMakeFiles/hello-world.dir/hello-world.cpp.o
17. [100%] Linking CXX executable hello-world
-D
开关用于为CMake设置任何类型的变量:逻辑变量、路径等等option
可接受三个参数:1) 表示该选项的变量的名称。
2)"help string" 记录选项的字符串,在CMake的终端或图形用户界面中可见。
3)[initial value] 选项的默认值,可以是 ON 或 OFF 。
1. $ cmake -D CMAKE_CXX_COMPILER=clang++ ..
②通过导出环境变量 CXX (C++编译器)、 CC (C编译器)和 FC (Fortran编译器)。例如,使用这个命令使用 clang++ 作为 C++ 编译器:
$ env CXX=clang++ cmake ..
跨平台
注意点
我们建议使用 -D CMAKE__COMPILER CLI选项设置编译器,而不是导
出 CXX 、 CC 和 FC 。这是确保跨平台并与非POSIX兼容的唯一方法。为了避免变量污染环境,这些变量可能会影响与项目一起构建的外部库环境。
cmake额外变量和编译器交互:
①CMAKE__COMPILER_LOADED :如果为项目启用了语言 ,则将设置为 TRUE 。
②CMAKE__COMPILER_ID :编译器标识字符串,编译器供应商所特有。例如, GCC 用于
GNU编译器集合, AppleClang 用于macOS上的Clang, MSVC 用于Microsoft Visual
Studio编译器。注意,不能保证为所有编译器或语言定义此变量。
③CMAKE_COMPILER_IS_GNU :如果语言 是GNU编译器集合的一部分,则将此
逻辑变量设置为 TRUE 。注意变量名的 部分遵循GNU约定:C语言为 CC , C++语言
为 CXX , Fortran语言为 G77 。
④CMAKE__COMPILER_VERSION :此变量包含一个字符串,该字符串给定语言的编译器
版本。版本信息在 major[.minor[.patch[.tweak]]] 中给出。但是,对
于 CMAKE__COMPILER_ID ,不能保证所有编译器或语言都定义了此变量。
1. cmake_minimum_required(VERSION 3.5 FATAL_ERROR)
2. project(recipe-06 LANGUAGES C CXX)
3.
# 由于这里设置了使用CXX,所以为true,也就是1
4. message(STATUS "Is the C++ compiler loaded? ${CMAKE_CXX_COMPILER_LOADED}")
5. if(CMAKE_CXX_COMPILER_LOADED)
6. message(STATUS "The C++ compiler ID is: ${CMAKE_CXX_COMPILER_ID}")
7. message(STATUS "Is the C++ from GNU? ${CMAKE_COMPILER_IS_GNUCXX}")
8.
message(STATUS "The C++ compiler version is:
${CMAKE_CXX_COMPILER_VERSION}")
9. endif()
10.
11. message(STATUS "Is the C compiler loaded? ${CMAKE_C_COMPILER_LOADED}")
12. if(CMAKE_C_COMPILER_LOADED)
13. message(STATUS "The C compiler ID is: ${CMAKE_C_COMPILER_ID}")
14. message(STATUS "Is the C from GNU? ${CMAKE_COMPILER_IS_GNUCC}")
15. message(STATUS "The C compiler version is: ${CMAKE_C_COMPILER_VERSION}")
16. endif()
注意,这个例子不包含任何目标,没有要构建的东西,我们只关注配置步骤:
1. $ mkdir -p build
2. $ cd build
3. $ cmake ..
4.
5. ...
6. -- Is the C++ compiler loaded? 1
7. -- The C++ compiler ID is: GNU
8. -- Is the C++ from GNU? 1
9. -- The C++ compiler version is: 8.1.0
10. -- Is the C compiler loaded? 1
11. -- The C compiler ID is: GNU
12. -- Is the C from GNU? 1
13. -- The C compiler version is: 8.1.0
14. ...
背景
配置时,可以为Debug或Release构建设置相关的选项或属性,例如:编译器和链接器标志
控制变量
控制生成构建系统使用的配置变量是
CMAKE_BUILD_TYPE
该变量默认为空,CMake识别的值为:
1. Debug:用于在没有优化的情况下,使用带有调试符号构建库或可执行文件。
2. Release:用于构建的优化的库或可执行文件,不包含调试符号。
3. RelWithDebInfo:用于构建较少的优化库或可执行文件,包含调试符号。
4. MinSizeRel:用于不增加目标代码大小的优化方式,来构建库或可执行文件。
# set minimum cmake version
cmake_minimum_required(VERSION 3.5 FATAL_ERROR)
# project name and language
project(recipe-07 LANGUAGES C CXX)
# we default to Release build type
if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE Release CACHE STRING "Build type" FORCE)
endif()
message(STATUS "Build type: ${CMAKE_BUILD_TYPE}")
message(STATUS "C flags, Debug configuration: ${CMAKE_C_FLAGS_DEBUG}")
message(STATUS "C flags, Release configuration: ${CMAKE_C_FLAGS_RELEASE}")
message(STATUS "C flags, Release configuration with Debug info: ${CMAKE_C_FLAGS_RELWITHDEBINFO}")
message(STATUS "C flags, minimal Release configuration: ${CMAKE_C_FLAGS_MINSIZEREL}")
message(STATUS "C++ flags, Debug configuration: ${CMAKE_CXX_FLAGS_DEBUG}")
message(STATUS "C++ flags, Release configuration: ${CMAKE_CXX_FLAGS_RELEASE}")
message(STATUS "C++ flags, Release configuration with Debug info: ${CMAKE_CXX_FLAGS_RELWITHDEBINFO}")
message(STATUS "C++ flags, minimal Release configuration: ${CMAKE_CXX_FLAGS_MINSIZEREL}")
1. if(NOT CMAKE_BUILD_TYPE)
2. set(CMAKE_BUILD_TYPE Release CACHE STRING "Build type" FORCE)
3. endif()
4. message(STATUS "Build type: ${CMAKE_BUILD_TYPE}")
②打印出CMake设置的相应编译标志:
1.message(STATUS "C flags, Debug configuration: ${CMAKE_C_FLAGS_DEBUG}")
2.message(STATUS "C flags, Release configuration: ${CMAKE_C_FLAGS_RELEASE}")
3.message(STATUS "C flags, Release configuration with Debug info:${CMAKE_C_FLAGS_RELWITHDEBINFO}")
4.message(STATUS "C flags, minimal Release configuration:${CMAKE_C_FLAGS_MINSIZEREL}")
5. message(STATUS "C++ flags, Debug configuration: ${CMAKE_CXX_FLAGS_DEBUG}")
6.
message(STATUS "C++ flags, Release configuration:
${CMAKE_CXX_FLAGS_RELEASE}")
7.
message(STATUS "C++ flags, Release configuration with Debug info:
${CMAKE_CXX_FLAGS_RELWITHDEBINFO}")
8.
message(STATUS "C++ flags, minimal Release configuration:
${CMAKE_CXX_FLAGS_MINSIZEREL}")
③验证配置的输出:(这里显示默认构建release)
1. $ mkdir -p build
2. $ cd build
3. $ cmake ..
4.
5. ...
6. -- Build type: Release
7. -- C flags, Debug configuration: -g
8. -- C flags, Release configuration: -O3 -DNDEBUG
9. -- C flags, Release configuration with Debug info: -O2 -g -DNDEBUG
10. -- C flags, minimal Release configuration: -Os -DNDEBUG
11. -- C++ flags, Debug configuration: -g
12. -- C++ flags, Release configuration: -O3 -DNDEBUG
13. -- C++ flags, Release configuration with Debug info: -O2 -g -DNDEBUG
14. -- C++ flags, minimal Release configuration: -Os -DNDEBUG
④切换构建类型为debug:(上面指定了release是默认的构建)
1. $ cmake -D CMAKE_BUILD_TYPE=Debug ..
2.
3. -- Build type: Debug
4. -- C flags, Debug configuration: -g
5. -- C flags, Release configuration: -O3 -DNDEBUG
6. -- C flags, Release configuration with Debug info: -O2 -g -DNDEBUG
7. -- C flags, minimal Release configuration: -Os -DNDEBUG
8. -- C++ flags, Debug configuration: -g
9. -- C++ flags, Release configuration: -O3 -DNDEBUG
10. -- C++ flags, Release configuration with Debug info: -O2 -g -DNDEBUG
11. -- C++ flags, minimal Release configuration: -Os -DNDEBUG
1. .
2. ├─ CMakeLists.txt
3. ├─ compute-areas.cpp
4. ├─ geometry_circle.cpp
5. ├─ geometry_circle.hpp
6. ├─ geometry_polygon.cpp
7. ├─ geometry_polygon.hpp
8. ├─ geometry_rhombus.cpp
9. ├─ geometry_rhombus.hpp
10. ├─ geometry_square.cpp
11. └─ geometry_square.hpp
# 1、set minimum cmake version
cmake_minimum_required(VERSION 3.5 FATAL_ERROR)
# 2、project name and language
project(recipe-08 LANGUAGES CXX)
# 3、打印当前编译器标志。CMake将对所有C++目标使用这些
message("C++ compiler flags: ${CMAKE_CXX_FLAGS}")
# 4、为目标准备了标志列表,其中一些将无法在Windows上使用
list(APPEND flags "-fPIC" "-Wall")
if(NOT WIN32)
list(APPEND flags "-Wextra" "-Wpedantic")
endif()
# 5、添加了一个新的目标—— geometry 库,并列出它的源依赖关系:
add_library(geometry
STATIC
geometry_circle.cpp
geometry_circle.hpp
geometry_polygon.cpp
geometry_polygon.hpp
geometry_rhombus.cpp
geometry_rhombus.hpp
geometry_square.cpp
geometry_square.hpp
)
# 6、为这个库目标设置了编译选项
target_compile_options(geometry
PRIVATE
${flags}
)
# 7、然后,将生成 compute-areas 可执行文件作为一个目标:
add_executable(compute-areas compute-areas.cpp)
#8、还为可执行目标设置了编译选项:
target_compile_options(compute-areas
PRIVATE
"-fPIC"
)
# 9、为执行文件链接库
target_link_libraries(compute-areas geometry)
- PRIVATE,编译选项会应用于给定的目标,
不会传递给与目标相关的目标
。我们的示例中, 即使 compute-areas 将链接到 geometry 库, compute-areas 也不会继承 geometry 目标上设置的编译器选项。- INTERFACE,给定的编译选项将
只应用于指定目标
,并传递给与目标相关
的目标。- PUBLIC,编译选项将应用于
指定目标和使用它的目标
。
②如何验证,这些标志是否按照我们的意图正确使用呢?或者换句话说,如何确定项目在CMake构建时,实际使用了哪些编译标志?
- 方法一:使用CMake将额外的参数传递给本地构建工具。本例中会设置环境变量 VERBOSE=1
输出:
1. $ mkdir -p build
2. $ cd build
3. $ cmake ..
4. $ cmake --build . -- VERBOSE=1
5.
5. ... lots of output ...
6. 7.
8. [ 14%] Building CXX object CMakeFiles/geometry.dir/geometry_circle.cpp.o
9.
/usr/bin/c++ -fPIC -Wall -Wextra -Wpedantic -o
CMakeFiles/geometry.dir/geometry_circle.cpp.o -c /home/bast/tmp/cmakecookbook/chapter-01/recipe-08/cxx-example/geometry_circle.cpp
10. [ 28%] Building CXX object CMakeFiles/geometry.dir/geometry_polygon.cpp.o
11.
/usr/bin/c++ -fPIC -Wall -Wextra -Wpedantic -o
CMakeFiles/geometry.dir/geometry_polygon.cpp.o -c /home/bast/tmp/cmakecookbook/chapter-01/recipe-08/cxx-example/geometry_polygon.cpp
12. [ 42%] Building CXX object CMakeFiles/geometry.dir/geometry_rhombus.cpp.o
13.
/usr/bin/c++ -fPIC -Wall -Wextra -Wpedantic -o
CMakeFiles/geometry.dir/geometry_rhombus.cpp.o -c /home/bast/tmp/cmakecookbook/chapter-01/recipe-08/cxx-example/geometry_rhombus.cpp
14. [ 57%] Building CXX object CMakeFiles/geometry.dir/geometry_square.cpp.o
15.
/usr/bin/c++ -fPIC -Wall -Wextra -Wpedantic -o
CMakeFiles/geometry.dir/geometry_square.cpp.o -c /home/bast/tmp/cmakecookbook/chapter-01/recipe-08/cxx-example/geometry_square.cpp
16.
17. ... more output ...
18.
19. [ 85%] Building CXX object CMakeFiles/compute-areas.dir/compute-areas.cpp.o
20.
/usr/bin/c++ -fPIC -o CMakeFiles/compute-areas.dir/compute-areas.cpp.o -c
/home/bast/tmp/cmake-cookbook/chapter-01/recipe-08/cxx-example/compute-areas.cpp
21.
22. ... more output ...
- 方法二:不用对 CMakeLists.txt 进行修改。如果想在这个项目中修
改 geometry 和 compute-areas 目标的编译器选项,可以使用CMake参数进行配置:
$ cmake -D CMAKE_CXX_FLAGS="-fno-exceptions -fno-rtti" ..
这个命令将编译项目,禁用异常和运行时类型标识(RTTI)。
也可以使用全局标志,可以使用 CMakeLists.txt 运行以下命令:
1. $ cmake -D CMAKE_CXX_FLAGS="-fno-exceptions -fno-rtti" ..
这将使用 -fno-rtti - fpic - wall - Wextra - wpedantic
配置 geometry 目标,同时使用 -
fno exception -fno-rtti - fpic
配置 compute-areas (因为对compute-areas配置的flags是PRIVATE的)
_STANDARD
属性。1. std::unique_ptr cat = Cat("Simon");
2. std::unique_ptr dog = Dog("Marlowe);
# 1、声明最低要求的CMake版本,项目名称和语言:
# set minimum cmake version
cmake_minimum_required(VERSION 3.5 FATAL_ERROR)
# project name and language
project(recipe-09 LANGUAGES CXX)
# 2、要求在Windows上导出所有库符号:
set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)
# 3、需要为库添加一个目标,这将编译源代码为一个动态库:
add_library(animals
SHARED
Animal.cpp
Animal.hpp
Cat.cpp
Cat.hpp
Dog.cpp
Dog.hpp
Factory.hpp
)
# 4、现在,为目标设置了 CXX_STANDARD 、 CXX_EXTENSIONS 和 CXX_STANDARD_REQUIRED
# 属性。还设置了 position_independent ent_code 属性,以避免在使用一些编译器构建DSO
#时 出现问题:
set_target_properties(animals
PROPERTIES
CXX_STANDARD 14
CXX_EXTENSIONS OFF
CXX_STANDARD_REQUIRED ON
POSITION_INDEPENDENT_CODE 1
)
# 5、然后,为”动物农场”的可执行文件添加一个新目标,并设置它的属性:
add_executable(animal-farm animal-farm.cpp)
set_target_properties(animal-farm
PROPERTIES
CXX_STANDARD 14
CXX_EXTENSIONS OFF
CXX_STANDARD_REQUIRED ON
)
#8、将可执行文件链接到库
target_link_libraries(animal-farm animals)
1. $ mkdir -p build
2. $ cd build
3. $ cmake ..
4. $ cmake --build .
5. $ ./animal-farm
6.
7. I'm Simon the cat!
8. I'm Marlowe the dog!
- CXX_STANDARD会设置我们想要的标准。
- CXX_EXTENSIONS告诉CMake,只启用 ISO C++ 标准的编译器标志,而不使用特定编译器的扩展。
- CXX_STANDARD_REQUIRED指定所选标准的版本。如果这个版本不可用,CMake将停止配置并出现错误。当这个属性被设置为 OFF 时,CMake将寻找下一个标准的最新版本,直到一个合适的标志。这意味着,首先查找 C++14 ,然后是 C++11 ,然后是 C++98 。(译者注:目前会从 C++20 或 C++17 开始查找)
起因
已经使用过 if-else-endif
。CMake还提供了创建循环的语言工具: foreach endforeach 和 while-endwhile
。两者都可以与 break 结合使用,以便尽早从循环中跳出。本示例将展示如何使用 foreach ,来循环源文件列表。我们将应用这样的循环,在引入新目标的前提下,来为一组源文件进行优化降级。
cmake
# 1、与示例8中一样,指定了CMake的最低版本、项目名称和语言,并声明了几何库目标:
# set minimum cmake version
cmake_minimum_required(VERSION 3.5 FATAL_ERROR)
# project name and language
project(recipe-10 LANGUAGES CXX)
add_library(geometry
STATIC
geometry_circle.cpp
geometry_circle.hpp
geometry_polygon.cpp
geometry_polygon.hpp
geometry_rhombus.cpp
geometry_rhombus.hpp
geometry_square.cpp
geometry_square.hpp
)
# 2、使用 -O3 编译器优化级别编译库,对目标设置一个私有编译器选项:
# we wish to compile the library with the optimization flag: -O3
target_compile_options(geometry
PRIVATE
-O3
)
# 3、然后,生成一个源文件列表,以较低的优化选项进行编译
list(
APPEND sources_with_lower_optimization
geometry_circle.cpp
geometry_rhombus.cpp
)
# 4、循环这些源文件,将它们的优化级别调到 -O2 。使用它们的源文件属性完成:
# we use the IN LISTS foreach syntax to set source properties
message(STATUS "Setting source properties using IN LISTS syntax:")
foreach(_source IN LISTS sources_with_lower_optimization)
set_source_files_properties(${_source} PROPERTIES COMPILE_FLAGS -O2)
message(STATUS "Appending -O2 flag for ${_source}")
endforeach()
# 5、打印这些文件的优化属性
# we demonstrate the plain foreach syntax to query source properties
# which requires to expand the contents of the variable
message(STATUS "Querying sources properties using plain syntax:")
foreach(_source ${sources_with_lower_optimization})
get_source_file_property(_flags ${_source} COMPILE_FLAGS)
message(STATUS "Source ${_source} has the following extra COMPILE_FLAGS: ${_flags}")
endforeach()
# 6、最后,添加 compute-areas 可执行目标,并将 geometry 库连接上去:
add_executable(compute-areas compute-areas.cpp)
target_link_libraries(compute-areas geometry)
1. $ mkdir -p build
2. $ cd build
3. $ cmake ..
4.
4. ...
5. -- Setting source properties using IN LISTS syntax:
6. -- Appending -O2 flag for geometry_circle.cpp
-------------------------------------
. $ cmake --build . -- VERBOSE=1
foreach-endforeach
语法可用于在变量列表上,表示重复特定任务。本示例中,使用它来操作、设置和获取项目中特定文件的编译器标志。(CMake代码片段中引入了另外两个新命令:)
②set_source_files_properties(file PROPERTIES property value)
,它将属性设置为给定
文件的传递值。与目标非常相似,文件在CMake中也有属性,允许对构建系统进行非常细粒度的控制。源文件的可用属性列表可以在这里找到:
https://cmake.org/cmake/help/v3.5/manual/cmakeproperties.7.html#source-file-propertie
③get_source_file_property(VAR file property)
,检索给定文件所需属性的值,并将其存储
在CMake VAR 变量中。
④CMake中,列表是用分号分隔的字符串组
。列表可以由 list 或 set 命令创建。例
如, set(var a b c d e) 和 list(APPEND a b c d e) 都创建了列表 a;b;c;d;e 。
⑤单独优化的备注
:为了对一组文件降低优化,将它们收集到一个单独的目标(库)中,并为这个目标显式地设置优化级别,而不是附加一个标志,这样可能会更简洁,不过在本示例中,我们的重点是 foreach & endforeach
。
⑥foreach()
的四种使用方式:
foreach(loop_var arg1 arg2 ...)
: 其中提供循环变量和显式项列表。当foreach(loop_var rangetotal)
或 foreach(loop_var range start stop [step])
。foreach(loop_var IN LISTS [list1[...]])
。参数解释为列表,其内容就会自动展开。 foreach(loop_var IN ITEMS [item1 [...]])
。参数的内容没有展# 1、定义CMake最低版本和项目名称。请注意,语言是 NONE :
# set minimum cmake version
cmake_minimum_required(VERSION 3.5 FATAL_ERROR)
# project name, in this case no language required
project(recipe-01 LANGUAGES NONE)
#2、根据检测到的操作系统信息打印消息:
# print custom message depending on the operating system
if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
message(STATUS "Configuring on/for Linux")
elseif(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
message(STATUS "Configuring on/for macOS")
elseif(CMAKE_SYSTEM_NAME STREQUAL "Windows")
message(STATUS "Configuring on/for Windows")
elseif(CMAKE_SYSTEM_NAME STREQUAL "AIX")
message(STATUS "Configuring on/for IBM AIX")
else()
message(STATUS "Configuring on/for ${CMAKE_SYSTEM_NAME}")
endif()
CMAKE_SYSTEM_NAME
,因此不需要使用定制命令、工具或脚本来查询此信息。然后,可以使用此变量的值实现特定于操作系统的条件和解决方案。在具有 uname 命令的系统上,将此变量设置为 uname -s 的输出。该变量在macOS上设置为“Darwin”。在Linux和Windows上,它分别计算为“Linux”和“Windows”。
#include
#include
#include
std::string say_hello() {
#ifdef IS_WINDOWS
return std::string("Hello from Windows!");
#elif IS_LINUX
return std::string("Hello from Linux!");
#elif IS_MACOS
return std::string("Hello from macOS!");
#else
return std::string("Hello from an unknown system!");
#endif
}
int main() {
std::cout << say_hello() << std::endl;
return EXIT_SUCCESS;
}
# 1、设置了CMake最低版本、项目名称和支持的语言
# set minimum cmake version
cmake_minimum_required(VERSION 3.5 FATAL_ERROR)
# project name and language
project(recipe-02 LANGUAGES CXX)
# 2、定义可执行文件及其对应的源文件:
# define executable and its source file
add_executable(hello-world hello-world.cpp)
# 3、通过定义以下目标编译定义,让预处理器知道系统名称
# let the preprocessor know about the system name
if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
target_compile_definitions(hello-world PUBLIC "IS_LINUX")
endif()
if(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
target_compile_definitions(hello-world PUBLIC "IS_MACOS")
endif()
if(CMAKE_SYSTEM_NAME STREQUAL "Windows")
target_compile_definitions(hello-world PUBLIC "IS_WINDOWS")
endif()
CMAKE_SIZEOF_VOID_P
是检查当前CPU是否具有32位或64位架构的唯一“真正”可移植的方法。#include
#include
#include
EIGEN_DONT_INLINE
double simple_function(Eigen::VectorXd &va, Eigen::VectorXd &vb) {
// this simple function computes the dot product of two vectors
// of course it could be expressed more compactly
double d = va.dot(vb);
return d;
}
int main() {
int len = 1000000;
int num_repetitions = 100;
// generate two random vectors
Eigen::VectorXd va = Eigen::VectorXd::Random(len);
Eigen::VectorXd vb = Eigen::VectorXd::Random(len);
double result;
auto start = std::chrono::system_clock::now();
for (auto i = 0; i < num_repetitions; i++) {
result = simple_function(va, vb);
}
auto end = std::chrono::system_clock::now();
auto elapsed_seconds = end - start;
std::cout << "result: " << result << std::endl;
std::cout << "elapsed seconds: " << elapsed_seconds.count() << std::endl;
}
我们期望向量化可以加快
simple_function
中的点积操作。
# 1、 声明一个 C++11 项目:
cmake_minimum_required(VERSION 3.5 FATAL_ERROR)
project(recipe-06 LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# 2、使用Eigen库,我们需要在系统上找到它的头文件:
find_package(Eigen3 3.3 REQUIRED CONFIG)
# 3、CheckCXXCompilerFlag.cmake 标准模块文件:
include(CheckCXXCompilerFlag)
# 4、检查 -march=native 编译器标志是否工作:
check_cxx_compiler_flag("-march=native" _march_native_works)
# 5、另一个选项 -xHost 编译器标志也开启
check_cxx_compiler_flag("-xHost" _xhost_works)
# 6、设置了一个空变量 _CXX_FLAGS ,来保存刚才检查的两个编译器中找到的编译器标志。如果看
到 _march_native_works ,我们将 _CXX_FLAGS 设置为 -march=native 。如果看
到 _xhost_works ,我们将 _CXX_FLAGS 设置为 -xHost 。如果它们都不起作
用, _CXX_FLAGS 将为空,并禁用矢量化:
set(_CXX_FLAGS)
if(_march_native_works)
message(STATUS "Using processor's vector instructions (-march=native compiler flag set)")
set(_CXX_FLAGS "-march=native")
elseif(_xhost_works)
message(STATUS "Using processor's vector instructions (-xHost compiler flag set)")
set(_CXX_FLAGS "-xHost")
else()
message(STATUS "No suitable compiler flag found for vectorization")
endif()
add_executable(linear-algebra-unoptimized linear-algebra.cpp)
# 7、 为了便于比较,我们还为未优化的版本定义了一个可执行目标,不使用优化标志
target_link_libraries(linear-algebra-unoptimized
PRIVATE
Eigen3::Eigen
)
# 8、此外,我们定义了一个优化版本:
add_executable(linear-algebra linear-algebra.cpp)
target_compile_options(linear-algebra
PRIVATE
${_CXX_FLAGS}
)
target_link_libraries(linear-algebra
PRIVATE
Eigen3::Eigen
)
1. $ cmake --build .
2. $ ./linear-algebra-unoptimized
3.
4. result: -261.505
5. elapsed seconds: 1.97964
6.
7. $ ./linear-algebra
8.
9. result: -261.505
10. elapsed seconds: 1.05048
GNU编译器
使用 -march=native
标志来实现这一点,而Intel编译器
使用 -xHost
标志。CheckCXXCompilerFlag.cmake
模块提供的 check_cxx_compiler_flag
函数进行编译器标志的检查:check_cxx_compiler_flag("-march=native" _march_native_works)
这个函数接受两个参数:
- 第一个是要检查的编译器标志。
- 第二个是用来存储检查结果(true或false)的变量。如果检查为真,我们将工作标志添加
到 _CXX_FLAGS 变量中,该变量将用于为可执行目标设置编译器标志。
③本示例可与前一示例相结合,可以使用 cmake_host_system_information
查询处理器功能。
cmake --help-module-list
获得现有模块的列表。但是,不是所有的库和程序都包含在其中,有时必须自己编写检测脚本find_library:查找一个库文件
find_package:从外部项目查找和加载设置
find_path:查找包含指定文件的目录
find_program:找到一个可执行程序
#1. 首先,定义CMake最低版本和项目名称。注意,这里不需要任何语言支持
# set minimum cmake version
cmake_minimum_required(VERSION 3.5 FATAL_ERROR)
# project name and language
project(recipe-01 LANGUAGES NONE)
#2. 然后,使用 find_package 命令找到Python解释器:
# detect python
find_package(PythonInterp REQUIRED)
#3. 然后,执行Python命令并捕获它的输出和返回值:
# Execute a tiny Python script
execute_process(
COMMAND
${PYTHON_EXECUTABLE} "-c" "print('Hello, world!')"
RESULT_VARIABLE _status
OUTPUT_VARIABLE _hello_world
ERROR_QUIET
OUTPUT_STRIP_TRAILING_WHITESPACE
)
#4. 最后,打印Python命令的返回值和输出:
message(STATUS "RESULT_VARIABLE is: ${_status}")
message(STATUS "OUTPUT_VARIABLE is: ${_hello_world}")
# compare the "manual" messages with the following handy helper
include(CMakePrintHelpers)
cmake_print_variables(_status _hello_world)
1. $ mkdir -p build
2. $ cd build
3. $ cmake ..
4.
5. -- Found PythonInterp: /usr/bin/python (found version "3.6.5")
6. -- RESULT_VARIABLE is: 0
7. -- OUTPUT_VARIABLE is: Hello, world!
8. -- Configuring done
9. -- Generating done
10.
-- Build files have been written to: /home/user/cmake-cookbook/chapter03/recipe-01/example/build
find_package
是用于发现和设置包的CMake模块的命令。这些模块包含CMake命令,用于标识系统标准位置中的包。CMake模块文件称为 Find.cmake
,当调用 find_package()
时,模块中的命令将会运行。CMakeLists.txt
中使用这些变量。对于Python解释器,相关模块FindPythonInterp.cmake
附带的设置了一些CMake变量:PYTHONINTERP_FOUND:是否找到解释器
PYTHON_EXECUTABLE:Python解释器到可执行文件的路径
PYTHON_VERSION_STRING:Python解释器的完整版本信息
PYTHON_VERSION_MAJOR:Python解释器的主要版本号
PYTHON_VERSION_MINOR :Python解释器的次要版本号
PYTHON_VERSION_PATCH:Python解释器的补丁版本号
③可以强制CMake,查找特定版本的包。例如,要求Python解释器的版本大于或等于
2.7:
find_package(PythonInterp 2.7)
④可以强制满足依赖关系:
find_package(PythonInterp REQUIRED)
(如果在查找位置中没有找到适合Python解释器的可执行文件,CMake将中止配置。)
⑤软件包没有安装在标准位置时,CMake无法正确定位它们。用户可以使用CLI的 -D 参数传递相应的选项,告诉CMake查看特定的位置。Python解释器可以使用以下配置
$ cmake -D PYTHON_EXECUTABLE=/custom/location/python ..
这将指定非标准 /custom/location/pytho 安装目录中的Python可执行文件。
准备工作
我们将展示如何提取Eigen库文件,并使用提取的源文件编译我们的项目。这个示例中,将重用第3章
第7节的线性代数例子 linear-algebra.cpp ,用来检测外部库和程序、检测特征库。这里,假设已
经包含Eigen库文件,已在项目构建前下载。
linear-algebra.cpp
#include
#include
#include
#include
#include
#include
#include
int main(int argc, char **argv) {
if (argc != 2) {
std::cout << "Usage: ./linear-algebra dim" << std::endl;
return EXIT_FAILURE;
}
std::chrono::time_point<std::chrono::system_clock> start, end;
std::chrono::duration<double> elapsed_seconds;
std::time_t end_time;
std::cout << "Number of threads used by Eigen: " << Eigen::nbThreads()
<< std::endl;
// Allocate matrices and right-hand side vector
start = std::chrono::system_clock::now();
int dim = std::atoi(argv[1]);
Eigen::MatrixXd A = Eigen::MatrixXd::Random(dim, dim);
Eigen::VectorXd b = Eigen::VectorXd::Random(dim);
end = std::chrono::system_clock::now();
// Report times
elapsed_seconds = end - start;
end_time = std::chrono::system_clock::to_time_t(end);
std::cout << "matrices allocated and initialized "
<< std::put_time(std::localtime(&end_time), "%a %b %d %Y %r\n")
<< "elapsed time: " << elapsed_seconds.count() << "s\n";
start = std::chrono::system_clock::now();
// Save matrix and RHS
Eigen::MatrixXd A1 = A;
Eigen::VectorXd b1 = b;
end = std::chrono::system_clock::now();
end_time = std::chrono::system_clock::to_time_t(end);
std::cout << "Scaling done, A and b saved "
<< std::put_time(std::localtime(&end_time), "%a %b %d %Y %r\n")
<< "elapsed time: " << elapsed_seconds.count() << "s\n";
start = std::chrono::system_clock::now();
Eigen::VectorXd x = A.lu().solve(b);
end = std::chrono::system_clock::now();
// Report times
elapsed_seconds = end - start;
end_time = std::chrono::system_clock::to_time_t(end);
double relative_error = (A * x - b).norm() / b.norm();
std::cout << "Linear system solver done "
<< std::put_time(std::localtime(&end_time), "%a %b %d %Y %r\n")
<< "elapsed time: " << elapsed_seconds.count() << "s\n";
std::cout << "relative error is " << relative_error << std::endl;
return 0;
}
## 1、首先,使能C++11项目
cmake_minimum_required(VERSION 3.5 FATAL_ERROR)
project(recipe-01 LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
## 2、我们将自定义目标添加到构建系统中,自定义目标将提取构建目录中的库文件
add_custom_target(unpack-eigen #add_custom_target,创建执行自定义命令的目标
ALL
COMMAND
${CMAKE_COMMAND} -E tar xzf ${CMAKE_CURRENT_SOURCE_DIR}/eigen-eigen-5a0156e40feb.tar.gz
COMMAND
${CMAKE_COMMAND} -E rename eigen-eigen-5a0156e40feb eigen-3.3.4
WORKING_DIRECTORY
${CMAKE_CURRENT_BINARY_DIR}
COMMENT
"Unpacking Eigen3 in ${CMAKE_CURRENT_BINARY_DIR}/eigen-3.3.4"
)
## 3、 为源文件添加了一个可执行目标:
add_executable(linear-algebra linear-algebra.cpp)
## 4、由于源文件的编译依赖于Eigen头文件,需要显式地指定可执行目标对自定义目标的依赖关系:
add_dependencies(linear-algebra unpack-eigen)
## 5、最后,指定包含哪些目录:
target_include_directories(linear-algebra
PRIVATE
${CMAKE_CURRENT_BINARY_DIR}/eigen-3.3.4
)
add_custom_target
这个命令:1. add_custom_target(unpack-eigen
2. ALL
3. COMMAND
4.
${CMAKE_COMMAND} -E tar xzf ${CMAKE_CURRENT_SOURCE_DIR}/eigen-eigen5a0156e40feb.tar.gz
4. COMMAND
5. ${CMAKE_COMMAND} -E rename eigen-eigen-5a0156e40feb eigen-3.3.4
6. WORKING_DIRECTORY
7. ${CMAKE_CURRENT_BINARY_DIR}
8. COMMENT
9. "Unpacking Eigen3 in ${CMAKE_CURRENT_BINARY_DIR}/eigen-3.3.4"
10. )
构建系统中引入了一个名为 unpack-eigen
的目标。因为我们传递了 ALL
参数,目标将始终被执
行。 构建过程中必须执行一系列没有输出的命令
时,可以使用 add_custom_target
命令。正如我们在本示例中所示,可以将自定义目标指定为项目中其他目标的依赖项。此外,自定义目标还可以依赖于其他目标。
②COMMAND
参数指定要执行哪些命令。本例中,我们希望提取存档并将提取的目录重命名
为 egan -3.3.4
,通过以下两个命令实现:
11. ${CMAKE_COMMAND} -E tar xzf ${CMAKE_CURRENT_SOURCE_DIR}/eigen-eigen2. 5a0156e40feb.tar.gz
12. ${CMAKE_COMMAND} -E rename eigen-eigen-5a0156e40feb eigen-3.3.4
注意,使用 -E 标志调用CMake命令本身来执行实际的工作。对于许多常见操作,CMake实现了一个
对所有操作系统都通用的接口
,这使得构建系统独立于特定的平台。
③add_custom_target
命令中的下一个参数是工作目录。我们的示例中,它对应于构建目录: CMAKE_CURRENT_BINARY_DIR
④最后一个参数 COMMENT
,用于指定CMake在执行自定义目标时输出什么样的消息。
⑤使用 -E
标志可以以与操作系统无关的方式,运行许多公共操作。运行 cmake -E
或 cmake -E help
可以获得特定操作系统的完整列表。例如,这是Linux系统上命令的摘要
13. Usage: cmake -E <command> [arguments...]
14. Available commands:
3.
capabilities - Report capabilities built into cmake in JSON
format
15. chdir dir cmd [args...] - run command in a given directory
16. compare_files file1 file2 - check if file1 is same as file2
6.
copy <file>... destination - copy files to destination (either file or
directory)
7.
copy_directory <dir>... destination - copy content of <dir>... directories
to 'destination' directory
17. copy_if_different <file>... destination - copy files if it has changed
18. echo [<string>...] - displays arguments as text
19. echo_append [<string>...] - displays arguments as text but no new line
20. env [--unset=NAME]... [NAME=VALUE]... COMMAND [ARG]...
21. - run command in a modified environment
22. environment - display the current environment
23. make_directory <dir>... - create parent and <dir> directories
24. md5sum <file>... - create MD5 checksum of files
25. sha1sum <file>... - create SHA1 checksum of files
26. sha224sum <file>... - create SHA224 checksum of files
27. sha256sum <file>... - create SHA256 checksum of files
28. sha384sum <file>... - create SHA384 checksum of files
29. 20. sha512sum <file>... - create SHA512 checksum of files
21. remove [-f] <file>... - remove the file(s), use -f to force it
22. remove_directory dir - remove a directory and its contents
23. rename oldname newname - rename a file or directory (on one volume)
24. server - start cmake in server mode
25. sleep <number>... - sleep for given number of seconds
26. tar [cxt][vf][zjJ] file.tar [file/dir1 file/dir2 ...]
27. - create or extract a tar or zip archive
28. time command [args...] - run command and display elapsed time
29. touch file - touch a file.
30. touch_nocreate file - touch a file but do not create it.
31. Available on UNIX only:
32. create_symlink old new - create a symbolic link new -> old
①:CMake中,C++是默认的编程语言。不过,我们还是建议使用 LANGUAGES
选项在 project 命令中显式地声明项目的语言
project(recipe-01 LANGUAGES CXX)
②涉及到的文件分类
①Makefile : make 将运行指令来构建项目。
②CMakefile :包含临时文件的目录,CMake用于检测操作系统、编译器等。此外,根据所选的生成器,它还包含特定的文件。
③cmake_install.cmake :处理安装规则的CMake脚本,在项目安装时使用。
④CMakeCache.txt :如文件名所示,CMake缓存。CMake在重新运行配置时使用这个文件。
③命令实现编译
$ cmake -H. -Bbuild
该命令是跨平台的,使用了 -H 和 -B 为CLI选项。 -H 表示当前目录中搜索
根 CMakeLists.txt 文件。 -Bbuild 告诉CMake在一个名为 build 的目录中生成所有的文件。
④CMake不强制指定构建目录执行名称或位置,我们完全可以把它放在项目路径之外。这样做同样有效:
1. $ mkdir -p /tmp/someplace
2. $ cd /tmp/someplace
3. $ cmake /path/to/source
4. $ cmake --build .
⑤CMake接受其他值作为 add_library
的第二个参数的有效值,我们来看下本书会用到的值:
⑥展示OBJECT库的使用
,如下
1. cmake_minimum_required(VERSION 3.5 FATAL_ERROR)
2. project(recipe-03 LANGUAGES CXX)
3.
4. add_library(message-objs
5. OBJECT
6. Message.hpp
7. Message.cpp
8. )
9.
10. # this is only needed for older compilers
11. # but doesn't hurt either to have it
12. set_target_properties(message-objs
13. PROPERTIES
14. POSITION_INDEPENDENT_CODE 1
15. )
16.
17. add_library(message-shared
18. SHARED
19. $
20. )
21.
22. add_library(message-static
23. STATIC
24. $
25. )
26.
27. add_executable(hello-world hello-world.cpp)
28.
29. target_link_libraries(hello-world message-static)
7、如CMake语言文档中描述,逻辑真或假可以用多种方式表示:
《1》如果将逻辑变量设置为以下任意一种: 1 、 ON 、 YES 、 true 、 Y 或非零数,则逻辑变量为 true 。
《2》如果将逻辑变量设置为以下任意一种: 0 、 OFF 、 NO 、 false 、 N 、 IGNORE、NOTFOUND 、空字符串,或者以 -NOTFOUND 为后缀,则逻辑变量为 false 。
8、BUILD_SHARED_LIBS
是CMake的一个全局标志。因为CMake内部要查询 BUILD_SHARED_LIBS 全局变量,所以 add_library 命令可以在不传递 STATIC/SHARED/OBJECT 参数的情况下调用;如果为 false 或未定义,将生成一个静态库
9、if(XXX) + else() + endif()
也可以用 if(USE_LIBRARY)…else(USE_LIBRARY)… endif(USE_LIBIRAY)
,这个格式并不唯一,可以根据个人喜好来决定使用哪种格式。
10、-D给cmake传递参数
①-D
开关用于为CMake设置任何类型的变量:逻辑变量、路径等等
②option
可接受三个参数:
1) 表示该选项的变量的名称。
2)"help string" 记录选项的字符串,在CMake的终端或图形用户界面中可见。
3)[initial value] 选项的默认值,可以是 ON 或 OFF 。
11、我们为动物和动物农场目标设置了一些属性:
- CXX_STANDARD会设置我们想要的标准。
- CXX_EXTENSIONS告诉CMake,只启用 ISO C++ 标准的编译器标志,而不使用特定编译器的扩展。
- CXX_STANDARD_REQUIRED指定所选标准的版本。如果这个版本不可用,CMake将停止配置并出现错误。当这个属性被设置为 OFF 时,CMake将寻找下一个标准的最新版本,直到一个合适的标志。这意味着,首先查找 C++14 ,然后是 C++11 ,然后是 C++98 。(译者注:目前会从 C++20 或 C++17 开始查找)
12、foreach-endforeach
语法可用于在变量列表上,表示重复特定任务。本示例中,使用它来操作、设置和获取项目中特定文件的编译器标志。
(CMake代码片段中引入了另外两个新命令:)
13、set_source_files_properties(file PROPERTIES property value)
,它将属性设置为给定
文件的传递值。与目标非常相似,文件在CMake中也有属性,允许对构建系统进行非常细粒度的控制。源文件的可用属性列表可以在这里找到:
https://cmake.org/cmake/help/v3.5/manual/cmakeproperties.7.html#source-file-propertie
114、get_source_file_property(VAR file property)
,检索给定文件所需属性的值,并将其存储
在CMake VAR 变量中。
15、CMake中,列表是用分号分隔的字符串组
。列表可以由 list 或 set 命令创建。例
如, set(var a b c d e) 和 list(APPEND a b c d e) 都创建了列表 a;b;c;d;e 。
16、单独优化的备注
:为了对一组文件降低优化,将它们收集到一个单独的目标(库)中,并为这个目标显式地设置优化级别,而不是附加一个标志,这样可能会更简洁,不过在本示例中,我们的重点是 foreach & endforeach
。
17、foreach()
的四种使用方式:
foreach(loop_var arg1 arg2 ...)
: 其中提供循环变量和显式项列表。当foreach(loop_var rangetotal)
或 foreach(loop_var range start stop [step])
。foreach(loop_var IN LISTS [list1[...]])
。参数解释为列表,其内容就会自动展开。 foreach(loop_var IN ITEMS [item1 [...]])
。参数的内容没有展18、CMake为目标操作系统定义了 CMAKE_SYSTEM_NAME
,因此不需要使用定制命令、工具或脚本来查询此信息。然后,可以使用此变量的值实现特定于操作系统的条件和解决方案。在具有 uname 命令的系统上,将此变量设置为 uname -s 的输出。该变量在macOS上设置为“Darwin”。在Linux和Windows上,它分别计算为“Linux”和“Windows”。
19、为了最小化从一个平台转移到另一个平台时的成本,应该避免直接使用Shell命令,还应该避免显式的路径分隔符(Linux和macOS上的前斜杠/和Windows上的后斜杠\)。CMake代码中只使用前斜杠作为路径分隔符,CMake将自动将它们转换为所涉及的操作系统环境。
20、《1》指示编译器为我们检查处理器,并为当前体系结构生成本机指令。不同的编译器供应商会使用不同的标志来实现这一点:GNU编译器
使用 -march=native
标志来实现这一点,而Intel编译器
使用 -xHost
标志。
《2》使用 CheckCXXCompilerFlag.cmake
模块提供的 check_cxx_compiler_flag
函数进行编译器标志的检查:
check_cxx_compiler_flag("-march=native" _march_native_works)
这个函数接受两个参数:
- 第一个是要检查的编译器标志。
- 第二个是用来存储检查结果(true或false)的变量。如果检查为真,我们将工作标志添加
到 _CXX_FLAGS 变量中,该变量将用于为可执行目标设置编译器标志。
21、
①细看 add_custom_target
这个命令:
1. add_custom_target(unpack-eigen
2. ALL
3. COMMAND
4.
${CMAKE_COMMAND} -E tar xzf ${CMAKE_CURRENT_SOURCE_DIR}/eigen-eigen5a0156e40feb.tar.gz
4. COMMAND
5. ${CMAKE_COMMAND} -E rename eigen-eigen-5a0156e40feb eigen-3.3.4
6. WORKING_DIRECTORY
7. ${CMAKE_CURRENT_BINARY_DIR}
8. COMMENT
9. "Unpacking Eigen3 in ${CMAKE_CURRENT_BINARY_DIR}/eigen-3.3.4"
10. )
构建系统中引入了一个名为 unpack-eigen
的目标。因为我们传递了 ALL
参数,目标将始终被执
行。 构建过程中必须执行一系列没有输出的命令
时,可以使用 add_custom_target
命令。正如我们在本示例中所示,可以将自定义目标指定为项目中其他目标的依赖项。此外,自定义目标还可以依赖于其他目标。
②COMMAND
参数指定要执行哪些命令。本例中,我们希望提取存档并将提取的目录重命名
为 egan -3.3.4
,通过以下两个命令实现:
11. ${CMAKE_COMMAND} -E tar xzf ${CMAKE_CURRENT_SOURCE_DIR}/eigen-eigen2. 5a0156e40feb.tar.gz
12. ${CMAKE_COMMAND} -E rename eigen-eigen-5a0156e40feb eigen-3.3.4
注意,使用 -E 标志调用CMake命令本身来执行实际的工作。对于许多常见操作,CMake实现了一个
对所有操作系统都通用的接口
,这使得构建系统独立于特定的平台。
③add_custom_target
命令中的下一个参数是工作目录。我们的示例中,它对应于构建目录: CMAKE_CURRENT_BINARY_DIR
④最后一个参数 COMMENT
,用于指定CMake在执行自定义目标时输出什么样的消息。
⑤使用 -E
标志可以以与操作系统无关的方式,运行许多公共操作。运行 cmake -E
或 cmake -E help
可以获得特定操作系统的完整列表。例如,这是Linux系统上命令的摘要
13. Usage: cmake -E <command> [arguments...]
14. Available commands:
3.
capabilities - Report capabilities built into cmake in JSON
format
15. chdir dir cmd [args...] - run command in a given directory
16. compare_files file1 file2 - check if file1 is same as file2
6.
copy <file>... destination - copy files to destination (either file or
directory)
7.
copy_directory <dir>... destination - copy content of <dir>... directories
to 'destination' directory
17. copy_if_different <file>... destination - copy files if it has changed
18. echo [<string>...] - displays arguments as text
19. echo_append [<string>...] - displays arguments as text but no new line
20. env [--unset=NAME]... [NAME=VALUE]... COMMAND [ARG]...
21. - run command in a modified environment
22. environment - display the current environment
23. make_directory <dir>... - create parent and <dir> directories
24. md5sum <file>... - create MD5 checksum of files
25. sha1sum <file>... - create SHA1 checksum of files
26. sha224sum <file>... - create SHA224 checksum of files
27. sha256sum <file>... - create SHA256 checksum of files
28. sha384sum <file>... - create SHA384 checksum of files
29. 20. sha512sum <file>... - create SHA512 checksum of files
21. remove [-f] <file>... - remove the file(s), use -f to force it
22. remove_directory dir - remove a directory and its contents
23. rename oldname newname - rename a file or directory (on one volume)
24. server - start cmake in server mode
25. sleep <number>... - sleep for given number of seconds
26. tar [cxt][vf][zjJ] file.tar [file/dir1 file/dir2 ...]
27. - create or extract a tar or zip archive
28. time command [args...] - run command and display elapsed time
29. touch file - touch a file.
30. touch_nocreate file - touch a file but do not create it.
31. Available on UNIX only:
32. create_symlink old new - create a symbolic link new -> old