02 cmake使用案例

余生颓废 提交于 2020-02-07 07:42:32

本章节通过一个工程介绍下cmake工程各个模块。使用JetBrains Clion开发工具组织代码。

https://github.com/jasbin2008/cmake-learn.git

1. 多个源文件组织

创建一个工程,添加以下文件:
代码组织
操作步骤:
1)在根CMakeLists.txt中配置所有子目录下的源文件

# ./CMakeLists.txt

cmake_minimum_required(VERSION 3.10)
PROJECT(PROJECT_ONE)
add_executable(main main.cpp mod1/mod1.cpp mod1/mod1_func.cpp) # 指明需要的源代码文件就好

2)在main.cpp中添加mod1.h,直接调用

2. 使用动态库

现在以动态库的形式重新构建mod1:
代码组织
1)在mod1文件夹中创建CMakeLists.txt,用于创建动态库mod1

# ./mod1/CMakeLists.txt

add_library(mod1 SHARED mod1.cpp mod1_func.cpp)

2)在根目录下的CMakeLists.txt中配置mod1
# ./CMakeLists.txt

cmake_minimum_required(VERSION 3.5)
PROJECT(PROJECT_ONE)
add_subdirectory(mod1 lib)   # 添加一个模块,并且将编译好库文件放置在 build/lib 目录
add_executable(main main.cpp)
target_link_libraries(main mod1)   # 链接 mod1

3)在main.cpp中添加mod1.h,调用动态库mo1

注意:如果在Windows下使用开发工具clion,生成的动态库是dll,直接调用会出错误,建议在Linux平台下运行

3. 使用静态库

多文件组织
1)在mod2文件夹中新增CMakeLists.txt,用于生成静态库mod2
# ./mod1/mod2/CMakeLists.txt

add_library(mod2 STATIC mod2.cpp)

2)在mod1文件夹中修改CMakeLists.txt,配置静态库mod2
# ./mod1/CMakeLists.txt

add_subdirectory(mod2 mo2_lib) #新增 mod2 模块, 编译好的库置于 build/lib/mod2_lib 中
link_directories(mod2_lib) #添加链接器的查找路径 build/lib/mod2_lib
add_library(mod1 SHARED mod1.cpp mod1_func.cpp)
target_link_libraries(mod1 mod2)

3)在mod1_func.cpp中添加mo2.h,调用静态库mod2

4. 安装程序

各个目录的CMakeLists.txt各自负责自己目录下要安装的文件:
安装
# ./CMakeLists.txt

cmake_minimum_required(VERSION 3.10)
PROJECT(PROJECT_ONE)
add_subdirectory(mod1 lib)          # 添加一个模块,并且将编译好库文件放置在 build/lib 目录
add_executable(main main.c)
target_link_libraries(main mod1)         # 链接 mod1

set(CMAKE_INSTALL_PREFIX $ENV{HOME}/usr)              # 安装路径前缀 /home/cao/usr
install(DIRECTORY doc/ DESTINATION share/PROJECT_ONE) # 安装项目文档
install(TARGETS main RUNTIME DESTINATION bin )        # main 安装到 usr/bin

# ./mod1/CMakeLists.txt

add_subdirectory(mod2 mo2_lib)  # 新增 mod2 模块, 编译好的库置于 build/lib/mod2_lib 中
link_directories(mod2_lib)      # 添加链接器的查找路径 build/lib/mod2_lib
add_library(mod1 SHARED mod1.c mod1_func.c) # 生成动态库 libmod1.so
target_link_libraries(mod1 mod2) # 将 libmod2.a 链接进入 libmod1.so 中

install(TARGETS mod1 ARCHIVE DESTINATION lib LIBRARY DESTINATION lib)  # 安装到 usr/lib
install(FILES mod1.h DESTINATION include/mod1) # 安装到 usr/include/mod1

# ./mod1/mod2/CMakeLists.txt

add_library(mod2 SHARED mod2.c) # 生成静态库 libmod2.a
install(TARGETS mod2 ARCHIVE DESTINATION LIBRARY DESTINATION lib)  # 安装到 usr/lib
install(FILES mod2.h DESTINATION include/mod2) # 安装到 usr/include/mod2

5. 使用Find模块

Find
1)添加Find模块,命名符合Find<name>.cmake规范,这里以添加libxml2库为例:
FINDLIBXML2.cmake

find_path(LIBXML2_INCLUDE_DIR xmlmemory.h /usr/local/include/libxml2/libxml)
find_library(LIBXML2_LIBRARY NAMES libxml2.so PATH /usr/local/lib)

if(LIBXML2_INCLUDE_DIR AND LIBXML2_LIBRARY)
    set(LIBXML2_FOUND TRUE)
endif(LIBXML2_INCLUDE_DIR AND LIBXML2_LIBRARY)

if (LIBXML2_FOUND)
    if(NOT LIBXML2_FOUND_QUIETLY)
        message(STATUS "Found Hello: ${LIBXML2_LIBRARY}")
    endif(NOT LIBXML2_FOUND_QUIETLY)
