CMake初步(2)
轉自:《你所不知的OSG》第一章:CMake初步(2)
http://bbs.osgchina.org/forum.php?mod=viewthread&tid=1229&fromuid=3434
在開始本節的學習之前,我們先總結一下之前所了解到的CMake基本詞法和命令。
CMake命令通常使用如下的格式:
- COMMAND( ARG1 ARG2 … )
變量,以${MY_VAIRABLE}的形式表達,其儲存類型為字符串類型,但是可以根據具體命令的要求自動轉換為布爾型、整型或者浮點類型。變量可以出現在字符串中,也可以實現“內省”。變量有用戶自定義和系統內置兩種,用戶自定義變量使用SET命令設置;而系統變量由系統自動賦值,例如${PROJECT_SOURCE_DIR}。
枚舉量,例如ADD_LIBRARY可以設置要生成的鏈接庫為SHARED或者STATIC,還可以設置為MODULE(插件,可動態調用,但不作為其他工程的依賴),除此之外的賦值都是不能被識別的。
值,也就是任意的字符串內容,它可以用來指示要編譯的源代碼文件名,也可以表達一段文字提示信息,或者表達特定的功能。值可以使用引號進行標識,多數情況下也可以不用。
前文中我們已經了解到的命令列舉如下,此外這里還簡要地介紹了另一些可能在各類CMake工程中遇到的命令及其語法格式。CMake部分命令的語法歌是十分復雜,這里僅僅介紹它的某一種實現形式,建議讀者閱讀CMake的幫助文檔以獲取更多信息。
括號中為該命令的一個或多個參數項,其中使用“[…]”包含的項表示可忽略項,使用“…|…”分隔的項表示只能選擇其中一項。
ADD_CUSTOM_COMMAND(
TARGET name
PRE_BUILD|PRE_LINK|POST_BUILD
COMMAND cmd1 [COMMAND cmd2 …] ):
為目標工程name添加一個或多個新的自定義的編譯規則cmd1,cmd2等,執行時機可以選擇編譯前,鏈接前或者編譯后。它的作用相當于Visual Studio工程的“Custom Build Step”屬性。
ADD_CUSTOM_TARGET( name COMMAND cmd1 [COMMAND cmd2 …] ):
添加一個名為name的編譯目標,并指定一個或多個自定義的命令cmd1,cmd2等。注意ADD_CUSTOM_COMMAND與這個命令的區別:前者是針對一個已有的子工程進行自定義編譯規則的設置;后者則是建立一個新的自定義的目標工程,例如一個專用于將已生成文件拷貝到指定文件夾的INSTALL工程;以及與之作用截然相反的UNINSTALL工程。
ADD_DEFINITIONS( -DMACRO1 –DMACRO2 … ):
添加-D預編譯宏定義,可以一次添加多個。
ADD_EXECUTABLE( name [WIN32]
source1 source2 … ):
指定一個名為name的可執行程序工程,其源文件為source1,source2等,此外還可以追加一個枚舉量WIN32,表示此程序為Win32程序,使用WinMain作為程序入口。
ADD_LIBRARY( name [STATIC|SHARED|MODULE]
source1 source2 … ):
指定一個名為name的鏈接庫工程,其源文件為source1,source2等,此外還可以指示該工程的生成結果為靜態庫(STATIC),動態庫(SHARED)還是模塊(MODULE)。
ADD_SUBDIRECTORY( dir ):
指示下一級CMake腳本所在位置位于dir子目錄。
CMAKE_MINIMUM_REQUIRED( VERSION major[.minor[.patch]] ):
指示當前腳本所需的CMake版本,不能低于版本號major.minor.patch。
CONFIGURE_FILE( infile outfile ):
將文件infile復制到outfile的位置,同時執行其中變量的自動配置和更替,即,將infile中所有形同“${VAR}”和“@VAR@”的變量替換為對應的內容,并拷貝到outfile中,而這個新生成的outfile文件也可以在之后的腳本代碼中得以使用。
FIND_LIBRARY( ${var}
NAMES name1 [name2 …]
PATHS path1 [path2 …]
PATH_SUFFIXES suffix1 [suffix2 …] ):
搜索一個外部的鏈接庫文件,并將結果的全路徑保存到var變量中。要搜索的鏈接庫文件名字可能是name1,name2等;搜索路徑為path1,path2等;此外還可以指定路徑的后綴詞為suffix1,suffix2等。因此,系統將嘗試在path1/suffix1,path1/suffix2,path2/suffix1,path2/suffix2這些目錄中搜索名為name1或name2的鏈接庫文件,并將結果(路徑和文件名)保存到var中。
FIND_PACKAGE( name ):
在指定的模塊目錄中搜索一個名為Find<name>.cmake(例如,FindOSG.cmake)的CMake腳本模塊文件,執行其中的內容,以圖搜索到指定的外部依賴庫頭文件和庫文件位置。
FIND_PATH( ${var}
NAMES name1 [name2 …]
PATHS path1 [path2 …]
PATH_SUFFIXES suffix1 [suffix2 …] ):
搜索一個包含name1,name2等文件的目錄,并將此路徑(不包含文件名)保存到var變量中,搜索路徑為path1,path2等;此外還可以指定路徑的后綴詞為suffix1,suffix2等。通常可以使用此命令來確認外部頭文件的依賴路徑。
FIND_PROGRAM( ${var}
NAMES name1 [name2 …]
PATHS path1 [path2 …]
PATH_SUFFIXES suffix1 [suffix2 …] ):
搜索一個外部的可執行程序,并將結果的全路徑保存到var變量中。要搜索的程序名字可能是name1,name2等;搜索路徑為path1,path2等;此外還可以指定路徑的后綴詞為suffix1,suffix2等。
INCLUDE( file ):
在當前文件中包含另一個CMake腳本文件的內容。
INCLUDE_DIRECTORIES( dir1 dir2 … ):
指定編譯器搜索頭文件的依賴路徑,可以添加多個。
INSTALL( TARGETS proj1 proj2
RUNTIME DESTINATION runtime_dir
LIBRARY DESTINATION library_dir
ARCHIVE DESTINATION archive_dir):
這只是此命令的一種語法格式,安裝目標工程proj1,proj2等到指定的文件夾。其中,可執行文件安裝到RUNTIME DESTINATION指定的runtime_dir目錄;動態鏈接庫安裝到LIBRARY DESTINATION指定的library_dir目錄;靜態鏈接庫安裝到ARCHIVE DESTINATION指定的archive_dir目錄。如果需要安裝頭文件或者數據文件,則通常使用INSTALL( FILES … DESTINATION … )的形式。
LINK_DIRECTORIES( dir1 dir2 … ):
設置外部依賴庫的搜索路徑。
MESSAGE( [SEND_ERROR|STATUS|FATAL_ERROR] “text” … ):
在控制臺或者對話框輸出一行或多行調試信息文本text,枚舉量用于控制信息的類型(錯誤,狀態顯示,致命錯誤)。
OPTION( ${var} “text” value ):
向用戶提供一個可選項,提示信息為text,初始值為value,并將最終的結果傳遞到var變量中。在CMake-GUI中它將以配置選項的方式出現。
PROJECT( name ):
設置整個工程的名稱為name。
SET( variable value
[CACHE FILEPATH|PATH|STRING|BOOL “text”] ):
定義一個用戶自定義變量variable,取值為value。此外還可以使用CACHE關鍵字,允許用戶在CMake-GUI中修改變量的值,修改方式包括文件對話框(FILEPATH),目錄對話框(PATH),編輯框(STRING)或者復選框(BOOL),并使用text作為提示信息。
SET_TARGET_PROPERTIES( name PROPERTIES prop value ):
設置名為name的工程的屬性,這里主要可選的prop屬性包括PROJECT_LABEL, DEBUG_POSTFIX,OUTPUT_NAME等等,value為設置值。
TARGET_LINK_LIBRARIES( name
lib1 lib2 …
[debug|optimized] lib1 lib2 … ):
指定工程name所用的依賴庫,并可以使用debug和optimized關鍵字分別指定DEBUG與RELEASE版本所用的一個或多個依賴庫。
也許您并不一定完全明白這里所說的每一句話,這也是我們之所以把詞法和語法的介紹放在“Hello World”例子之后的一個原因——沒錯,僅僅是這些單詞的羅列未免太枯燥了。那么,為什么不馬上拾起我們剛剛才完成的簡單腳本工程,在上面添磚加瓦一番呢?說不定這才是您充分理解和深入學習CMake的關鍵呢。
是的,實踐才是最好的老師。要充分理解CMake的強大之處,以及確保自己具備足夠的力量去閱讀OSG的CMake腳本源代碼,勢必還要再多做一些更為復雜的練習才行。不過在此之前,我們還是再多了解一些CMake的常用內置變量和腳本命令,以及CMake中條件語句,循環語句和宏函數的概念用法。
包括前文介紹的PROJECT_SOURCE_DIR在內,以下內置全局變量都可以在CMake腳本中以“${…}”的形式直接加以應用,以方便腳本代碼的定位和功能實現:
- CMAKE_BUILD_TYPE:工程的編譯生成的版本類型,可選項包括Debug,Release,RelWithDebInfo和MinSizeRel。
- CMAKE_COMMAND:也就是CMake可執行文件本身的全路徑,例如/usr/local/bin/cmake或者C:\Program Files\CMake 2.6\bin\cmake.exe。
- CMAKE_DEBUG_POSTFIX:Debug版本生成目標的后綴,通常可以設置為“d”字符,例如Debug版本的OSG核心庫為osgd.dll,而Release版為osg.dll。
- CMAKE_GENERATOR:編譯器名稱,例如“Unix Makefiles”,“Visual Studio 7”等。
- CMAKE_INSTALL_PREFIX:工程安裝目錄,所有生成和調用所需的可執行程序,庫文件,頭文件都會安裝到該路徑下,Unix/Linux下默認為/usr/local,Windows下默認為C:\Program Files。
- CMAKE_MODULE_PATH:設置搜索CMakeModules模塊(.cmake)的額外路徑。
- PROJECT_BINARY_DIR:工程生成工作所在的目錄,即前文所述的“out-of-source”的目錄;對于“in-source”形式的編譯工作,該變量與PROJECT_SOURCE_DIR所指向的目錄相同。
- PROJECT_NAME:工程名稱,即使用PROJECT命令設置的名稱。
- PROJECT_SOURCE_DIR:工程源代碼文件所在的目錄。
- CYGWIN:標識當前系統是否為Cygwin。
- MSVC:標識當前系統是否使用Microsoft Visual C。
- UNIX:標識當前系統是否為Unix系列(包括Linux,Cygwin和Apple)。
- WIN32:標識當前系統是否為Windows及Win64。
不必擔心這里介紹的命令和變量太多,也不必擔心它們會很快消失在您的腦海深處。下一節我們將嘗試創建一個稍微復雜一些的工程VersionMe,并爭取將上文涉及到的大部分命令和內置變量派上用場,以求在實戰中讓您領略到CMake的強大魅力。
不過在結束枯燥的本章之前,我們還需要介紹一下CMake中重要的條件語句語法,循環語句語法和宏函數。它們分別相當于C程序中的if…else,while/for以及函數的作用,并且條件和循環語句都可以嵌套工作。毫無疑問,它們在腳本語言的流程控制過程中必然不可或缺。
CMake中的條件語句基本格式為:
- IF( expression )
- …
- ELSE( expression )
- …
- ENDIF( expression )
或者,
- IF( expression1 )
- …
- ELSEIF( expression2 )
- …
- ELSE()
- …
- ENDIF()
這里的expression是判斷條件,和C/C++類似,CMake的條件也存在“與/或/非”以及“等于/大于/小于”等幾種操作符,分別用AND/OR/NOT以及EQUAL/LESS/GREATER來表示。當判斷條件為真,執行IF后的命令段,否則繼續判斷并執行相應條件對應的命令段,或者不執行任何操作。例如:
- IF ( ${number} GREATER 4 )
- …
- ENDIF( ${number}GREATER 4 )
表示判斷變量number是否大于4,進而執行對應的語句段。此時用戶定義的字符串變量會被自動轉換為整型變量以便進行判斷。
此外形同這樣的判斷語句也是十分常見的:
- IF ( NOT ${variable} )
- …
- ENDIF( NOT ${variable} )
如果變量variable的值為空,0,N,NO,OFF,FALSE,NOTFOUND這幾種之一的話,則認為此變量表示“假”,即此處的“NOT ${variable}”為真。
CMake中的循環語句基本格式為:
- FOREACH( var arg1 arg2 … )
- …
- ENDFOREACH( var )
這里設置一個循環的局部變量var,每次將其賦為arg1,arg2等變量(或者變量數組)中的一個值,并執行循環中的命令段。例如:
FOREACH( var ${OPENGL_LIBRARIES} ),它表示將局部變量var每次設置為變量OPENGL_LIBRARIES中的一個值。后者的內容可能為“opengl32.lib;glu32.lib”的形式。
CMake自動將分號分隔的字符串認為是數組,因此會自動從該變量中擇取var的取值。
另一種表達循環語句的語法格式為:
- WHILE ( expression )
- …
- ENDWHILE( expression )
這里的expression和IF語句判斷字段中的含義相同。
CMake中的宏函數可以理解為C語言的函數,它改變代碼執行跳轉的流程并簡化了腳本程序的開發,其基本格式為:
- MACRO( funcname [arg1 [arg2 …]] )
- …
- ENDMACRO( funcname )
和函數的編寫要求一樣,CMake的宏函數必須指定一個函數名funcname,以及零個或多個輸入參數arg1,arg2等。需要調用宏函數的時候,只要直接使用funcname(arg1 arg2)的形式就可以了,例如:
- MACRO( MY_FUNC arg1 arg2 )
- …
- ENDMACRO( MY_FUNC )
在主程序中,需要調用此宏函數時,只需執行形同下面的語句:
- MYFUNC( param1 param2 )
就可以將實際參數param1和param2傳入宏函數體。
此外,宏函數體內可以使用內置變量${ARGC},${ARGV}和${ARGN}來表達傳入參數的屬性:${ARGC}保存了傳入參數的個數;${ARGV}保存一個傳入參數組成的數組,可以供FOREACH語句使用;${ARGN}則比較特殊,它保存了“顯式參數”之外的所有“隱式參數”所組成的數組。對于上面的例句來說,arg1,arg2就是顯式參數,而如果用戶在調用MY_FUNC時采用下面的形式:
- MYFUNC( param1 param2 other1 other2 … )
那么other1,other2等就是隱式參數,可以用${ARGN}來獲取它們的數組。這對于CMake而言是完全合法的,并且可以因此定義不定參數項的宏函數,從而大大增強了腳本程序的靈活性。
下面我們將著手設計一個稍微復雜一些的工程,名為VersionMe。它同樣包括一個動態鏈接庫工程和一個依賴于它的可執行工程,并且計劃實現以下一些功能:
- 可以查詢當前操作系統的版本;
- 可以查詢系統中安裝的OpenSceneGraph庫的版本;
- 可以執行上一次我們創建的HelloWorld工程的方法;
為了使我們的工程更具備開源工程的特征,我們模仿OpenSceneGraph的文件夾結構,設計如下:
名為VersionLib的鏈接庫工程的頭文件和源代碼文件將分別保存到include和src兩個目錄下;而名為test的可執行工程則單獨保存在同名目錄下;此外,CMakeModules目錄專職負責保存相關的CMake腳本數據。
這個工程的重點毫無疑問是VersionLib鏈接庫,它的工作是提供顯示系統版本和OSG版本的函數,以及調用之前的Hello::sayHello方法,再次在控制臺界面上顯示“Hello CMake!”這一行簡單而友好的歡迎文字。
VersionLib庫包括一個頭文件Version和一個源代碼文件Version.cpp,其代碼如下:
- /* Version */
- #ifndef H_VERSION
- #define H_VERSION
- #if defined(_MSC_VER) || defined(__CYGWIN__) || defined(__MINGW32__)
- # ifdef VERSIONME_LIBRARY
- # define VERSIONME_EXPORT __declspec(dllexport)
- # else
- # define VERSIONME_EXPORT __declspec(dllimport)
- # endif
- #else
- # define VERSIONME_EXPORT
- #endif
- class VERSIONME_EXPORT Version
- {
- public:
- void printHello();
- const char* systemVersion();
- const char* osgVersion();
- };
- #endif
- /* Version.cpp */
- #ifdef _MSC_VER
- # include <windows.h>
- #else
- # include <sys/utsname.h>
- #endif
- #include <sstream>
- #include <HelloLib/Hello>
- #include <osg/Version>
- #include <VersionLib/Version>
- static char g_version[255] = "";
- void Version::printHello()
- {
- Hello hello;
- hello.sayHello();
- }
- const char* Version::systemVersion()
- {
- std::stringstream stream;
- #ifdef _MSC_VER
- OSVERSIONINFO osvi;
- ZeroMemory( &osvi, sizeof(OSVERSIONINFO) );
- osvi.dwOSVersionInfoSize = sizeof( OSVERSIONINFO );
- GetVersionEx( &osvi );
- stream << "Windows-";
- stream << osvi.dwMajorVersion << "." << osvi.dwMinorVersion << "."
- << osvi.dwBuildNumber;
- #else
- struct utsname buff;
- uname( &buff );
- stream << buff.sysname << "-" << buff.release;
- #endif
- stream >> g_version;
- return g_version;
- }
- const char* Version::osgVersion()
- {
- return osgGetVersion();
- }
此外,這里我們引用頭文件Hello并調用了其中的函數,因此在之后的CMake腳本設計過程中,要考慮正確搜索和指定Hello頭文件依賴目錄以及依賴庫的路徑位置。
而Test工程的源代碼就很簡單了,只有一個test.cpp文件,內容如下:
- /* test.cpp */
- #include <iostream>
- #include <VersionLib/Version>
- int main( int argc, char** argv )
- {
- Version obj;
- std::cout << "Operating System Version: " << obj.systemVersion() << std::endl;
- std::cout << "OpenSceneGraph Version: " << obj.osgVersion() << std::endl;
- obj.printHello();
- return 0;
- }
以上就是VersionMe工程設計的所有要求。下面,我們將開始CMake腳本的設計。
- PROJECT( VersionMe )
- CMAKE_MINIMUM_REQUIRED( VERSION 2.4.7 )
- IF( COMMAND CMAKE_POLICY )
- CMAKE_POLICY( SET CMP0003 NEW )
- ENDIF( COMMAND CMAKE_POLICY )
- SET( CMAKE_MODULE_PATH
- "${PROJECT_SOURCE_DIR}/CMakeModules/;${CMAKE_MODULE_PATH}" )
- SET( CMAKE_DEBUG_POSTFIX "d"
- CACHE STRING "add a postfix, usually d on windows" )
- OPTION( BUILD_TEST "Set to ON to build the test application." ON )
- IF( WIN32 )
- IF( MSVC )
- ADD_DEFINITIONS( -D_SCL_SECURE_NO_WARNINGS )
- ADD_DEFINITIONS( -D_CRT_SECURE_NO_DEPRECATE )
- ENDIF( MSVC )
- ENDIF( WIN32 )
- FIND_PACKAGE( Hello )
- FIND_PACKAGE( osg )
- INCLUDE( CustomModules )
- INCLUDE_DIRECTORIES( include )
- ADD_SUBDIRECTORY( src/VersionLib )
- IF( BUILD_TEST )
- ADD_SUBDIRECTORY( test )
- ENDIF( BUILD_TEST )
首先設置工程名稱和所需的CMake版本,此處還使用了一個CMAKE_POLICY命令來設置版本的兼容性,以避免一些不必要的警告信息。
之后,我們指定基本的CMake配置參數,包括模塊搜索目錄,Debug版本生成目標的后綴,一個用戶選項BUILD_TEST,以及針對Visual Studio的一些宏定義設置。(通過判斷WIN32和MSVC內置變量來執行這一動作)
后面的工作比較重要一些,首先是使用FIND_PACKAGE來搜索外部依賴庫的路徑。CMake中預置了很多模塊搜索的腳本,可以用來檢索各種知名工程的文件和路徑信息,并以CMake變量的方式返回。例如,FIND_PACKAGE(OpenGL)將自動調用CMake安裝目錄share/cmake-2.6/Modules文件夾中的FindOpenGL.cmake腳本的內容,自動搜索或者由用戶指定OpenGL頭文件和鏈接庫的位置,并主要返回以下變量:
- OPENGL_FOUND:是否找到OpenGL庫的標識;
- OPENGL_GLU_FOUND:是否找到GLU庫的標識;
- OPENGL_INCLUDE_DIR:頭文件gl.h所在的文件夾路徑;
- OPENGL_gl_LIBRARY:鏈接庫opengl32的絕對路徑;
- OPENGL_glu_LIBRARY:鏈接庫glu32的絕對路徑;
這個時候可以用下面的形式為名為ProjName的工程指定依賴庫OpenGL的頭文件和鏈接庫文件,腳本代碼如下:
- INCLUDE_DIRECTORIES( ${OPENGL_INCLUDE_DIR} )
- TARGET_LINK_LIBRARIES( ProjName
- ${OPENGL_gl_LIBRARY}
- ${OPENGL_glu_LIBRARY}
- )
回到我們的VersionMe工程來,這里我們試圖搜索兩個外部依賴庫的信息:Hello代表我們之前生成的HelloWorld工程,而OSG表示外部的OpenSceneGraph工程。后者的搜索模塊在CMake的較新版本中已經包含,包括Findosg,FindosgUtil,FindosgViewer等多個cmake文件,可以使用FIND_PACKAGE直接調用并返回相應的頭文件和鏈接庫路徑變量。(通常名為OSG_INCLUDE_DIR,OSG_LIBRARY等)
但是Hello這個庫卻不可能有相應的模塊文件,畢竟CMake可不知道我們之前做了什么。因此我們需要手動編寫名為FindHello.cmake的腳本,并將其保存在CMakeModules目錄下。具體的腳本編寫方法我們稍后再說。
在使用FIND_PACKAGE搜索到外部Hello庫和OSG庫的具體位置之后,(也可能找不到,此時需要在CMake-GUI中手動進行設置),下一步還要使用INCLUDE命令包含一個自定模塊CustomModules.cmake。它的工作是定義了幾個方便VersionMe工程設置的宏函數,以免我們在后面的子工程創建過程中編寫過多的代碼;OSG中有一個類似功能的模塊文件OsgMacroUtils.cmake,有興趣的朋友不妨提前閱讀一下。
筆者在CustomModules中手動編寫了三個實用的宏函數:INCLUDE_FOR_PROJECT,LINK_PROJECT以及INSTALL_PROJECT。它們不僅將在VersionMe的設計中大顯身手,也許更可以為您自己的工程腳本編寫提供一些助力。
在INCLUDE命令之后,注意我們還使用了一次INCLUDE_DIRECTORIES命令。這是什么意思呢?再次考慮VersionMe的文件夾結構,和之前的HelloWorld工程不同,這一次所有的頭文件不再與源代碼文件放在同一目錄下,而是放置在單獨的include文件夾,以便于更有效的文件管理。但是,對于編譯器而言,這樣會使它無法正確搜索到#include引用的頭文件位置。因此我們必須提前設置好相應的頭文件依賴路徑,并且這一設置對于所有的子工程都是統一的。
僅對于上例而言,下面兩種指定頭文件路徑的方式是等價的:
- INCLUDE_DIRECTORIES( include )
- INCLUDE_DIRECTORIES( “${PROJECT_SOURCE_DIR}/include” )
我們快速瀏覽一下VersionLib子工程和test子工程的腳本代碼:
- SET( LIB_NAME VersionLib )
- SET( HEADER_PATH ${PROJECT_SOURCE_DIR}/include/${LIB_NAME} )
- SET( ${LIB_NAME}_HEADERS
- ${HEADER_PATH}/Version
- )
- SET( ${LIB_NAME}_SOURCES
- Version.cpp
- )
- ADD_DEFINITIONS( -DVERSIONME_LIBRARY )
- ADD_LIBRARY( ${LIB_NAME} SHARED
- ${${LIB_NAME}_HEADERS}
- ${${LIB_NAME}_SOURCES}
- )
- SET_TARGET_PROPERTIES( ${LIB_NAME}
- PROPERTIES DEBUG_POSTFIX
- "${CMAKE_DEBUG_POSTFIX}"
- )
- INCLUDE_FOR_PROJECT( ${LIB_NAME} HELLO OSG )
- LINK_PROJECT( ${LIB_NAME} HELLO OSG )
- INSTALL_PROJECT( ${LIB_NAME} )
之后我們依次調用三個自定義的宏函數,也就是前文說過的INCLUDE_FOR_PROJECT,LINK_PROJECT和INSTALL_PROJECT。它們的工作依次是:指定工程所用的頭文件依賴目錄(Hello庫和OSG庫的頭文件目錄);工程所需的外部鏈接庫文件絕對路徑(HelloLib.lib和osg.lib);以及指定安裝這個工程的相關生成文件到預設的目錄中。
這些宏函數的具體實現過程稍后詳述。
子工程test的腳本代碼為:
- SET( EXAMPLE_NAME Test )
- SET( EXAMPLE_FILES
- test.cpp
- )
- ADD_EXECUTABLE( ${EXAMPLE_NAME} test.cpp )
- SET_TARGET_PROPERTIES( ${EXAMPLE_NAME}
- PROPERTIES DEBUG_POSTFIX
- "${CMAKE_DEBUG_POSTFIX}" )
- LINK_PROJECT( ${EXAMPLE_NAME} VersionLib )
- INSTALL_PROJECT( ${EXAMPLE_NAME} )
Test子工程依賴于即將生成的VersionLib庫,不必擔心它們的編譯順序上會有什么偏差,CMake會為我們安排好一切
用于搜索HelloLib庫的FindHello.cmake,它是怎樣編寫的?
用于實現實用宏函數的CustomModules.cmake,它又是怎樣編寫的?
是的,看來我們之前欠下的債務還沒有完全還清啊,那么,首先我們來解析一下:如何編寫腳本,搜索一個外部依賴庫的信息并作為CMake變量傳遞給當前工程。
- FIND_PATH( HELLO_INCLUDE_DIR HelloLib/Hello
- PATHS
- $ENV{PATH}
- /usr/include/
- /usr/local/include/
- )
- FIND_LIBRARY( HELLO_LIBRARY
- NAMES HelloLib
- PATHS
- $ENV{PATH}
- /usr/lib
- /usr/local/lib
- )
按照FIND_PATH和FIND_LIBRARY的語法可知,系統將自動在PATHS指定的目錄中搜索HelloLib/Hello文件和HelloLib文件的位置,并將結果返回到變量中;如果不能找到,那么CMake-GUI中會自動出現紅色的選項欄,允許用戶自己選擇文件的位置,以便正確執行后面的編譯工作。
這里還出現了一個特殊的格式“$ENV{PATH}”,它表示搜索系統環境變量PATH中定義的所有路徑,并且可以將PATH替換為別的環境變量名稱;此外我們還可以使用形如“[HKEY_LOCAL_MACHINE\\...]”的字樣,顧名思義,它指定搜索Windows注冊表相應位置保存的路徑信息,當然這樣的寫法在Unix/Linux系統中沒有效果。
而對于宏函數的實現,我們在這里只介紹INCLUDE_FOR_PROJECT的寫法,并期望讀者朋友能夠舉一反三,自行閱讀和實驗其它兩個宏函數的內容,理解和更新它們的用法,進而設計出屬于您自己的更加豐富多彩的CMake腳本來。宏函數的內容如下:
- MACRO( INCLUDE_FOR_PROJECT PROJNAME )
- FOREACH( varname ${ARGN} )
- IF( ${varname}_INCLUDE_DIR )
- INCLUDE_DIRECTORIES( "${${varname}_INCLUDE_DIR}" )
- ELSE( ${varname}_INCLUDE_DIR )
- INCLUDE_DIRECTORIES( "${varname}" )
- ENDIF( ${varname}_INCLUDE_DIR )
- ENDFOREACH( varname )
- ENDMACRO( INCLUDE_FOR_PROJECT PROJNAME )
- INCLUDE_FOR_PROJECT( VersionLib HELLO OSG )
FOREACH命令將依次取出HELLO和OSG字符串,并賦值到變量${varname}中。這是我們運用了剛剛提到過的一個命名慣例:對于使用FIND_PATH搜索頭文件的返回變量,通常命名為“…_INCLUDE_DIR”,而使用FIND_LIBRARY搜索庫文件的結果變量命名為“..._LIBRARY”,因此,現在我們直接嘗試查找用戶變量${varname}_INCLUDE_DIR,也就是HELLO_INCLUDE_DIR和OSG_INCLUDE_DIR,并取出其中的結果。
如果這個環境變量存在,那么使用INCLUDE_DIRECTORIES將其中的路徑信息傳遞給子工程;否則的話,直接將變量${varname}本身傳遞給子工程,換句話說,對于這個宏函樹來說,下面的調用形式:
- INCLUDE_FOR_PROJECT( VersionLib “E:/Projects/HelloWorld” )
完成后的CMake-GUI界面如下,注意筆者使用了“out-of-source”的編譯方式,并且設置了與源代碼目錄不同的安裝目錄:
最后的編譯生成和執行結果如下。怎么樣,是否覺得這個其實再簡單不過的工程突然變得十分專業了呢?您的工程也可以如此!
1.6 參考資料
好了,本以為只是《你所不知的OSG》中普普通通的一章,卻不料其內容遠遠超出了筆者的預計,不知是否也遠遠超出了您對于冗長文章的忍耐能力?(^_^)不過這依然只是管中窺豹,見一斑而已——CMake網站上已經提供了最新教材書籍的購買方式,包括《Mastering CMake》等四本相關圖書已經出版,而另有四本正緊鑼密鼓地籌劃之中……怎么樣,光是出版物的規模就已經如此宏大。您是否要驚嘆一下,這小小的“輔助編譯生成系統”,實際上卻大有文章呢?
本文所述的內容只是最淺顯的CMake應用,對于命令語法的解釋非常不全面。并且為了保持文章的“初級性”,刪去了不少高級用戶才可能用到的指令和枚舉參數,以及正則表達式,數組賦值,字符串規則等一系列深入有趣卻難以再納入本文篇幅的內容。如是種種,還需要讀者朋友自行鉆研發掘,并幫助其發展壯大。
以下列出了可用的CMake學習參考資料網址。