本章節通過一個工程介紹下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模塊
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 # 編譯,然後運行