02 cmake使用案例

本章節通過一個工程介紹下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  # 編譯,然後運行
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章