admin

例谈CMake
CMakeCMake是跨平台,开源的编译工具。直接编写Makefile较复杂工作量较大,而使用CMake可以自动化...
扫描右侧二维码阅读全文
20
2019/02

例谈CMake

CMake

CMake是跨平台,开源的编译工具。直接编写Makefile较复杂工作量较大,而使用CMake可以自动化地生成Makefile 减轻工作负担。 除此之外,了解CMake还有助于我们学习编译相关知识。我也是CMake的初学者,本文原文来自于一篇英文博客,链接如下。

原文
完整代码

Example1: Hello, World!

项目结构如下,只有一个文件hello.cpp

dreamboy@Elon_Mask:~/exploringBB/extras/cmake$ cd helloworld/
 dreamboy@Elon_Mask:~/exploringBB/extras/cmake/helloworld$ ls
 CMakeLists.txt  helloworld.cpp
/* hello.cpp */
# include<iostream>
int main(int argc, char *argv[]){
   std::cout << "Hello World!" << std::endl;
   return 0;
}

为此我们编写的CMakeLists.txt文件也非常简单,只包含必要的三行:

cmake_minimum_required(VERSION 2.8.9)
project (hello)
add_executable(hello helloworld.cpp)
  • 第一行cmake_minumum_required(VERSION XXX)给出要求的cmake的最低版本,可根据自己的情况修改。(cmake --version查看版本)
  • 第二行 project(hello) 设定项目名称
  • 第三行 add_executable(hello helloworld.cpp) 第一个参数给出目标可执行文件,第二个参数给出源文件依赖,用空格分割

现在我们已经可以用cmake 编译项目, 运行命令cmake ., 其中 . 表示的是当前目录

dreamboy@Elon_Mask:~/exploringBB/extras/cmake/helloworld$ cmake .
-- The C compiler identification is GNU 5.4.0
-- The CXX compiler identification is GNU 5.4.0
-- 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
-- Detecting C compile features
-- Detecting C compile features - 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
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done
-- Generating done
-- Build files have been written to: /home/dreamboy/exploringBB/extras/cmake/helloworld
dreamboy@Elon_Mask:~/exploringBB/extras/cmake/helloworld$ ls
CMakeCache.txt  CMakeFiles  cmake_install.cmake  CMakeLists.txt  helloworld.cpp  Makefile
dreamboy@Elon_Mask:~/exploringBB/extras/cmake/helloworld$ make
Scanning dependencies of target hello
[ 50%] Building CXX object CMakeFiles/hello.dir/helloworld.cpp.o
[100%] Linking CXX executable hello
[100%] Built target hello
dreamboy@Elon_Mask:~/exploringBB/extras/cmake/helloworld$ ./hello 
Hello World!

Example2: 多目录结构的project

当我们的工程结构越来越复杂时,cmake的便捷性就凸现出来了。一般在利用make时,我们会在顶层目录编写Makefile,由其去触发子目录的Makefile,进而完成整个项目的编译工作。本例中项目结构如下, 头文件和源代码实现分离:

dreamboy@Elon_Mask:~/exploringBB/extras/cmake/helloworld$ cd ../student
dreamboy@Elon_Mask:~/exploringBB/extras/cmake/student$ ls
CMakeLists.txt  include  src
dreamboy@Elon_Mask:~/exploringBB/extras/cmake/student$ tree
.
├── CMakeLists.txt
├── include
│   └── Student.h
└── src
    ├── mainapp.cpp
    └── Student.cpp

2 directories, 4 files

CMakeLists, 只比上面的example1 多了两行

cmake_minimum_required(VERSION 2.8.9)
project(directory_test)

# 包含头文件
include_directories(include)

# 手工逐一加入源文件
# set(SOURCES src/mainapp.cpp src/Student.cpp)

# 利用GLOB一次性包含所有源文件
file(GLOB SOURCES "src/*.cpp")

