cmake
一 cmake基础-生成可执行文件和库
在编写 CMake 之前,要确保你已经清楚了如何运行 CMake 来构建文件。 几乎所有 CMake 项目都一样。
这是经典的构建流程:
1 mkdir build 2 cd build 3 cmake .. 4 make
不是每个项目都必须这样做,你也可以建立一个奇奇怪怪的文件夹名。但我们默认遵守这个规则,即建立一个build文件夹来存生成的构建项。
现在来将一个源文件编译为可执行文件,这是源文件,文件名为main.cpp:
#include<iostream> using namespace std; int main() { cout<<"hello cmake"<<endl; return 0; }
同时,在这个源文件的目录写CMakeLists.txt文件:
#第一行,设置CMake所需的最低版本。如果使用的CMake版本低于该版本,则会发出致命错误: cmake_minimum_required(VERSION 3.5 FATAL_ERROR) #第二行,声明了项目的名称(recipe-01
)和支持的编程语言(CXX代表C++): project(recipe-01 LANGUAGES CXX)
#指示CMake创建一个新目标:可执行文件main.cpp
。这个可执行文件是通过编译和链接源文件main.cpp
生成的。CMake将为编译器使用默认设置,并自动选择生成工具: add_executable(hello main.cpp)
现在,建立一个build目录,并在其中配置项目:
$ mkdir -p build $ cd build $ cmake .. -- The CXX compiler identification is GNU 8.1.0 -- Check for working CXX compiler: /usr/bin/c++ -- Check for working CXX compiler: /usr/bin/c++ -- works -- Detecting CXX compiler ABI info -- Detecting CXX compiler ABI info - done -- Detecting CXX compile features -- Detecting CXX compile features - done -- Configuring done -- Generating done -- Build files have been written to: /home/user/cmake-cookbook/chapter-01/recipe-01/cxx-example/build
如果一切顺利,项目的配置已经在build
目录中生成。我们现在可以编译可执行文件:
$ cmake --build . Scanning dependencies of target main [ 50%] Building CXX object CMakeFiles/main.dir/main.cpp.o [100%] Linking CXX executable main [100%] Built target main
NOTE:CMake语言不区分大小写,但是参数区分大小写。
NOTE:在与CMakeLists.txt
相同的目录中执行cmake .
,原则上足以配置一个项目。然而,CMake会将所有生成的文件写到项目的根目录中。这将是一个源代码内构建,通常是不推荐的,因为这会混合源代码和项目的目录树。我们首选的是源外构建。
构建和链接静态库和动态库
NOTE:可执行文件和目标文件没有什么本质的不同,可执行文件区别于目标文件的地方在于,可执行文件有一个入口函数,这个函数也就是我们在C语言当中定义的main函数,main函数在执行过程中会用到所有可执行文件当中的代码和数据。而这个main函数是被谁调用执行的呢,答案就是操作系统(Operating System) [1] 彻底理解链接器:三,库与可执行文件 - SegmentFault 思否
回看第一个例子,这里并不再为可执行文件提供单个源文件,我们现在将引入一个类,用来包装要打印到屏幕上的消息。更新一下的main.cpp
:
#include "Message.hpp" #include <cstdlib> #include <iostream> 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; }
Message
类包装了一个字符串,并提供重载过的<<
操作,并且包括两个源码文件:Message.hpp
头文件与Message.cpp
源文件。Message.hpp
中的接口包含以下内容:
#pragma once #include <iosfwd> #include <string> 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); };
NOTE:以上代码的 std::ostream &printObject(std::ostream &os); 声明了一个函数printObject,返回的是一个ostream对象的引用,具体看 c++中引用作为返回值 - 知乎 (zhihu.com)
Message.cpp
实现如下:
#include "Message.hpp" #include <iostream> #include <string> std::ostream &Message::printObject(std::ostream &os) { os << "This is my very nice message: " << std::endl; os << message_; return os; }
这里有两个文件需要编译,所以CMakeLists.txt
必须进行修改。本例中,先把它们编译成一个库,而不是直接编译成可执行文件:
#创建目标——静态库。库的名称和源码文件名相同,具体代码如下: add_library(message STATIC Message.hpp Message.cpp ) #创建main可执行文件的目标部分不需要修改: add_executable(main main.cpp) #最后,将目标库链接到可执行目标: target_link_libraries(main message)
NOTE:必须先生成可执行文件再链接库.
CMake接受其他值作为add_library
的第二个参数的有效值,我们来看下会用到的值:
- STATIC:用于创建静态库,即编译文件的打包存档,以便在链接其他目标时使用,例如:可执行文件。
- SHARED:用于创建动态库,即可以动态链接,并在运行时加载的库。可以在
CMakeLists.txt
中使用add_library(message SHARED Message.hpp Message.cpp)
从静态库切换到动态共享对象(DSO)。 - OBJECT:可将给定
add_library
的列表中的源码编译到目标文件,不将它们归档到静态库中,也不能将它们链接到共享对象中。如果需要一次性创建静态库和动态库,那么使用对象库尤其有用。我们将在本示例中演示。 - MODULE:又为DSO组。与
SHARED
库不同,它们不链接到项目中的任何目标,不过可以进行动态加载。该参数可以用于构建运行时插件。
现在展示OBJECT
库的使用,修改CMakeLists.txt
,如下:
cmake_minimum_required(VERSION 3.5 FATAL_ERROR) project(recipe-03 LANGUAGES CXX) add_library(message-objs OBJECT Message.hpp Message.cpp ) # this is only needed for older compilers # but doesn't hurt either to have it set_target_properties(message-objs PROPERTIES POSITION_INDEPENDENT_CODE 1 ) add_library(message-shared SHARED $<TARGET_OBJECTS:message-objs> ) add_library(message-static STATIC $<TARGET_OBJECTS:message-objs> ) add_executable(main main.cpp) target_link_libraries(main message-static)