关于CMake,这篇真的很到位!!

李肖遥

共 24017字,需浏览 49分钟

 · 2020-09-19


关注、星标公众号,直达精彩内容

来源:txp玩Linux

作者 : txp

整理 : 李肖遥

最近很多读者在群里问道CMake的问题,与Makefile的差别等等,今天给大家整理了一篇文章,字数较多,希望对大家有帮助。

今天给大家分享的是工程管理工具cmake,没有先介绍makefile工程管理工具(坦白来说,这两者都差不多,cmake最终还是会生成Makefile的,只是说cmake语法稍微比较简单一些,没有Makefile那么复杂!)。

就自己个人经历,现在一般公司去写Makefile和cmake的比较少(当然去招聘网站上,有的时候还是可以看到有这个技能要求会写的,所以说能够自己写出来是最为完美的!),一般都是直接使用厂家的Makefile或者Cmake;

但作为学习,还是要认真学习里面的原理,比如出现了错误,你要能够定位到错误并把它解决掉,因为可能错误就出现在配置好的Makefile或者Cmake里面,所以你要看的懂里面代码的意思(也就是说,你知道这个工具是这样用,但是也要明白它的原理机制,做到之知其然,知其所以个然来!),这样才能把问题解决掉。

而且就个人见解,在传统的linux工程管理用Makefile的比较多(Uboot里面也是大量使用Makefile来进行管理工程);在新型领域,比如物联网开发(特别是一些开源项目等),用Cmake的比较多(当然也有可能是例外哈!);好了,废话就不多说了,开始来学习了:

一、Cmake学习使用:

1、安装Cmake管理工具:

一般实际嵌入式linux开发,几乎都是用Ubuntu来开发的,因为那啥,安装啥应用程序的非常方便,只需一个命令“apt install + 应用程序名称” 大部分都直接搞定,不用再去配置(特殊的,就例外,还要一些其他相关配置!),安装Cmake就是一条命令直接搞定:

root@txp-virtual-machine:/home/txp# apt install cmake
Reading package lists... Done
Building dependency tree    

2、先从一个简单示例,来得出一般书写步骤规律:

这里先写一个简单的代码工程main.c,然后再使用我们的cmake来管理代码工程:

#include 

int main(void)
{
    printf("TXP嵌入式\n");
    return 0;

}

然后开始写cmake工程管理文件,我在当前目录建立一个CMakeLists.txt文件,然后再往里面开始工程管理代码

root@txp-virtual-machine:/home/txp/test# pwd
/home/txp/test
root@txp-virtual-machine:/home/txp/test# touch CMakeLists.txt
root@txp-virtual-machine:/home/txp/test# ls
CMakeLists.txt  main.c


CMakeLists.txt文件里面的内容如下:

cmake_minimum_required (VERSION 2.8)

project (main)

add_executable(main main.c)

解释一下这三条语句分别代表什么意思:

1、表示cmake最低执行版本是2.8才有效来管理我们的工程项目。

2、表示整个工程名为main

3、表示最终要生成的elf文件的名字叫main,使用的源文件是main.c

现在我们来实现cmake的功能,在当前目录下,使用命令"cmake ."(.表示当前目录,而..表示上一级目录),生成makefile等相关文件;然后再执行一下make命令进行编译工程,就能生成可执行文件main了,同时也会生成makefile文件,最后就可以执行可执行main文件,就能得到我们所要的结果:

root@txp-virtual-machine:/home/txp/test# cmake .
-- The C compiler identification is GNU 4.8.4
-- The CXX compiler identification is GNU 4.8.4
-- Check for working C compiler: /usr/bin/cc
-- Check for working C compiler: /usr/bin/cc -- works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working CXX compiler: /usr/bin/c++
-- Check for working CXX compiler: /usr/bin/c++ -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Configuring done
-- Generating done
-- Build files have been written to: /home/txp/test


root@txp-virtual-machine:/home/txp/test# ls
CMakeCache.txt  CMakeFiles  cmake_install.cmake  CMakeLists.txt  main  main.c  Makefile


root@txp-virtual-machine:/home/txp/test# make
Scanning dependencies of target main
[100%] Building C object CMakeFiles/main.dir/main.c.o
Linking C executable main
[100%] Built target main





root@txp-virtual-machine:/home/txp/test# ./main
TXP嵌入式

