[unix]用cmake寫hello world

cmake

cmake:開源庫跨平臺編譯,測試,打包工具。主要是通過使用平臺獨立配置文件來產生配置文件以及生成基於平臺的makefile來控制軟件的編譯過程。
cmake可以:
1. 能夠自動尋找軟件所需要的程序,庫文件,頭文件等。這些文件可能因爲平臺的不同而不同。
2. 能夠獨立於源代碼文件夾,在其他地方生成build文件。
3. 能夠生成複雜,可控的版本文件。
4. 有選擇的控制編譯所需要的文件。
5. 方便的在共享庫和靜態庫間切換。
6. 依據平臺生成makefile。

基礎的cmake用法以及語法

要使用cmake非常簡單,只要新建若干個CMakeLists文件就能控制編譯過程。CMakeLists文件需要包含對於如何構建該項目的描述。這些語句成爲一系列的指令(command),有着如下的形式:

command (args...)
command是指令的名稱,而args則是由白字符分割的一串參數。cmake是大小寫不敏感的。

cmake 支持簡單變量,變量可以通過使用像${VAR} 的語法來引用。當然也可以爲一個變量設置多個參數成爲一個參數組,比如
set(Foo a b c)
就將Foo這個變量設置爲a b c,這樣就能將命令寫成如下形式:
command("${Foo}") 等同於 command("a b c")
系統變量可以通過這種形式訪問到。

Hello World cmake 0.1版

#include <stdio.h>


int main(int argc, char **argv)
{
    printf("hello world!\n");
    return 0;
}

要生成生成這麼簡單的一個可執行文件只需要在CMakeLists.txt中加上兩句

project(hello)
add_executable(hello hello.c)

然後在當前目錄下

$ cmake .
$ make
$ ./hello

即可看到效果了。

project命令指明瞭工作空間以及隨後add_executable命令將可執行目標添加進構建過程。

Hello World cmake 0.2版

這一版本中,我們將hello world程序打散,拆成多個文件。
hello.c

#include <stdio.h>

int main(int argc, char** argv)
{
    say_hello();
    return 0;
}

func.c

void say_hello()
{
    printf("hello world!\n");
}

這一次我們發現say_hello函數將打印出hello world,而該函數則在獨立的func.c文件中存在
這次我們CMakeLists.txt中變成了這樣

project(hello)
add_executable(hello hello.c func.c)

Hello World cmake 0.3版

在本版的基礎上,我們將加入跨平臺支持。
hello.c

#include <stdio.h>


int main(int argc, char **argv)
{
    hello_all();
    say_hello();
    return 0;
}

hello_all爲全平臺支持函數,而say_hello依據平臺不同而有不同表現。
func_all.c 全平臺支持實現

void hello_all(){
    printf("hello world,my friends.\n");
}

func_app.c/func_uni.c/func_win.c

void say_hello(){
    printf("hello world,apple\n");
    //printf("hello world,linux\n"); uni.c
    //printf("hello world,windows\n"); win.c
}

最後我們的CMakeLists.txt文件則需要一點點的邏輯判斷,判斷哪些是文件是需要被編譯進執行程序的。

cmake_minimum_required(VERSION 3.0)
project(hello)
set(HELLO_SRCS hello.c func_all.c)
if(WIN32)
    set(HELLO_SRCS ${HELLO_SRCS} func_win.c)
elseif(APPLE)
    set(HELLO_SRCS ${HELLO_SRCS} func_app.c)
elseif(UNIX)
    set(HELLO_SRCS ${HELLO_SRCS} func_uni.c)
endif()

add_executable(hello ${HELLO_SRCS})

cmake語法

CMakeLists由註釋,命令以及白字符租車個。
註釋由#前綴直到該行結束爲止。
命令則由命令名稱,括號(),白字符,分割參數等構成。所有白字符都被忽略,除了那些用來分隔參數的。\可以用來放置某些字符被當作通常意義上的解析它們。
基本命令:

project

