CMake - 讓人頭痛的止痛藥

CMake 編譯

MinGW + Sublime Text 簡(jiǎn)單編譯
CMake with Sublime Text

Why CMake缘圈?

先回答上面的問(wèn)題:被逼的劣光!這三個(gè)字是認(rèn)真的。

不管 CMake - Cross platform Make 是否是一個(gè)優(yōu)秀的構(gòu)建工具糟把,不管你是否認(rèn)同 CMake绢涡,都無(wú)法否認(rèn) CMake 目前是 C++ 的 defacto build system。

參考代碼倉(cāng)庫(kù)示例:

Sublime + MinGW + CMake 打造自動(dòng)化 C++ 工程

CMake 是跨平臺(tái)編譯工具遣疯,比 make 更為高級(jí)雄可,通過(guò)編寫(xiě) CMakeLists.txt 文件,然后用 cmake 命令將其轉(zhuǎn)化為 make 所需要的 makefile 文件缠犀,最后用 make -G 命令生成指定編譯平臺(tái)的腳本或工程文件数苫。

目前 CMake 已經(jīng)支持 Ninja、GCC 等編譯平臺(tái)辨液,同時(shí)也支持生成 Visual Studio虐急、 Xcode、CodeBlocks滔迈、Sublime Text 等 IDE 的工程文件止吁。支持 cmake 和 cmake-gui 兩種工作方式。

cmake path_to_cmakelists.txt -G "Sublime Text 2 - MinGW Makefiles"

目前已存在多種 Make 工具燎悍,GNU Make 敬惦,QT 的 qmake ,微軟的 nmake间涵,BSD Make仁热,Makepp 等等。這些 Make 工具遵循著不同的規(guī)范和標(biāo)準(zhǔn)勾哩,所執(zhí)行的 Makefile 格式也千差萬(wàn)別抗蠢。如果使用上面的 Make 工具,就得為每一種標(biāo)準(zhǔn)寫(xiě)一次 Makefile思劳,這將是一件讓人抓狂的工作迅矛。而 CMake 就是為了解決這種工作而開(kāi)發(fā)出來(lái)讓人抓狂的工具!

cmake 命令提供了相關(guān)的文檔潜叛,可以使用命令打印到文件中秽褒。例如壶硅,以下命令會(huì)將所有 CMake 的模塊文檔保存到 cmake_modules.rst 文件中:

>cmake --help-modules cmake_modules.rst

reStructuredText 這種文件可以理解為是 Markdown 文件的精簡(jiǎn)版。

CMake 目前支持的編譯系統(tǒng):

  • AppleClang: Apple Clang for Xcode versions 4.4+.
  • Clang: Clang compiler versions 2.9+.
  • GNU: GNU compiler versions 4.4+.
  • MSVC: Microsoft Visual Studio versions 2010+.
  • SunPro: Oracle SolarisStudio versions 12.4+.
  • Intel: Intel compiler versions 12.1+.
  • NVIDIA CUDA: NVIDIA nvcc compiler 7.5+.

CMake 提供 5 個(gè)工具:

  • Command-Line Tools

    • cmake 生成編譯腳本
    • ctest 運(yùn)行測(cè)試如 ctest -R Qt -j8
    • cpack 打包安裝程序
  • Interactive Dialogs

    • cmake-gui 圖形界面的 cmake
    • ccmake CMake curses interface

在當(dāng)前目標(biāo)下執(zhí)行 cmake path_to_cmakelists_txt 命令销斟,就會(huì)根據(jù)指定的列表文件生成編譯腳本庐椒,也可以直接在源代碼目錄中執(zhí)行這個(gè)命令,除非列表文件指定了禁止在源目錄生成蚂踊。當(dāng)前目錄和指定的 CMakeLists.txt 所在的目錄是就 path-to-build 和 path-to-source 也對(duì)應(yīng) cmake-gui 兩個(gè)目錄约谈。

CMake 強(qiáng)大的功能按以下類別進(jìn)行劃分,這也是主要的學(xué)習(xí)內(nèi)容:

命令分類 功能描述
cmake-buildsystem 構(gòu)建系統(tǒng)犁钟,高邏輯級(jí)別上定義構(gòu)建目標(biāo)棱诱,生成執(zhí)行文件、庫(kù)文件輸出等
cmake-commands 重點(diǎn)內(nèi)容涝动,各種命令功能支持迈勋,分成 Scripting、Project醋粟、CTest 三類
cmake-compile-features 為各種編譯器提供參數(shù)或設(shè)置
cmake-developer CMake 擴(kuò)展開(kāi)發(fā)支持靡菇,如編寫(xiě) Find Module 腳本
cmake-env-variables 環(huán)境變量讀寫(xiě)支持
cmake-file-api 提供文件 API 訪問(wèn) <build>/.cmake/api/
cmake-generator-expressions 表達(dá)式生成器,在腳本運(yùn)行過(guò)程中生成個(gè)表達(dá)式的值
cmake-generators CMake 生成器昔穴,即 -G 指定生成的 Makefile 類型
cmake-language CMake 腳本語(yǔ)言
cmake-modules CMake 提供了一系列的模塊镰官,如 FindPNG 找圖像庫(kù),還有 FindPHP4 等等
cmake-packages 依賴模塊功能支持吗货,如查找依賴模塊 find_package
cmake-policies 考慮后向兼容,不同版本的 CMake 可按指定策略運(yùn)行編譯腳本
cmake-properties 編譯腳本屬性支持狈网,如 INCLUDE_DIRECTORIES 屬性包含頭文件的目錄列表
cmake-qt CMake 提供 Qt 4 和 Qt 5 庫(kù)支持
cmake-server 棄用宙搬,使用 cmake-file-api 替代
cmake-toolchains 工具鏈接支持,如使用語(yǔ)言設(shè)置拓哺、平臺(tái)交叉編譯等
cmake-variables CMake 提供的變量支持非常豐富勇垛,內(nèi)置的變量按編譯工具、平臺(tái)等分成多類
cpack-generators 打包生成器士鸥,Archive闲孤、NSIS、NuGet烤礁、RPM讼积、WIX 等等