如果你再好奇一下的话,可以打开看看Makefile里面的内容是啥,很容易想到是用makefile的方式来实现对工程main的管理,这里我就不画蛇添足把源代码贴出来了。cmake_install.cmake 是一些相关配置选项:

# Install script for directory: /home/txp/test

# Set the install prefix
IF(NOT DEFINED CMAKE_INSTALL_PREFIX)
  SET(CMAKE_INSTALL_PREFIX "/usr/local")
ENDIF(NOT DEFINED CMAKE_INSTALL_PREFIX)
STRING(REGEX REPLACE "/$" "" CMAKE_INSTALL_PREFIX "${CMAKE_INSTALL_PREFIX}")

# Set the install configuration name.
IF(NOT DEFINED CMAKE_INSTALL_CONFIG_NAME)
  IF(BUILD_TYPE)
    STRING(REGEX REPLACE "^[^A-Za-z0-9_]+" ""
           CMAKE_INSTALL_CONFIG_NAME "${BUILD_TYPE}")
  ELSE(BUILD_TYPE)
    SET(CMAKE_INSTALL_CONFIG_NAME "")
  ENDIF(BUILD_TYPE)
  MESSAGE(STATUS "Install configuration: \"${CMAKE_INSTALL_CONFIG_NAME}\"")
ENDIF(NOT DEFINED CMAKE_INSTALL_CONFIG_NAME)

# Set the component getting installed.
IF(NOT CMAKE_INSTALL_COMPONENT)
  IF(COMPONENT)
    MESSAGE(STATUS "Install component: \"${COMPONENT}\"")
    SET(CMAKE_INSTALL_COMPONENT "${COMPONENT}")
  ELSE(COMPONENT)
    SET(CMAKE_INSTALL_COMPONENT)
  ENDIF(COMPONENT)
ENDIF(NOT CMAKE_INSTALL_COMPONENT)

# Install shared libraries without execute permission?
IF(NOT DEFINED CMAKE_INSTALL_SO_NO_EXE)
  SET(CMAKE_INSTALL_SO_NO_EXE "1")
ENDIF(NOT DEFINED CMAKE_INSTALL_SO_NO_EXE)

IF(CMAKE_INSTALL_COMPONENT)
  SET(CMAKE_INSTALL_MANIFEST "install_manifest_${CMAKE_INSTALL_COMPONENT}.txt")
ELSE(CMAKE_INSTALL_COMPONENT)
  SET(CMAKE_INSTALL_MANIFEST "install_manifest.txt")
ENDIF(CMAKE_INSTALL_COMPONENT)

FILE(WRITE "/home/txp/test/${CMAKE_INSTALL_MANIFEST}" "")
FOREACH(file ${CMAKE_INSTALL_MANIFEST_FILES})
  FILE(APPEND "/home/txp/test/${CMAKE_INSTALL_MANIFEST}" "${file}\n")
ENDFOREACH(file)
~                 

而CMakeFiles是一个文件夹,里面文件内容如下:

root@txp-virtual-machine:/home/txp/test/CMakeFiles# ls
2.8.12.2  cmake.check_cache  CMakeDirectoryInformation.cmake  
CMakeOutput.log  CMakeTmp  main.dir  Makefile2 
Makefile.cmake  progress.marks  TargetDirectories.txt

小结:
从上面简单的示例我们可以看出,Cmake所有的语句都是写在一个名为"CMakeLists.txt"的文本文件里面,所以完成一个工程用cmake管理的完整步骤如下:

1、先建立CMakeLists.txt(这跟我们用makefile来管理工程一样,都有一个特殊的文件来专门书写工程管理代码的,cmake也不例外,使用CMakeLists.txt文本文件来专门书写代码管理工程项目)。

2、然后进行往CMakeLists.txt写配置,具体简单配置参考上面的形式写,复杂的配置,我们会在下面进行演示讲解的。

3、接着使用命令"cmake .",这里必须是CMakeLists.txt同级目录下执行这个命令哈,生成makefile等文件

4、最后使用make命令进行工程编译,进而生成可执行文件。

3、同一个目录有多个源文件:

这里我们在当前目录再添加两个文件:test1.c和test1.h。test1.c文件内容如下:

#include 
#include "test1.h"

void func(int a)
{
    printf("a=%d\n",a);
}

test1.h里面的内容如下:

#ifndef _TEST1_H
#define _TEST1_H

void func(int a);

#endif /* _TEST1_H */

