C/C++ Build tool

323 阅读4分钟

概述

  • cmake/meson //生成特定平台的makefile(.makefile .sln .ninja)
  • make/ninja/vcbuild //根据makefile编译exe (.so .out .dll .exe)

CMake

  • 自定义变量
  set (NAME main.c)
  ${NMAE} #引用变量,if判断除外

单目录

  • CMake 最低版本号要求
    cmake_minimum_required (VERSION 2.8)
  • 项目信息
    project (Demo2)
  • 指定生成目标
    add_executable(Demo main.cc MathFunctions.cc)

Tips: 查找当前目录下的所有源文件并将名称保存到 DIR_SRCS 变量 aux_source_directory(. DIR_SRCS)

多目录

  • 添加 math 子目录
    add_subdirectory(math)
  • 指定生成目标
    add_executable(Demo main.cc)
  • 添加链接库
    target_link_libraries(Demo MathFunctions)
  • 生成链接库 add_library (MathFunctions ${DIR_LIB_SRCS})

在该文件中使用命令 add_library 将 src 目录中的源文件编译为静态链接库。

自动生成头文件

  • 加入一个配置头文件(*.h.in),根据CMake的option来控制编译参数。

安装

1. TARGETS

install (TARGETS T1 T2 RUNTIME DESTINATION binary LIBRARY DESTINATION lib ARCHIVE DESTINATION lib)

Tips:

windowslinux
RUNTIME*.exe *.dll*.out
LIBRARY*.so
ARCHIVE*.lib*.a

Of course,we can install all targets in one Directory such as:

install (TARGETS T1 T2 DESTINATION binary)
install (TARGETS MathFunctions  DESTINATION "C:/Users/test/Desktop" )

2. DIRECTORY

