CMake 編譯
- CMake Manual
- CMake Tutorial
- User Interaction Guide
- Using Dependencies Guide
- Ninja - small build system
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ù)示例:
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)。
使用 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