首页 > 代码库 > cmake 学习笔记(一)

cmake 学习笔记(一)

  • 最大的Qt4程序群(KDE4)採用cmake作为构建系统
  • Qt4的python绑定(pyside)採用了cmake作为构建系统
  • 开源的图像处理库 opencv 採用cmake 作为构建系统
  • ...

看来不学习一下cmake是不行了,一点一点来吧,找个最简单的C程序,慢慢复杂化,试试看:

样例一

单个源文件 main.c

样例二

==>分解成多个 main.c hello.h hello.c

样例三

==>先生成一个静态库,链接该库

样例四

==>将源文件放置到不同的文件夹

样例五

==>控制生成的程序和库所在的文件夹

样例六

==>使用动态库而不是静态库

样例一

一个经典的C程序,怎样用cmake来进行构建程序呢?

//main.c
#include <stdio.h>
int main()
{
    printf("Hello World!/n");
    return 0;
}

编写一个 CMakeList.txt 文件(可看做cmake的project文件):

project(HELLO)
set(SRC_LIST main.c)
add_executable(hello ${SRC_LIST})

然后,建立一个随意文件夹(比方本文件夹下创建一个build子文件夹),在该build文件夹下调用cmake

  • 注意:为了简单起见,我们从一開始就採用cmake的 out-of-source 方式来构建(即生成中间产物与源码分离),并始终坚持这样的方法,这也就是此处为什么单独创建一个文件夹,然后在该文件夹下运行 cmake 的原因
cmake .. -G"NMake Makefiles"
nmake

或者

cmake .. -G"MinGW Makefiles"
make

就可以生成可运行程序 hello(.exe)

文件夹结构

+
| 
+--- main.c
+--- CMakeList.txt
|
/--+ build/
   |
   +--- hello.exe

cmake 真的不太好用哈,使用cmake的过程,本身也就是一个编程的过程,仅仅有多练才行。

我们先看看:前面提到的这些都是什么呢?

CMakeList.txt

第一行 project 不是强制性的,但最好始终都加上。这一行会引入两个变量

  • HELLO_BINARY_DIR 和 HELLO_SOURCE_DIR

同一时候,cmake自己主动定义了两个等价的变量

  • PROJECT_BINARY_DIR  PROJECT_SOURCE_DIR

由于是out-of-source方式构建,所以我们要时刻区分这两个变量相应的文件夹

能够通过message来输出变量的值

message(${PROJECT_SOURCE_DIR})

set 命令用来设置变量

add_exectuable 告诉project生成一个可运行文件。

add_library 则告诉生成一个库文件。

  • 注意:CMakeList.txt 文件里,命令名字是不区分大写和小写的,而參数和变量是大写和小写相关的。

cmake命令

cmake 命令后跟一个路径(..),用来指出 CMakeList.txt 所在的位置。

因为系统中可能有多套构建环境,我们能够通过-G来制定生成哪种project文件,通过 cmake -h 可得到具体信息。

要显示运行构建过程中具体的信息(比方为了得到更具体的出错信息),能够在CMakeList.txt内增加:

  • SET( CMAKE_VERBOSE_MAKEFILE on )

或者运行make时

  • $ make VERBOSE=1

或者

  • $ export VERBOSE=1
  • $ make

样例二

一个源文件的样例一似乎没什么意思,拆成3个文件再试试看:

  • hello.h 头文件
#ifndef DBZHANG_HELLO_
#define DBZHANG_HELLO_
void hello(const char* name);
#endif //DBZHANG_HELLO_
  • hello.c
#include <stdio.h>
#include "hello.h"

void hello(const char * name)
{
    printf ("Hello %s!/n", name);
}
  • main.c
#include "hello.h"
int main()
{
    hello("World");
    return 0;
}
  • 然后准备好CMakeList.txt 文件

 

project(HELLO)
set(SRC_LIST main.c hello.c)
add_executable(hello ${SRC_LIST})

运行cmake的过程同上,文件夹结构

 

+
| 
+--- main.c
+--- hello.h
+--- hello.c
+--- CMakeList.txt
|
/--+ build/
   |
   +--- hello.exe

样例非常easy,没什么可说的。

样例三

接前面的样例,我们将 hello.c 生成一个库,然后再使用会怎么样?

改写一下前面的CMakeList.txt文件试试:

project(HELLO)
set(LIB_SRC hello.c)
set(APP_SRC main.c)
add_library(libhello ${LIB_SRC})
add_executable(hello ${APP_SRC})
target_link_libraries(hello libhello)

和前面相比,我们加入了一个新的目标 libhello,并将其链接进hello程序

然后想前面一样,执行cmake,得到

