Cmake简介

Posted by Dandan on November 18, 2021

前言

最开始学习C肯定是从最初的gcc编译一个可执行文件学起,那时候我们知道编译的主要工作流程是:源码–》预处理(.i)–》编译(.s)–》汇编(.o)–》链接(exe)–》可执行程序。当源文件较多时,就不适合使用gcc编译代码,就用到了makefile,通过编译顺序等使用Make就可以自动完成编译工作,效率极大提升。
做了sdwan项目,发现openwrt等集成了很多的源码包都是通过cmake编译的,发现比makefile更好用。

介绍

cmake编译流程

  • 编写源文件
  • 编写CMakeLists.txt
  • 根据CMakeLists.txt 生成makefile
  • makefile调用gcc完成编译

CMakeLists.txt主要内容

  • cmake_minimum_required(VERSION 2.8.0):用于指定cmake所需最低版本;
  • project(Project) :用于指定项目名称;
  • include_directories() :用于包含头文件目录;
  • aux_source_directory(src dir_srcs):用于包含源文件目录;
  • set(TEST_MATH) :用于设置环境变量,编译用到的源文件全部都要放到这里;
  • add_executable(${PROJECT_NAME} ${TEST_MATH}):用于添加要编译的可执行文件;
  • target_link_libraries(${PROJECT_NAME} m):用于添加可执行文件所需要的库;

常用的变量

  • CMAKE_MAJOR_VERSION:cmake 主版本号;
  • CMAKE_MINOR_VERSION:cmake 次版本号;
  • CMAKE_C_FLAGS:设置 C 编译选项;
  • CMAKE_CXX_FLAGS:设置 C++ 编译选项;
  • PROJECT_SOURCE_DIR:工程的根目录;
  • PROJECT_BINARY_DIR:运行 cmake 命令的目录;
  • CMAKE_CURRENT_SOURCE_DIR:当前CMakeLists.txt 所在路径;
  • CMAKE_CURRENT_BINARY_DIR:目标文件编译目录;
  • EXECUTABLE_OUTPUT_PATH:重新定义目标二进制可执行文件的存放位置
  • LIBRARY_OUTPUT_PATH:重新定义目标链接库文件的存放位置

简单的示例

# 指定所需版本
cmake_minimum_required(VERSION 2.6)

# 将自定义的CMake模块路径添加到CMake模块搜索路径中, 这是用来包含一些额外的CMake模块,以便在构建过程中使用
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/CMakeModules/")
set(PLUGINS_DIR "${CMAKE_INSTALL_PREFIX}/${LIB_INSTALL_DIR}/sysrepo/plugins/" CACHE PATH "Sysrepo plugins directory.")

project(sysrepo-XXXXdevice)

# 设置库文件的输出目录
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)

set(SOURCES
    src/device.c)

if(CMAKE_BUILD_TYPE MATCHES "debug")
  add_executable(${CMAKE_PROJECT_NAME} ${SOURCES})
  target_compile_definitions(${CMAKE_PROJECT_NAME} PRIVATE DEBUG=1)
  install(TARGETS ${CMAKE_PROJECT_NAME}
      DESTINATION lib/sysrepo/plugins)
else()
    # 添加-g选项到C编译选项中
    set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g")
  add_library(${CMAKE_PROJECT_NAME} MODULE ${SOURCES})
  install(TARGETS ${CMAKE_PROJECT_NAME} LIBRARY
      DESTINATION lib/sysrepo/plugins)
endif()

set_target_properties(${CMAKE_PROJECT_NAME} PROPERTIES OUTPUT_NAME ${CMAKE_PROJECT_NAME} PREFIX "")

# 使用CMake的Find模块来查找Sysrepo库
find_package(SYSREPO REQUIRED)
# 将Sysrepo库链接到项目中,以便在编译时可以使用Sysrepo的功能
target_link_libraries(${CMAKE_PROJECT_NAME} ${SYSREPO_LIBRARIES})
include_directories(${SYSREPO_INCLUDE_DIRS})

find_package(UCI REQUIRED)
include_directories(${UCI_INCLUDE_DIR})
target_link_libraries(${CMAKE_PROJECT_NAME} ${UCI_LIBRARIES})

# 将项目的目标文件(可执行文件或动态链接库,取决于构建类型)安装到之前定义的插件目录(PLUGINS_DIR)中
install(TARGETS ${CMAKE_PROJECT_NAME} DESTINATION ${PLUGINS_DIR})

这里还用到了其他的模块,目录在CMakeModules中。

cmake_module

这个目录通常包含很多的.cmake文件, 用于找特定的库。以查找uci库为例,在CMakeLists.txt使用:

find_package(UCI REQUIRED)
include_directories(${UCI_INCLUDE_DIR})
target_link_libraries(${CMAKE_PROJECT_NAME} ${UCI_LIBRARIES})

在CMakeModules目录下会有一个文件:FindUCI.cmake, 格式通常是:FindXXX..cmake,内容格式通常如下:

# This is a FindXXX.cmake module for finding the XXX library.

# Define a variable to store the library path.
set(XXX_LIBRARY "")

# Define a variable to store the include directories.
set(XXX_INCLUDE_DIR "")

# Check for the XXX library.
find_library(XXX_LIBRARY
    NAMES XXX
    PATHS /usr/lib /usr/local/lib
)

# Check for the XXX header files.
find_path(XXX_INCLUDE_DIR
    NAMES xxx.h
    PATHS /usr/include /usr/local/include
)

# Check if the XXX library and headers were found.
if(XXX_LIBRARY AND XXX_INCLUDE_DIR)
    set(XXX_FOUND TRUE)
else()
    set(XXX_FOUND FALSE)
endif()

# Provide information about the XXX library.
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(XXX
    REQUIRED_VARS XXX_LIBRARY XXX_INCLUDE_DIR
)

# If the XXX library was found, set the XXX_FOUND variable to TRUE.
if(XXX_FOUND)
    set(XXX_FOUND TRUE)
else()
    set(XXX_FOUND FALSE)
endif()

# Export the results to the parent scope (if needed).
if(XXX_FOUND)
    set(XXX_FOUND TRUE PARENT_SCOPE)
    set(XXX_LIBRARY ${XXX_LIBRARY} PARENT_SCOPE)
    set(XXX_INCLUDE_DIR ${XXX_INCLUDE_DIR} PARENT_SCOPE)
endif()

写的uci如下:

# UCI_FOUND - true if library and headers were found
# UCI_INCLUDE_DIRS - include directories
# UCI_LIBRARIES - library directories

find_package(PkgConfig)
pkg_check_modules(PC_UCI QUIET uci)

find_path(UCI_INCLUDE_DIR uci.h
    HINTS ${PC_UCI_INCLUDEDIR} ${PC_UCI_INCLUDE_DIRS})

find_library(UCI_LIBRARY NAMES uci libuci
    HINTS ${PC_UCI_LIBDIR} ${PC_UCI_LIBRARY_DIRS})

set(UCI_LIBRARIES ${UCI_LIBRARY})
set(UCI_INCLUDE_DIRS ${UCI_INCLUDE_DIR})

include(FindPackageHandleStandardArgs)

find_package_handle_standard_args(UCI DEFAULT_MSG UCI_LIBRARY UCI_INCLUDE_DIR)

mark_as_advanced(UCI_INCLUDE_DIR UCI_LIBRARY)