以下是和當(dāng)前工程有關(guān)的變量:

<PROJECT-NAME>_BINARY_DIR
<PROJECT-NAME>_DESCRIPTION
<PROJECT-NAME>_HOMEPAGE_URL
<PROJECT-NAME>_SOURCE_DIR
<PROJECT-NAME>_VERSION
<PROJECT-NAME>_VERSION_MAJOR
<PROJECT-NAME>_VERSION_MINOR
<PROJECT-NAME>_VERSION_PATCH
<PROJECT-NAME>_VERSION_TWEAK
PROJECT_BINARY_DIR
PROJECT_DESCRIPTION
PROJECT_HOMEPAGE_URL
PROJECT_NAME
PROJECT_SOURCE_DIR
PROJECT_VERSION
PROJECT_VERSION_MAJOR
PROJECT_VERSION_MINOR
PROJECT_VERSION_PATCH
PROJECT_VERSION_TWEAK

因此 CMake 的編譯基本步驟如下:

  • 在當(dāng)前目錄為 cmake 配置 CMakeLists.txt;
  • 在當(dāng)前目錄執(zhí)行 cmake . 命令生成 make 使用的 makefile;
  • 執(zhí)行 make 進(jìn)行編譯;

如果編譯軟件使用了外部庫(kù)脚仔,事先并不知道它的頭文件和鏈接庫(kù)的位置勤众。CMake 使用 find_package 命令來(lái)查找它們的路徑,然后在編譯命令中加上包含它們的路徑:

FIND_PACKAGE( <name> [version] [EXACT] [QUIET] [NO_MODULE] [ [ REQUIRED | COMPONENTS ] [ componets... ] ] )

如:

FIND_PACKAGE( name REQUIRED)

CMake 解決項(xiàng)目的依賴時(shí)鲤脏,會(huì)自動(dòng)查找那些已知的軟件通常會(huì)保存的目錄路徑们颜,如果找不到再通過(guò)依賴包的 Config-file 來(lái)查找吕朵。這條命令執(zhí)行后,CMake 會(huì)到變量 CMAKE_MODULE_PATH 指示的目錄中查找文件 Find<name>.cmake 并執(zhí)行窥突,然后這個(gè)腳本返回 <name>affe_FOUND努溃、<name>_INCLUDE_DIRS<name>_LIBRARIES 這些變量阻问,如查找 Caffe:

find_package(Caffe REQUIRED)

if (NOT Caffe_FOUND)
    message(FATAL_ERROR "Caffe Not Found!")
endif (NOT Caffe_FOUND)

include_directories(${Caffe_INCLUDE_DIRS})

add_executable(demo ssd_detect.cpp)
target_link_libraries(demo ${Caffe_LIBRARIES})

首先明確一點(diǎn)茅坛,cmake 本身不提供任何搜索庫(kù)的便捷方法,比如下面將要提到的 FindXXX.cmake 和 XXXConfig.cmake则拷,庫(kù)的作者通常會(huì)提供這兩個(gè)文件贡蓖,以方便使用者調(diào)用。

find_package 采用兩種模式搜索庫(kù):

  • Module 模式:搜索 CMAKE_MODULE_PATH 指定路徑下的 FindXXX.cmake 文件煌茬,執(zhí)行該文件斥铺,由它找到 XXX 庫(kù),并賦值給 XXX_INCLUDE_DIRS坛善、XXX_LIBRARIES 兩個(gè)變量晾蜘。

  • Config 模式:搜索 XXX_DIR 指定路徑下的 XXXConfig.cmake 文件,執(zhí)行該文件從而找到 XXX 庫(kù)眠屎,并給 XXX_INCLUDE_DIRS剔交、XXX_LIBRARIES 兩個(gè)變量賦值。

兩種模式看起來(lái)似乎差不多改衩,不過(guò) cmake 默認(rèn)采取 Module 模式岖常,如果 Module 模式未找到庫(kù),才會(huì)采取 Config 模式葫督。如果 XXX_DIR 路徑下找不到 XXXConfig.cmake 文件竭鞍,則會(huì)找 /usr/local/lib/cmake/XXX/ 中的 XXXConfig.cmake 文件¢暇担總之偎快,Config 模式是一個(gè)備選策略。通常洽胶,庫(kù)安裝時(shí)會(huì)拷貝一份 XXXConfig.cmake 到系統(tǒng)目錄中晒夹,因此在沒(méi)有顯式指定搜索路徑時(shí)也可以順利找到。

