How do I get cmake to halt processing on error [duplicate] - cmake

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("${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?

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;
}

Include a function like this one. It is similar to what you do with these differences:
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)
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()

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.

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.

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

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.

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 ..

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.

Related

Prohibit cmake build on the top-level source

I would like to prohibit cmake in-source build especially at top-level.
For example prohibited case is cmake -S . -B ., however, I would like to allow cmake -S . -B build
Thus, these following options are not fitted here.
set(CMAKE_DISABLE_SOURCE_CHANGES ON)
set(CMAKE_DISABLE_IN_SOURCE_BUILD ON)
I added small script in CMakeLists.txt
get_filename_component(srcdir "${CMAKE_SOURCE_DIR}" REALPATH)
get_filename_component(bindir "${CMAKE_BINARY_DIR}" REALPATH)
if("${srcdir}" STREQUAL "${bindir}")
message(FATAL_ERROR "Do not build on the top of the sources")
endif()
It works, but CMakeCache.txt and CMakeFiles are still created. How can I keep my sources clean?
With git, git clean -d -f -x can help me, but I would like to have a solution with cmake itself.
"CMakeCache.txt and CMakeFiles are still created" -- you cannot avoid this with CMake <=3.24, the latest development version at time of writing. You can suggest deleting that folder and file in your FATAL_ERROR message.
if(CMAKE_SOURCE_DIR STREQUAL CMAKE_BINARY_DIR)
message(FATAL_ERROR "Out of source builds required with CMake. "
"Please clean up the CMakeFiles/ directory and CMakeCache.txt file.")
endif()

Generate script during build with cmake

I have a cmake project with a single source file called main.c. I want to additionally provide a wrapper script which calls main with specific parameters.
My CMakeLists.txt looks as follows:
cmake_minimum_required(VERSION 3.1...3.16)
file(WRITE ${CMAKE_BINARY_DIR}/wrapper "#!/usr/bin/env bash\n")
file(APPEND ${CMAKE_BINARY_DIR}/wrapper "./main options\n")
add_executable(main main.c)
add_custom_target(wrapper_target
ALL DEPENDS wrapper)
add_custom_target(main_target
ALL DEPENDS main wrapper_target)
add_dependencies(main wrapper_target)
install(
TARGETS main
RUNTIME DESTINATION bin/)
install(
PROGRAMS wrapper
DESTINATION bin/)
If I run cmake --install ., the script wrapper is installed together with the binary main. Running cmake --build . produces the script wrapper, but it is not marked as executable (on Linux).
How can I tell cmake to also generate wrapper during build and mark it as executable?
Note: I need this for an automated build system which runs build and not install, and expects a specific file to be available on build.
Try:
file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/wrapper.tmp
"#!/usr/bin/env bash
# Note that './main' is relative from whatever directory you are in
# Use just main assuming the install prefix is in your bath
# Or use $<TARGET_FILE:main>
# Or maybe ${CMAKE_INSTALL_PREFIX}/bin/main
./main options
")
# add execute permissions
file(
COPY ${CMAKE_CURRENT_BINARY_DIR}/wrapper.tmp
DESTINATION ${CMAKE_CURRENT_BINARY_DIR}
FILE_PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE
)
# Rename the file
file(RENAME
${CMAKE_CURRENT_BINARY_DIR}/wrapper.tmp
${CMAKE_CURRENT_BINARY_DIR}/wrapper
)
How can I tell cmake to also generate wrapper during build and mark it as executable?
file( is a in a cmake script - it is executed during configuration phase, when cmake is executed. To generate the file during build use add_custom_command, the most "portable" way in cmake sense would be to run a cmake script inside add_custom_command:
add_custom_command
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/wrapper
COMMAND $(CMAKE_COMMAND)
-D CMAKE_CURRENT_BINARY_DIR=${CMAKE_CURRENT_BINARY_DIR}
-P ${CMAKE_CURRENT_SOURCE_DIR}/the_cmake_script.cmake
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/the_cmake_script.cmake
COMMAND Generating wrapper script...
VERBATIM
)
then inside the_cmake_script.cmake you could do the script above - add_custom_command will execute the command cmake -P <the script> during build of you project. That way you can DEPEND properly on the wrapper script.
CMake 3.20 added support for FILE_PERMISSIONS attribute to the FILE command. So one could simply:
FILE(GENERATE OUTPUT ${CMAKE_BINARY_DIR}/wrapper
CONTENT
"#!/usr/bin/env bash
./main options
"
FILE_PERMISSIONS OWNER_EXECUTE OWNER_WRITE OWNER_READ)

CMake Error: file INSTALL destination is not a directory

