<menu id="w8yyk"><menu id="w8yyk"></menu></menu>
  • <dd id="w8yyk"><nav id="w8yyk"></nav></dd>
    <menu id="w8yyk"></menu>
    <menu id="w8yyk"><code id="w8yyk"></code></menu>
    <menu id="w8yyk"></menu>
    <xmp id="w8yyk">
    <xmp id="w8yyk"><nav id="w8yyk"></nav>
  • 網站首頁 > 物聯資訊 > 技術分享

    CMake初步(2)

    2016-09-28 00:00:00 廣州睿豐德信息科技有限公司 閱讀
    睿豐德科技 專注RFID識別技術和條碼識別技術與管理軟件的集成項目。質量追溯系統、MES系統、金蝶與條碼系統對接、用友與條碼系統對接

    轉自:《你所不知的OSG》第一章:CMake初步(2)

    http://bbs.osgchina.org/forum.php?mod=viewthread&tid=1229&fromuid=3434

     

    1.4 詞法和語法

    在開始本節的學習之前,我們先總結一下之前所了解到的CMake基本詞法和命令。

    CMake命令通常使用如下的格式:
    1. 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中的條件語句基本格式為:

    1. IF( expression )
    2. ELSE( expression )
    3. ENDIF( expression )
    復制代碼

    或者,

    1. IF( expression1 )
    2. ELSEIF( expression2 )
    3. ELSE()
    4. ENDIF()
    復制代碼

    這里的expression是判斷條件,和C/C++類似,CMake的條件也存在“與/或/非”以及“等于/大于/小于”等幾種操作符,分別用AND/OR/NOT以及EQUAL/LESS/GREATER來表示。當判斷條件為真,執行IF后的命令段,否則繼續判斷并執行相應條件對應的命令段,或者不執行任何操作。例如:

    1. IF ( ${number} GREATER 4 )
    2. ENDIF( ${number}GREATER 4 )
    復制代碼

    表示判斷變量number是否大于4,進而執行對應的語句段。此時用戶定義的字符串變量會被自動轉換為整型變量以便進行判斷。
    此外形同這樣的判斷語句也是十分常見的:

    1. IF ( NOT ${variable} )
    2. ENDIF( NOT ${variable} )
    復制代碼

    如果變量variable的值為空,0,N,NO,OFF,FALSE,NOTFOUND這幾種之一的話,則認為此變量表示“假”,即此處的“NOT ${variable}”為真。

    CMake中的循環語句基本格式為:

    1. FOREACH( var arg1 arg2 … )
    2. ENDFOREACH( var )
    復制代碼

    這里設置一個循環的局部變量var,每次將其賦為arg1,arg2等變量(或者變量數組)中的一個值,并執行循環中的命令段。例如:

    FOREACH( var ${OPENGL_LIBRARIES} ),它表示將局部變量var每次設置為變量OPENGL_LIBRARIES中的一個值。后者的內容可能為“opengl32.lib;glu32.lib”的形式。

    CMake自動將分號分隔的字符串認為是數組,因此會自動從該變量中擇取var的取值。

    另一種表達循環語句的語法格式為:

    1. WHILE ( expression )
    2. ENDWHILE( expression )
    復制代碼

    這里的expression和IF語句判斷字段中的含義相同。

    CMake中的宏函數可以理解為C語言的函數,它改變代碼執行跳轉的流程并簡化了腳本程序的開發,其基本格式為:

    1. MACRO( funcname [arg1 [arg2 …]] )
    2. ENDMACRO( funcname )
    復制代碼

    和函數的編寫要求一樣,CMake的宏函數必須指定一個函數名funcname,以及零個或多個輸入參數arg1,arg2等。需要調用宏函數的時候,只要直接使用funcname(arg1 arg2)的形式就可以了,例如:

    1. MACRO( MY_FUNC arg1 arg2 )
    2. ENDMACRO( MY_FUNC )
    復制代碼

    在主程序中,需要調用此宏函數時,只需執行形同下面的語句:

    1. MYFUNC( param1 param2 )
    復制代碼

    就可以將實際參數param1和param2傳入宏函數體。

    此外,宏函數體內可以使用內置變量${ARGC},${ARGV}和${ARGN}來表達傳入參數的屬性:${ARGC}保存了傳入參數的個數;${ARGV}保存一個傳入參數組成的數組,可以供FOREACH語句使用;${ARGN}則比較特殊,它保存了“顯式參數”之外的所有“隱式參數”所組成的數組。對于上面的例句來說,arg1,arg2就是顯式參數,而如果用戶在調用MY_FUNC時采用下面的形式:

    1. MYFUNC( param1 param2 other1 other2 … )
    復制代碼

    那么other1,other2等就是隱式參數,可以用${ARGN}來獲取它們的數組。這對于CMake而言是完全合法的,并且可以因此定義不定參數項的宏函數,從而大大增強了腳本程序的靈活性。

     

     

    1.5 VersionMe工程設計

    下面我們將著手設計一個稍微復雜一些的工程,名為VersionMe。它同樣包括一個動態鏈接庫工程和一個依賴于它的可執行工程,并且計劃實現以下一些功能:
    • 可以查詢當前操作系統的版本;
    • 可以查詢系統中安裝的OpenSceneGraph庫的版本;
    • 可以執行上一次我們創建的HelloWorld工程的方法;

    為了使我們的工程更具備開源工程的特征,我們模仿OpenSceneGraph的文件夾結構,設計如下:
    RFID設備管理軟件

    名為VersionLib的鏈接庫工程的頭文件和源代碼文件將分別保存到include和src兩個目錄下;而名為test的可執行工程則單獨保存在同名目錄下;此外,CMakeModules目錄專職負責保存相關的CMake腳本數據。

    這個工程的重點毫無疑問是VersionLib鏈接庫,它的工作是提供顯示系統版本和OSG版本的函數,以及調用之前的Hello::sayHello方法,再次在控制臺界面上顯示“Hello CMake!”這一行簡單而友好的歡迎文字。

    VersionLib庫包括一個頭文件Version和一個源代碼文件Version.cpp,其代碼如下:
    1. /* Version */
    2. #ifndef H_VERSION
    3. #define H_VERSION
    4. #if defined(_MSC_VER) || defined(__CYGWIN__) || defined(__MINGW32__)
    5. #    ifdef VERSIONME_LIBRARY
    6. #        define VERSIONME_EXPORT __declspec(dllexport)
    7. #    else
    8. #        define VERSIONME_EXPORT __declspec(dllimport)
    9. #    endif
    10. #else
    11. #    define VERSIONME_EXPORT
    12. #endif
    13. class VERSIONME_EXPORT Version
    14. {
    15. public:
    16.     void printHello();
    17.     const char* systemVersion();
    18.     const char* osgVersion();
    19. };
    20. #endif
    21. /* Version.cpp */
    22. #ifdef _MSC_VER
    23. #    include <windows.h>
    24. #else
    25. #    include <sys/utsname.h>
    26. #endif
    27. #include <sstream>
    28. #include <HelloLib/Hello>
    29. #include <osg/Version>
    30. #include <VersionLib/Version>
    31. static char g_version[255] = "";
    32. void Version::printHello()
    33. {
    34.     Hello hello;
    35.     hello.sayHello();
    36. }
    37. const char* Version::systemVersion()
    38. {
    39.     std::stringstream stream;
    40. #ifdef _MSC_VER
    41.     OSVERSIONINFO osvi;
    42.     ZeroMemory( &osvi, sizeof(OSVERSIONINFO) );
    43.     osvi.dwOSVersionInfoSize = sizeof( OSVERSIONINFO );
    44.     GetVersionEx( &osvi );
    45.     stream << "Windows-";
    46.     stream << osvi.dwMajorVersion << "." << osvi.dwMinorVersion << "."
    47.           << osvi.dwBuildNumber;
    48. #else
    49.     struct utsname buff;
    50.     uname( &buff );
    51.     stream << buff.sysname << "-" << buff.release;
    52. #endif
    53.     stream >> g_version;
    54.     return g_version;
    55. }
    56. const char* Version::osgVersion()
    57. {
    58.     return osgGetVersion();
    59. }
    復制代碼 具體的實現過程我們不作介紹,這里的osgGetVersion()是OSG的版本查詢函數,而GetVersionEx()和uname則分別是Windows和Linux下的版本查詢API函數。嗯,是否覺得這里Windows API的實現更加費勁呢?(^_^)

    此外,這里我們引用頭文件Hello并調用了其中的函數,因此在之后的CMake腳本設計過程中,要考慮正確搜索和指定Hello頭文件依賴目錄以及依賴庫的路徑位置。

    而Test工程的源代碼就很簡單了,只有一個test.cpp文件,內容如下:
    1. /* test.cpp */
    2. #include <iostream>
    3. #include <VersionLib/Version>
    4. int main( int argc, char** argv )
    5. {
    6.     Version obj;
    7.     std::cout << "Operating System Version: " << obj.systemVersion() << std::endl;
    8.     std::cout << "OpenSceneGraph Version: " << obj.osgVersion() << std::endl;
    9.     obj.printHello();
    10.     return 0;
    11. }
    復制代碼 我們希望在CMake-GUI窗口中有一個用戶選項,用來決定是否編譯這個Test工程。另外,我們還希望能夠使用類似“make install”這樣的命令來安裝所有工程相關的動態庫,靜態庫,頭文件和可執行文件。(之前的HelloWorld工程是無法這樣做的)

    以上就是VersionMe工程設計的所有要求。下面,我們將開始CMake腳本的設計。 位于VersionMe根目錄下的CMakeLists.txt腳本如下:
    1. PROJECT( VersionMe )
    2. CMAKE_MINIMUM_REQUIRED( VERSION 2.4.7 )
    3. IF( COMMAND CMAKE_POLICY )
    4.    CMAKE_POLICY( SET CMP0003 NEW )
    5. ENDIF( COMMAND CMAKE_POLICY )
    6. SET( CMAKE_MODULE_PATH
    7.      "${PROJECT_SOURCE_DIR}/CMakeModules/;${CMAKE_MODULE_PATH}" )
    8. SET( CMAKE_DEBUG_POSTFIX "d"
    9.      CACHE STRING "add a postfix, usually d on windows" )
    10. OPTION( BUILD_TEST "Set to ON to build the test application." ON )
    11. IF( WIN32 )
    12.     IF( MSVC )
    13.         ADD_DEFINITIONS( -D_SCL_SECURE_NO_WARNINGS )
    14.         ADD_DEFINITIONS( -D_CRT_SECURE_NO_DEPRECATE )
    15.     ENDIF( MSVC )
    16. ENDIF( WIN32 )
    17. FIND_PACKAGE( Hello )
    18. FIND_PACKAGE( osg )
    19. INCLUDE( CustomModules )
    20. INCLUDE_DIRECTORIES( include )
    21. ADD_SUBDIRECTORY( src/VersionLib )
    22. IF( BUILD_TEST )
    23.     ADD_SUBDIRECTORY( test )
    24. ENDIF( BUILD_TEST )
    復制代碼 這一次的代碼編寫恐怕比之前豐富了不少,有點眼暈嗎?沒關系,我們結合上一節的CMake命令和內置變量介紹,一點一點地分析。

    首先設置工程名稱和所需的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的頭文件和鏈接庫文件,腳本代碼如下:
    1. INCLUDE_DIRECTORIES( ${OPENGL_INCLUDE_DIR} )
    2. TARGET_LINK_LIBRARIES( ProjName
    3.                          ${OPENGL_gl_LIBRARY}
    4.                          ${OPENGL_glu_LIBRARY}
    5. )
    復制代碼 注意Windows環境下不存在OPENGL_INCLUDE_DIR,因為沒有必要做特別指定。

    回到我們的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引用的頭文件位置。因此我們必須提前設置好相應的頭文件依賴路徑,并且這一設置對于所有的子工程都是統一的。

    僅對于上例而言,下面兩種指定頭文件路徑的方式是等價的:
    1. INCLUDE_DIRECTORIES( include )
    2. INCLUDE_DIRECTORIES( “${PROJECT_SOURCE_DIR}/include” )
    復制代碼 這之后的腳本代碼就輕松一點了,ADD_SUBDIRECTORY命令指定了下一級子工程CMake腳本的位置,此外我們還通過判斷用戶是否選擇了BUILD_TEST選項,來決定是否編譯test工程。(即是否進入test工程所在的腳本目錄)

    我們快速瀏覽一下VersionLib子工程和test子工程的腳本代碼:
    1. SET( LIB_NAME VersionLib )
    2. SET( HEADER_PATH ${PROJECT_SOURCE_DIR}/include/${LIB_NAME} )
    3. SET( ${LIB_NAME}_HEADERS
    4.      ${HEADER_PATH}/Version
    5. )
    6. SET( ${LIB_NAME}_SOURCES
    7.      Version.cpp
    8. )
    9. ADD_DEFINITIONS( -DVERSIONME_LIBRARY )
    10. ADD_LIBRARY( ${LIB_NAME} SHARED
    11.                ${${LIB_NAME}_HEADERS}
    12.                ${${LIB_NAME}_SOURCES}
    13. )
    14. SET_TARGET_PROPERTIES( ${LIB_NAME}
    15.                           PROPERTIES DEBUG_POSTFIX
    16.                           "${CMAKE_DEBUG_POSTFIX}"
    17. )
    18. INCLUDE_FOR_PROJECT( ${LIB_NAME} HELLO OSG )
    19. LINK_PROJECT( ${LIB_NAME} HELLO OSG )
    20. INSTALL_PROJECT( ${LIB_NAME} )
    復制代碼 雖然看上去復雜了一些,但其實源文件設置和工程屬性設置的腳本代碼并沒有太多深奧的地方。無非是大量使用CMake變量而已,如果您熟悉其它程序語言設計的話,相信很快會對此習以為常,并能夠適當地在自己的工程中加以借鑒。當使用ADD_LIBRARY新增了一個動態鏈接庫工程之后,我們還額外地使用SET_TARGET_PROPERTIES設置了工程Debug版本的生成目標后綴——它有什么作用呢?還記得您編譯OSG的時候,會生成osgd,osgDBd,osgViewerd等一系列有別于Release版本文件的鏈接庫吧,這里就是同樣的道理,這個命令將為VersionLib庫的Debug版本文件新增一個用于區分的后綴,使之成為VersionLibd.dll的形式,要知道編譯器自己可不會這樣做。

    之后我們依次調用三個自定義的宏函數,也就是前文說過的INCLUDE_FOR_PROJECT,LINK_PROJECT和INSTALL_PROJECT。它們的工作依次是:指定工程所用的頭文件依賴目錄(Hello庫和OSG庫的頭文件目錄);工程所需的外部鏈接庫文件絕對路徑(HelloLib.lib和osg.lib);以及指定安裝這個工程的相關生成文件到預設的目錄中。

    這些宏函數的具體實現過程稍后詳述。

    子工程test的腳本代碼為:
    1. SET( EXAMPLE_NAME Test )
    2. SET( EXAMPLE_FILES
    3.     test.cpp
    4. )
    5. ADD_EXECUTABLE( ${EXAMPLE_NAME} test.cpp )
    6. SET_TARGET_PROPERTIES( ${EXAMPLE_NAME}
    7.                           PROPERTIES DEBUG_POSTFIX
    8.                           "${CMAKE_DEBUG_POSTFIX}" )
    9. LINK_PROJECT( ${EXAMPLE_NAME} VersionLib )
    10. INSTALL_PROJECT( ${EXAMPLE_NAME} )
    復制代碼 注意我們沒有必要為Test工程設置頭文件依賴目錄了,從test.cpp的源代碼可以看出,它只需要引用VersionLib/Version這個頭文件,(以及一個可以由編譯器自動找到的STL頭文件iostream)而這個文件的路徑位于:${PROJECT_SOURCE_DIR}/include,毫無疑問,它在前文中已經由INCLUDE_DIRECTORIES設置完畢了。

    Test子工程依賴于即將生成的VersionLib庫,不必擔心它們的編譯順序上會有什么偏差,CMake會為我們安排好一切 那么,是時候啟動CMake-GUI,設置好所需的選項和路徑,然后開始編譯VersionMe工程,觀察輸出結果,并結束這一篇“規模宏大”的教程了……不,先等一下,我們是否忘記什么了?
    用于搜索HelloLib庫的FindHello.cmake,它是怎樣編寫的?

    用于實現實用宏函數的CustomModules.cmake,它又是怎樣編寫的?

    是的,看來我們之前欠下的債務還沒有完全還清啊,那么,首先我們來解析一下:如何編寫腳本,搜索一個外部依賴庫的信息并作為CMake變量傳遞給當前工程。
    1. FIND_PATH( HELLO_INCLUDE_DIR HelloLib/Hello
    2.   PATHS
    3.   $ENV{PATH}
    4.   /usr/include/
    5.   /usr/local/include/
    6. )
    7. FIND_LIBRARY( HELLO_LIBRARY
    8.   NAMES HelloLib
    9.   PATHS
    10.   $ENV{PATH}
    11.   /usr/lib
    12.   /usr/local/lib
    13. )
    復制代碼 由于HelloLib是我們之前的章節中實現的簡單工程,因此我們也很清楚,使用它需要一些什么工作:找到一個頭文件Hello,以及找到一個靜態鏈接庫文件HelloLib.lib(或者libHelloLib.so)。為了讓之后的各個子工程都能夠正確識別這些文件的位置,我們使用兩個變量HELLO_INCLUDE_DIR和HELLO_LIBRARY返回搜索到的信息,并且這樣的命名方式也符合其它CMake搜索模塊的一貫命名原則,以及最為重要的,符合我們自定義的宏函數的執行需要!

    按照FIND_PATH和FIND_LIBRARY的語法可知,系統將自動在PATHS指定的目錄中搜索HelloLib/Hello文件和HelloLib文件的位置,并將結果返回到變量中;如果不能找到,那么CMake-GUI中會自動出現紅色的選項欄,允許用戶自己選擇文件的位置,以便正確執行后面的編譯工作。

    這里還出現了一個特殊的格式“$ENV{PATH}”,它表示搜索系統環境變量PATH中定義的所有路徑,并且可以將PATH替換為別的環境變量名稱;此外我們還可以使用形如“[HKEY_LOCAL_MACHINE\\...]”的字樣,顧名思義,它指定搜索Windows注冊表相應位置保存的路徑信息,當然這樣的寫法在Unix/Linux系統中沒有效果。

    而對于宏函數的實現,我們在這里只介紹INCLUDE_FOR_PROJECT的寫法,并期望讀者朋友能夠舉一反三,自行閱讀和實驗其它兩個宏函數的內容,理解和更新它們的用法,進而設計出屬于您自己的更加豐富多彩的CMake腳本來。宏函數的內容如下:
    1. MACRO( INCLUDE_FOR_PROJECT PROJNAME )
    2.     FOREACH( varname ${ARGN} )
    3.         IF( ${varname}_INCLUDE_DIR )
    4.             INCLUDE_DIRECTORIES( "${${varname}_INCLUDE_DIR}" )
    5.         ELSE( ${varname}_INCLUDE_DIR )
    6.             INCLUDE_DIRECTORIES( "${varname}" )
    7.         ENDIF( ${varname}_INCLUDE_DIR )
    8.     ENDFOREACH( varname )
    9. ENDMACRO( INCLUDE_FOR_PROJECT PROJNAME )
    復制代碼 如我們在上一節介紹的那樣,宏函數INCLUDE_FOR_PROJECT包含了至少一個輸入參數PROJNAME,但事實上它并沒有在函數體中發揮作用,而是僅僅用來標示當前調用宏函數的工程名稱。此外,有關${ARGN}的意義,也請大家回顧一下之前的內容,它的作用是排列所有“隱式輸入”的參數,例如:
    1. INCLUDE_FOR_PROJECT( VersionLib HELLO OSG )
    復制代碼 這一函數調用傳入的實際參數,${PROJNAME}對應“VersionLib”,而${ARGN}對應“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}本身傳遞給子工程,換句話說,對于這個宏函樹來說,下面的調用形式:
    1. INCLUDE_FOR_PROJECT( VersionLib “E:/Projects/HelloWorld” )
    復制代碼 也是可以的。它將直接轉換為INCLUDE_DIRECTORIES(“E:/Projects/HelloWorld”)的形式。

    完成后的CMake-GUI界面如下,注意筆者使用了“out-of-source”的編譯方式,并且設置了與源代碼目錄不同的安裝目錄:
    RFID設備管理軟件
    最后的編譯生成和執行結果如下。怎么樣,是否覺得這個其實再簡單不過的工程突然變得十分專業了呢?您的工程也可以如此!
    RFID設備管理軟件

    1.6 參考資料

    好了,本以為只是《你所不知的OSG》中普普通通的一章,卻不料其內容遠遠超出了筆者的預計,不知是否也遠遠超出了您對于冗長文章的忍耐能力?(^_^)不過這依然只是管中窺豹,見一斑而已——CMake網站上已經提供了最新教材書籍的購買方式,包括《Mastering CMake》等四本相關圖書已經出版,而另有四本正緊鑼密鼓地籌劃之中……怎么樣,光是出版物的規模就已經如此宏大。您是否要驚嘆一下,這小小的“輔助編譯生成系統”,實際上卻大有文章呢?

    本文所述的內容只是最淺顯的CMake應用,對于命令語法的解釋非常不全面。并且為了保持文章的“初級性”,刪去了不少高級用戶才可能用到的指令和枚舉參數,以及正則表達式,數組賦值,字符串規則等一系列深入有趣卻難以再納入本文篇幅的內容。如是種種,還需要讀者朋友自行鉆研發掘,并幫助其發展壯大。

    以下列出了可用的CMake學習參考資料網址。

     

    RFID管理系統集成商 RFID中間件 條碼系統中間層 物聯網軟件集成
    最近免费观看高清韩国日本大全