install(DIRECTORY "${MVNCDIR}/lib/" DESTINATION "lib"

3. FILES

install(FILES "${CMAKE_BINARY_DIR}/output/bin/ssleay32.dll" DESTINATION "bin")

4. 使用wix 生成一键安装包

set(CPACK_GENERATOR WIX)
set(CPACK_WIX_UPGRADE_GUID "F9AAAAE2-D6AF-4EA4-BF46-B3E265400CC7")
set(CPACK_WIX_PATCH_FILE "${CMAKE_SOURCE_DIR}/CPackWixPatches.xml")
include (InstallRequiredSystemLibraries)
include (CPack)

FindPkgConfig

使用pc文件获取相关的目录信息, 使用pkg-config,三个常用字符串。

CMake
PKG_CONFIG_FOUND          ... if pkg-config executable was found
PKG_CONFIG_EXECUTABLE     ... pathname of the pkg-config program
PKG_CONFIG_VERSION_STRING ... the version of the pkg-config program found
                              (since CMake 2.8.8)

常用的三个函数:

  • pkg_get_variable()

示例:pkg_get_variable(GI_GIRDIR gobject-introspection-1.0 girdir)
gobject-introspection-1.0中的girdir变量读取到GI_GIRDIR

  • pkg_check_modules() 示例:pkg_check_modules (GLIB2 glib-2.0) 检查glib-2.0 并自动设置以下变量
<XXX>_FOUND          ... set to 1 if module(s) exist
<XXX>_LIBRARIES      ... only the libraries (without the '-l')
<XXX>_LINK_LIBRARIES ... the libraries and their absolute paths
<XXX>_LIBRARY_DIRS   ... the paths of the libraries (without the '-L')
<XXX>_LDFLAGS        ... all required linker flags
<XXX>_LDFLAGS_OTHER  ... all other linker flags
<XXX>_INCLUDE_DIRS   ... the '-I' preprocessor flags (without the '-I')
<XXX>_CFLAGS         ... all required cflags
<XXX>_CFLAGS_OTHER   ... the other compiler flags
  • pkg_search_module 与pkg_check_modules类似,匹配一个module成功后,就退出。 pkg_search_module (BAR libxml-2.0 libxml2 libxml>=2)

变量

第一个读取的是环境变量,通过export name= 传入 第二个读取的是局部变量,通过-D参数传入

message(STATUS "env $ENV{NAME}")
message(STATUS "yy ${NAME}")

find_package()

使用*.cmake 信息获取相关的配置信息

cmake本身不提供任何搜索库的便捷方法,所有搜索库并给变量赋值的操作必须由cmake代码完成。find_package采用两种模式搜索库:

  • Module模式:搜索CMAKE_MODULE_PATH指定路径下的FindXXX.cmake文件。(这个内部大部分调用的find_lib 函数,查找/usr/lib 以及/usr/local/lib目录下的内容)
  • Config模式:搜索XXX_DIR指定路径下的XXXConfig.cmake文件。
  1. CMAKE_MODULE_PATH 以及 XX_DIR不一定是一个路径,可能是多个路径
  2. cmake先执行Module模式,后config模式
  3. 要么设置CMAKE_MODULE_PATH(对应的FindXXX.cmake),要么设置XXX_DIR(对应XXXConfig.cmake)
  4. CMAKE中可以使用命令行参数传递变量,也支持使用全局环境变量的形式传递,建议使用前者。

使用示例

方法1:使用json-c-config, 仅仅是对当前package有效。
find_package("json-c")  //在CMakelists.txt
cmake .. -Djson-c_DIR=/home/test/install/lib/cmake/json-c //传入 json-c-config.cmake的路径

方法2: 使用json-c-config,所有的find_package以及find_file都会在这个目录下去查找,只需要到install目录,不需要到lib/cmake/jsonc。
find_package("json-c")  //在CMakelists.txt
cmake .. -DCMAKE_PREFIX_PATH=/home/test/install/ //传入安装路径

方法3: 使用Findjson-c.cmake,通过指定 `CMAKE_MODULE_PATH`

函数

# basic function
function(doubleIt VALUE)
    math(EXPR RESULT "${VALUE} * 2")
    message("${RESULT}")
endfunction()

doubleIt("4")                           # Prints: 8

# with returnvalue
function(doubleItReturn VARNAME VALUE)
    math(EXPR RESULT "${VALUE} * 2")
    set(${VARNAME} "${RESULT}" PARENT_SCOPE)    # Set the named variable in caller's scope
endfunction()

doubleItReturn(RESULT "4")                    # Tell the function to set the variable named RESULT
message("${RESULT}")                    # Prints: 8

# with macro
macro(doubleItMac VARNAME VALUE)
    math(EXPR ${VARNAME} "${VALUE} * 2")        # Set the named variable in caller's scope
endmacro()

doubleItMac(RESULT "4")                    # Tell the macro to set the variable named RESULT
message("${RESULT}")                    # Prints: 8

# with list
function(doubleEach)
    foreach(ARG ${ARGN})                # Iterate over each argument
        math(EXPR N "${ARG} * 2")       # Double ARG's numeric value; store result in N
        message("${N}")                 # Print N
    endforeach()
endfunction()

doubleEach(5 6 7 8)                     # Prints 10, 12, 14, 16 on separate lines
  1. 简单函数与带返回值函数的区别就是带返回值的函数,对PARENT_SCOPE参数进行设置
  2. 函数与宏定义的区别,就是作用域的不同
  3. 函数名字都是大小写不敏感
  4. 使用其他文件中的函数,需要include,类似c++, find_package 内部其实执行的就是include(FindSDL2.cmake),

参考示例 preshing.com/20170522/le…

modern cmake

推荐使用target_include_directories(show PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)而不是include_directories() 因为在当这个target直接给其他库使用时,直接使用这次target就可以了,不需要额外增加包含目录,因为include目录已经在打包在target这个大结构体中。 private,public,interface区别(11,10,01的区别)public=private+interface

INTERFACE 表示这个target不使用这个包含目录,但是会传递给使用它的target
PRIVATE 表示这个target使用这个包含目录,但是不会传递给使用它的目录。
PUBLIC 表示这个target使用这个包含目录,且也传递给使用它的target

调试命令

cmake 调试

cmake --debug-output cmake文件级别调用细节 cmake --trace cmake函数级别调用细节 cmake --trace-expand cmake函数级别调用细节(已经将变量替换)

make 调试

make VERBOSE=1或者make VERBOSE=ON

EXCLUDE_FROM_ALL

正如字面意思,从all(默认)中排除这个target, 需要手动指定。

在以下几种情况中适用

  • make 默认执行的是make all, 但是如果这个项目exclude_from_all,就需要手动指定target才行。
  • make install 默认执行的也是make install all, 如果这个项目设置了install exclude_from_all, make install 也不会安装这个项目。但目前没找到单独install 某个target的操作。

设置exclude_from_all还有一种方式,就是add_subdirectory的时候加入,这个目录下所有的都会设置成exclude_from_all。

default output path

这是输出目录,这种可以解决windows 运行需要设置path的问题

set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)

target类别

  1. STATIC 静态链接库
  2. SHARED 动态链接库
  3. MODULE 动态链接库,但是只能作为plugin使用,例如dlopen等,且不能被其他target link
  4. OBJECT 源码编译文件集合,没有archive,可以被其他target link, 以及作为源码被其他target编译。

参考代码如下