I am running the command "sudo make install", the relevant cmake_install.cmake file is at the bottom. The exact error message I receive is:
CMake Error at cmake_install.cmake:36 (file):
file INSTALL destination:
~/Desktop/Geant/geant4.10.04-install/share/Geant4-10.4.0/geant4make is not
a directory.
Makefile:104: recipe for target 'install' failed
make: *** [install] Error 1
This is perplexing to me as I can navigate to that exact directory, it exists and whats more, it was made during this installation, so the make install is creating this directory and then saying that it doesn't exist...
Also, when I originally did the cmake command, my CMAKE_INSTALL_PREFIX is "~/Desktop/Geant/geant4.10.04-install", but since the make install command was able to make the geant4.10.04-install directory in the correct place, I don't think that is the problem.
The first 50ish lines of the cmake_install.cmake file (I can post the rest if need be...) :
# Install script for directory: /home/kagnew/Desktop/Geant/geant4.10.04
# Set the install prefix
if(NOT DEFINED CMAKE_INSTALL_PREFIX)
set(CMAKE_INSTALL_PREFIX "~/Desktop/Geant/geant4.10.04-install")
endif()
string(REGEX REPLACE "/$" "" CMAKE_INSTALL_PREFIX "${CMAKE_INSTALL_PREFIX}")
# Set the install configuration name.
if(NOT DEFINED CMAKE_INSTALL_CONFIG_NAME)
if(BUILD_TYPE)
string(REGEX REPLACE "^[^A-Za-z0-9_]+" ""
CMAKE_INSTALL_CONFIG_NAME "${BUILD_TYPE}")
else()
set(CMAKE_INSTALL_CONFIG_NAME "Release")
endif()
message(STATUS "Install configuration: \"${CMAKE_INSTALL_CONFIG_NAME}\"")
endif()
# Set the component getting installed.
if(NOT CMAKE_INSTALL_COMPONENT)
if(COMPONENT)
message(STATUS "Install component: \"${COMPONENT}\"")
set(CMAKE_INSTALL_COMPONENT "${COMPONENT}")
else()
set(CMAKE_INSTALL_COMPONENT)
endif()
endif()
# Install shared libraries without execute permission?
if(NOT DEFINED CMAKE_INSTALL_SO_NO_EXE)
set(CMAKE_INSTALL_SO_NO_EXE "1")
endif()
if(NOT CMAKE_INSTALL_COMPONENT OR "${CMAKE_INSTALL_COMPONENT}" STREQUAL "Development")
file(INSTALL DESTINATION "${CMAKE_INSTALL_PREFIX}/share/Geant4-10.4.0/geant4make" TYPE FILE MESSAGE_LAZY PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE FILES "/home/kagnew/Desktop/Geant/geant4-build/InstallTreeFiles/geant4make.sh")
endif()
UPDATE: As suggested by Tsyvarev, changing the beginning of my prefix path from "~" to "/home/user/" seems to have fixed the problem
Using the environmental variable $ENV{HOME} is preferable to hardcoding /home/<user> because it will use the correct top-level directory (i.e. /Users instead of /home on macOS, if you're doing a cross-platform build), and it will automatically expand to include the name of the user invoking cmake, making it better suited to collaborative environments.
Additionally, using $ENV{HOME} should make the sudo in front of make install unnecessary, though depending on when the variable is expanded, $ENV{HOME} may refer to the user invoking cmake or the user invoking make install (i.e. /root if you use sudo), so your mileage may vary.
EDIT: I found my way to this question because I was getting the same "CMake Error: file INSTALL destination is not a directory" output due to using ~. It would seem that for certain purposes CMake just really doesn't like ~. $ENV{HOME} has exactly the same value as ~, except that CMake doesn't freak out when you try to use it in CMAKE_INSTALL_PREFIX.

CMake: Imported libraries as OUTPUT or BYPRODUCTS of another custom command or target

I want to extract a static library from a ZIP-file and link against it.
Having the following setting:
add_library(COMSDK_LIB STATIC IMPORTED GLOBAL)
set_property(TARGET COMSDK_LIB PROPERTY IMPORTED_LOCATION "/tmp/lib/libRTSClientSdk.a")
And the imported library being used in another CMakeLists.txt:
target_link_libraries(mylib COMSDK_LIB)
Would it be possible that the imported library is generated by another add_custom_command or add_custom_target?
I tried the following, but it did NOT work:
add_custom_command(
OUTPUT "/tmp/lib/libRTSClientSdk.a"
COMMAND unzip -x client_sdk.zip -o /tmp
DEPENDS client_sdk.zip
)
The given error message was:
$ ninja
ninja: error: '/tmp/lib/libRTSClientSdk.a', needed by 'mylib.dll', missing and no known rule to make it
The problem is that Your custom command is being executed during make step, and it's expecting extracted dependency earlier --- during cmake execution. So, basically, You need to get that static library from SDK before You're using it in CMake rules or during linkage itself.
Two of possible solutions are listed in CMake mailing list.
Solution #1: (executed in CMake side)
Re-phrasing one of the code snippets, downloading and unzipping is being done during CMake execution:
set(CLIENTSDK_ZIP "cliendsdk.zip")
set(CLIENTSDK_URL "http://example.com/${CLIENTSDK_ZIP}")
set(CLIENTSDK_LIB "libRTSClientSdk.a")
set(CLIENTSDK_OUTPUT_DIR "/tmp/sdk/dir")
# Basically just downloading zip file:
message(STATUS "Downloading ${CLIENTSDK_URL}")
execute_process(COMMAND wget ${CLIENTSDK_URL}
WORKING_DIRECTORY ${CLIENTSDK_OUTPUT_DIR}
RESULT_VARIABLE errno
ERROR_VARIABLE err)
if (NOT ${errno} EQUAL 0)
message(ERROR "Failed downloading ${CLIENTSDK_URL}. Code: ${err}")
endif()
# Extracting downloaded zip file:
message(STATUS "Extracting ${CLIENTSDK_ZIP})
execute_process(COMMAND unzip ${CLIENTSDK_ZIP}
WORKING_DIRECTORY ${CLIENTSDK_OUTPUT_DIR}
RESULT_VARIABLE errno
ERROR_VARIABLE err)
if (NOT ${errno} EQUAL 0)
message(ERROR "Failed extracting ${CLIENTSDK_ZIP}. Code: ${err}")
endif()
# Importing into CMake scope one library from extracted zip:
add_library(specific_sdk_lib STATIC IMPORTED)
set_target_properties(specific_sdk_lib
PROPERTIES IMPORTED_LOCATION ${CLIENTSDK_OUTPUT_DIR}/lib/${CLIENTSDK_LIB})
# Adding rule for linking to specific static library from extracted zip:
target_link_libraries(mylib specific_sdk_lib)
Solution #2 (executed in make side):
# We must include ExternalProject CMake module first!
include("ExternalProject")
set(CLIENTSDK_URL "http://example.com/clientsdk.zip")
set(CLIENTSDK_LIB "libRTSClientSdk.a")
set(CLIENTSDK_PREFIX "3rd_party")
set(CLIENTSDK_EXTRACTED_DIR
"${CMAKE_CURRENT_BINARY_DIR}/${CLIENTSDK_PREFIX}/src/DownloadClientSDK/")
ExternalProject_Add("DownloadClientSDK"
PREFIX ${CLIENTSDK_PREFIX}
URL "${CLIENTSDK_URL}"
# Suppress ExternalProject configure/build/install targets:
CONFIGURE_COMMAND ""
BUILD_COMMAND ""
INSTALL_COMMAND "")
add_library(COMSDK_LIB STATIC IMPORTED)
set_target_properties(COMSDK_LIB PROPERTIES IMPORTED_LOCATION
${CLIENTSDK_EXTRACTED_DIR}/${CLIENTSDK_LIB})
# Require all that download/unzip mumbojumbo only for COMSDK_LIB:
add_dependencies(COMSDK_LIB DownloadClientSDK)
target_link_libraries(my_library COMSDK_LIB)
Basically, CMake and it's ExternalProject module takes care (generates proper make targets) of recognizing archive format, unzipping it, and if needed - configuring, building and installing.

How to use CPACK_RPM_POST_INSTALL_SCRIPT_FILE?

Here is the setup I tried using CMake 2.8.2 to reproduce the problem:
/test.sh:
/CMakeLists.txt:
cmake_minimum_required(VERSION 2.8)
SET(CPACK_PACKAGE_NAME test)
SET(CPACK_PACKAGE_VERSION 1.0)
LIST(APPEND CPACK_GENERATOR RPM)
SET(CPACK_RPM_POST_INSTALL_SCRIPT_FILE "test.sh")
INCLUDE(CPack)
then:
mkdir build && cd build && cmake .. && make package
Results:
CPackRPM:Warning: CPACK_RPM_POST_INSTALL_SCRIPT_FILE does not exists - ignoring
How to make the build system aware of my file test.sh ?
You need to use absolute path:
SET(CPACK_RPM_POST_INSTALL_SCRIPT_FILE "${CMAKE_CURRENT_SOURCE_DIR}/test.sh")
This is needed because CPackRPM needs the absolute path of the file as CPack does not know that test.sh is relative to source tree.