然后在main。c里面进行调用func()这个函数:

#include 
#include "test1.h"
int main(void)
{
    func(6);
    printf("TXP嵌入式\n");
    return 0;

}

然后这个时候我们的CMakeLists.txt里面书写形式肯定要改了:

cmake_minimum_required (VERSION 2.8)

project (main)

add_executable(main main.c test1.c)

最终结果如下:

root@txp-virtual-machine:/home/txp/test# cmake .
-- Configuring done
-- Generating done
-- Build files have been written to: /home/txp/test
root@txp-virtual-machine:/home/txp/test# make
Scanning dependencies of target main
[ 50%] Building C object CMakeFiles/main.dir/main.c.o
[100%] Building C object CMakeFiles/main.dir/test1.c.o
Linking C executable main
[100%] Built target main
root@txp-virtual-machine:/home/txp/test# ls
1  CMakeCache.txt  CMakeFiles  cmake_install.cmake  CMakeLists.txt  main  main.c  Makefile  test1.c  test1.h
root@txp-virtual-machine:/home/txp/test# ./main
a=6
TXP嵌入式

小结:

上面的CMakeLists.txt的写法,你会注意到add_executable(main main.c test1.c)多了一个源文件(你可以把源码看作成加工的原材料,在makefile里面也是这样来理解目标文件的生成!),所以如果你还要往当前目录下添加源文件的话,在书写CMakeLists.txt文本文件时,直接在第三个语句里面添加源文件;不过这种方式有缺陷,比如说,如果当前文件下有几百个源文件,这个时候你不可能一个个去手写敲吧,不然这样就又回到了原始社会,就不能体现cmake的优越性出来了!更多用法,且听下回细解!


二、用好 Cmake,高兴一整天(甚至...):

1、多个源文件,使用命令 aux_source_directory(dir var):

在上一篇文章最后结尾的时候,有一个问题,就是在同一目录下面,有多个源文件的时候,这个时候你不能都往下面第三条命令里面一直手动添加源文件,那工作效率多低啊:

cmake_minimum_required(VERSION 2.8)

project(main)

add_executable(main main.c test1.c)

于是乎为了解决这种低效率的操作,在 cmake 里面有一条指令可以完全搞定这个问题;不过为了说明问题,在这之前我又添加了两个文件:test2.c 和 test2.h:

root@txp-virtual-machine:/home/txp/test# ls
1               cmake_install.cmake  main.c    test1.h  touch1.c
CMakeCache.txt  CMakeLists.txt       Makefile  test2.c  touch1.h
CMakeFiles      main                 test1.c   test2.h

test2.c内容如下:

#include 
#include "test2.h"

void func1()
{
  printf("i like the cmake\n");
}

test2.h内容如下:

#ifndef _TEST2_H_
#define _TEST2_H_

void func1();

#endif

最后main.c里面调用了func1函数:

#include 
#include "test1.h"
#include "test2.h"
int main(void)
{
    func1();
    func(8);
    printf("TXP嵌入式\n");
    return 0;

}

接下来我们的重点就来了,在cmake里面可以使用aux_source_directory(dir var)就可以搞定上面效率低的问题,接下来我们在CMakeLists.txt这样操作:

cmake_minimum_required(VERSION 2.8)

project(main)

aux_source_directory(. SRC_LIST)

add_executable(main ${SRC_LIST})


然后再进行编译:

root@txp-virtual-machine:/home/txp/test# cmake .
-- Configuring done
-- Generating done
-- Build files have been written to: /home/txp/test


root@txp-virtual-machine:/home/txp/test# make
Scanning dependencies of target main
[ 25%] Building C object CMakeFiles/main.dir/main.c.o
[ 50%] Linking C executable main


root@txp-virtual-machine:/home/txp/test# ./main
i like the cmake
the b is 8
TXP嵌入式


说明:

aux_source_directory(. SRC_LIST):表示是把当当前目录下的所有源文件都添加到源列表变量里面去,最后用add_executable(main ${SRC_LIST})把所有有用的源文件加工成目标文件main。不过这方法也有他的缺点,就是把当前目录下的源文件都添加到变量SRC_LIST,如果我们不需要一些没有用的文件(只要拿到所需的源文件就行),可以进行这样操作:

cmake_minimum_required(VERSION 2.8)

project(main)

set(SRC_LIST
        ./main.c
        ./test1.c
        ./test2.c
         )