以下是一個(gè) reStructuredText 格式展示的 Find Module 編寫(xiě)格式示范姊氓,具體參考 cmake-developer 文檔:

# Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
# file Copyright.txt or https://cmake.org/licensing for details.

#[=======================================================================[.rst:
FindFoo
-------

Finds the Foo library.

Imported Targets
^^^^^^^^^^^^^^^^

This module provides the following imported targets, if found:

``Foo::Foo``
  The Foo library

Result Variables
^^^^^^^^^^^^^^^^

This will define the following variables:

``Foo_FOUND``
  True if the system has the Foo library.
``Foo_VERSION``
  The version of the Foo library which was found.
``Foo_INCLUDE_DIRS``
  Include directories needed to use Foo.
``Foo_LIBRARIES``
  Libraries needed to link to Foo.

Cache Variables
^^^^^^^^^^^^^^^

The following cache variables may also be set:

``Foo_INCLUDE_DIR``
  The directory containing ``foo.h``.
``Foo_LIBRARY``
  The path to the Foo library.

#]=======================================================================]

每個(gè)構(gòu)建腳本都奔構(gòu)建目標(biāo)來(lái)的丐怯,生成可執(zhí)行文件或是類庫(kù),如果是類庫(kù)他膳,那么可以指定靜態(tài) STATIC 或動(dòng)態(tài) SHARED 等:

add_library(archive archive.cpp zip.cpp lzma.cpp)
add_library(archive SHARED archive.cpp zip.cpp lzma.cpp)
add_library(archive STATIC archive.cpp zip.cpp lzma.cpp)
add_library(archive OBJECT archive.cpp zip.cpp lzma.cpp)

add_executable(zipapp zipapp.cpp)
target_link_libraries(zipapp archive)

生成共享庫(kù)的 add_library 命令格式如下:

add_library(libname [SHARED|STATIC|MODULE][EXCLUDE_FROM_ALL]source1 source2 ... sourceN)
  • SHARED 動(dòng)態(tài)庫(kù)(擴(kuò)展名為.so)
  • STATIC 靜態(tài)庫(kù)(擴(kuò)展名為.a)
  • MODULE 在使用 dyld 的系統(tǒng)有效响逢,如果不支持 dyld,則被當(dāng)作 SHARED 對(duì)待棕孙。
  • EXCLUDE_FROM_ALL 參數(shù)的意思是這個(gè)庫(kù)不會(huì)被默認(rèn)構(gòu)建舔亭,除非有其他的組件依賴或者手工構(gòu)建些膨。

CMake 會(huì)根據(jù)的生成庫(kù)的設(shè)置,為編譯鏈接程序提供和種鏈接方式:

set(CMAKE_EXE_LINKER_FLAGS "-static")

| 連接方式 | 連接選項(xiàng) | 優(yōu)缺點(diǎn) |
| 全靜態(tài) | -static -pthread -lrt -ldl | 生成的文件比較大钦铺,沒(méi)有運(yùn)行依賴订雾。|
| 全動(dòng)態(tài) | -pthread -lrt -ldl | 生成文件最小,并且可能有依賴不兼容問(wèn)題矛洞。 |
| 半靜態(tài) | -static-libgcc -L. -pthread -lrt -ldl | 靈活度大洼哎,結(jié)合了全靜態(tài)與全動(dòng)態(tài)兩種鏈接方式的優(yōu)點(diǎn)。 |

CMake 屬性和命令有些名字區(qū)別不大沼本,通常用大小寫(xiě)區(qū)別開(kāi)來(lái)噩峦,例如以下兩個(gè)屬性包含的是目錄列表:

  • INCLUDE_DIRECTORIES 包含頭文件目錄列表,供預(yù)處理程序搜索頭文件
  • LINK_DIRECTORIES 屬性包含目錄列表抽兆,包含鏈接階段使用的共享庫(kù)识补、模塊等

相關(guān)的命令 target_include_directories 為編譯的目標(biāo)提供頭文件目錄,指定的目標(biāo)必須已經(jīng)使用 add_executable()add_library() 定義:

target_include_directories(mylib PUBLIC
    $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include/mylib>
    $<INSTALL_INTERFACE:include/mylib>  # <prefix>/include/mylib
)

這個(gè)命令會(huì)將設(shè)置的目錄賦值給 INCLUDE_DIRECTORIES 屬性辫红,也可以使用 set_property() 命令來(lái)設(shè)置屬性凭涂。

還有兩條和鏈接庫(kù)目錄有關(guān)的命令:

link_directories([AFTER|BEFORE] directory1 [directory2 ...])

target_link_directories(<target> [BEFORE]
  <INTERFACE|PUBLIC|PRIVATE> [items1...]
  [<INTERFACE|PUBLIC|PRIVATE> [items2...] ...])

兩者的差別就在于 target_link_directories 只為指定的編譯目標(biāo)提供鏈接庫(kù)目錄,供鏈接程序查找依賴文件贴妻。