add_library(archive OBJECT archive.cpp zip.cpp lzma.cpp)
# usage 1
add_library(archiveExtras STATIC $<TARGET_OBJECTS:archive> extras.cpp)
# usage 2
add_executable(test_exe test.cpp)
target_link_libraries(test_exe archive)
  1. IMPORT 直接外部编译好的二进制target,自生不会build 任何文件

参考代码如下

add_library(foo STATIC IMPORTED)
set_property(TARGET foo PROPERTY IMPORTED_LOCATION /path/to/libfoo.a)
add_executable(myexe src1.c src2.c)
target_link_libraries(myexe foo)

gitlab.kitware.com/cmake/commu…

  1. INTERFACE 一组属性的集合,不依赖磁盘上任何文件(理论上,也可强行设置),自身不会build,但是可以设置属性,以供其他target linked to, 比较适合只有头文件的库,因为他们自生不需要编译,依赖其他库去编译。 stackoverflow.com/questions/3… cmake.org/cmake/help/… mariobadr.com/creating-a-…

导出cmake文件

以供outside project 使用

export

需要搭配BUILD_INTERFACE使用 这里cmake文件使用的build目录

add_library(show SHARED src/show.cpp)
install(TARGETS show EXPORT ExportTarget)# add each target you want export to a export group
target_include_directories(show PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>) # use build interface
export(EXPORT ExportTarget FILE ${PROJECT_BINARY_DIR}/ExportTarget.cmake) # export

参考链接 build_interface export

install(EXPORT)

需要搭配INSTALL_INTERFACE使用 这里cmake文件使用的install目录

add_library(show SHARED src/show.cpp)
install(TARGETS show EXPORT ExportTarget) # add each target you want export to a export group
target_include_directories(show PUBLIC $<INSTALL_INTERFACE:include>) # use install interface
install(EXPORT ExportTarget DESTINATION ${CMAKE_INSTALL_PREFIX}/cmake) # install

config

上述两个方案需用用户去include(xxx.cmake),为了支持find_modules()这个函数,需要生成xx-config.cmake文件。其实config.cmake文件内部其实就是include(xxx.cmake)。

include(CMakePackageConfigHelpers)
# 在build下先使用configure_package函数生成一个文件,再使用install file的形式拷贝到对应的目录
configure_package_config_file("xx.cmake.in" ExportTargetConfig.cmake INSTALL_DESTINATION ${CMAKE_INSTALL_PREFIX}/cmake)
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/ExportTargetConfig.cmake" DESTINATION "${CMAKE_INSTALL_PREFIX}/cmake")

VScode

系统默认有Debug,Release,RelWithDebInfo,MinSizeRel这四种,如果用户在.vscode目录下创建了上述文件,则系统的则被覆盖。

系统默认有一个cmake-kits.json,如果用户在.vscode目录下创建了上述文件,则系统与用户的同时存在。 如果修改系统的json文件,其配置应该与具体项目无关。

make

定义变量 objects = main.o kbd.o command.o display.o

使用变量 $objects

目标(target)

target ... : prerequisites ... command

  1. 文件中的第一个目标文件(target),作为最终目标
  2. 目标生成的条件:1. target不存在 2. 依赖文件更新过。

依赖(prerequisites)

  1. 会递归依赖
  2. 需要将头文件加入依赖,因为头文件不参与编译,头文件的修改可能导致不重新编译
  3. 依赖并非必须,例如 clean

规则(command)

  1. 定义任意shell命令
  2. 规则可进行隐形推导

其他

  1. 规则前面需要tab
  2. .phony告诉make 这是一个伪目标,不用检查依赖关系,始终运行rules 典型事例就是当文件中存在clean文件时,其rules就不执行
  3. 内置符号的含义 all: library.cpp main.cpp
  • $@ evaluates to all
  • $< evaluates to library.cpp
  • $^ evaluates to library.cpp main.cpp
  • $? evaluates to the newer one(updated prerequistites)
  1. 自动推导,对于.o 文件make会自动识别,并自己推导命令

Ninja

  • common build cmake .. -G Ninja && ninja
  • show all targets ninja -t targets
  • show graph ninja -t graph mytarget | dot -Tpng -ograph.png
  • build single target ninja xx
  • clean ninja clean

与make指令很类似,默认多线程编译

windows上使用

  • 把Ninja.exe添加到环境变量。
  • 把cl.exe 添加到环境变量,建议使用native tools command prompt

linux上使用

  • sudo apt instal ninja-build

不同make工具对比

makeNinjamsbuild
OSLinuxBothWindows
build commandmakeninjamsbuild
build fileMakefileninja.buildxxx.sln
cmake enableONONON
advantagecmake defaultcross-platform,fastcmake default