天天看點

使用 CMake 進行跨平台軟體開發建構系統 Installing CMake Simple CMake CMake Commands 條件編譯 What about Subdirectories? 子目錄? 回到Zlib To Infinity and Further

作者:Andrej Cedilnik 翻譯:宇舟

在每個系統建構你的工程,而無須關心建立可執行檔案和動态庫的具體方法。

當觀察一大堆工程,會發現一件事:對建構過程的描述總是存儲在一組檔案中。這些檔案可能是簡單的shell腳本、 Makefiles、Jam檔案、基于複雜的腳本的工程像Autoconf和Automake。

最近,一個新的玩家 CMake 加入了軟體建構遊戲。CMake 使用原生建構工具,像 Make 甚至是微軟的 Visual Studio,而不直接是一個的建構程式。支援多個平台,in-source 和 out-source 建構,跨庫依賴檢測,并行建構,可配置的頭檔案。極大的降低了跨平台軟體開發和維護過程的複雜性。

建構系統

觀察下大部分軟體開發工程,無疑你會面對一個共同的問題。你有一大堆源檔案,一些檔案依賴于其他的,你想生成最終的二進制 檔案。有時候你想做更複雜的事,但是在大多數情況下就這樣。

你有個小的工程想在你linux上建構。你坐下很快的寫出下面的 Makefile:

main.o: main.c main.h

        cc -c main.c

MyProgram: main.o

        cc -o MyProgram main.o -lm -lz

      

當檔案寫好後,你要做的隻是輸入 make 指令,然後工程被建構。如果任何檔案被改變,所有必要的檔案會被重新建構。很好,現在你可以祝賀自己并喝一杯。

除非,你的老闆過來說:“我們剛得到一個新的XYZ型計算機,你要在上面建構這個軟體。”是以,你把檔案複制過去,輸入 make 指令,并得到下面的錯誤資訊:

你知道那個XYZ型計算機上有個編譯器叫做 cc-XYZ ,是以修改 Makefile 後重試。但是發現系統沒有zlib。是以你去掉 -lz 參數,直接包進zlib的代碼,搞定。

就像你看到的,這個問題是當使用 Makefile 時,隻要檔案移到新的使用不同編譯器名字或參數的平台,make失效了。

看個這個問題的更詳細的例子。讓我們看下我們最愛的壓縮庫 zlib 。 zlib 是個相當簡單的庫,由17個C源檔案和11個頭檔案組成。編譯zlib是簡單的。所有要做的是編譯每個C檔案然後把他們連結到一起。你可以寫個 Makefile 解決他,但是在每個單獨平台下,你必須去修改 Makefile 以便可用。

像 Autoconf 和 Automake 之類的工具在UNIX和UNIX類平台下很好的解決了這個問題。但是他們通常太複雜。使事情變的更糟糕了,在大多數工程中,開發者最終要在 Autoconf的輸入檔案中寫shell腳本。結果很快變成依賴于開發者的假設。因為,Autoconf的結果依賴于shell,這些配置檔案在沒有 Bourne Shell或者類似标準/bin/sh的平台上是無效的。Autoconf 和 Automake 也依賴于幾個系統上安裝的工具。

CMake 是這些問題的一個解決方案。相對于其他類似工具,CMake 對底層系統做更少的假設。CMake使用标準C++實作,是以他可以在大多數現代作業系統上運作。它不使用除了系統的本地建構工具外的其他的工具。

Installing CMake

在一些平台下,像 Debian GNU/Linux ,CMake 是标準包。對于大多數其他平台,包括UNIX、Mac OS X、Microsof Windows,CMake 二進制包可以直接從 CMake Web site 下載下傳。你可以嘗試執行 cmake --help 以檢測CMake是否被安裝。這個指令會顯示CMake版本和使用資訊。

Simple CMake

現在CMake安裝好了,我們可以在我們的工程中使用他了。我們要先準備CMake的輸入檔案叫做 CMakeLists.txt 。例如,下面是個簡單的 CMakeLists.txt:

PROJECT(MyProject C)

ADD_LIBRARY(MyLibrary STATIC libSource.c)

ADD_EXECUTABLE(MyProgram main.c)

TARGET_LINK_LIBRARIES(MyProgram MyLibrary z m)

      

使用 CMake 建構工程是極簡單的。在包含CMakeLists.txt 的目錄中,輸入下面的2個指令,path是到源碼的路徑:

cmake path

make

      

CMake 讀取 CMakeLists.txt 檔案從源目錄,在目前目錄下為系統産生适當的Makefiles。CMake 維護依賴的頭檔案的清單,是以依賴檢測是被確定的。如果你要添加更多的源檔案,隻要簡單的添加到清單中。當 Makefiles 産生後,你不必再執行 CMake ,因為對 CMakeLists.txt 的依賴檢測已經添加到産生的 Makefils 中。如果你想確定依賴重新産生,你可以執行 make depend 。

CMake Commands

CMake 本質上是一個簡單的解釋器。CMake 輸入檔案有一個極度簡單但是強大的文法。它由指令、原始流控制構造、宏和變量組成。所有的指令有完全一樣的文法:

例如,指令 ADD_LIBRARY 指定一個庫應該被建立。第一個參數是庫的名字,第二個可選參數是用來指定這個庫是靜态的還是動态的,其他的參數是源檔案的清單。你想要動态庫?簡單的用 SHARED 替換 STATIC。所有的指令的清單請看 CMake 的文檔。

還有一些流控制構造,例如 IF 和 FOREACH 。IF 中的表達式不能包含其他指令,可以使用NOT、AND、OR。下面是一個通常使用 IF 語句的例子:

IF(UNIX)

  IF(APPLE)

    SET(GUI "Cocoa")

  ELSE(APPLE)

    SET(GUI "X11")

  ENDIF(APPLE)

ELSE(UNIX)

  IF(WIN32)

    SET(GUI "Win32")

  ELSE(WIN32)

    SET(GUI "Unknown")

  ENDIF(WIN32)

ENDIF(UNIX)

MESSAGE("GUI system is ${GUI}")

      

這個例子展現了對 IF 語句和變量的使用。

FOREACH 指令的參數,第一個是儲存疊代内容的變量,後面的是被疊代的清單。例如,如果有一列可執行檔案需要建立,每個可執行檔案是被從同名源檔案建立,可以像下面 這樣使用FOREACH:

SET(SOURCES source1 source2 source3)

FOREACH(source ${SOURCES})

  ADD_EXECUTABLE(${source} ${source}.c)

ENDFOREACH(source)

      

使用宏構造可以定義一個宏。當我們要經常建立一些可執行檔案并且要連結一些庫。下面的宏可以是我們的生活更簡單點。在這個 例子 中,CREATE_EXECUTABLE 是宏的名字,其他的是參數。在宏内部,所有的參數被看作變量。宏一被建立,即可被當作正常指令使用。CREATE_EXECUTABLE的定義和使用象這 樣:

MACRO(CREATE_EXECUTABLE NAME

  SOURCES LIBRARIES)

  ADD_EXECUTABLE(${NAME} ${SOURCES})

  TARGET_LINK_LIBRARIES(${NAME}

    ${LIBRARIES})

ENDMACRO(CREATE_EXECUTABLE)

ADD_LIBRARY(MyLibrary libSource.c)

CREATE_EXECUTABLE(MyProgram main.c MyLibrary)

      

宏不等價于一般程式語言中的函數或過程,宏不能被遞歸調用。

條件編譯

好的建構程式的一個重要特性是能将建構的一部分打開或關閉。建構程式也應該能找到和設定好你的工程需要的系統資源的位置。 所有這些功能在 CMake 中使用條件編譯實作。讓我示範一個例子。讓我們假設你的工程有2個模式,正常模式和調試模式。調試模式添加一些調試代碼到正常代碼中。是以,你的代碼中充 滿這樣的代碼片段:

#ifdef DEBUG

  fprintf(stderr,

          "The value of i is: %dn", i);

#endif /* DEBUG */

      

為了告訴 CMake 添加一個 -DDEBUG 到編譯指令中,你可以對屬性COMPILE_FLAGS使用SET_SOURCE_FILES_PROPERTIES指令。但是可能你不想每次都通過修改 CMakeLists.txt 檔案以在調試模式和正常模式中切換。OPTION指令可以用來建立一個布爾型變量可以用于被在建構工程前設定。前一個例子可以被改進為:

OPTION(MYPROJECT_DEBUG

  "Build the project using debugging code"

  ON)

IF(MYPROJECT_DEBUG)

  SET_SOURCE_FILE_PROPERTIES(

    libSource.c main.c

    COMPILE_FLAGS -DDEBUG)

ENDIF(MYPROJECT_DEBUG)

      

現在,你問:“我該怎樣設定這個變量?” CMake 帶了3個GUI工具。在UNIX類系統中,有一個終端GUI工具叫做ccmake,這是一個基于文本的,可以在一個遠端終端連接配接上使用的。CMake也有 微軟Windows和Mac OS X版本的GUI工具。

當你在使用CMake産生的Makefiles,如果你已經執行過CMake,現在隻要輸入指令 make edit_cache 。這個指令會運作一個合适的GUI工具。在所有的GUI工具中,你有一些用于設定變量的選項。就像你看到的,CMake有幾個預設選項,例如 EXECUTABLE_OUTPUT_PATH 、LIBRARY_OUTPUT_PATH(可執行文結合庫檔案被輸出到的路徑),在我們前面的例子中還有MYPROJECT_DEBUG。當改變一些變 量的值後,你按下配置按鈕或c鍵(在ccmake中)。

在GUI工具中,你将設定幾種不同類型的條目。MYPROJECT_DEBUG是布爾型的,另一種常見的變量類型是路 徑。假設我們的程式依賴于Pyhon.h檔案的位置。我們在CMakeLists.txt檔案中加入下面的用于嘗試尋找一個檔案的指令:

FIND_PATH(PYTHON_INCLUDE_PATH Python.h

  /usr/include

  /usr/local/include)

      

因為在每個單獨的工程中都去重複的指定所有的位置是浪費的。你可以包含(include)其他叫做子產品 (modules)的CMakes檔案。 CMake自帶一些有用子產品,從用于搜尋不同軟體包的到幹一些實際的事或定義一些宏的子產品。全部的子產品清單請看CMake的子產品子目錄。例如,有個子產品叫 做FindPythonLibs.cmake,可以在大多數系統的用于查找Python庫檔案和頭檔案路徑。然而如果CMake不能找到你要的,你總是可 以在GUI工具中指定。你也可以通過指令行通路CMake變量。下面的一行指令設定MYPROJECT_DEBUG為OFF:

What about Subdirectories? 子目錄?

作為一個軟體開發者,你可能要把源代碼組織到子目錄中。不同的子目錄可以代表不同的庫、可執行檔案、測試、文檔。現在我 們可以啟用或禁用子目錄以便 建構工程的一部分跳過另一部分。使用SUBDIRS 指令告訴 CMake 處理一個子目錄。這個指令使CMake到指定的子目錄下去找 CMakeLists.txt 檔案。使用這個指令可以使我們的工程更有組織。我們移動所有的庫檔案到庫子目錄中,頂級 CMakeLists.txt 現在看起來像這樣:

PROJECT(MyProject C)

SUBDIRS(SomeLibrary)

INCLUDE_DIRECTORIES(SomeLibrary)

ADD_EXECUTABLE(MyProgram main.c)

TARGET_LINK_LIBRARIES(MyProgram MyLibrary)

      

INCLUDE_DIRECTORIES 指令告訴編譯器到哪裡去找 main.c 用到的頭檔案。是以,即使你的工程有500個子目錄,你把你的所有源檔案都放到子目錄中,也不會有依賴問題。CMake幫你都幹了。

