使用cmake,如何禁用源码内部构建?

50

我希望禁止人们在我们的源代码树中混杂生成的CMake文件...... 更重要的是,禁止他们踩踏那些不属于我们正在使用CMake构建过程的Makefiles。(最好不要问为什么)

我想到的解决方法是,在我的CMakeLists.txt文件顶部添加几行代码,如下所示:

if("${PROJECT_SOURCE_DIR}" STREQUAL "${PROJECT_BINARY_DIR}")
   message(SEND_ERROR "In-source builds are not allowed.")
endif("${PROJECT_SOURCE_DIR}" STREQUAL "${PROJECT_BINARY_DIR}")

然而,用这种方式做似乎太啰嗦了。此外,如果我尝试进行源码构建,它仍会在错误抛出之前在源树中创建 CMakeFiles/ 目录和 CMakeCache.txt 文件。

我是否遗漏了更好的方法?


6
到目前为止,这是我找到的最佳解决方案。但是你可以让信息更具有启发性:message(FATAL_ERROR "不允许在源文件中进行构建。请创建一个单独的文件夹进行构建:\n mkdir build; cd build; cmake ..\n在此之前,请删除已经创建的文件:\n rm -rf CMakeCache.txt CMakeFiles") - Tronic
1
如果您只使用 endif() 来关闭条件语句,我就不会认为它很冗长。 - Burak
1
如果今天你可以使用 endif(),那就太好了。我想当我写这个问题的时候,这是不可能的。 - mpontillo
@mpontillo 你后来找到了一个可行的解决方案吗? - undefined
@ChukwujiobiCanon,我已经从我在这个问题中讨论的项目中转移了,但是如果我相信下面得到最多赞的答案,看起来CMake在这个领域取得了进展!请告诉我们哪种方法对您最有效。 - undefined
很不幸,它无法阻止CMake生成CMakeCache.txt和CMakeFiles/,因此它并没有比报错更好的效果。 - undefined
8个回答

58

CMake有两个未记录的选项:CMAKE_DISABLE_SOURCE_CHANGESCMAKE_DISABLE_IN_SOURCE_BUILD

cmake_minimum_required (VERSION 2.8)

# add this options before PROJECT keyword
set(CMAKE_DISABLE_SOURCE_CHANGES ON)
set(CMAKE_DISABLE_IN_SOURCE_BUILD ON)

project (HELLO)

add_executable (hello hello.cxx)
andrew@manchester:~/src% cmake .
CMake Error at /usr/local/share/cmake-2.8/Modules/CMakeDetermineSystem.cmake:160 (FILE):
  file attempted to write a file: /home/andrew/src/CMakeFiles/CMakeOutput.log
  into a source directory.

/home/selivanov/cmake-2.8.8/Source/cmMakefile.cxx

bool cmMakefile::CanIWriteThisFile(const char* fileName)
{
  if ( !this->IsOn("CMAKE_DISABLE_SOURCE_CHANGES") )
    {
    return true;
    }
  // If we are doing an in-source build, than the test will always fail
  if ( cmSystemTools::SameFile(this->GetHomeDirectory(),
                               this->GetHomeOutputDirectory()) )
    {
    if ( this->IsOn("CMAKE_DISABLE_IN_SOURCE_BUILD") )
      {
      return false;
      }
    return true;
    }

  // Check if this is subdirectory of the source tree but not a
  // subdirectory of a build tree
  if ( cmSystemTools::IsSubDirectory(fileName,
      this->GetHomeDirectory()) &&
    !cmSystemTools::IsSubDirectory(fileName,
      this->GetHomeOutputDirectory()) )
    {
    return false;
    }
  return true;
}

8
不幸的是,这并不能阻止 CMake 创建 CMakeCache.txt 和 CMakeFiles/,因此它并不比报错更好(而且更糟糕的是,它会给出晦涩难懂的信息)。 - Tronic
1
只需创建一个名为“CMakeCache.txt”的目录即可避免创建缓存文件。 - Andrew Selivanov
13
如果它们是无证明文件的,你应该谨慎使用这些选项。它们可能会在未来版本中没有警告地更改。 - aled
不幸的是,我在使用晦涩的CXX编译器组合时出现了回归错误。最好不要出于好奇心使用未记录的选项。 - Mike T

7

包含一个类似于这个文件的函数。它与您所做的相似,但存在以下区别:

  1. It is encapsulated in a function, which is called when you include the PreventInSourceBuilds.cmake module. Your main CMakeLists.txt must include it:

    set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/CMake)
    include(PreventInSourceBuilds)
    
  2. It uses get_filename_component() with REALPATH parameter that resolves symlinks before comparing the paths.

