【问题标题】:Getting CMake to build out of source without wrapping scripts让 CMake 在不包装脚本的情况下从源代码构建
【发布时间】:2012-06-23 23:27:04
【问题描述】:

我正在尝试让 CMake 构建到目录“build”中,如 project/build,其中 CMakeLists.txt 位于 project/

我知道我能做到:

mkdir build
cd build
cmake ../

但这很麻烦。我可以把它放在一个脚本中并调用它,但是向 CMake 提供不同的参数(比如 -G "MSYS Makefiles")是不愉快的,或者我需要在每个平台上编辑这个文件。

最好我会在主 CMakeLists.txt 中执行类似 SET(CMAKE_OUTPUT_DIR build) 的操作。请告诉我这是可能的,如果可以,怎么做?或者其他一些可以轻松指定不同参数的源代码构建方法?

【问题讨论】:

  • 我的项目架构和你一样。我的“构建”目录总是在这里。我个人发现输入 cmake .. 没什么大不了的。

标签: configuration cmake


【解决方案1】:

CMake 3.13 或更高版本支持 command line options -S-B 分别指定源和二进制目录。

cmake -S . -B build -G "MSYS Makefiles"

这将在当前文件夹中查找CMakeLists.txt,并在其中创建一个build 文件夹(如果尚不存在)。

对于旧版本的 CMake,您可以使用未记录的 CMake 选项 -H-B 在调用 cmake 时指定源和二进制目录:

cmake -H. -Bbuild -G "MSYS Makefiles"

注意选项和目录路径之间不能有空格。

【讨论】:

  • 不错的选择。但是 Max 说“向 cmake 提供不同的论点是令人不快的”。他希望它进入 CMakeList。
【解决方案2】:

我最近发现的一个解决方案是将外源构建概念与 Makefile 包装器相结合。

在我的顶级 CMakeLists.txt 文件中,我包含以下内容以防止源内构建:

if ( ${CMAKE_SOURCE_DIR} STREQUAL ${CMAKE_BINARY_DIR} )
    message( FATAL_ERROR "In-source builds not allowed. Please make a new directory (called a build directory) and run CMake from there. You may need to remove CMakeCache.txt." )
endif()

然后,我创建一个顶级 Makefile,并包含以下内容:

# -----------------------------------------------------------------------------
# CMake project wrapper Makefile ----------------------------------------------
# -----------------------------------------------------------------------------

SHELL := /bin/bash
RM    := rm -rf
MKDIR := mkdir -p

all: ./build/Makefile
    @ $(MAKE) -C build

./build/Makefile:
    @  ($(MKDIR) build > /dev/null)
    @  (cd build > /dev/null 2>&1 && cmake ..)