以下類似的命令用于指定鏈接過(guò)程使用的依賴共享庫(kù)的鏈接:

link_libraries([item1 [item2 [...]]]
               [[debug|optimized|general] <item>] ...)
target_link_libraries(<target> ... <item>... ...)

類似地切油,target 前綴的命令表示只為指定的編譯目標(biāo)提供鏈接庫(kù),而且這個(gè)目標(biāo)要已經(jīng)使用 add_executable()add_library() 定義名惩。

庫(kù)文件或可以執(zhí)行文件生成后就可以執(zhí)行安裝命令澎胡,將其拷貝到指定的位置:

install(TARGETS Tutorial DESTINATION bin)

按 CMake 教程,一般 CMakeList.txt 編寫(xiě)流程:

# (Step 1) ==========================================
# A Basic Starting Point
# - Adding a Version Number and Configured Header File
# - Specify the C++ Standard

cmake_minimum_required( VERSION 2.8 )
project(Tutorial VERSION 1.0)

set(CMAKE_CXX_FLAGS "-std=c++11" )
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED True)
?
# (Step 2) ==========================================
# Adding a Library

add_library(MathFunctions mysqrt.cxx)
# add the MathFunctions library
add_subdirectory(MathFunctions)

# add the executable
add_executable(Tutorial tutorial.cxx)

# (Step 3) ==========================================
# Adding Usage Requirements for Library

# target_compile_definitions()
# target_compile_options()

target_link_libraries(Tutorial PUBLIC MathFunctions)

# add the binary tree to the search path for include files
# so that we will find TutorialConfig.h
target_include_directories(Tutorial PUBLIC
                          "${PROJECT_BINARY_DIR}"
                          "${PROJECT_SOURCE_DIR}/MathFunctions"
                          )

find_package(OpenCV REQUIRED)
# If the package has been found, several variables will
# be set, you can find the full list with descriptions
# in the OpenCVConfig.cmake file.
# Print some message showing some of them
message(STATUS "OpenCV library status:")
message(STATUS "    version: ${OpenCV_VERSION}")
message(STATUS "    libraries: ${OpenCV_LIBS}")
message(STATUS "    include path: ${OpenCV_INCLUDE_DIRS}")

# (Step 4) ==========================================
# Installing and Testing
# - Install Rules
# - Testing Support

install(TARGETS Tutorial DESTINATION bin)
install(FILES "${PROJECT_BINARY_DIR}/TutorialConfig.h"
  DESTINATION include
  )

# (Step 5) ==========================================
# Adding System Introspection
# - Specify Compile Definition

# (Step 6) ==========================================
# Adding a Custom Command and Generated File

add_executable(MakeTable MakeTable.cxx)
add_custom_command(
  OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/Table.h
  COMMAND MakeTable ${CMAKE_CURRENT_BINARY_DIR}/Table.h
  DEPENDS MakeTable
  )

# (Step 7) ==========================================
# Building an Installer

include(InstallRequiredSystemLibraries)
set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/License.txt")
set(CPACK_PACKAGE_VERSION_MAJOR "${Tutorial_VERSION_MAJOR}")
set(CPACK_PACKAGE_VERSION_MINOR "${Tutorial_VERSION_MINOR}")
include(CPack)

# (Step 10) ==========================================
# Adding Generator Expressions

add_library(tutorial_compiler_flags INTERFACE)
target_compile_features(tutorial_compiler_flags INTERFACE cxx_std_11)

set(gcc_like_cxx "$<COMPILE_LANG_AND_ID:CXX,ARMClang,AppleClang,Clang,GNU>")
set(msvc_cxx "$<COMPILE_LANG_AND_ID:CXX,MSVC>")
target_compile_options(tutorial_compiler_flags INTERFACE
  "$<${gcc_like_cxx}:$<BUILD_INTERFACE:-Wall;-Wextra;-Wshadow;-Wformat=2;-Wunused>>"
  "$<${msvc_cxx}:$<BUILD_INTERFACE:-W3>>"
)

# (Step 11) ==========================================
# Adding Export Configuration
        
install(TARGETS MathFunctions tutorial_compiler_flags
        DESTINATION lib
        EXPORT MathFunctionsTargets)
install(FILES MathFunctions.h DESTINATION include)

# (Step 12) ==========================================
# Packaging Debug and Release

set(CMAKE_DEBUG_POSTFIX d)
add_library(tutorial_compiler_flags INTERFACE)

add_executable(Tutorial tutorial.cxx)
set_target_properties(Tutorial PROPERTIES DEBUG_POSTFIX ${CMAKE_DEBUG_POSTFIX})

target_link_libraries(Tutorial PUBLIC MathFunctions)

set_property(TARGET MathFunctions PROPERTY VERSION "1.0.0")
set_property(TARGET MathFunctions PROPERTY SOVERSION "1")

Mixing Static and Shared

cmake_minimum_required(VERSION 3.10)

# set the project name and version
project(Tutorial VERSION 1.0)

# specify the C++ standard
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED True)

# control where the static and shared libraries are built so that on windows
# we don't need to tinker with the path to run the executable
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}")
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}")
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}")

option(BUILD_SHARED_LIBS "Build using shared libraries" ON)

