[unix]用cmake写hello world

本文详细介绍了如何使用cmake在Unix环境下构建项目,从基础的cmake语法到Hello World项目的逐步升级,包括project、流控制结构(if、foreach、while、function、macro)、正则表达式、版本检查以及模块(Module)的使用,特别是库链接的多种方式。cmake作为跨平台的编译、测试和打包工具,通过CMakeLists文件控制编译过程,支持变量、流控制和模块化,使得项目构建更加灵活和高效。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值