- A+
1. c++项目构建与CMake简介
在Windows系统上我们通常使用Visual Studio(VS)来生成我们的c++项目。我们只需在VS相应的层次目录中添加相应的文件即可,而不需要手动指定各个文件的具体路径及依赖包含关系。
在Linux系统上我们通常使用CMake来构建c++项目。cmake是一个跨平台的编译工具,CMake 的组态档取名为 CMakeLists.txt。换一个通俗点的意思就是我只需要写一个CMakeLists.txt文件来指明与我的项目有关的文件、依赖等,便可将这个项目正常编译及生成可执行文件。
注:CMake 并不直接建构出最终的软件,而是产生标准的建构档(如 Unix 的 Makefile 或 Windows Visual C++ 的 projects/workspaces),然后再依一般的建构方式使用。即当我们使用cmake
命令后,我们还要使用make
命令生成可执行文件。
2.使用g++编译cpp文件
g++是GNU开发的C++编译器,是GCC(GNU Compiler Collection)GNU编译器套件的组成部分。对应一个简单的c++程序,我们一般可以使用g++命令进行直接编译。
/*文件名为main.cpp*/ #include <iostream> int main(){ std::cout<<"hello,worldn"; return 0; }
我们可以使用如下命令进行编译:
g++ -o hello main.cpp
我们可以看到程序正常编译与执行:
当然g++以及gcc有较多的命令与功能,这里不再详细展开说明。
3. Cmake入门
3.1 一个简单的CMakeLists.txt:
cmake_minimum_required(VERSION 3.9) #最低版本 project(helloworld VERSION 1.0) #项目名称及自定义版本号 add_executable(hello_cmake main.cpp) #添加生成可执行文件
接下来我们使用cmake .
&&make
&&./hello_cmake
便可编译、生成与运行我们所编译的文件了。
-
cmake .
:在当前目录下生成标准建构档(可以理解为中间文件),这里的 “.” 指代的是当前目录。 -
make
:编译中间文件,生成可执行文件。
./hello_cmake
:运行可执行文件。
这样,我们便使用CMake编译与生成了第一个c++文件。到目前为止它与上文中使用到的g++的作用是一样的。而当一个c++项目越复杂时,使用CMake的优势也将越来越明显。
3.2 创建build文件夹,不同文件分离与有序化
如果你运行了cmake .
&&make
这两个命令,你就会发现在你的源文件所在的目录下多出了很多其他的文件。这些文件是项目编译中很重要的文件,但其实你可以完全不用去管他。我们只需要关注CMakeLists.txt与源文件。
上述问题的解决方法是在项目文件夹中创建一个build文件用于保存这些“无关紧要的编译文件”——mkdir build
。
这样你只需要进入build目录使用cmake ..
&&make
便可以生成二进制文件了(可执行文件)。
注:cmake ..
中 ”..“ 指代上一级目录。所以在build目录中使用cmake ..
指代的是:调用上一级目录中的CMakeLists.txt文件,并将生成的中间文件保存在当前目录。
附:对于较大型的c++项目,合理利用文件夹分类文件可以帮到我们很多。例如创建src
文件夹存放源文件(源代码),创建bin
(binary)存放可执行文件,创建include
存放头文件。
3.3 CMake使项目中包含头文件
我们创建一个include目录并在目录下编写一个较为简单的header:
#pragma once #include <iostream> class Blah{ public: inline void boo(){ std::cout<<"Boo!n"; } };
然后再在源文件中添加一个#include "header.h"
来引用头文件。这时如果不改变CMakeLists.txt文件,显然源文件并不知道这个header.h的具体位置。
所以,我们需要修改CMakeLists.txt文件(事实上只需要添加一行语句)
cmake_minimum_required(VERSION 3.9) project(helloworld VERSION 1.0) add_executable(hello_cmake main.cpp) target_include_directories(hello_cmake PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)
target_include_directories
:指定编译给定目标时要使用的include目录。这里即在编译生成hello_make可执行文件时,使用CMakeLists.txt所在的目录下的include目录。(INTERFACE、PUBLIC和PRIVATE关键字用于指定target_include_directories的影响范围)。在引用库路径时使用这个命令更多,这里不在展开。
当然在CMake中include_directories
也可以指定头文件目录。其作用是:将给定的目录添加到编译器用来搜索头文件的目录中。它的范围相较于target_include_directories
更大。不需要针对于某一个给定的目标。所有生成的可执行文件都可以在include_directories
指定的目录下搜索查找头文件。
3.4 CMake处理多个源文件的编译
- 修改
add_executable
,添加多个源文件,例如:
add_executable(hello_cmake main.cpp src/Blah.cpp)
- 通过修改CMakeLists.txt文件指定源文件的路径。
在CMakeLists.txt中添加如下代码即可:
file(GOLB_RECURSE SRC_FILES src/*.cpp) add_executable(hello ${SRC_FILES})
这里的${},相当于一个变量。在CMake中经常使用!
我们可以使用以下语句设置相应路径变量,则MYLIB_INCLUDE_DIRS为设置的路径。
set(MYLIB_INCLUDE_DIRS /tmp/customPATH/include)
3.5 CMake中引用相关文件(librarypackage)
我们首先需要了解以下概念。
linux下有两种库:动态库(.so)和静态库(.a)(共享库)。二者的不同点在于代码被载入的时刻不同。
-
静态库的代码在编译过程中已经被载入可执行程序,因此体积比较大。
-
动态库(共享库)的代码在可执行程序运行时才载入内存,在编译过程中仅简单的引用,因此代码体积比较小。
- My own lib:
add_library(mylib STATIC lib/blah.cpp) #STATIC 静态 #添加lib add_executable(hello_cmake main.cpp) #生成可执行文件 target_link_libraries(hello_cmake PUBLIC mylib) #链接库文件
- external lib with find_package:
这里以sfml作为外部依赖包为例,它需要首先从软件仓库下载,再在CMakeLists.txt文件中编写如下语句。
find_package(SFML 2 REQUIRED network audio graphics window system) target_include_directories(hello_cmake PUBLIC ${SFML_INCLUDE_DIR}) target_link_libraries(hello_cmake PUBLIC ${SFML_LIBRARIES} ${SFML_DEPENDENCIES}) #链接库文件
这样我们就可以在源文件中加入相关include语句了。
#include <SFML/System.hpp> #include <SFML/Main.hpp>
- external lib manually (手动引用库文件)
find_library(MYLIB mylib) find_library(MYLIB mylib PATHS /tmp/customPATH)
find_library可以加入REQUIRED参数,它表示为如果未找到任何内容,则停止处理并显示错误消息。
3.6 CMake的嵌套使用
主要操作如下:
add_subdirectory(source_dir [binary_dir] [EXCLUDE_FROM_ALL])
作用:添加一个子目录并构建该子目录。
例如在库文件中,我们可能还需要添加头文件的路径。而将其全部写在外部的CMakeLists.txt文件中又会略显臃肿。
这时我们可以在lib文件中,编写一个新的CMakeList.txt,然后仅仅将相关代码放上去。例如
add_library(mylib STATIC blah.cpp) #STATIC 静态 #添加lib target_include_directories(hello_cmake PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include) #指定头文件
4. 案例代码
cmake_minimum_required(VERSION 3.9) project(example_test1) set (EXECUTABLE_OUTPUT_PATH /home/tom/文档/example_test1/bin) aux_source_directory (/home/tom/文档/example_test1/src SRC_LIST) include_directories (/home/tom/文档/example_test1/ltkcpp/include) find_library(LIBLTKCPP libltkcpp_x86_64.a /home/tom/文档/example_test1/ltkcpp/lib) find_library(LIBLTKCPP_IMPINJ libltkcppimpinj_x86_64.a /home/tom/文档/example_test1/ltkcpp/lib) # it is not recommended to statically link for ssl and crypto libraries find_library(LIBSSL ssl REQUIRED) find_library(LIBCRYPTO crypto REQUIRED) # The ETK does not contain a host static library for xml2. Add the generic # name 'xml2' to link against the dynmaic library when compiling for host. find_library(LIBXML2 NAMES libxml2.a xml2 REQUIRED) set(LIBS ${LIBLTKCPP} ${LIBLTKCPP_IMPINJ} ${LIBCRYPTO} ${LIBSSL} ${LIBXML2} ) add_executable(main ${SRC_LIST}) target_link_libraries(main PRIVATE ${LIBS})
aux_source_directory (< dir > < variable >) 搜集所有在指定路径下的源文件的文件名,将输出结果列表储存在指定的变量中。