語法:project(projectname [CXX] [C [Java] [NONE])
解釋:如果沒有選擇語言,則項目默認解釋爲C/C++的。如果爲NONE則不支持任何語言。同時若是制定了CXX也就同時指定了C與語言。
對於任何出現在項目中的project命令,cmake將會創建頂級的IDE項目文件,
項目獎包涵出現在其中的所有Target,以及任何通過add_subdirectory載入的子目錄。如果add_subdirectory命令使用了EXCLUDE_FROM_ALL選項,那麼生成的子項目不會出現在頂級的Makefile文件,這點特性十分有用,當你生成對於項目主體無關乎重要的子項目。

流控制

像其他語言一樣,cmake也提供了流控制結構來幫助你解決項目構建問題。
cmake提供瞭如下的三種流控制結構:
1. 條件判斷(e.g if)
2. 循環(e.g foreach and while)
3. 過程定義(e.g macro and function)

if

如同其他語言中的if一樣,cmake中的if具有相同的意義和用法。
語法:

if(FOO)
    #do something here
else(FOO)
    #do something else
endif(FOO)

事實上以上的FOO變量條件是可以不寫的,所以就有了以下形式的:

if(FOO)
    #do something here
else()
    #do something else
endif()

但是當你寫上了變量條件的時候就提供了額外的機會進行差錯檢查。
比如如下的寫法就是錯誤的:

set(FOO 1)

if(${FOO})
    #do something
endif(1)
    #ERROR

就像其他語言一樣,cmake也支持elseif語法
語法:

if(MSVC80)
    #do something here
elseif(MSVC90)
    #do something else
elseif(APPLE)
    #do something else
endif()

但是if指令同時也存在缺陷,即它不支持c語言形式的表達式,例如${FOO} && ${BAR} || ${FUBAR}
這裏就要將以下if最基本語法:
if(variable): 當變量的值非空時候爲True,e.g 0,FALSE,OFF,or NOTFOUND 就爲False
if(NOT variable):當變量的值爲空時候爲True
if(variable1 AND variable2): 當都爲True時整個表達式才爲True
if(variable1 OR variable2): 當有一個爲True時,才爲True
if(COMMAND command-name):當提供的comman-name 可以執行的時候纔會是True
if(DEFINED variable):只要定義過該變量就爲True,無關乎設置了什麼值
if(EXISTS file-name)/if(EXISTS directory-name):當文件或者是目錄存在時爲True
if(IS_DIRECTORY directory-name)/if(IS_ABSOLUTE name):當提供的name 爲目錄名或者是文件名時爲True,絕對路徑有效。
if(name1 IS_NEWER_THAN name2):當文件name1 比 文件name2 修改時間新的時候爲True
if(variablle MATCHES regex)/if(string MATCHES regex):當匹配相應的表達式的時候爲True
另外提供的選項如:
EQUAL,LESS,GREATER可以用來進行數值比較
STRLESS,STREQUAL,STRGREATER 可以用來進行文本比較
VERSION_LESS,VERSION_EQUAL,VERSION_GREATER可以用來比較major[.minor[.patch[.tweak]]]形式的版本信息.

對於以上出現的運算符都存在着運算優先級:
1. 對於括號()所包裹住的表達式具有最高優先級
2. 然後纔是EXIST,COMMAND,DEFINED以及類似的前綴運算符
3. 之後是EQUAL,LESS,GREATER,STREQUAL,STRLESS,STRGREATER,MATCHES運算符
4. 其次是NOT運算符
5. 最後纔是AND,OR
cmake 認爲以下值爲真值:ON,1,YES,TRUE,Y,以下值則爲假值:OFF,0,NO,FALSE,N,NOTFOUND,*-NOTFOUND,IGNORE.這些值對於大小寫不敏感。

foreach

語法:

foreach(tfile TESTCASE1 TESTCASE2 TESTCASE3 TESTCASE4 TESTCASE5)
    #do something
    #可以用${tfile}來訪問每一次迭代過程中的變量
endforeach(tfile)

while

語法:

while(condtion expression)
    #do something
endwhile()

function

語法:

function(function_name arg0 arg1 ...)
    #do something
    #可以通過arg0,arg1..來訪問函數參數
endfunction()

#調用
function_name(arg0 arg1 ..)

這裏需要注意的是,參數是有作用於的,比如在父作用域中,調用某個子過程,則該子過程不會獲得父作用域中的變量,只可以通過參數傳遞。同時對於子過程中對於變量值的改變不會直接的發生在父作用域。除非通過域聲明,比如:

function(DeterminTime _time)
    # pass the result up to whatever invoked this
    set(${_time} "1:23:45" PARENT_SCOPE)
endfunction()

DeterminTime(current_time)

if(DEFINED current_time)
    message("now is ${current_time}")
endif()

macro

語法:

macro(macro_name arg0 arg1)
    #do something
    #just like function
endmacro(macro_name)

#調用
macro_name(arg0 arg1)

macro與function定義過程類似,但是與之不同的是macro不會像function那樣改變作用域,即macro調用與父作用域是處於同一作用域的,即類似域c語言中的macro定義一樣。

正則表達式

表達式的語法基本類似於其他語言:
^:以什麼開頭的字符串
$:以什麼結尾的字符串
.:只匹配一個字符
[]:匹配任何在括號內的字符
[^]:匹配人和不在括號內的字符
[-]:匹配人任何在括號內範圍的字符
*:匹配任何該符號前面的模式0 or 多次
+:匹配任何該符號前面的模式至少一次
?:匹配任何該符號前面的模式至多一次
():保存該組內匹配的表達式值,以便在之後使用
(|):匹配左邊或者右邊的表達式
以上基本語法看個人發揮,可隨意組合

檢查cmake的版本

由於cmake 版本更新,所以會有某些新的指令,對於老版本的cmake不兼容問題。所以可以有如下幾種解決方式:
1. 對於單獨的某個命令 可以測試是否可以使用

# test if the command exists

if(COMMAND some_new_command)
    #use command
    some_new_command(ARGS...)
endif()
  1. 通過比較cmake的版本信息來確定
# look for newer version of cmake
#if(${CMAKE_VERSION} VERSION_GREATER 3.0)
    #do something
endif()
  1. 當然也可以在CMakeLists.txt的起始來指定cmake的最低版本
cmake_minimum_required(VERSION 3.0)

...

模塊(Module)的使用

代碼重用技術可以提高軟件的生產效率,同時cmake 也對代碼重用技術提供了支持。
cmake 安裝包內置 某些共享庫,靜態庫的配置文件,這些配置在/usr/share/cmake/Modules文件夾內,極少數安裝的軟件也會將配置文件放在/usr/local/share/xxx/xxxConfig.cmake
/usr/local/share/cmake目錄下的文件類似於:

...
FindosgSim.cmake
FindosgTerrain.cmake
FindosgText.cmake
FindosgUtil.cmake
...

這些配置文件以.cmake結尾。這些.cmake文件只是指令的集合,就比如FindQt.cmake:

...
file(GLOB GLOB_TEMP_VAR /usr/lib*/qt-3*/bin/qmake /usr/lib*/qt3*/bin/qmake)
if(GLOB_TEMP_VAR)
  set(QT3_INSTALLED TRUE)
endif()
set(GLOB_TEMP_VAR)

file(GLOB GLOB_TEMP_VAR /usr/local/qt-x11-commercial-3*/bin/qmake)
if(GLOB_TEMP_VAR)
  set(QT3_INSTALLED TRUE)
endif()
set(GLOB_TEMP_VAR)
...

就比如說我像編譯的時候加入Qt的庫文件,那麼CMakeLists.txt文件可以這麼寫:

include(FindQt)

target_link_libraries(Foo ${Qt_LIBRARY})

cmake將會在CMAKE_MODULE_PATH變量指定的目錄中尋找cmake文件,如果找不到則會在cmake安裝目錄的Modules子目錄中尋找。

Modules大致可以分爲一下幾種類別:
1. Find Modules:這些模塊決定了軟件元素,比如說頭文件和庫文件位置。
2. System Introspection Modules:系統自省模塊,主要是測試系統所支持的float長度等一些系統限制
3. Utility Modules:工具模塊,提供了對某些情況的支持

Find Modules

cmake提供了一些Find Modules,這些模塊指定了頭文件以及庫文件的位置,如果找不到這些模塊,那麼可以用戶爲這些屬性設置緩存條目。
就拿FindPNG.cmake來舉例子吧:

# FindPNG
#
# Find libpng, the official reference library for the PNG image format.
#
# This module will set the following variables in your project:
#
# ``PNG_INCLUDE_DIRS``
#   where to find png.h, etc.
# ``PNG_LIBRARIES``
#   the libraries to link against to use PNG.
# ``PNG_DEFINITIONS``
#   You should add_definitons(${PNG_DEFINITIONS}) before compiling code
#   that includes png library files.
# ``PNG_FOUND``
#   If false, do not try to use PNG.
# ``PNG_VERSION_STRING``
#   the version of the PNG library found (since CMake 2.8.8)
#
# The following variables may also be set, for backwards compatibility:
#
# ``PNG_LIBRARY``
#   where to find the PNG library.
# ``PNG_INCLUDE_DIR``
#   where to find the PNG headers (same as PNG_INCLUDE_DIRS)
#
# Since PNG depends on the ZLib compression library, none of the above
# will be defined unless ZLib can be found.

if(ZLIB_FOUND)
  find_path(PNG_PNG_INCLUDE_DIR png.h
  /usr/local/include/libpng             # OpenBSD
  )

  list(APPEND PNG_NAMES png libpng)

  if(NOT PNG_LIBRARY)
    find_library(PNG_LIBRARY_RELEASE NAMES ${PNG_NAMES})
    find_library(PNG_LIBRARY_DEBUG NAMES ${PNG_NAMES_DEBUG})
    include(${CMAKE_CURRENT_LIST_DIR}/SelectLibraryConfigurations.cmake)
    select_library_configurations(PNG)
    mark_as_advanced(PNG_LIBRARY_RELEASE PNG_LIBRARY_DEBUG)
  endif()
  unset(PNG_NAMES)

  # Set by select_library_configurations(), but we want the one from
  # find_package_handle_standard_args() below.
  unset(PNG_FOUND)

  if (PNG_LIBRARY AND PNG_PNG_INCLUDE_DIR)
      # png.h includes zlib.h. Sigh.
      set(PNG_INCLUDE_DIRS ${PNG_PNG_INCLUDE_DIR} ${ZLIB_INCLUDE_DIR} )
      set(PNG_INCLUDE_DIR ${PNG_INCLUDE_DIRS} ) # for backward compatiblity
      set(PNG_LIBRARIES ${PNG_LIBRARY} ${ZLIB_LIBRARY})

      if (CYGWIN)
        if(BUILD_SHARED_LIBS)
           # No need to define PNG_USE_DLL here, because it's default for Cygwin.
        else()
          set (PNG_DEFINITIONS -DPNG_STATIC)
        endif()
      endif ()
  ...

這裏我只是摘取了部分的FindPNG.cmake內容,從前面的註釋部分可以看到該部分聲明瞭該.cmake文件定義了一些可以在CMakeLists.txt中可以使用的變量,e.g:PNG_LIBRARIES,PNG_INCLUDE_DIRS。後面的主體部分則是如何定義這些變量。

find_path命令用來確定PNG的include 文件,第一個參數是找到的路徑保存在哪個變量中,第二個參數是要找到的頭文件名,第三個參數是需要查找的路徑。
find_library 命令用來確定PNG的lib庫文件
之後就是設置之某些變量的過程了。

System Introspection Modules

這類模塊通常以Test 或者Check爲前綴,比如TestBigEndian,CheckTypeSize

庫鏈接

通用的鏈接方式

target_link_libraries(myexe /path/to/libfoo.a)
myexe是需要鏈接的目標名,/path/to/libfoo.a是庫文件地址
多個庫需要鏈接進目標:
target_link_libraries(myexe /path/to/libA.so /path/to/libB.so)

鏈接系統庫

在unix-like系統中也會提供某些lib庫文件,這些文件放在/usr/lib or /lib文件夾中,這些文件夾被認爲是默認搜索目錄。
比如一下代碼:

find_library(M_LIB m)
target_link_libraries(myexe ${M_LIB})

find_library(M_LIB m)將會找到/usr/lib/libm.so

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章