# configure a header file to pass the version number only
configure_file(TutorialConfig.h.in TutorialConfig.h)

# add the MathFunctions library
add_subdirectory(MathFunctions)

# add the executable
add_executable(Tutorial tutorial.cxx)
target_link_libraries(Tutorial PUBLIC MathFunctions)

實(shí)際使用中绢片,CMake 提供豐富的功能滤馍,列如:

include_directories(
    ${PROJECT_SOURCE_DIR}/../include/mq 
    ${PROJECT_SOURCE_DIR}/../include/incl 
    ${PROJECT_SOURCE_DIR}/../include/rapidjson
)
target_include_directories(${PROJECT_NAME} )

# 它相當(dāng)于 g++ -L 選項(xiàng)的作用,也相當(dāng)于環(huán)境變量中增加 LD_LIBRARY_PATH
link_directories(directory1 directory2 ...)
link_directories("/home/server/third/lib")

# 設(shè)定 SRC 變量底循,將源代碼路徑統(tǒng)一管理
set(SRC 
    ${PROJECT_SOURCE_DIR}/../include/incl/a.cpp 
    ${PROJECT_SOURCE_DIR}/../include/mq/b.cpp 
    ${PROJECT_SOURCE_DIR}/c.cpp
)
?
# 創(chuàng)建共享庫(kù)/靜態(tài)庫(kù)或引用庫(kù) add_library
?
# 設(shè)置生成共享庫(kù)的路徑
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/lib)
 
# 成的共享庫(kù)文件就叫做 lib[LIB_NAME].so
set(LIB_NAME freetype)

add_library(${LIB_NAME} SHARED ${SRC})
add_library(${LIB_NAME} STATIC ${SRC})

# 把 ${LIB_NAME} 庫(kù)和其它依賴的庫(kù)鏈接起來(lái)
# 以 libpthread.so 為例,這個(gè)命令相當(dāng) -lpthread
target_link_libraries(${LIB_NAME} pthread dl)
   
# 可執(zhí)行文件生成
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/bin)
add_executable(${PROJECT_NAME} ${SRC})
# 可執(zhí)行文件所依賴的庫(kù)
target_link_libraries(${PROJECT_NAME} pthread dl ${LIB_NAME})

訪問(wèn)環(huán)境變量槐瑞,讀取環(huán)境變量使用 $ENV{JAVA_HOME} 這樣的格式熙涤,寫(xiě)入環(huán)境變量使用 set:

set( ENV{PATH} /home/martink )

if(NOT DEFINED ENV{JAVA_HOME})
    message(FATAL_ERROR "not defined environment variable:JAVA_HOME")  
endif()
#不能用if(ENV{JAVA_HOME})形式來(lái)判斷是否定義 
#但可以用if($ENV{JAVA_HOME})

總結(jié)一下,就可以看出來(lái)困檩,讀取環(huán)境變量時(shí)要在ENV前加符號(hào)祠挫,而寫(xiě)和if判斷是否定義時(shí),ENV{JAVA_HOME}指代變量名所以不加符號(hào)。

使用 C++ 11 標(biāo)準(zhǔn)悼沿,可以通過(guò)不同方式設(shè)置:

# 設(shè)置C++標(biāo)準(zhǔn)為 C++ 11
set(CMAKE_CXX_STANDARD 11)

# 檢查c++編譯器標(biāo)志等舔,設(shè)置c++11支持變量
include(CheckCXXCompilerFlag)
CHECK_CXX_COMPILER_FLAG("-std=c++11" COMPILER_SUPPORTS_CXX11)
CHECK_CXX_COMPILER_FLAG("-std=c++0x" COMPILER_SUPPORTS_CXX0X)

# 使用變量設(shè)置編譯標(biāo)志
if(COMPILER_SUPPORTS_CXX11)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
elseif(COMPILER_SUPPORTS_CXX0X)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++0x")
else()
message(STATUS "The compiler ${CMAKE_CXX_COMPILER} has no C++11 support. Please use a different C++ compiler.")
endif()

CMake Ctest

Demo目錄結(jié)構(gòu)如下:

Test/
├── add.cpp
└── CMakeLists.txt

add.cpp

#include <iostream>
#include <stdlib.h>
int main(int argc, char *argv[])
{
    if (argc != 3) {
        std::cout << "parameter error" << std::endl;
        return -1; 
    }   
 
    int a, b;
    a = atoi(argv[1]);
    b = atoi(argv[2]);
    std::cout << a << " + " << b << " is " << a + b << std::endl;
    return 0;
}

CMakeLists.txt

CMAKE_MINIMUM_REQUIRED(VERSION 3.3)
ADD_EXECUTABLE(add add.cpp)
enable_testing()
ADD_TEST(NAME test_add_2_3 COMMAND add 2 3)
SET_TESTS_PROPERTIES(test_add_2_3
    PROPERTIES PASS_REGULAR_EXPRESSION "5")
ADD_TEST(NAME test_add_4_5 COMMAND add 4 5)
SET_TESTS_PROPERTIES(test_add_4_5
    PROPERTIES PASS_REGULAR_EXPRESSION "9")