add_executable(main ${SRC_LIST})

这样是能够通过编译的:

root@txp-virtual-machine:/home/txp/test# cmake .
-- Configuring done
-- Generating done
-- Build files have been written to: /home/txp/test
root@txp-virtual-machine:/home/txp/test# make
[100%] Built target main

2、在上面的例子中,我们会发现同一目录下源文件比较乱,所以在cmake里面有这样的规则,可以把相同类型以及相关的源文件放到同一个目录,比如说,现在我在test目录下创建test1和test2两个目录文件,并同时把test1.c、test1.h、test2.c、test2.h分别放到这两个目录下去:

root@txp-virtual-machine:/home/txp/test# mkdir -p test1 test2
root@txp-virtual-machine:/home/txp/test# ls
@               CMakeFiles           main      test1    test2
1               cmake_install.cmake  main.c    test1.c  test2.c
CMakeCache.txt  CMakeLists.txt       Makefile  test1.h  test2.h

然后把相关文件一到这两个目录文件下去:

root@txp-virtual-machine:/home/txp/test# mv test1.c test1.h test1

root@txp-virtual-machine:/home/txp/test# mv test2.c test2.h test2
root@txp-virtual-machine:/home/txp/test# ls
@  CMakeCache.txt  cmake_install.cmake  main    Makefile  test2
1  CMakeFiles      CMakeLists.txt       main.c  test1

root@txp-virtual-machine:/home/txp/test# tree

├── cmake_install.cmake
├── CMakeLists.txt
├── main
├── main.c
├── Makefile
├── test1
│   ├── test1.c
│   └── test1.h
└── test2
    ├── test2.c
    └── test2.h


然后这个时候要修改CMakeLists.txt里面的规则属性了:

cmake_minimum_required(VERSION 2.8)

project(main)

include_directories(test1 test2)
aux_source_directory(test1 SRC_LIST)
aux_source_directory(test2 SRC_LIST1)

add_executable(main main.c  ${SRC_LIST} ${SRC_LIST1})

然后编译输出,也是能够通过的:

root@txp-virtual-machine:/home/txp/test# cmake .
-- Configuring done
-- Generating done
-- Build files have been written to: /home/txp/test
root@txp-virtual-machine:/home/txp/test# make
Scanning dependencies of target main
[ 25%] Building C object CMakeFiles/main.dir/main.c.o
[ 50%] Building C object CMakeFiles/main.dir/test1/test1.c.o
[ 75%] Building C object CMakeFiles/main.dir/test2/test2.c.o
[100%] Linking C executable main
[100%] Built target main
root@txp-virtual-machine:/home/txp/test# ls
@  CMakeCache.txt  cmake_install.cmake  main    Makefile  test2
1  CMakeFiles      CMakeLists.txt       main.c  test1
root@txp-virtual-machine:/home/txp/test# ./main
i like the cmake
the b is 8
TXP嵌入式

说明:

这里出现了一个新的命令:include_directories。该命令是用来向工程添加多个指定头文件的搜索路径,路径之间用空格分隔。

其实在实际开发工程中,一般会把源文件放到src目录下,把头文件放入到include文件下,生成的对象文件放入到build目录下,最终输出的elf文件会放到bin目录下,这样让人看起来一目了然


、Cmake中添加链接库文件:

在上一篇文Cmake文章里面,我们同样在文章的最后面留了一个问题实现,就是把源文件放到src目录下,把头文件放到include目录下去,这样也比较符合别人和自己日后去配置工程(一看到这两个目就能知道啥意思了,清晰明了),同时在linux环境下生成的elf文件放到bin目录下;不过在文章发出去了几天,后面有网友又有提出了一些新的需求:


(如果网友有啥实际需要,可以私聊我,只要在我自身能力之内,我都可以写成文章出来分享给大家)熟悉我的网友都知道,我也是小白,会从很基础的东西开始分享开始,虽然都是比较理论化的东西,但是都是点滴的积累(有的时候,其实你真正在有些项目开发过程中,学到的东西不是很多,更多的是依靠平时的基础积累加以扩展,所以总的来说,平时的折腾还是非常值得!);同时有啥比较实际一点的需求咋也慢慢深入,一步步来,稳扎稳打,知识性的东西来不得半点虚假和马虎。好了,开始进入主题分享了:

一、src、include、bin目录的使用(更加正规化):