add_executable(testStudent ${SOURCES})
  • include_directories用于把头文件包含进build环境
  • set(SOURCES 1.cpp 2.cpp) 其中set函数用于设定变量, 后面的源文件用空格分隔
  • file(GLOB SOURCES "src/*.cpp") GLOB(或GLOB_RECURSE)用来创建满足globbing表达式的列表(src/*.cpp)并赋值给SOURCES
  • add_executable(testStudent ${SOURCES})同样用来添加可执行文件及其依赖

为了使我们的项目结构看起来更加清爽,我们这次选择建立build目录来编译(用cmake ..选择CMakeLists的位置 )

dreamboy@Elon_Mask:~/exploringBB/extras/cmake/student$ mkdir build
dreamboy@Elon_Mask:~/exploringBB/extras/cmake/student$ ls
build  CMakeLists.txt  include  src
dreamboy@Elon_Mask:~/exploringBB/extras/cmake/student$ cd build
dreamboy@Elon_Mask:~/exploringBB/extras/cmake/student/build$ cmake ..
-- The C compiler identification is GNU 5.4.0
-- The CXX compiler identification is GNU 5.4.0
-- 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
-- Detecting C compile features
-- Detecting C compile features - 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
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done
-- Generating done
-- Build files have been written to: /home/dreamboy/exploringBB/extras/cmake/student/build
dreamboy@Elon_Mask:~/exploringBB/extras/cmake/student/build$ make
Scanning dependencies of target testStudent
[ 33%] Building CXX object CMakeFiles/testStudent.dir/src/Student.cpp.o
[ 66%] Building CXX object CMakeFiles/testStudent.dir/src/mainapp.cpp.o
[100%] Linking CXX executable testStudent
[100%] Built target testStudent
dreamboy@Elon_Mask:~/exploringBB/extras/cmake/student/build$ ls
CMakeCache.txt  CMakeFiles  cmake_install.cmake  Makefile  testStudent
dreamboy@Elon_Mask:~/exploringBB/extras/cmake/student/build$ ./testStudent 
A student with name Joe

查看目录结构:

dreamboy@Elon_Mask:~/exploringBB/extras/cmake/student$ ls
build  CMakeLists.txt  include  src
dreamboy@Elon_Mask:~/exploringBB/extras/cmake/student$ tree
.
├── build
│   ├── CMakeCache.txt
│   ├── CMakeFiles
│   │   ├── 3.11.0
│   │   │   ├── CMakeCCompiler.cmake
│   │   │   ├── CMakeCXXCompiler.cmake
│   │   │   ├── CMakeDetermineCompilerABI_C.bin
│   │   │   ├── CMakeDetermineCompilerABI_CXX.bin
│   │   │   ├── CMakeSystem.cmake
│   │   │   ├── CompilerIdC
│   │   │   │   ├── a.out
│   │   │   │   ├── CMakeCCompilerId.c
│   │   │   │   └── tmp
│   │   │   └── CompilerIdCXX
│   │   │       ├── a.out
│   │   │       ├── CMakeCXXCompilerId.cpp
│   │   │       └── tmp
│   │   ├── cmake.check_cache
...

这样一来当我们需要重新编译的时候,只需删除build 目录即可,

dreamboy@Elon_Mask:~/exploringBB/extras/cmake/student$ rm -rf build
dreamboy@Elon_Mask:~/exploringBB/extras/cmake/student$ ls
CMakeLists.txt  include  src

可以看到与编译前的结构完全相同

Example3: 编译共享库(.so)

动态库我们大家都不陌生,我们在用GCC编译源代码文件(.c)生成目标(.o)文件后有时就需要链接.so动态库,这么做的好处一个是减小可执行文件的大小,且在链接库改变时我们的项目无需重新编译。在目录结构上基本与上一个例子相同,只是移除了mainapp.cpp 因为主函数不需要

dreamboy@Elon_Mask:~/exploringBB/extras/cmake/studentlib_shared$ tree
.
├── CMakeLists.txt
├── include
│   └── Student.h
└── src
    └── Student.cpp

2 directories, 3 files

CMakeLists文件内容则如下:

cmake_minimum_required(VERSION 2.8.9)
project(directory_test)
set(CMAKE_BUILD_TYPE Release)

# 包含头文件
include_directories(include)

 # 定义SOURCES变量
file(GLOB SOURCES "src/*.cpp")

# 生成动态库
add_library(testStudent SHARED ${SOURCES})

# 设置安装动态库的位置(/usr/lib), 非必须,可用`sudo make install`安装
install(TARGETS testStudent DESTINATION /usr/lib)
  • set(CMAKE_BUILD_TYPE Release) 表明此次编译是一次发行的编译, CMAKE_BUILD_TYPE为内置变量
  • add_library(testStudent SHARED $(SOURCES)) 中第一个参数为生成共享库的名字,第二个参数表示生成共享库(还可以设置为STATIC表示生成静态链接库 $(SOURCES则表示依赖的源文件)), 该函数与add_executable()相对
  • install(TARGETS testStudent DESTINATION /usr/lib)第一个参数与第三个参数固定, 会生成libtestStudent.so

输出如下, 可以看到必须要sudo才能安装成功

dreamboy@Elon_Mask:~/exploringBB/extras/cmake/studentlib_shared$ mkdir build
dreamboy@Elon_Mask:~/exploringBB/extras/cmake/studentlib_shared$ cd build
dreamboy@Elon_Mask:~/exploringBB/extras/cmake/studentlib_shared/build$ cmake ..
-- The C compiler identification is GNU 5.4.0
-- The CXX compiler identification is GNU 5.4.0
-- 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
-- Detecting C compile features
-- Detecting C compile features - 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
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done
-- Generating done
-- Build files have been written to: /home/dreamboy/exploringBB/extras/cmake/studentlib_shared/build
dreamboy@Elon_Mask:~/exploringBB/extras/cmake/studentlib_shared/build$ ls
CMakeCache.txt  CMakeFiles  cmake_install.cmake  Makefile
dreamboy@Elon_Mask:~/exploringBB/extras/cmake/studentlib_shared/build$ make
Scanning dependencies of target testStudent
[ 50%] Building CXX object CMakeFiles/testStudent.dir/src/Student.cpp.o
[100%] Linking CXX shared library libtestStudent.so
[100%] Built target testStudent

我们可以用ldd命令查看.so文件的依赖关系

dreamboy@Elon_Mask:~/exploringBB/extras/cmake/studentlib_shared/build$ ldd libtestStudent.so 
    linux-vdso.so.1 =>  (0x00007fff7d702000)
    libstdc++.so.6 => /usr/lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007f4949d5f000)
    libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f4949995000)
    libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f494968c000)
    /lib64/ld-linux-x86-64.so.2 (0x00007f494a2e4000)
    libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007f4949476000)
    ```
尝试安装: 必须要sudo权限(/usr/lib的写权限)
``` ascii
dreamboy@Elon_Mask:~/exploringBB/extras/cmake/studentlib_shared/build$ make install
[100%] Built target testStudent
Install the project...
-- Install configuration: "Release"
-- Installing: /usr/lib/libtestStudent.so
CMake Error at cmake_install.cmake:55 (file):
  file INSTALL cannot copy file
  "/home/dreamboy/exploringBB/extras/cmake/studentlib_shared/build/libtestStudent.so"
  to "/usr/lib/libtestStudent.so".
Makefile:107: recipe for target 'install' failed
make: *** [install] Error 1
dreamboy@Elon_Mask:~/exploringBB/extras/cmake/studentlib_shared/build$ sudo make install
sudo: 无法解析主机:Elon_Mask
[sudo] dreamboy 的密码: 
[100%] Built target testStudent
Install the project...
-- Install configuration: "Release"
-- Installing: /usr/lib/libtestStudent.so

Example4:编译静态库

静态库即把库文件代码都复制到我们的项目可执行文件中,会增大可执行文件的大小,好处就是可以降低查找索引,调用的时间, CMakeLists与上面的动态库基本相同,除了把SHARED参数换为STATIC

dreamboy@Elon_Mask:~/exploringBB/extras/cmake/studentlib_shared$ tree
.
├── CMakeLists.txt
├── include
│   └── Student.h
└── src
    └── Student.cpp

2 directories, 3 files

CMakeLists文件内容则如下:

cmake_minimum_required(VERSION 2.8.9)
project(directory_test)
set(CMAKE_BUILD_TYPE Release)

# 包含头文件
include_directories(include)

 # 定义SOURCES变量
file(GLOB SOURCES "src/*.cpp")

# 生成动态库
add_library(testStudent STATIC ${SOURCES})

# 设置安装动态库的位置(/usr/lib), 非必须,可用`sudo make install`安装
install(TARGETS testStudent DESTINATION /usr/lib)

编译完成后,我们可以用ar命令查看静态库文件的组成, 静态库文件以.a结尾(在windows平台下以.lib结尾), 其实就是.o目标文件的打包

dreamboy@Elon_Mask:~/exploringBB/extras/cmake/studentlib_static/build$ ar -t libtestStudent.a 
Student.cpp.o

此外,我们可以用GNU下的nm -C命令列出目标二进制文件中的符号,如下,其中T表示代码, U表示未定义的, R是只读的数据

dreamboy@Elon_Mask:~/exploringBB/extras/cmake/studentlib_static/build$ nm -C libtestStudent.a 

Student.cpp.o:
                 U __cxa_atexit
                 U __dso_handle
0000000000000000 t _GLOBAL__sub_I__ZN7StudentC2ENSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE
                 U memcpy
                 U __stack_chk_fail
0000000000000000 T Student::display()
00000000000000a0 T Student::Student(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >)
00000000000000a0 T Student::Student(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >)
                 U std::ctype<char>::_M_widen_init() const
0000000000000000 W std::ctype<char>::do_widen(char) const
                 U std::ostream::put(char)
                 U std::ostream::flush()
                 U std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::_M_create(unsigned long&, unsigned long)
                 U std::ios_base::Init::Init()
                 U std::ios_base::Init::~Init()
                 U std::basic_ostream<char, std::char_traits<char> >& std::__ostream_insert<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*, long)
                 U std::__throw_bad_cast()
                 U std::__throw_logic_error(char const*)
                 U std::cout
0000000000000000 b std::__ioinit
0000000000000000 V typeinfo for Student
0000000000000000 V typeinfo name for Student
0000000000000000 V vtable for Student
                 U vtable for __cxxabiv1::__class_type_info

Example5: 动态库或是静态库的使用

cmake_minimum_required(VERSION 2.8.9)
project (TestLibrary)

# 动态库
set ( PROJECT_LINK_LIBS libtestStudent.so )
link_directories( ~/exploringBB/extras/cmake/studentlib_shared/build )

# 静态库
#set ( PROJECT_LINK_LIBS libtestStudent.a )
#link_directories( ~/exploringBB/extras/cmake/studentlib_static/build )

include_directories(~/exploringBB/extras/cmake/studentlib_shared/include)

add_executable(libtest libtest.cpp)
target_link_libraries(libtest ${PROJECT_LINK_LIBS} )
  • 动态库与静态库的使用方式基本一致,先set(PROJECT_LINK_LIBS libname) 定义库的名字, PROJECT_LINK_LIBS为内置变量
  • 然后link_directories(path)给出动态库的位置(如有多个位置,用空格分隔)
  • 最后先add_executable()给出依赖关系再用target_link_libraries(libtest, $(PROJECT_LINK_LIBS))链接库

cpp 文件如下:

# include"Student.h" 

int main(int argc, char *argv[]){
   Student s("Joe");
   s.display();
   return 0;
}

运行结果:

dreamboy@Elon_Mask:~/exploringBB/extras/cmake/usestudentlib/build$ cmake ..
-- The C compiler identification is GNU 5.4.0
-- The CXX compiler identification is GNU 5.4.0
-- 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
-- Detecting C compile features
-- Detecting C compile features - 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
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done
-- Generating done
-- Build files have been written to: /home/dreamboy/exploringBB/extras/cmake/usestudentlib/build
dreamboy@Elon_Mask:~/exploringBB/extras/cmake/usestudentlib/build$ make
Scanning dependencies of target libtest
[ 50%] Building CXX object CMakeFiles/libtest.dir/libtest.cpp.o
[100%] Linking CXX executable libtest
[100%] Built target libtest
dreamboy@Elon_Mask:~/exploringBB/extras/cmake/usestudentlib/build$ ls
CMakeCache.txt  CMakeFiles  cmake_install.cmake  libtest  Makefile
dreamboy@Elon_Mask:~/exploringBB/extras/cmake/usestudentlib/build$ ./libtest 
A student with name Joe
Last modification:March 14th, 2019 at 09:08 pm
If you think my article is useful to you, please feel free to appreciate

Leave a Comment