在 CMakeLists.txt 里面,我們添加了兩個(gè)測(cè)試用例糟趾。其中 PASS_REGULAR_EXPRESSION 用來(lái)測(cè)試輸出是否包含后面的字符串慌植。

在 Test 目錄下建立 build 目錄:

cd build && cmake .., make, make test

像上面的方式寫(xiě)測(cè)試用例還是比較繁瑣甚牲,還可以定義宏來(lái)簡(jiǎn)化:

CMAKE_MINIMUM_REQUIRED(VERSION 3.3)
ADD_EXECUTABLE(add add.cpp)
enable_testing()
 
macro(do_test ARG1 ARG2 RESULT)
    ADD_TEST(NAME test_add_${ARG1}_${ARG2} COMMAND add ${ARG1} ${ARG2})
    SET_TESTS_PROPERTIES(test_add_${ARG1}_${ARG2}
        PROPERTIES PASS_REGULAR_EXPRESSION ${RESULT})
endmacro(do_test)
do_test(2 3 5)
do_test(4 5 9)

配合 CPPUNIT 使用如下:

#include <cppunit/TestResult.h>
#include <cppunit/TestResultCollector.h>
#include <cppunit/TextOutputter.h>
#include <cppunit/TestRunner.h>
#include <cppunit/extensions/HelperMacros.h>
 
class StringTest : public CppUnit::TestFixture
{
    CPPUNIT_TEST_SUITE(StringTest);
    CPPUNIT_TEST(testSwap);
    CPPUNIT_TEST(testFind);
    CPPUNIT_TEST_SUITE_END();
public:
    void setUp()
    {   
        m_str1 = "Hello, world";
        m_str2 = "Hi, cppunit";
    }   
    void tearDown()
    {   
 
    }   
    void testSwap()
    {   
        std::string str1 = m_str1;
        std::string str2 = m_str2;
        m_str1.swap(m_str2);
        CPPUNIT_ASSERT(m_str1 == str2);
        CPPUNIT_ASSERT(m_str2 == str2);
    }   
    void testFind()
    {   
        int pos1 = m_str1.find(',');
        int pos2 = m_str2.rfind(',');
        CPPUNIT_ASSERT_EQUAL(5, pos1);
        CPPUNIT_ASSERT_EQUAL(2, pos2);
    }   
protected:
    std::string m_str1;
    std::string m_str2;
};
 
CPPUNIT_TEST_SUITE_REGISTRATION(StringTest);
int main(int argc, char *argv[])
{
    CppUnit::TestResult r;
    CppUnit::TestResultCollector rc;
    r.addListener(&rc);
 
    CppUnit::TestRunner runner;
    runner.addTest(CppUnit::TestFactoryRegistry::getRegistry().makeTest());
    runner.run(r);
 
    CppUnit::TextOutputter o(&rc, std::cout);
    o.write();
 
    return rc.wasSuccessful()?0:-1;
}

測(cè)試是軟件開(kāi)發(fā)過(guò)程中極其重要的一環(huán),詳盡周密的測(cè)試能夠減少軟件BUG蝶柿,提高軟件品質(zhì)丈钙。測(cè)試包括單元測(cè)試、系統(tǒng)測(cè)試等交汤。其中單元測(cè)試是指針對(duì)軟件功能單元所作的測(cè)試雏赦,這里的功能單元可以是一個(gè)類的屬性或者方法,測(cè)試的目的是看這些基本單元是否工作正常芙扎。由于單元測(cè)試的內(nèi)容很基礎(chǔ)星岗,因此可以看作是測(cè)試工作的第一環(huán),該項(xiàng)工作一般由開(kāi)發(fā)人員自行完成戒洼。如果條件允許俏橘,單元測(cè)試代碼的開(kāi)發(fā)應(yīng)與程序代碼的開(kāi)發(fā)同步進(jìn)行。

雖然不同程序的單元測(cè)試代碼不盡相同施逾,但測(cè)試代碼的框架卻非常相似敷矫,于是便出現(xiàn)了一些單元測(cè)試類庫(kù),CppUnit便是其中之一汉额。

CppUnit 是 XUnit 中的一員曹仗,XUnit 是一個(gè)大家族,還包括 JUnit 和 PythonUnit 等蠕搜。CppUnit 簡(jiǎn)單實(shí)用怎茫,學(xué)習(xí)和使用起來(lái)都很方便,網(wǎng)上已有一些文章對(duì)其作介紹妓灌,但本文更著重于講解其中的基本概念和使用方法轨蛤,以幫助初次接觸CppUnit的人員快速入門(mén)。

CMake OpenCV

使用 OpenCV 創(chuàng)建一個(gè)簡(jiǎn)單的程序 DisplayImage.cpp虫埂,如下所示祥山。

#include <stdio.h>
#include <opencv2/opencv.hpp>

using namespace cv;

int main(int argc, char** argv )
{
    if ( argc != 2 )
    {
        printf("usage: DisplayImage.out <Image_Path>\n");
        return -1;
    }
    Mat image;
    image = imread( argv[1], 1 );
    if ( !image.data )
    {
        printf("No image data \n");
        return -1;
    }
    namedWindow("Display Image", WINDOW_AUTOSIZE );
    imshow("Display Image", image);
    waitKey(0);
    return 0;
}