else(LIBXML2_FOUND)
    if(LIBXML2_FOUND_QUIETLY)
        message(FATAL_ERROR "Could not find hello library")
    endif(LIBXML2_FOUND_QUIETLY)
endif(LIBXML2_FOUND)

2)修改根CMakeLists.txt,添加模块查找代码

set(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake) # find_package会在CMAKE_MODULE_PATH查找

# 连接 libxml2.lib
find_package(LIBXML2)
if(LIBXML2_FOUND)
    include_directories(${LIBXML2_INCLUDE_DIR})
    include_directories(/usr/local/include/libxml2)
    link_directories(/usr/local/lib)
    target_link_libraries(main xml2)
else(LIBXML2_FOUND)
    message(FATAL_ERROR "libxml2.so not be found!")
endif(LIBXML2_FOUND)

3)在main.cpp中使用库

// main.c
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "mod1/mod1.h"
#include "xmlmemory.h"
#include "parser.h"

const char *curDoc = "<?xml version=\"1.0\"?>"
                     "<story>"
                     "<storyinfo>"
                     "<author>John Fleck</author>"
                     "<datewritten>June 2, 2002</datewritten>"
                     "<keyword>example keyword</keyword>"
                     "</storyinfo>"
                     "<body>"
                     "<headline>This is the headline</headline>"
                     "<para>This is the body text.</para>"
                     "</body>"
                     "</story>";

int main( int argc, char *argv[] )
{
    xmlDocPtr doc;
    xmlNodePtr cur;
    //doc = xmlParseFile(docname);
    doc = xmlParseDoc((const xmlChar *)curDoc);
    if (doc == NULL) {
        fprintf(stderr, "Document not parsed successfully. \n");
        return -1;
    }

    printf("parse document success!\n");

    cur = xmlDocGetRootElement(doc);
    if (cur == NULL) {
        fprintf(stderr, "empty document\n");
        xmlFreeDoc(doc);
        return -1;
    }

    if (xmlStrcmp(cur->name, (const xmlChar *) "story")) {
        fprintf(stderr, "document of the wrong type, root node != story");
        xmlFreeDoc(doc);
        return -1;
    }

    printf("root elment value is %s\n", cur->name);

    xmlFreeDoc(doc);
    return 0;
}

6. 参数控制

在CMakeLists.txt中配置参数,控制源代码中代码的编译部分,比如可以通过一个参数控制,是用自己写的库还是系统库?
参数控制
1)添加cmakeconfig.h.in

#define AUTHOR "@AUTHOR@"
#define RELEASE_DATE "@RELEASE_DATE@"
#define USE_MY_LIB @USE_MY_LIB@

2)在根CMakeList.txt中配置,并包含生成的cmakeconfig.h目录

# 通过cmakeconfig.h传递参数给源文件
set(AUTHOR "user")
set(RELEASE_DATE "2019-11-06")
set(USE_MY_LIB "0")
configure_file(
    ${PROJECT_SOURCE_DIR}/cmakeconfig.h.in
    ${PROJECT_BINARY_DIR}/cmakeconfig.h
)
include_directories(${PROJECT_BINARY_DIR})

3)在main中使用

#ifdef USE_MY_LIB
    printf("use my library\n");
#else
    printf("use %s library\n", AUTHOR)
#endif

7. 调试版本和发布版本

上述的代码编译后都是不可调试的,并且没有做编译优化,我们希望能够编译成一个调试版本与一个发布版本。做法如下:
我们将build目录作为开发版本编译目录,与之相对的新建一个release目录作为发布版本
build目录下我们执行cmake -DMAKE_BUILD_TYPE=Debug ..,编译命令会使用-g
release目录下我们执行cmake -DMAKE_BUILD_TYPE=Release ..,编译命令会使用-O3 -DNDEBUG
所以,在源代码中,我们可以使用NDEBUG宏来控制,在开发版输出调试信息,而在发布版本去掉调试信息。
调试
1)修改CMakeLists.txt,添加开发版本的编译参数

# 设置debug和release调试信息
set(CMAKE_C_FLAGS_DEBUG "-g -Wall -pedantic -DDEBUG")
set(CMAKE_CXX_FLAGS_DEBUG "-g -Wall -pedantic -DDEBUG")
message(STATUS "debug flags: ${CMAKE_C_FLAGS_DEBUG}")
message(STATUS "release flags: ${CMAKE_C_FLAGS_RELEASE}")

2)在main.cpp中使用

#ifndef NDEBUG
   printf("author: %s, release_date: %s\n", AUTHOR, RELEASE_DATE); //只在开发版本编译
#endif

3)添加sh直接编译

开发调试脚本

#!/bin/bash
rm -rf build/*                                # 清理上一次的结果

cd build && cmake -DCMAKE_BUILD_TYPE=debug .. # 进入debug目录,执行构建

make && ./main                                # 编译,然后运行

开发发布版本

#!/bin/bash
if [ ! -d ./release ]
then
  mkdir release
else
  rm -rf release/* 
fi

cd release && cmake -DCMAKE_BUILD_TYPE=release ..

make && ./main 
.. # 进入debug目录,执行构建

make && ./main  # 编译,然后运行
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!