如何使用CMake構建c++項目


1. c++項目構建與CMake簡介

在Windows系統上我們通常使用Visual Studio(VS)來生成我們的c++項目。我們只需在VS相應的層次目錄中添加相應的文件即可,而不需要手動指定各個文件的具體路徑及依賴包含關系。

圖1. 微軟開源的Calculator源碼

在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

我們可以看到程序正常編譯與執行:

圖2. Ubuntu下執行g++命令

當然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:編譯中間文件,生成可執行文件。

圖3. 執行make命令
  • ./hello_cmake:運行可執行文件。

這樣,我們便使用CMake編譯與生成了第一個c++文件。到目前為止它與上文中使用到的g++的作用是一樣的。而當一個c++項目越復雜時,使用CMake的優勢也將越來越明顯。

3.2 創建build文件夾,不同文件分離與有序化

如果你運行了cmake . &&make這兩個命令,你就會發現在你的源文件所在的目錄下多出了很多其他的文件。這些文件是項目編譯中很重要的文件,但其實你可以完全不用去管他。我們只需要關注CMakeLists.txt源文件

上述問題的解決方法是在項目文件夾中創建一個build文件用於保存這些“無關緊要的編譯文件”——mkdir build

圖4. 創建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處理多個源文件的編譯

  1. 修改add_executable,添加多個源文件,例如:
add_executable(hello_cmake main.cpp src/Blah.cpp)
  1. 通過修改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)(共享庫)。二者的不同點在於代碼被載入的時刻不同。

  • 靜態庫的代碼在編譯過程中已經被載入可執行程序,因此體積比較大。

  • 動態庫(共享庫)的代碼在可執行程序運行時才載入內存,在編譯過程中僅簡單的引用,因此代碼體積比較小。

  1. 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) #鏈接庫文件
  1. 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>
  1. 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 >) 搜集所有在指定路徑下的源文件的文件名,將輸出結果列表儲存在指定的變量中。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM