命令
源文件收集
一般情況下,我們通過逐個列出的方式,設定源文件集(set(SRC_LIST src/main.c src/other.c)
)。這樣的好處是可以明確控制那些文件會被加入到工程中。 但是有些時候,如果源文件較多,一個一個列出來的話可能會有些麻煩,對於這種情況,可以使用CMake提供的file
命令來自動收集源文件。使用方法如下。
file(GLOB_RECURSE SRC_CORE src/*.c)
通過上面這行代碼,會生成一個源文件列表,每一項是一個源文件的全路徑。可以通過下面的代碼來剔除一些不想加入工程的源文件。
foreach(rm_file ${SRC_CORE})
string(REGEX MATCH ".*/filename1.c|.*/filename2.c" need_remove_file ${rm_file})
if(need_remove_file)
list(REMOVE_ITEM SRC_CORE ${need_remove_file})
endif(need_remove_file)
endforeach(rm_file)
上面使用了CMake的正則表達式,匹配兩個文件,分別爲.*/filename1.c
和.*filename2.c
,其中.*
代表了任意字符串,filename1.c
是文件名。
同一個工程同時生成靜態庫和動態庫
有了如標題所述的需求,在網上搜索了一下,發現大部分博客都是講通過設置OUTPUT_NAME
,使兩個工程(一個動態庫,一個靜態庫)輸出同名二進制文件。但是我不希望同樣的源碼,要創建兩個工程。 可以使用下面的方法爲使用一個工程同時生成動態庫和靜態庫。
# 添加控制選項
# BUILD_SHARED_LIBS 是CMake內置變量
# 針對WIN32可以
if(WIN32)
option(BUILD_SHARED_LIBS "Build Shared libs" ON)
option(BUILD_AS_DLL "Build as dll" ${BUILD_SHARED_LIBS})
endif()
add_library(libHello ${SRC_LIST})
target_link_libraries(libHello ${EXTRA_LIB})
if(BUILD_AS_DLL)
set_target_properties(libHello PROPERTIES COMPILE_DEFINITIONS BUILD_AS_DLL)
endif()
設置頭文件目錄和靜態庫
很多時候,我們會使用第三方庫,因此,我們要在工程裏面包含第三方庫的頭文件,鏈接第三方提供的靜態鏈接庫(如果有的話)。
# 包含頭文件
# include_directories 具有繼承性
# 當前 CMakeLists 裏面包含的目錄,會被 add_subdirectory 裏面的 CMakeLists 繼承
include_directories ( "D:/Program Files (x86)/Lua/include" )
# 庫目錄
# eg. 鏈接 Linux 下的 X11
link_directories(/usr/include/X11)
SET(SYS_LIB_LIST "x11")
# 鏈接庫
set(LUA_LIB "D:/Program Files (x86)/Lua/lib/lua51.lib")
target_link_libraries(target ${LUA_LIB})
生成配置頭文件
一般可以用CMake來進行平臺檢測,那麼CMake的檢測結果如何告訴C++工程呢?CMake提供了configure_file
命令。 configure_file
命令輸入一個文本文件,處理文本文件中包含的CMake的指令,然後生成一個文本文件。
configure_file(
"${PROJECT_SOURCE_DIR}/platform/config.h.in"
"${PROJECT_BINARY_DIR}/config.h"
)
/*
config.h.in 文件
*/
#cmakedefine PLATFORM_WIN32
#cmakedefine PLATFORM_LINUX
如果 CMakeLists.txt 中定義了PLATFORM_WIN32
變量,那麼#cmakedefine PLATFORM_WIN32
就會變成C++的宏定義語句#define PLATFORM_WIN32
。
生成Visual Studio篩選器
可以通過一個宏來爲收集的文件建立篩選器。代碼如下:
# file(GLOB_RECURSE all_files *.*)
# create_filters(all_files)
# add_executable(app ${all_files})
macro(create_filters source_files)
if(MSVC)
# 獲取當前目錄
set(current_dir ${CMAKE_CURRENT_SOURCE_DIR})
foreach(src_file ${${source_files}})
# 求出相對路徑
string(REPLACE ${current_dir}/ "" rel_path_name ${src_file})
# 刪除相對路徑中的文件名部分
string(REGEX REPLACE "(.*)/.*" \\1 rel_path ${rel_path_name})
# 比較是否是當前路徑下的文件
string(COMPARE EQUAL ${rel_path_name} ${rel_path} is_same_path)
# 替換成Windows平臺的路徑分隔符
string(REPLACE "/" "\\" rel_path ${rel_path})
if(is_same_path)
set(rel_path "\\")
endif(is_same_path)
# CMake 命令
source_group(${rel_path} FILES ${src_file})
endforeach(src_file)
endif(MSVC)
endmacro(create_filters)
編譯選項
Linux使用 C++ 11
在Linux下編譯下面的代碼時出錯。
typedef std::map<key, value> Dict_Map;
Dict_Map::iterator it = DictMap.begin();
while(it != DictMap.end())
{
it = DictMap.erase(it);
}
出錯提示是erase函數沒有返回值
。 查看源碼,發現#if __cplusplus >= 201103L
時,iterator erase(const_iterator __position)
。否則,void erase(iterator __position)
。 改成使用 C++ 11 就好了。
# Use C++11 in linux
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -std=gnu++0x")
MSVC 禁用特定警告
一般情況下,不要禁用任何警告,而應該積極解決每一個警告。 但是有些情況下,當你充分了解某些警告可能造成的後果時,也可以禁用該警告。 比如要禁用警告4819(該文件包含不能在當前代碼頁中表示的字符),可以採用下面的語句:
if(MSVC)
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd4819")
endif()
調試運行
Visual Studio設置工作目錄
使用Visual Studio開發的時候,假設編譯構建生成的程序在binarypath/bin/debug
目錄下。我們調試時,會有一些數據要使用,假設這些數據在binarypath/data
目錄下,如果程序查找數據文件時,只在當前目錄下查找,那麼在這種情況下,就無法找到數據。要解決這個問題,可以在Visual Studio中設置工作目錄,這樣程序就能找到數據文件。 Visual Studio的工作目錄設置是保存在projectname.vcxproj.user
文件中,CMake沒有提供專門的命令來設置工作目錄,但是我們可以通過類似配置文件的方式來手動生成projectname.vcxproj.user
文件。 perconfig.vcxproj.user.in
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='@USERFILE_CONFIGNAME@|@USERFILE_PLATFORM@'">
<DebuggerFlavor>WindowsLocalDebugger</DebuggerFlavor>
<LocalDebuggerCommandArguments>@USERFILE_COMMAND_ARGUMENTS@</LocalDebuggerCommandArguments>
<LocalDebuggerWorkingDirectory>@USERFILE_WORKING_DIRECTORY@</LocalDebuggerWorkingDirectory>
</PropertyGroup>
vcxproj.user.in
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="@USERFILE_VC_VERSION@" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
@USERFILE_CONFIGSECTIONS@
</Project>
CMake 模塊
file(READ "${_launchermoddir}/perconfig.vcxproj.user.in" _perconfig)
set(USERFILE_CONFIGSECTIONS)
# 對於每一個配置(Debug, Release, RelWithDebInfo, MinSizeRel),生成對應的配置項
foreach(USERFILE_CONFIGNAME ${CMAKE_CONFIGURATION_TYPES})
string(CONFIGURE "${_perconfig}" _temp @ONLY ESCAPE_QUOTES)
string(CONFIGURE
"${USERFILE_CONFIGSECTIONS}${_temp}"
USERFILE_CONFIGSECTIONS
ESCAPE_QUOTES)
endforeach()
configure_file("${_launchermoddir}/vcxproj.user.in"
${VCPROJNAME}.vcxproj.user
@ONLY)
perconfig.vcxproj.user.in中出現的@USERFILE_CONFIGNAME@
、@USERFILE_PLATFORM@
、@USERFILE_PLATFORM@
、@USERFILE_COMMAND_ARGUMENTS@
、@USERFILE_WORKING_DIRECTORY@
等,都是在CMake模塊中預先設置好的變量,通過string(CONFIGURE "${_perconfig}" _temp @ONLY ESCAPE_QUOTES)
語句填到配置裏面去的。
拷貝運行時庫
對於需要動態鏈接的庫,一般是直接在可執行程序目錄下查找的,所以對於項目中使用的第三方動態鏈接庫,我們需要在用CMake構建項目的時候,將這些動態鏈接庫拷貝到最終可執行文件目錄下。
拷貝動態鏈接庫,用於調試時運行。
macro(copy_dll depdir dllname)
foreach(configuration ${CMAKE_CONFIGURATION_TYPES})
# 對應於不同的構建版本
set(dllpath "${depdir}/bin/${configuration}/${dllname}")
if(EXISTS ${dllpath})
configure_file(${dllpath} ${CMAKE_BINARY_DIR}/bin/${configuration}/${dllname} COPYONLY)
endif()
endforeach()
endmacro()
安裝動態鏈接庫,用於最終應用程序安裝部署。
macro(install_dll depdir dllname)
foreach(configuration ${CMAKE_CONFIGURATION_TYPES})
set(dllpath "${depdir}/bin/${configuration}/${dllname}")
if(EXISTS ${dllpath})
install(FILES ${dllpath} DESTINATION bin/${configuration} CONFIGURATIONS ${configuration})
endif()
endforeach()
endmacro()
安裝
設置安裝路徑前綴
使用install
命令安裝項目的時候,如果路徑給出的是全路徑,那麼會直接使用這個路徑,如果給出的是相對路徑,那麼會加上CMAKE_INSTALL_PREFIX
,默認情況下這個值爲C:/Program Files (x86)
。可以直接在CMakeLists裏面設置這個值,也可以在CMake GUI裏面設置。
set(CMAKE_INSTALL_PREFIX "D:/Program Files (x86)/" CACHE PATH "Project install directory" FORCE)
根據配置設置安裝目錄
CMake預定義了四種編譯配置: Debug、Release、RelWithDebInfo、MinSizeRel。大多數時候,我們只會安裝Release版本。但是在某些情況下,比如A項目鏈接B項目的靜態庫,當A項目採用Debug編譯時,需要鏈接B項目的Debug版本,當A項目採用Release編譯時,需要鏈接B項目的Release版本。
假設我們使用了link_directories(${PROJECT_BINARY_DIR}/lib)
命令設置了靜態庫查找目錄,那麼在Visual Studio中,實際查找目錄是/projectpath/lib
和/projectpath/lib/$(Configuration)
,$(Configuration)
表示編譯配置。
因此,我們在設置安裝目錄時,也可以採用lib/Debug
和lib/Release
這種結構。使用下面的代碼可以完成這個功能。
foreach(lib_file platform)
install(TARGETS ${lib_file}
RUNTIME DESTINATION "bin/Debug" CONFIGURATIONS Debug
LIBRARY DESTINATION "lib/Debug" CONFIGURATIONS Debug
ARCHIVE DESTINATION "lib/Debug" CONFIGURATIONS Debug
)
install(TARGETS ${lib_file}
RUNTIME DESTINATION "bin/Release" CONFIGURATIONS Release
LIBRARY DESTINATION "lib/Release" CONFIGURATIONS Release
ARCHIVE DESTINATION "lib/Release" CONFIGURATIONS Release
)
install(TARGETS ${lib_file}
RUNTIME DESTINATION "bin/RelWithDebInfo" CONFIGURATIONS RelWithDebInfo
LIBRARY DESTINATION "lib/RelWithDebInfo" CONFIGURATIONS RelWithDebInfo
ARCHIVE DESTINATION "lib/RelWithDebInfo" CONFIGURATIONS RelWithDebInfo
)
install(TARGETS ${lib_file}
RUNTIME DESTINATION "bin/MinSizeRel" CONFIGURATIONS MinSizeRel
LIBRARY DESTINATION "lib/MinSizeRel" CONFIGURATIONS MinSizeRel
ARCHIVE DESTINATION "lib/MinSizeRel" CONFIGURATIONS MinSizeRel
)
endforeach()
工具
作爲一個項目構建工具,CMake用一種優雅的方式,描述了項目構建的步驟。 但是對於大型項目而言,光是使用CMake簡化項目構建過程還是不夠的,還需要配備一套程序配合CMake來完成項目構建的過程。
編譯加速
CMake只完成了從源文件到建立項目工程的過程,而真正編譯,則還是要依賴於特定的編譯工具,比如Visual Studio、gcc等。 對於大型工程而言,工程的編譯時間也是一件值得優化的事。目前,有一些工具可以加速基於CMake構建的工程的編譯過程。