How do I get project root dir in the cmake install [code] section?

Viewed 34

I use cmake 3.22. I try to get root directory of the my project in the install[CODE] section in the my top level CMakeLists.txt file:

cmake_minimum_required(VERSION 3.22)
...
project(myProject CXX)
...
install(CODE [[
  message(STATUS "PROJECT_SOURCE_DIR: ${PROJECT_SOURCE_DIR}")
  message(STATUS "CMAKE_BINARY_DIR: ${CMAKE_BINARY_DIR}")
  message(STATUS "CMAKE_SOURCE_DIR: ${CMAKE_SOURCE_DIR}")
]])

I get the following outputs during installation ${PROJECT_SOURCE_DIR}:

-- PROJECT_SOURCE_DIR:
-- CMAKE_BINARY_DIR: /project_root/build/config_type_dir
-- CMAKE_SOURCE_DIR: /project_root/build/config_type_dir

But I need to get my /project_root path.

Doc says about ${PROJECT_SOURCE_DIR} variable:

This is the source directory of the last call to the project() command made in the current directory scope or one of its parents.

https://cmake.org/cmake/help/latest/variable/PROJECT_SOURCE_DIR.html

Is it a bug? Or I do something wrong?

2 Answers

Inside install(CODE) you specify a code which becomes part of the other script. That script will be executed from the build directory at installation stage via mechanism cmake -P. So, inside that script both CMAKE_SOURCE_DIR and CMAKE_BINARY_DIR variables refers to the directory, from which the script is executed, and many project-related variables like PROJECT_SOURCE_DIR are simply not set.

If in the installation code you want to use values of variables from your project, then you need to embed these values directly to the code.

E.g., instead of bracket argument you could use quoted argument:

install(CODE "
  message(STATUS \"PROJECT_SOURCE_DIR: ${PROJECT_SOURCE_DIR}\")
  message(STATUS \"CMAKE_BINARY_DIR: ${CMAKE_BINARY_DIR}\")
  message(STATUS \"CMAKE_SOURCE_DIR: ${CMAKE_SOURCE_DIR}\")
")

That way CMake automatically expands all ${...} references to the current values of the variables.

Alternatively, if you do not want to escape double quotes ("), you could leave the code in a bracket form, but transform it via string(CONFIGURE) command:

# Create 'my_install_code' variable which has all variables expanded
string(CONFIGURE [[
  message(STATUS "PROJECT_SOURCE_DIR: ${PROJECT_SOURCE_DIR}")
  message(STATUS "CMAKE_BINARY_DIR: ${CMAKE_BINARY_DIR}")
  message(STATUS "CMAKE_SOURCE_DIR: ${CMAKE_SOURCE_DIR}")
]] my_install_code)

install(CODE "${my_install_code}")

Is it a bug? Or I do something wrong?

No, it's just that not all variables are available in install(CODE) segments. Here's a simple demo of detecting all the variables available there:

cmake_minimum_required(VERSION 3.24)
project(example)

install(CODE [[
get_property(vars DIRECTORY PROPERTY VARIABLES)
foreach (var IN LISTS vars)
  message(STATUS "${var} = ${${var}}")
endforeach ()
]])

See in the terminal:

$ cmake -S . -B build
-- Install configuration: ""
-- CMAKE_ARGC = 4
-- CMAKE_ARGV0 = /home/reinking/.local/lib/python3.9/site-packages/cmake/data/bin/cmake
-- CMAKE_ARGV1 = -DCMAKE_INSTALL_PREFIX=foo
-- CMAKE_ARGV2 = -P
-- CMAKE_ARGV3 = /path/to/build/cmake_install.cmake
-- CMAKE_BINARY_DIR = /path/to
-- CMAKE_COMMAND = /home/reinking/.local/lib/python3.9/site-packages/cmake/data/bin/cmake
-- CMAKE_CPACK_COMMAND = /home/reinking/.local/lib/python3.9/site-packages/cmake/data/bin/cpack
-- CMAKE_CROSSCOMPILING = FALSE
-- CMAKE_CTEST_COMMAND = /home/reinking/.local/lib/python3.9/site-packages/cmake/data/bin/ctest
-- CMAKE_CURRENT_BINARY_DIR = /path/to
-- CMAKE_CURRENT_LIST_DIR = /path/to/build
-- CMAKE_CURRENT_LIST_FILE = /path/to/build/cmake_install.cmake
-- CMAKE_CURRENT_SOURCE_DIR = /path/to
-- CMAKE_FILES_DIRECTORY = /CMakeFiles
-- CMAKE_HOST_SYSTEM_NAME = Linux
-- CMAKE_HOST_UNIX = 1
-- CMAKE_INSTALL_CONFIG_NAME = 
-- CMAKE_INSTALL_PREFIX = foo
-- CMAKE_INSTALL_PREFIX = foo
-- CMAKE_INSTALL_SO_NO_EXE = 1
-- CMAKE_MAJOR_VERSION = 3
-- CMAKE_MINOR_VERSION = 24
-- CMAKE_OBJDUMP = /usr/bin/objdump
-- CMAKE_PATCH_VERSION = 1
-- CMAKE_ROOT = /home/reinking/.local/lib/python3.9/site-packages/cmake/data/share/cmake-3.24
-- CMAKE_SCRIPT_MODE_FILE = /path/to/build/cmake_install.cmake
-- CMAKE_SOURCE_DIR = /path/to
-- CMAKE_TWEAK_VERSION = 0
-- CMAKE_VERSION = 3.24.1
-- UNIX = 1

So if you want to bake a variable into your install script, you'll have to generate the file. Here's one technique:

string(CONFIGURE [[
  set(PROJECT_SOURCE_DIR "@PROJECT_SOURCE_DIR@")

  message(STATUS "PROJECT_SOURCE_DIR: ${PROJECT_SOURCE_DIR}")
  message(STATUS "CMAKE_BINARY_DIR: ${CMAKE_BINARY_DIR}")
  message(STATUS "CMAKE_SOURCE_DIR: ${CMAKE_SOURCE_DIR}")
]] install_script @ONLY)

install(CODE "${install_script}")
Related