51

I want to disallow people from cluttering our source tree with generated CMake files... and, more importantly, disallow them from stepping on existing Makefiles that are not part of the same build process we're using CMake for. (best not to ask)

The way I have come up with to do this is to have a few lines at the top of my CMakeLists.txt, as follows:

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

Edited to note: in older versions of cmake, this would not have been possible with this syntax: the last line would have needed to read:

endif("${PROJECT_SOURCE_DIR}" STREQUAL "${PROJECT_BINARY_DIR}")

However, doing it this way seems too verbose. Additionally, if I try an in-source build it still creates the the CMakeFiles/ directory, and the CMakeCache.txt file in the source tree before the error is thrown.

Am I missing a better way to do this?

6
  • 6
    This is the best solution that I've found so far. You could make the message more informative, though: message(FATAL_ERROR "In-source builds are not permitted. Make a separate folder for building:\nmkdir build; cd build; cmake ..\nBefore that, remove the files already created:\nrm -rf CMakeCache.txt CMakeFiles")
    – Tronic
    Commented Jun 16, 2013 at 15:08
  • 1
    I wouldn't call it verbose if you closed the condition with just an endif().
    – Burak
    Commented Apr 14, 2023 at 8:21
  • 1
    That's nice if you can just use endif() today. I think when I wrote this question, that was not possible.
    – mpontillo
    Commented Apr 25, 2023 at 5:53
  • @mpontillo did you later find a viable solution to this? Commented Nov 15, 2023 at 7:04
  • @ChukwujiobiCanon, I've since moved on from the project I'm discussing in this question, but if I believe the most-upvoted answers below, it looks like CMake has made progress in this area! Please let us know what works best for you.
    – mpontillo
    Commented Nov 17, 2023 at 1:00

8 Answers 8

58

CMake has two undocumented options: CMAKE_DISABLE_SOURCE_CHANGES and CMAKE_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;
}
4
  • 8
    Unfortunately this does not prevent CMake from creating CMakeCache.txt and CMakeFiles/ and thus it is no better than signaling an error (and is worse in that it gives a cryptic message).
    – Tronic
    Commented Jun 16, 2013 at 15:04
  • 1
    Just make a directory with name "CMakeCache.txt" to avoid creating cache file. Commented Jun 20, 2013 at 13:26
  • 13
    If they are undocumented you should be wary to use these options. They may change without warning in future versions.
    – aled
    Commented Mar 25, 2014 at 13:52
  • Unfortunately I get regression errors using obscure CXX compiler combinations. It's probably best to not use undocumented options for good reasons.
    – Mike T
    Commented May 27, 2020 at 8:12
7

Include a function like this one. It is similar to what you do with these differences:

  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.

In case the github link changes, here's the module source code (which should be placed in a PreventInSouceBuilds.cmake, in a directory called CMake, in the above example):

#
# 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()
1
  • Thanks. I've edited this answer to include the source code. I like this approach.
    – mpontillo
    Commented Mar 25, 2014 at 16:16
6

I have a cmake() shell function in my .bashrc/.zshrc similar to this one:

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
}

I prefer this low ceremony solution. It got rid of my colleagues' biggest complaint when we switched to CMake, but it doesn't prevent people who really want to do an in-source/top-of-tree build from doing so—they can just invoke /usr/bin/cmake directly (or not use the wrapper function at all). And it's stupid simple.

1
  • The problem with that is that is not portable and is not explicit in the sources.
    – aled
    Commented Mar 25, 2014 at 14:02
4

I think I like your way. The cmake mailing list does a good job at answering these types of questions.

As a side note: you could create a "cmake" executable file in the directory which fails. Depending on whether or not "." is in their path (on linux). You could even symlink /bin/false.

In windows, I am not sure if a file in your current directory is found first or not.

4
  • Good idea; I'll interpret that as putting something on the $PATH that will intercept the call to cmake - or simply a separate script that will wrap it. Or maybe a standard alias. Unless there is a 'native' way to do this in cmake I think that's the way to go. And yes, I believe on Windows '.' is implicitly on the $PATH; on UNIX it is not, but we could always put a cmake wrapper somewhere else in the $PATH.
    – mpontillo
    Commented Jul 31, 2009 at 0:25
  • 28
    What sort of nutter has . on their path?
    – Draemon
    Commented Sep 22, 2010 at 21:48
  • 2
    The fake "cmake" idea won't work by default, as usually "." won't be in their path. Commented Aug 19, 2014 at 14:22
  • 1
    I think the CMakeLists.txt solution is a little better despite the verbosity because it doesn't rely on the user having a particular path. Moreover, I will be putting that in all my future cmake setups.
    – aselle
    Commented Sep 8, 2014 at 17:39
1

You can configure your .bashrc file like this one

Look at the functions cmakekde and kdebuild. Set BUILD and SRC env. variables and edit these functions according to your needs. This will build only in buildDir rather than srcDir

0

Just make the directory read-only by the people/processes doing the builds. Have a separate process that checks out to the directory from source control (you are using source control, right?), then makes it read-only.

1
  • Thanks for the answer. Unfortunately, because of the way our source control and system works, it would be impractical to do it this way. I was looking for a more general solution.
    – mpontillo
    Commented Jul 31, 2009 at 0:30
0

For those on Linux:

add to top-level CMakeLists.txt:

set(CMAKE_DISABLE_IN_SOURCE_BUILD ON)

create a file 'dotme' in your top-level or add to your .bashrc (globally):

#!/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

now run:

. ./dotme

when you try to run cmake in the top-level source tree:

$ cmake .
CMAKE_DISABLE_IN_SOURCE_BUILD ON

No CMakeFiles/ or CMakeCache.txt gets generated.

When doing out-of-source build and you need to run cmake first time just call the actual executable:

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

This is still the best answer for my purposes:

project(myproject)

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

or allow the build, but show a warning message:

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

However, there does not appear to be a simple way to avoid CMakeFiles/ and CMakeCache.txt being created in the source directory.

Not the answer you're looking for? Browse other questions tagged or ask your own question.