+
| 
+--- main.c
+--- hello.h
+--- hello.c
+--- CMakeList.txt
|
/--+ build/
   |
   +--- hello.exe
   +--- libhello.lib

里面有一点不爽,对不?

  • 由于我的可运行程序(add_executable)占领了 hello 这个名字,所以 add_library 就不能使用这个名字了
  • 然后,我们去了个libhello 的名字,这将导致生成的库为 libhello.lib(或 liblibhello.a),非常不爽
  • 想生成 hello.lib(或libhello.a) 怎么办?

加入一行

set_target_properties(libhello PROPERTIES OUTPUT_NAME "hello")

就能够了

样例四

在前面,我们成功地使用了库,但是源码放在同一个路径下,还是不太正规,怎么办呢?分开放呗

我们期待是这样一种结构

+
|
+--- CMakeList.txt
+--+ src/
|  |
|  +--- main.c
|  /--- CMakeList.txt
|
+--+ libhello/
|  |
|  +--- hello.h
|  +--- hello.c
|  /--- CMakeList.txt
|
/--+ build/

哇,如今须要3个CMakeList.txt 文件了,每个源文件文件夹都须要一个,还好,每个都不是太复杂

  • 顶层的CMakeList.txt 文件
project(HELLO)
add_subdirectory(src)
add_subdirectory(libhello)
  • src 中的 CMakeList.txt 文件
include_directories(${PROJECT_SOURCE_DIR}/libhello)
set(APP_SRC main.c)
add_executable(hello ${APP_SRC})
target_link_libraries(hello libhello)
  • libhello 中的 CMakeList.txt 文件
set(LIB_SRC hello.c)
add_library(libhello ${LIB_SRC})
set_target_properties(libhello PROPERTIES OUTPUT_NAME "hello")

恩,和前面一样,建立一个build文件夹,在其内执行cmake,然后能够得到

  • build/src/hello.exe
  • build/libhello/hello.lib

回头看看,这次多了点什么,顶层的 CMakeList.txt 文件里使用 add_subdirectory 告诉cmake去子文件夹寻找新的CMakeList.txt 子文件

在 src 的 CMakeList.txt 文件里,新添加了include_directories,用来指明头文件所在的路径。

样例五

前面还是有一点不爽:假设想让可运行文件在 bin 文件夹,库文件在 lib 文件夹怎么办?

就像以下显示的一样:

   + build/
   |
   +--+ bin/
   |  |
   |  /--- hello.exe
   |
   /--+ lib/
      |
      /--- hello.lib
  • 一种办法:改动顶级的 CMakeList.txt 文件
project(HELLO)
add_subdirectory(src bin)
add_subdirectory(libhello lib)

不是build中的文件夹默认和源码中结构一样么,我们能够指定其相应的文件夹在build中的名字。

这样一来:build/src 就成了 build/bin 了,但是除了 hello.exe,中间产物也进来了。还不是我们最想要的。

  • 还有一种方法:不改动顶级的文件,改动其它两个文件

src/CMakeList.txt 文件

include_directories(${PROJECT_SOURCE_DIR}/libhello)
#link_directories(${PROJECT_BINARY_DIR}/lib)
set(APP_SRC main.c)
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin)
add_executable(hello ${APP_SRC})
target_link_libraries(hello libhello)

libhello/CMakeList.txt 文件

set(LIB_SRC hello.c)
add_library(libhello ${LIB_SRC})
set(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib)
set_target_properties(libhello PROPERTIES OUTPUT_NAME "hello")

样例六

在样例三至五中,我们始终用的静态库,那么用动态库应该更酷一点吧。 试着写一下

假设不考虑windows下,这个样例应该是非常easy的,仅仅须要在上个样例的 libhello/CMakeList.txt 文件里的add_library命令中增加一个SHARED參数:

add_library(libhello SHARED ${LIB_SRC})

但是,我们既然用cmake了,还是兼顾不同的平台吧,于是,事情有点复杂:

  • 改动 hello.h 文件
#ifndef DBZHANG_HELLO_
#define DBZHANG_HELLO_
#if defined _WIN32
    #if LIBHELLO_BUILD
        #define LIBHELLO_API __declspec(dllexport)
    #else
        #define LIBHELLO_API __declspec(dllimport)
    #endif
#else
    #define LIBHELLO_API
#endif
LIBHELLO_API void hello(const char* name);
#endif //DBZHANG_HELLO_
  • 改动 libhello/CMakeList.txt 文件
set(LIB_SRC hello.c)
add_definitions("-DLIBHELLO_BUILD")
add_library(libhello SHARED ${LIB_SRC})
set(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib)
set_target_properties(libhello PROPERTIES OUTPUT_NAME "hello")

恩,剩下来的工作就和原来一样了。

 

cmake 学习笔记(一)