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,world\n";
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中引用相關文件(library\package)
我們首先需要了解以下概念。
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 >) 搜集所有在指定路徑下的源文件的文件名,將輸出結果列表儲存在指定的變量中。