distclean:
    @  ($(MKDIR) build > /dev/null)
    @  (cd build > /dev/null 2>&1 && cmake .. > /dev/null 2>&1)
    @- $(MAKE) --silent -C build clean || true
    @- $(RM) ./build/Makefile
    @- $(RM) ./build/src
    @- $(RM) ./build/test
    @- $(RM) ./build/CMake*
    @- $(RM) ./build/cmake.*
    @- $(RM) ./build/*.cmake
    @- $(RM) ./build/*.txt

ifeq ($(findstring distclean,$(MAKECMDGOALS)),)
    $(MAKECMDGOALS): ./build/Makefile
    @ $(MAKE) -C build $(MAKECMDGOALS)
endif

通过键入make 调用默认目标all,并调用目标./build/Makefile

目标./build/Makefile 所做的第一件事是使用$(MKDIR) 创建build 目录,这是mkdir -p 的一个变量。目录build 是我们将执行我们的源代码外构建的地方。我们提供参数-p 以确保mkdir 不会因为我们试图创建一个可能已经存在的目录而大喊大叫。

目标./build/Makefile 所做的第二件事是将目录更改为build 目录并调用cmake

回到all 目标,我们调用$(MAKE) -C build,其中$(MAKE) 是为make 自动生成的Makefile 变量。 make -C 在做任何事情之前更改目录。因此,使用$(MAKE) -C build就相当于使用cd build; make

总而言之,用make allmake 调用这个Makefile 包装器相当于这样做:

mkdir build
cd build
cmake ..
make 

目标distclean 调用cmake ..,然后调用make -C build clean,最后从build 目录中删除所有内容。我相信这正是您在问题中所要求的。

Makefile 的最后一部分评估用户提供的目标是否为distclean。如果没有,它会在调用它之前将目录更改为build。这是非常强大的,因为用户可以键入,例如,make clean,Makefile 会将其转换为 cd build; make clean 的等价物。

总之,这个 Makefile 包装器与强制的源外构建 CMake 配置相结合,使用户无需与命令 cmake 进行交互。此解决方案还提供了一种优雅的方法来从 build 目录中删除所有 CMake 输出文件。

附:在 Makefile 中,我们使用前缀 @ 来抑制 shell 命令的输出,使用前缀 @- 来忽略 shell 命令的错误。当使用rm 作为distclean 目标的一部分时,如果文件不存在,该命令将返回错误(它们可能已经使用带有rm -rf build 的命令行被删除,或者它们从未在第一次生成地方)。这个返回错误将强制我们的 Makefile 退出。我们使用前缀@- 来防止这种情况。如果文件已经被删除是可以接受的;我们希望我们的 Makefile 继续运行并删除其余部分。

另外需要注意的是:如果您使用可变数量的 CMake 变量来构建您的项目,此 Makefile 可能不起作用,例如,cmake .. -DSOMEBUILDSUSETHIS:STRING="foo" -DSOMEOTHERBUILDSUSETHISTOO:STRING="bar"。此 Makefile 假定您以一致的方式调用 CMake,或者通过键入 cmake .. 或通过提供 cmake 一致数量的参数(可以包含在 Makefile 中)。

最后,信用到期。这个 Makefile 包装器改编自 C++ Application Project Template 提供的 Makefile。

此答案最初发布于here。我认为它也适用于您的情况。

【讨论】:

  • 我认为这通常是一个很好的解决方案。但它使用的是包装脚本。虽然 OP 要求“不包装脚本”的解决方案。它也不会在 Windows 上工作。
【解决方案3】:

根据之前的答案,我编写了以下模块,您可以将其包含在内以强制执行源外构建。

set(DEFAULT_OUT_OF_SOURCE_FOLDER "cmake_output")

if (${CMAKE_SOURCE_DIR} STREQUAL ${CMAKE_BINARY_DIR})
    message(WARNING "In-source builds not allowed. CMake will now be run with arguments:
        cmake -H. -B${DEFAULT_OUT_OF_SOURCE_FOLDER}
")

    # Run CMake with out of source flag
    execute_process(
            COMMAND ${CMAKE_COMMAND} -H. -B${DEFAULT_OUT_OF_SOURCE_FOLDER}
            WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})

    # Cause fatal error to stop the script from further execution
    message(FATAL_ERROR "CMake has been ran to create an out of source build.
This error prevents CMake from running an in-source build.")
endif ()

这可行,但我已经注意到两个缺点:

  • 当用户懒惰并简单地运行cmake .时,他们总是会看到FATAL_ERROR。我找不到其他方法来阻止 CMake 执行任何其他操作并提前退出。
  • 传递给cmake 的原始调用的任何命令行参数都不会传递给“源外构建调用”。

欢迎提出改进此模块的建议。

【讨论】:

  • 避免致命错误的一种方法是使用 else() 而不是 endif() 并将 endif() 放在原始 cmake 代码的最后。
  • @hko 确实如此,但鉴于我将其放在 CMakeLists.txt 文件中包含的单独的可重用模块中,您的建议不适用。
猜你喜欢
  • 2016-06-03
  • 1970-01-01
  • 1970-01-01
  • 2012-10-05
  • 2015-03-15
  • 1970-01-01
  • 2021-03-17
  • 2014-09-23
  • 1970-01-01
相关资源
最近更新 更多