如果 github 链接发生变化,这里是模块源代码(应该放在一个名为 CMake 的目录下的 PreventInSouceBuilds.cmake 文件中,如上例所示)。
#
# This function will prevent in-source builds
function(AssureOutOfSourceBuilds)
  # make sure the user doesn't play dirty with symlinks
  get_filename_component(srcdir "${CMAKE_SOURCE_DIR}" REALPATH)
  get_filename_component(bindir "${CMAKE_BINARY_DIR}" REALPATH)

  # disallow in-source builds
  if("${srcdir}" STREQUAL "${bindir}")
    message("######################################################")
    message("# ITK should not be configured & built in the ITK source directory")
    message("# You must run cmake in a build directory.")
    message("# For example:")
    message("# mkdir ITK-Sandbox ; cd ITK-sandbox")
    message("# git clone http://itk.org/ITK.git # or download & unpack the source tarball")
    message("# mkdir ITK-build")
    message("# this will create the following directory structure")
    message("#")
    message("# ITK-Sandbox")
    message("#  +--ITK")
    message("#  +--ITK-build")
    message("#")
    message("# Then you can proceed to configure and build")
    message("# by using the following commands")
    message("#")
    message("# cd ITK-build")
    message("# cmake ../ITK # or ccmake, or cmake-gui ")
    message("# make")
    message("#")
    message("# NOTE: Given that you already tried to make an in-source build")
    message("#       CMake have already created several files & directories")
    message("#       in your source tree. run 'git status' to find them and")
    message("#       remove them by doing:")
    message("#")
    message("#       cd ITK-Sandbox/ITK")
    message("#       git clean -n -d")
    message("#       git clean -f -d")
    message("#       git checkout --")
    message("#")
    message("######################################################")
    message(FATAL_ERROR "Quitting configuration")
  endif()
endfunction()

AssureOutOfSourceBuilds()

谢谢。我已经编辑了这个答案,包括源代码。我喜欢这种方法。 - mpontillo

5

我在我的.bashrc/.zshrc中有一个类似于这个的cmake() shell函数:

function cmake() {
  # Don't invoke cmake from the top-of-tree
  if [ -e "CMakeLists.txt" ]
  then
    echo "CMakeLists.txt file present, cowardly refusing to invoke cmake..."
  else
    /usr/bin/cmake $*
  fi
}

我更喜欢这种简单的解决方案。当我们转换到CMake时,它消除了同事们最大的抱怨,但它并不会阻止那些真正想要进行源代码或树顶构建的人这样做——他们可以直接调用/usr/bin/cmake(或者根本不使用包装函数)。而且它非常简单。


问题在于它不具备可移植性,且源代码中也没有明确说明。 - aled

4

我认为你的方式不错。CMake邮件列表在回答这些问题方面做得很好。

顺便说一下:您可以在失败的目录中创建一个“cmake”可执行文件。根据是否在linux上将“。”包含在其路径中,您甚至可以使用符号链接/bin/false。

在Windows中,我不确定当前目录中的文件是否被优先找到。


好主意;我会将其解释为将某些内容放在$PATH上,以拦截对cmake的调用 - 或者只是一个包装它的单独脚本。或者可能是一个标准别名。除非在cmake中有一种“本地”方法来做到这一点,否则我认为这是正确的方法。是的,在Windows上,“。”隐式地在$PATH上;在UNIX上不是,但我们可以在$PATH的其他位置放置一个cmake包装器。 - mpontillo
28
什么样的疯子会在他们的路上遇到“.”? - Draemon
2
默认情况下,虚假的“cmake”想法不起作用,因为通常“.”不在它们的路径中。 - Ding-Yi Chen
1
我认为CMakeLists.txt的解决方案更好一些,尽管它有点啰嗦,因为它不依赖于用户具有特定的路径。此外,我将在所有未来的cmake设置中使用它。 - aselle

1

你可以像这个链接一样配置你的 .bashrc 文件

看一下函数 cmakekdekdebuild。设置 BUILD 和 SRC 环境变量,并根据你的需求编辑这些函数。这将只在 buildDir 中构建,而不是在 srcDir 中。


0

只需让进行构建的人/进程将目录设置为只读。有一个单独的进程从源代码控制中检出目录,然后将其设置为只读(您正在使用源代码控制,对吧?)。


谢谢你的回答。不幸的是,由于我们源代码控制和系统的工作方式,这种方法并不实际。我正在寻找一种更通用的解决方案。 - mpontillo

0

对于Linux用户:

在顶层CMakeLists.txt中添加以下内容:

set(CMAKE_DISABLE_IN_SOURCE_BUILD ON)

在您的顶层目录中创建一个名为“dotme”的文件,或将其添加到您的.bashrc文件中(全局):

#!/bin/bash
cmk() { if [ ! -e $1/CMakeLists.txt ] || ! grep -q "set(CMAKE_DISABLE_IN_SOURCE_BUILD ON)" $1/CMakeLists.txt;then /usr/bin/cmake $*;else echo "CMAKE_DISABLE_IN_SOURCE_BUILD ON";fi }

alias cmake=cmk

现在运行:

. ./dotme

当您尝试在顶层源代码树中运行cmake时:

$ cmake .
CMAKE_DISABLE_IN_SOURCE_BUILD ON

没有生成CMakeFiles/或CMakeCache.txt文件。

当进行源外构建并且您需要第一次运行cmake时,只需调用实际的可执行文件:

$ cd build
$ /usr/bin/cmake ..

0

对于我的目的来说,这仍然是最好的答案:

project(myproject)

if(PROJECT_SOURCE_DIR STREQUAL PROJECT_BINARY_DIR)
  message(FATAL_ERROR "In-source builds are not allowed")
endif()

或者允许构建,但显示警告消息:

if(PROJECT_SOURCE_DIR STREQUAL PROJECT_BINARY_DIR)
  message(WARNING "In-source builds are not recommended")
endif()

然而,似乎没有简单的方法可以避免在源目录中创建CMakeFiles/CMakeCache.txt


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接