1、先开始创建这三个目录结构,并把相应的文件放入进去:

root@txp-virtual-machine:/home/txp/testmy# mkdir bin build src include
root@txp-virtual-machine:/home/txp/testmy# ls
bin  build  include  src

include目录下文件放入(这里test1.h和test2.h的内容是接续前面的文章里面的内容,这里我就不再造轮子了):

root@txp-virtual-machine:/home/txp/testmy/include# ls
test1.h  test2.h

src目录下文件放入(这里test1.c和test2.c的内容是接续前面的文章里面的内容,这里我就不再造轮子了):

root@txp-virtual-machine:/home/txp/testmy/src# ls
main.c  test1.c  test2.c


最终我们还要在testmy目录和src目录下都创建一个CMakeLists.txt:

/*testmy目录下的CMakeLists.txt内容:*/


cmake_minimum_required(VERSION 2.8)

project(main)

add_subdirectory(src)


/*src目下CMakeLists.txt内容:*/

aux_source_directory(. SRC_LIST)

include_directories(../include)

add_executable(main ${SRC_LIST})

set (EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)


上面第一个CMakeLists.txt里面陌生的语句解释:

  • add_subdirectory(src)意思是可以向当前工程添加存放源文件的子目录,并可以指定中间二进制和目标二进制的存放位置(subdirectory字母就是子目录的意思,所以意思是:这里指定src目录下存放了源文件,当执行cmake时,就会进入src目录下去找src目录下的CMakeLists.txt,所以在src目录下也建立一个CMakeLists.txt),官方用法是这样的(不过这里暂时没去深究):

add_subdirectory
----------------

Add a subdirectory to the build.

::

  add_subdirectory(source_dir [binary_dir]
                   [EXCLUDE_FROM_ALL])

Add a subdirectory to the build.  The source_dir specifies the
directory in which the source CMakeLists.txt and code files are
located.  If it is a relative path it will be evaluated with respect
to the current directory (the typical usage), but it may also be an
absolute path.  The binary_dir specifies the directory in which to
place the output files.  If it is a relative path it will be evaluated
with respect to the current output directory, but it may also be an
absolute path.  If binary_dir is not specified, the value of
source_dir, before expanding any relative path, will be used (the
typical usage).  The CMakeLists.txt file in the specified source
directory will be processed immediately by CMake before processing in
the current input file continues beyond this command.

If the EXCLUDE_FROM_ALL argument is provided then targets in the
subdirectory will not be included in the ALL target of the parent
directory by default, and will be excluded from IDE project files.
Users must explicitly build targets in the subdirectory.  This is
meant for use when the subdirectory contains a separate part of the
project that is useful but not necessary, such as a set of examples.
Typically the subdirectory should contain its own project() command
invocation so that a full build system will be generated in the
subdirectory (such as a VS IDE solution file).  Note that inter-target
dependencies supercede this exclusion.  If a target built by the
parent project depends on a target in the subdirectory, the dependee
target will be included in the parent project build system to satisfy
the dependency.

第二个CMakeLists.txt内容分析:

  • aux_source_directory (. SRC_LIST):把当前目录的源文件:main.c test1.c  test2.c都放到变量SRC_LIST里面去。

  • include_directories (../include):把include目录的头文件包含进来。

  • set (EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin):这里面的EXECUTABLE_OUT_PATH和PROJECT_SOURCE_DIR是CMake自带的预定义变量,同时他们的作用分别如下:

  •  EXECUTABLE_OUTPUT_PATH :目标二进制可执行文件的存放位置

  • PROJECT_SOURCE_DIR:工程的根目录

所以最终生成的elf文件(也就是我们的最终可执行文件)就会放到bin目录下,然后我们build目录下会成一些配置中间文件。

具体步骤过程我写出来:

root@txp-virtual-machine:/home/txp/testmy# vim CMakeLists.txt
root@txp-virtual-machine:/home/txp/testmy# cd src
root@txp-virtual-machine:/home/txp/testmy/src# ls
main.c  test1.c  test2.c
root@txp-virtual-machine:/home/txp/testmy/src# vim CMakeLists.txt

最后架构如下:

root@txp-virtual-machine:/home/txp/testmy# tree
.
├── bin
├── build
├── CMakeLists.txt
├── include
│   ├── test1.h
│   └── test2.h
└── src
    ├── CMakeLists.txt
    ├── main.c
    ├── test1.c
    └── test2.c

2、编译运行:

root@txp-virtual-machine:/home/txp/testmy# cd build
root@txp-virtual-machine:/home/txp/testmy/build# ls
root@txp-virtual-machine:/home/txp/testmy/build# cmake ..
-- The C compiler identification is GNU 4.8.4
-- The CXX compiler identification is GNU 4.8.4
-- Check for working C compiler: /usr/bin/cc
-- Check for working C compiler: /usr/bin/cc -- works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working CXX compiler: /usr/bin/c++
-- Check for working CXX compiler: /usr/bin/c++ -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Configuring done
-- Generating done
-- Build files have been written to: /home/txp/testmy/build
root@txp-virtual-machine:/home/txp/testmy/build# make
Scanning dependencies of target main
[ 33%] Building C object src/CMakeFiles/main.dir/test2.c.o
[ 66%] Building C object src/CMakeFiles/main.dir/test1.c.o
[100%] Building C object src/CMakeFiles/main.dir/main.c.o
Linking C executable ../../bin/main
[100%] Built target main
root@txp-virtual-machine:/home/txp/testmy/build# cd ../bin
root@txp-virtual-machine:/home/txp/testmy/bin# ls
main

注意这里是切换到build目录下去执行cmake哈。 这里为啥这样做呢,我们课可以看到在build目录下执行cmake .. 和make命令后,生成的一些配置文件都会在这个目录下,不会在别的目录下,就这样看起来就舒服整洁多了:

root@txp-virtual-machine:/home/txp/testmy/build# ls
CMakeCache.txt  CMakeFiles  cmake_install.cmake  Makefile  src

现在整个架构如下:

root@txp-virtual-machine:/home/txp/testmy# tree
.
├── bin
│   └── main
├── build
│   ├── CMakeCache.txt
│   ├── CMakeFiles
│   │   ├── 2.8.12.2
│   │   │   ├── CMakeCCompiler.cmake
│   │   │   ├── CMakeCXXCompiler.cmake
│   │   │   ├── CMakeDetermineCompilerABI_C.bin
│   │   │   ├── CMakeDetermineCompilerABI_CXX.bin
│   │   │   ├── CMakeSystem.cmake
│   │   │   ├── CompilerIdC
│   │   │   │   ├── a.out
│   │   │   │   └── CMakeCCompilerId.c
│   │   │   └── CompilerIdCXX
│   │   │       ├── a.out
│   │   │       └── CMakeCXXCompilerId.cpp
│   │   ├── cmake.check_cache
│   │   ├── CMakeDirectoryInformation.cmake
│   │   ├── CMakeOutput.log
│   │   ├── CMakeTmp
│   │   ├── Makefile2
│   │   ├── Makefile.cmake
│   │   ├── progress.marks
│   │   └── TargetDirectories.txt
│   ├── cmake_install.cmake
│   ├── Makefile
│   └── src
│       ├── CMakeFiles
│       │   ├── CMakeDirectoryInformation.cmake
│       │   ├── main.dir
│       │   │   ├── build.make
│       │   │   ├── C.includecache
│       │   │   ├── cmake_clean.cmake
│       │   │   ├── DependInfo.cmake
│       │   │   ├── depend.internal
│       │   │   ├── depend.make
│       │   │   ├── flags.make
│       │   │   ├── link.txt
│       │   │   ├── main.c.o
│       │   │   ├── progress.make
│       │   │   ├── test1.c.o
│       │   │   └── test2.c.o
│       │   └── progress.marks
│       ├── cmake_install.cmake
│       └── Makefile
├── CMakeLists.txt
├── include
│   ├── test1.h
│   └── test2.h
└── src
    ├── CMakeLists.txt
    ├── main.c
    ├── test1.c
    └── test2.c

看一下最终执行结果:

root@txp-virtual-machine:/home/txp/testmy/bin# ./main
i like the cmake
a=8
TXP嵌入式

二、动态库和静态库的学习:

这个话题就回到了我们最开始那位网友提出的需求了;不过我们还是从简单的开始来学习,然后慢慢深入。有的时候我们只需要编译出动态库、静态库,然后给其他程序来调用,那么在cmake里面如何实现呢?具体如下:

注我这用到的源文件内容(test1.c test2.c test1.h test2.h main.c都是一样的来测试

1、为了清晰明了,这里我重新创建了一个目录工程来演示:

root@txp-virtual-machine:/home/txp# mkdir testcmake
root@txp-virtual-machine:/home/txp/testcmake# mkdir build lib lib_test
root@txp-virtual-machine:/home/txp/testcmake# ls
build  lib  lib_test

然后在lib_test目录下放我们的源文件test1.c test1.h,并同时在lib_test目录创建一个CMakeLists.txt;在testcmake目录也创建一个CMakeLists.txt:

root@txp-virtual-machine:/home/txp/testcmake/lib_test# ls
CMakeLists.txt  test1.c  test1.h



/*lib_test目录下的CMakeLists.txt的内容:

aux_source_directory(. SRC_LIST)

add_library(test1_shared SHARED ${SRC_LIST})
add_library(test1_static STATIC ${SRC_LIST})

set_target_properties(test1_shared PROPERTIES OUTPUT_NAME "test1")
set_target_properties(test1_static PROPERTIES OUTPUT_NAME "test1")

set (LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib)

解释说明:

  • add_library: 生成动态库或静态库(第1个参数指定库的名字;第2个参数决定是动态还是静态,如果没有就默认静态;第3个参数指定生成库的源文件)。

  • set_target_properties: 设置输出的名称,还有其它功能,如设置库的版本号等等。

  • LIBRARY_OUTPUT_PATH: 库文件的默认输出路径,这里设置为工程目录下的lib目录。

testcmake目录下的CMakeLists.txt内容:

cmake_minimum_required(VERSION 2.8)

project(main)

add_subdirectory(lib_test)

最后架构如下:

root@txp-virtual-machine:/home/txp/testcmake# ls
build  CMakeLists.txt  lib  lib_test
root@txp-virtual-machine:/home/txp/testcmake# tree
.
├── build
├── CMakeLists.txt
├── lib
└── lib_test
    ├── CMakeLists.txt
    ├── test1.c
    └── test1.h

3 directories, 4 files

2、编译结果:

root@txp-virtual-machine:/home/txp/testcmake/build# cmake ..
-- The C compiler identification is GNU 4.8.4
-- The CXX compiler identification is GNU 4.8.4
-- Check for working C compiler: /usr/bin/cc
-- Check for working C compiler: /usr/bin/cc -- works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working CXX compiler: /usr/bin/c++
-- Check for working CXX compiler: /usr/bin/c++ -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Configuring done
-- Generating done
-- Build files have been written to: /home/txp/testcmake/build
root@txp-virtual-machine:/home/txp/testcmake/build# make
Scanning dependencies of target test1_shared
[ 50%] Building C object lib_test/CMakeFiles/test1_shared.dir/test1.c.o
Linking C shared library ../../lib/libtest1.so
[ 50%] Built target test1_shared
Scanning dependencies of target test1_static
[100%] Building C object lib_test/CMakeFiles/test1_static.dir/test1.c.o
Linking C static library ../../lib/libtest1.a
[100%] Built target test1_static
root@txp-virtual-machine:/home/txp/testcmake/build# cd ../lib
root@txp-virtual-machine:/home/txp/testcmake/lib# ls
libtest1.a  libtest1.so

注意编译规则上面讲过,这里不再重复

从lib目录下我们可以看到生成了生成了静态库和动态库:libtest1.a  libtest1.so

2、对库进行链接:

现在我们使用刚才生成的库了。把build刚才的配置文件清空,并同时在testcmake目录下创建bin和src目录(跟刚才上面讲的差不多):

root@txp-virtual-machine:/home/txp/testcmake# mkdir src bin
root@txp-virtual-machine:/home/txp/testcmake# ls
bin  build  CMakeLists.txt  lib  lib_test  src
root@txp-virtual-machine:/home/txp/testcmake# cd src
root@txp-virtual-machine:/home/txp/testcmake/src# vim main.c

并在testcmake目录下的CMakeLists.txt内容改成:

cmake_minimum_required(VERSION 2.8)

project(main)

add_subdirectory(lib_test)

add_subdirectory(src)

src目录下要创建一个CMakeLists.txt:

aux_source_directory(.SRC_LIST)

include_directories(../lib_test)

link_directories(${PROJECT_SOURCE_DIR}/lib)

add_executable(main ${SRC_LIST})

target_link_libraries(main test1)

set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)

解释说明:

--link_directories: 添加非标准的共享库搜索路径。

--target_link_libraries: 把目标文件与库文件进行链接。

3、编译:

root@txp-virtual-machine:/home/txp/testcmake/build# cmake ..
-- The C compiler identification is GNU 4.8.4
-- The CXX compiler identification is GNU 4.8.4
-- Check for working C compiler: /usr/bin/cc
-- Check for working C compiler: /usr/bin/cc -- works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working CXX compiler: /usr/bin/c++
-- Check for working CXX compiler: /usr/bin/c++ -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Configuring done
-- Generating done
-- Build files have been written to: /home/txp/testcmake/build
root@txp-virtual-machine:/home/txp/testcmake/build# make
Scanning dependencies of target test1_shared
[ 33%] Building C object lib_test/CMakeFiles/test1_shared.dir/test1.c.o
Linking C shared library ../../lib/libtest1.so
[ 33%] Built target test1_shared
Scanning dependencies of target test1_static
[ 66%] Building C object lib_test/CMakeFiles/test1_static.dir/test1.c.o
Linking C static library ../../lib/libtest1.a
[ 66%] Built target test1_static
Scanning dependencies of target main
[100%] Building C object src/CMakeFiles/main.dir/main.c.o
Linking C executable ../../bin/main
[100%] Built target main
root@txp-virtual-machine:/home/txp/testcmake/build# cd ../bin
root@txp-virtual-machine:/home/txp/testcmake/bin# ls
main
root@txp-virtual-machine:/home/txp/testcmake/bin# ./main
a=6
TXP嵌入式

从上面可以执行成功。同时注意:

我们lib下面又动态库和静态库,而在我们src目录下的CMakeLists.txt里面的target_link_libraries (main test1)默认是使用动态库,如果lib目录下只有静态库,那么这种写法就会去链接静态库。也可以直接指定使用动态库还是静态库,写法是:target_link_libraries (main libtest1.so)或target_link_libraries (main libtest1.a)

同时我们可以查看elf文件到底使用了哪些库文件:

root@txp-virtual-machine:/home/txp/testcmake/bin# readelf -d ./main

Dynamic section at offset 0xe08 contains 26 entries:
  Tag        Type                         Name/Value
 0x0000000000000001 (NEEDED)             Shared library: [libtest1.so]
 0x0000000000000001 (NEEDED)             Shared library: [libc.so.6]
 0x000000000000000f (RPATH)              Library rpath: [/home/txp/testcmake/lib]
 0x000000000000000c (INIT)               0x4006a0
 0x000000000000000d (FINI)               0x400894
 0x0000000000000019 (INIT_ARRAY)         0x600df0
 0x000000000000001b (INIT_ARRAYSZ)       8 (bytes)
 0x000000000000001a (FINI_ARRAY)         0x600df8
 0x000000000000001c (FINI_ARRAYSZ)       8 (bytes)
 0x000000006ffffef5 (GNU_HASH)           0x400298
 0x0000000000000005 (STRTAB)             0x4004d8
 0x0000000000000006 (SYMTAB)             0x4002f8
 0x000000000000000a (STRSZ)              260 (bytes)
 0x000000000000000b (SYMENT)             24 (bytes)
 0x0000000000000015 (DEBUG)              0x0
 0x0000000000000003 (PLTGOT)             0x601000
 0x0000000000000002 (PLTRELSZ)           96 (bytes)
 0x0000000000000014 (PLTREL)             RELA
 0x0000000000000017 (JMPREL)             0x400640
 0x0000000000000007 (RELA)               0x400628
 0x0000000000000008 (RELASZ)             24 (bytes)
 0x0000000000000009 (RELAENT)            24 (bytes)
 0x000000006ffffffe (VERNEED)            0x400608
 0x000000006fffffff (VERNEEDNUM)         1
 0x000000006ffffff0 (VERSYM)             0x4005dc
 0x0000000000000000 (NULL)               0x0

三、小结:

这里还有一个网友的提的那个实现,现在我们知道了怎么去使用动态库和静态库就好办了;限于篇幅原因,这个测试留在下篇cmake文章里面来做实验看看到底能不能实现(这里我暂时还没做实验,等我做完实验就会分享出来,这篇文章分享花的时间比较久,大家先暂时消化一下)

推荐阅读:


嵌入式编程专辑
Linux 学习专辑
C/C++编程专辑

长按前往图中包含的公众号关注

浏览 37
点赞
评论
收藏
分享

手机扫一扫分享

举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

举报