代码改变世界

cmake 笔记

2024-06-20 18:55  清晨、午后  阅读(15)  评论(0编辑  收藏  举报

一、 一个完整的工程

  给工程起个名字

加上这句:project(hello)

命令:project(<PROJECT-NAME> [LANGUAGES] [<language-name>...])

作用:定义工程名称, 设置几个变量的名字: PROJECT_NAME, PROJECT_SOURCE_DIR, <PROJECT-NAME>_SOURCE_DIR, PROJECT_BINARY_DIR, <PROJECT-NAME>_BINARY_DIR, 高级用法请见参考链接2:CMake命令

  告诉CMake我的构建目标

add_executable(${PROJECT_NAME} ${hello_src})

  让CMake找到我的头文件

target_include_directories(${project_name} PRIVATE
    ${CMAKE_SOURCE_DIR}/include
)

  让CMake找到我的源文件

target_sources(${project_name} PRIVATE
    ${SRC}
    ${QRC}
)

  让CMake找到我的库文件

target_link_directories(${project_name} PRIVATE
    ${Qt_LIBSDIR}
)

  告诉CMake我要链接哪个库文件

set(Qt_LIBS
    Qt5::Core
    Qt5::Gui
    Qt5::Widgets
)

target_link_libraries(${project_name} PRIVATE
    ${Qt_LIBS}
)

  设置编译、连接参数

target_compile_features(${project_name} PRIVATE cxx_std_11)
target_compile_options(${project_name} PRIVATE -g -Wall)
target_link_options(${project_name} PRIVATE -Wl, --warn-unresolved-symbol)

  开始构建

通过以上步骤, 最后,在文件头部添加CMake版本检查,以我的环境为例,我的CMake版本是3.0,那么我在脚本最开始加上:

cmake_minimum_required ( VERSION 3.0)

完整的CMakeLists.txt如下所示:

cmake_minimum_required ( VERSION 3.0)

project(hello)

include_directories(${CMAKE_CURRENT_LIST_DIR}/include)

link_directories(${CMAKE_CURRENT_LIST_DIR}/lib)

aux_source_directory(${CMAKE_CURRENT_LIST_DIR}/src ${hello_src})

add_executable(${PROJECT_NAME} ${hello_src})

target_link_libraries(${PROJECT_NAME} util)

set(CMAKE_CXX_COMPILER      "clang++" )         # 显示指定使用的C++编译器

set(CMAKE_CXX_FLAGS   "-std=c++11")             # c++11
set(CMAKE_CXX_FLAGS   "-g")                     # 调试信息
set(CMAKE_CXX_FLAGS   "-Wall")                  # 开启所有警告

set(CMAKE_CXX_FLAGS_DEBUG   "-O0" )             # 调试包不优化
set(CMAKE_CXX_FLAGS_RELEASE "-O2 -DNDEBUG " )   # release包优化

  构建动态库、静态库

如果要构建动态库,将 add_executable(${PROJECT_NAME} ${hello_src}) 替换为 ADD_LIBRARY(${PROJECT_NAME} SHARED ${hello_src})  即可

如果要构建静态库,则为 ADD_LIBRARY(${PROJECT_NAME} STATIC ${hello_src}) 

可以将目标名称修改如下

  SET_TARGET_PROPERTIES (${PROJECT_NAME}_static PROPERTIES OUTPUT_NAME "hello")

也可以指定动态库版本

  SET_TARGET_PROPERTIES (${PROJECT_NAME}PROPERTIES VERSION 1.2 SOVERSION 1)

二、 其他

1. cmake help

CMake Reference Documentation — CMake 3.27.9 Documentation
CMAKE_SOURCE_DIR:根cmaek文件所在路径
CMAKE_CURRENT_SOURCE_DIR :当前cmake文件所在路径
PROJECT_SOURCE_DIR :当前cmake文件所在路径

现代CMake工具的设计理念和使用

  https://mp.weixin.qq.com/s/qIEsImvOWa3-nRx7Ik_R8g

全面的例子

  https://gitee.com/li736069609/qt_demo/blob/master/gui/CMakeLists.txt

2. cmake 中定义宏,代码中使用

# 新的语法
add_compile_definitions(OPENCV_VERSION="1.0.0") // 代码中可以直接使用 OPENCV_VERSION,值为 1.0.0
add_compile_definitions(WITH_OPENCV2) // 代码中可以直接使用 WITH_OPENCV2 进行条件编译
或者
add_compile_definitions(OPENCV_VERSION="1.0.0" WITH_OPENCV2)

# 旧的语法
add_definitions(-DSOURCE_DIR="aaa")  // 代码中的宏 SOURCE_DIR 就是字符串aaa
add_definitions(-DWITH_TEST)  // 代码中可以使用 WITH_TEST 进行条件编译

3. 静态库的连接顺序

工程在链接阶段静态库是有顺序要求的,如果懒得理这些顺序,就用  -Wl,--start-group  和 -Wl,--end-group 括起来即可

target_link_libraries(
    -Wl,--start-group
    libX3.a
    libX2.a
    libX1.a
    -Wl,--end-group
)

4. 大型工程中有用的几个设置

target_link_options(${project_name} PRIVATE -Wl, --no-undefined)
QMAKE_LFLAGS += -Wl,--no-undefined (qmake)

告诉链接器在链接过程中不允许有未定义的符号 (对动态库 so)

target_link_options(${project_name} PRIVATE -Wl, --warn-unresolved-symbol)

告诉链接器在链接过程中可以有未定义的符号 (对可执行程序)

target_link_options(${project_name} PRIVATE -Wl, --copy-dt-needed-entries)

当链接可执行文件时,依赖于libA.so,而libA.so又依赖于libB.so,而且可执行文件中还直接调用了libB.so中的函数,如果可执行文件没有显示声明连接libB.so,则会报错 DSO missing from command line,因为自从binutils 2.22版本以后,如果你在程序中使用了你依赖的动态库所依赖的动态库中的函数时,你就必须显式的指定你依赖的动态库所依赖的动态库,

5 Debug 与 Release 版本控制 

cmake .. -DCMAKE_BUILD_TYPE=Debug/Release

if (CMAKE_BUILD_TYPE)
    if (${CMAKE_BUILD_TYPE} STREQUAL "Debug")
        target_compile_options (${PROJECT_NAME}    PRIVATE -O0 -g -Wall)
    elseif (${CMAKE_BUILD_TYPE} STREQUAL "Release")
        target_compile_options (${PROJECT_NAME}    PRIVATE -O2 -g -Wall)
    else ()
        target_compile_options (${PROJECT_NAME}    PRIVATE -O2 -g -Wall)
    endif ()
else ()
    target_compile_options (${PROJECT_NAME}    PRIVATE -O2 -g -Wall)
endif ()    

6. find_package 使用

find_package( <name> [version] [EXACT] [QUIET] [NO_MODULE] [ [ REQUIRED | COMPONENTS ] [ componets... ] ] )

用来调用预定义在 CMAKE_MODULE_PATH 下的 Find<name>.cmake 模块。

(1)查找引用使用第三方库,如使用 curl 库

在工程根目录中建立 src 文件夹,并在其下创建 main.c 文件,内容如下:

#include <curl/curl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
FILE *fp;
int write_data(void *ptr, size_t size, size_t nmemb, void *stream)
{
int written = fwrite(ptr, size, nmemb, (FILE *)fp);
return written;
}
int main()
{
const char * path = “/tmp/curl-test”;
const char * mode = “w”;
fp = fopen(path,mode);
curl_global_init(CURL_GLOBAL_ALL);
CURLcode res;
CURL *curl = curl_easy_init();
curl_easy_setopt(curl, CURLOPT_URL, “http://www.linux-ren.org”);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_data);
curl_easy_setopt(curl, CURLOPT_VERBOSE, 1);
res = curl_easy_perform(curl);
curl_easy_cleanup(curl);
}

在工程根目录下建立主工程文件 CMakeLists.txt,内容如下

PROJECT(CURLTEST)
ADD_SUBDIRECTORY(src)

在 src 下创建 CMakeLists.txt

ADD_EXECUTABLE(curltest main.c)

 现在自然是没办法编译的,我们需要添加 curl 的头文件路径和库文件。

方法 1:
直接通过 INCLUDE_DIRECTORIES 和 TARGET_LINK_LIBRARIES 指令添加:
我们可以直接在 src/CMakeLists.txt 中添加:
INCLUDE_DIRECTORIES(/usr/include)
TARGET_LINK_LIBRARIES(curltest curl)
然后建立 build 目录进行外部构建即可。

方法 2
使用 FindCURL 模块。
向src/CMakeLists.txt 中添加:
FIND_PACKAGE(CURL)
IF(CURL_FOUND)
  INCLUDE_DIRECTORIES(${CURL_INCLUDE_DIR})
  TARGET_LINK_LIBRARIES(curltest ${CURL_LIBRARY})
ELSE(CURL_FOUND)
    MESSAGE(FATAL_ERROR ”CURL library not found”)
ENDIF(CURL_FOUND)
对于系统预定义的 Find<name>.cmake 模块,使用方法一般如上例所示:
每一个模块都会定义以下几个变量
    <name>_FOUND
    <name>_INCLUDE_DIR or <name>_INCLUDES
    <name>_LIBRARY or <name>_LIBRARIES
你可以通过<name>_FOUND 来判断模块是否被找到,如果没有找到,按照工程的需要关闭某些特性、给出提醒或者中止编译,上面的例子就是报出致命错误并终止构建。
如果<name>_FOUND 为真,则将<name>_INCLUDE_DIR 加入 INCLUDE_DIRECTORIES,将<name>_LIBRARY 加入 TARGET_LINK_LIBRARIES 中。

(2)自定义 cmake 模块,如编写属于自己的 FindHello 模块。

定义 cmake/FindHELLO.cmake 模块

FIND_PATH(HELLO_INCLUDE_DIR hello.h /usr/include/hello /usr/local/include/hello)
FIND_LIBRARY(HELLO_LIBRARY NAMES hello PATH /usr/lib /usr/local/lib)
IF (HELLO_INCLUDE_DIR AND HELLO_LIBRARY)
  SET(HELLO_FOUND TRUE)
ENDIF (HELLO_INCLUDE_DIR AND HELLO_LIBRARY)
IF (HELLO_FOUND) IF (NOT HELLO_FIND_QUIETLY) MESSAGE(STATUS "Found Hello: ${HELLO_LIBRARY}") ENDIF (NOT HELLO_FIND_QUIETLY) ELSE (HELLO_FOUND) IF (HELLO_FIND_REQUIRED) MESSAGE(FATAL_ERROR "Could not find hello library") ENDIF (HELLO_FIND_REQUIRED) ENDIF (HELLO_FOUND)

可以看到在该模块中定义了 HELLO_FOUND、HELLO_INCLUDE_DIR、HELLO_LIBRARY变量

前面的 curl 例子中我们使用了最简单的 FIND_PACKAGE 指令,其实他可以使用多种参数

QUIET 参数,对应与我们编写的 FindHELLO 中的 HELLO_FIND_QUIETLY ,如果不指定这个参数,就会执行:
MESSAGE(STATUS "Found Hello: ${HELLO_LIBRARY}")

REQUIRED 参数,对应于 FindHELLO.cmake 模块中的 HELLO_FIND_REQUIRED ,其含义是指这个共享库是否是工程必须的,如果使用了这个参数,说明这个链接库是必备库,如果找不到这个链接库,则工程不能编译。

使用自定义的 Hello 与使用 curl 的方法一致。

 设置动态库的版本号:

set_target_properties(${project_name} PROPERTIES VERSION 1.0.0 SOVERSION 1) 

 

------