回到Zlib

現在,我們要個“cmake化”的zlib。從一個簡單的 CMakeLists.txt 檔案開始:

PROJECT(ZLIB)

# source files for zlib

SET(ZLIB_SRCS

adler32.c   gzio.c

inftrees.c  uncompr.c

compress.c  infblock.c

infutil.c   zutil.c

crc32.c     infcodes.c

deflate.c   inffast.c

inflate.c   trees.c

)

ADD_LIBRARY(zlib ${ZLIB_SRCS})

ADD_EXECUTABLE(example example.c)

TARGET_LINK_LIBRARIES(example zlib)

      

現在你可以建構它了。然而,有些小事要記住。首先,zlib在有些平台需要unistd.h檔案。是以,我們加上下面的 測試代碼:

INCLUDE (

  ${CMAKE_ROOT}/Modules/CheckIncludeFile.cmake)

CHECK_INCLUDE_FILE(

  "unistd.h" HAVE_UNISTD_H)

IF(HAVE_UNISTD_H)

  ADD_DEFINITION(-DHAVE_UNISTD_H)

ENDIF(HAVE_UNISTD_H)

      

在Windows下,我們也必須為共享庫做一些而外的事。zlib需要加上-DZLIB_DLL參數編譯,以使導出宏正 确。是以,我們加上下面的選項:

OPTION(ZLIB_BUILD_SHARED

  "Build ZLIB shared" ON)

IF(WIN32)

  IF(ZLIB_BUILD_SHARED)

    SET(ZLIB_DLL 1)

  ENDIF(ZLIB_BUILD_SHARED)

ENDIF(WIN32)

IF(ZLIB_DLL)

  ADD_DEFINITION(-DZLIB_DLL)

ENDIF(ZLIB_DLL)

      

雖然這樣也能工作,但是有一個更好的方法。我們可以配置一個頭檔案以代替傳入ZLIB_DLL和 HAVE_UNISTD_H。我們要準備一個使用cmake标記的輸入檔案。下面是zlibConfig.h,一個zlib包含檔案的例子:

#ifndef _zlibConfig_h

#define _zlibConfig_h

#cmakedefine ZLIB_DLL

#cmakedefine HAVE_UNISTD_H

#endif

      

這裡,#cmakedefine VAR 被替換為 #define VAR或者#undef VAR,依賴于cmake中VAR是否被定義。我們使用下面的CMake指令讓CMake建立檔案 zlibConfig.h

CONFIGURE_FILE(

  ${ZLIB_SOURCE_DIR}/zlibDllConfig.h.in

  ${ZLIB_BINARY_DIR}/zlibDllConfig.h)

      

To Infinity and Further

通過這篇文章,你可以開始在你的日常工作中使用CMake。如果你的代碼足夠可以移植,現在你可以連接配接到你的朋友的 AIX系統上建構你的工程。CMake檔案也比Makefiles檔案容易了解,是以你的朋友可以檢查你的遺漏。

然而,這個例子涉及的比較淺,CMake有能力幹許多其他的工作。在1.6版中,你可以做平台獨立的TRY_RUN和 TRY_COMPILE建構, 以友善的測試系統的能力。CMake原生支援C和C++,但是有限支援建構java檔案。通過一些努力,你可以建構任何東西從Python、Emacs腳 本到LaTeX文檔。使用CMake作為測試架構驅動,你可以實作平台獨立的回歸測試。如果你想走的更遠,你可以使用CMake的C API為CMake寫一個插件,以添加你自己的指令。

CMake is being actively used in several projects such as VTK and ITK. Its benefits are enormous in traditional software development, however they become even more apparent, when portability is necessary. By using CMake for software development, your code will be significantly more "open", because it will build on a variety of platforms.

譯者注:

        CMake 已經發展到版本2.6了,這篇文章原文是2003寫的,當時才1.6。現在已經有了很大改進了。KDE4整個工程檔案就是用CMake的。

上一篇: [0] CMake綜述

繼續閱讀