為 CMake 命令創(chuàng)建一個(gè) CMakeLists.txt 文件:

cmake_minimum_required(VERSION 2.8)
project( DisplayImage )
# find_package( OpenCV REQUIRED )

include_directories(c:/download/OpenCV/opencv/build/include/)
link_directories(
    "c:/download/OpenCV/opencv/build/x64/vc15/lib/"
)

set(BUILD_SHARED_LIBS OFF)
set(OpenCV_LIBS 
    opencv_calib3d430
    opencv_core430
    opencv_dnn430
    opencv_features2d430
    opencv_flann430
    opencv_gapi430
    opencv_highgui430
    opencv_imgcodecs430
    opencv_imgproc430
    opencv_ml430
    opencv_objdetect430
    opencv_photo430
    opencv_python3
    opencv_stitching430
    opencv_ts430
    opencv_video430
    opencv_videoio430
    opencv_world430
    opencv_world430d
)
link_libraries( ${OpenCV_LIBS} )
add_executable( DisplayImage display.cpp )
target_link_libraries( DisplayImage ${OpenCV_LIBS} )

使用 CMake 生成可執(zhí)行文件:

cd <DisplayImage_directory>
cmake .
make

或者:

cmake --build .

在 Windows 平臺(tái)下和 MinGW 編譯器一起工作,指定生成 Makefile:

mkdir -p cmake-build && cd cmake-build
cmake .. -G"Unix Makefiles"

注意掉伏,不同編譯的器連接庫(kù)是沒(méi)辦法通過(guò)的缝呕,甚至同一套編譯器不同版本編譯出來(lái)的動(dòng)態(tài)鏈接庫(kù)也不能通用。所以要使用同版本的 MinGW 編譯出來(lái)鏈接庫(kù)斧散,除了使用 CMke 這個(gè)被逼著使用的東西供常,在 GCC 中可以選擇更通用的 GUN make。也可以像我一樣直接擼命令鸡捐,以下是 Sublime 下使用的編譯配置文件栈暇,直接保存到 Preferences - Browser Packages - User 目錄下,命名就取 MinGW.sublime-build箍镜,sublime 會(huì)自動(dòng)讀取這個(gè)編譯配置文件,使用快捷鍵 Ctrl-B 就可以調(diào)出編譯命令:

{
    "env": {
        "PATH":"C:/MinGW-OpenCV-4.1.1-x64/x64/mingw/bin;%PATH%",
        "inc":"-IC:/MinGW-OpenCV-4.1.1-x64/include",
        "libpath":"-LC:/MinGW-OpenCV-4.1.1-x64/x64/mingw/lib",
        "libs":"-lopencv_calib3d411 -lopencv_core411 -lopencv_dnn411 -lopencv_features2d411 -lopencv_flann411 -lopencv_gapi411 -lopencv_highgui411 -lopencv_imgcodecs411 -lopencv_imgproc411 -lopencv_ml411 -lopencv_objdetect411 -lopencv_photo411 -lopencv_stitching411 -lopencv_video411 -lopencv_videoio411",
        "cc":"g++.exe -Wall -Wno-unused-variable"
    },
    "shell_cmd": "ECHO MinGW GCC 8.1.0 Compiling $file_name ... && %cc% -g -std=c++11 %inc% -c \"$file\" -o $file_base_name.o && g++.exe %libpath% -o ${file_base_name}.exe ${file_base_name}.o %libs% && echo done.",
    "file_regex": "^(...*?):([0-9]*):?([0-9]*)",
    "working_dir": "${file_path}",
    "selector": "source.c++",
    "encoding":"gbk",
    "quiet": true,
    "variants":[
        {
            "name":"(-std=c++17)",
            "shell_cmd":"ECHO Compiling (-std=c++17) $file_name ... && %cc% -g -std=c++17 -c \"$file\" -o $file_base_name.o && g++.exe -o ${file_base_name}.exe ${file_base_name}.o && ECHO Start run $file_name ... && ${file_base_name} "
        },
        {
            "name":"(-std=c++14)",
            "shell_cmd":"ECHO Compiling (-std=c++14) $file_name ... && %cc% -g -std=c++14 -c \"$file\" -o $file_base_name.o && g++.exe -o ${file_base_name}.exe ${file_base_name}.o && ECHO Start run $file_name ... && ${file_base_name} "
        },
        {
            "name":"(-std=c++11)",
            "shell_cmd":"ECHO Compiling (-std=c++11) $file_name ... && %cc% -g -std=c++11 -c \"$file\" -o $file_base_name.o && g++.exe -o ${file_base_name}.exe ${file_base_name}.o && ECHO Start run $file_name ... && ${file_base_name} "
        },
        {
            "name":"(-std=c++11) with ENV",
            "shell_cmd":"ECHO Compiling (-std=c++11) $file_name ... && %cc% -g -std=c++11 %inc% -c \"$file\" -o $file_base_name.o && g++.exe %libpath% -o ${file_base_name}.exe ${file_base_name}.o %libs% && ECHO Start run $file_name ... && ${file_base_name} "
        },
        {
            "name":"(-std=c++11) Release with ENV",
            "shell_cmd":"ECHO Compiling (-std=c++11) $file_name ... && %cc% -DNDEBUG -std=c++11 %inc% -c \"$file\" -o $file_base_name.o && g++.exe %libpath% -o ${file_base_name}.exe ${file_base_name}.o %libs% && ECHO Start run $file_name ... && ${file_base_name} "
        }
    }
}

配置中加入 PATH 的路徑是為了運(yùn)行編譯出來(lái)的程序能找到 OpenCV 的 DLL 文件。另外注意喉刘,GCC 中的 ld 鏈接程序默認(rèn)會(huì)自動(dòng)查找引用引用庫(kù)目錄中 .lib 擴(kuò)展名的文件。如果薪夕,編譯 OpenCV 生成的文件是 .dll.a 這樣古怪的名字,那么就找不到了赫悄。

在 Windows 和 Linux 系統(tǒng)上原献,程序的編譯鏈接都有動(dòng)態(tài)和靜態(tài)兩種方式,動(dòng)態(tài)鏈接 .dll 文件和 .so 文件是在程序執(zhí)行時(shí)使用的埂淮,而 .lib 引用庫(kù)文件是在程序編譯階段用來(lái)定位符號(hào)用的姑隅。如何是靜態(tài)鏈接,會(huì)使用到 .a 靜態(tài)鏈接庫(kù)倔撞,靜態(tài)鏈接生成的程序文件運(yùn)行時(shí)就不需要依賴動(dòng)態(tài)鏈接庫(kù)了讲仰。

一般來(lái)說(shuō) Linux 中的庫(kù)文件名還可以這樣 libQt5Widgets.a 在引用時(shí)只需要取 Qt5Widgets 這部分,ld 查找的目錄順序是 /var/lib -> /usr/lib -> LD_LIBRARY_PATH 環(huán)境變量指定的目錄 -> 命令行指定的 -LPATH_TO_LIB 目錄痪蝇。

如果遇到以下提示鄙陡,請(qǐng)不要傻傻地去設(shè)置環(huán)境變量,這可以是因?yàn)?MinGW 使用的是 mingw32-make.exe 導(dǎo)致 CMake 檢測(cè)不到躏啰,復(fù)制一份改名 make.exe:

CMake Error: CMAKE_C_COMPILER not set, after EnableLanguage
CMake Error: CMAKE_CXX_COMPILER not set, after EnableLanguage

編譯前趁矾,還可以將 MinGW 編譯好的 OpenCV 的頭文件和庫(kù)文件放到對(duì)應(yīng)的位置:

C:\MinGW\x86_64-w64-mingw32\include

現(xiàn)在你應(yīng)該有一個(gè)可執(zhí)行文件,但它需要依賴 OpenCV 的動(dòng)態(tài)鏈接庫(kù)给僵,指定可以訪問(wèn)到的一個(gè)路徑毫捣。運(yùn)行它給出一個(gè)圖像位置作為參數(shù),即:

set path=C:\OpenCV\build\bin
./DisplayImage lena.jpg
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末帝际,一起剝皮案震驚了整個(gè)濱河市蔓同,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌蹲诀,老刑警劉巖斑粱,帶你破解...
    沈念sama閱讀 218,122評(píng)論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異脯爪,居然都是意外死亡珊佣,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,070評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)披粟,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人冷冗,你說(shuō)我怎么就攤上這事守屉。” “怎么了蒿辙?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,491評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵拇泛,是天一觀的道長(zhǎng)滨巴。 經(jīng)常有香客問(wèn)我,道長(zhǎng)俺叭,這世上最難降的妖魔是什么恭取? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,636評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮熄守,結(jié)果婚禮上蜈垮,老公的妹妹穿的比我還像新娘。我一直安慰自己裕照,他們只是感情好攒发,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,676評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著晋南,像睡著了一般惠猿。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上负间,一...
    開(kāi)封第一講書(shū)人閱讀 51,541評(píng)論 1 305
  • 那天偶妖,我揣著相機(jī)與錄音,去河邊找鬼政溃。 笑死趾访,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的玩祟。 我是一名探鬼主播腹缩,決...
    沈念sama閱讀 40,292評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼空扎!你這毒婦竟也來(lái)了藏鹊?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,211評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤转锈,失蹤者是張志新(化名)和其女友劉穎盘寡,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體撮慨,經(jīng)...
    沈念sama閱讀 45,655評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡竿痰,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,846評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了砌溺。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片影涉。...
    茶點(diǎn)故事閱讀 39,965評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖规伐,靈堂內(nèi)的尸體忽然破棺而出蟹倾,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 35,684評(píng)論 5 347
  • 正文 年R本政府宣布鲜棠,位于F島的核電站肌厨,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏豁陆。R本人自食惡果不足惜柑爸,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,295評(píng)論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望盒音。 院中可真熱鬧表鳍,春花似錦、人聲如沸里逆。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,894評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)原押。三九已至胁镐,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間诸衔,已是汗流浹背盯漂。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,012評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留笨农,地道東北人就缆。 一個(gè)月前我還...
    沈念sama閱讀 48,126評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像谒亦,于是被迫代替她去往敵國(guó)和親竭宰。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,914評(píng)論 2 355