Get the final install prefix at install-time

Hi,
I’m working on a C library project that provides some examples.
The examples are contained in a self contained subproject, and can be built from both the top library project (as normal executable targets) or installed as a component.

When installed, the subproject itself gets copied, not the binaries. It can detect it’s not a subproject anymore, and switches to find_package() (config mode) to find the library.

I’d like to make things fancier, and dynamically add the library install prefix to the installed find_package()'s PATHS, so that no matter where the library project is installed, if the examples component is enabled, they’ll be able to find the library config file.

Now, I know that in this case, the CMAKE_INSTALL_PREFIX variable, and $<INSTALL_PREFIX gen exp, are of no use here. Configuration and generation phases are already over when it’s time to install.

Since the install target depends on ALL, what would be needed is a build time command with access to build time-only data. That’s not possible.

Maybe a install(CODE/SCRIPT?
For example, the cmake --install <dir> cli command let’s the user specify a base install dir. Is that path stored somewhere an install script can read?

I took some time to test everything out, CMAKE_INSTALL_PREFIX was correctly set to the final value in “install script mode” (install(CODE).
Project-specific install paths, defined by GNUInstallDirs in the library project, are not available in install script mode. That’s understandable, so I put them directly in the script with variable interpolation.

The complete snippet looks like this

# include(GNUInstallDirs) in root CMakeLists.txt
set(examples_install_dir "${CMAKE_INSTALL_DATADIR}/VTExamples")
string(JOIN "\n" install_script
       "set(examples_install_dir \"${examples_install_dir}\")"
       "set(module_path \"${CMAKE_CURRENT_LIST_DIR}/installed.cmake.in\")"
       [[
set(visualt_install_path ${CMAKE_INSTALL_PREFIX})
set(configured_module_output "${visualt_install_path}/${examples_install_dir}/cmake/interface.cmake")
message(STATUS "Installing: ${configured_module_output}")
message(STATUS "VisualT: installed examples will include \"${visualt_install_path}\" as a package search path")
configure_file(${module_path}
               ${configured_module_output}
               @ONLY)
]])
install(CODE "${install_script}"
        COMPONENT VisualT_examples
        EXCLUDE_FROM_ALL)

The installed.cmake.in module gets installed as interface.cmake where it’s included by the installed examples subproject.

Where can I find a more complete description of what’s available in “install script mode”?
And main differences between standard script mode and install script mode.
For example, I don’t expect CMAKE_INSTALL_PREFIX to be defined in script mode… I don’t know.

I also noticed that the output gets breaks when installing in windows, using windows-style paths.

PS D:\Users\Nemo\OneDrive-shared\c\VisualT\cmake-build-debug-win> cmake --install . --component VisualT_examples --prefix "D:\Users\Nemo\Desktop\VisualT" --config Debug
-- Installing: D:\Users\Nemo\Desktop\VisualT/share/VTExamples

The installation process still works, but my visualt_install_path replaced with configure_file comes out incorrect. I fixed with a file(TO_CMAKE_PATH ${visualt_install_path} visualt_install_path) before the file configuration.

@Artemis Your examples are a separate CMake project that you add with External_Project_Add? Is this intended or can you change this? You can have separate component installs for example as full installation or minimal installation. Would this be a cleaner solution?

The installation prefix is set at configure time, why can you not use this? When you find an installation i think you typically find it in your project or on common places like those used with GNUInstallDirs. So by “no matter where” you mean you want to accept a user input after the project has been build?

Hi @hex !
The subproject containing the examples is included with a simple add_subdirectory().
It’s root CMakeLists.txt includes a module called interface.cmake.
This module contains the code that defines the installation.
When installing the examples subproject, the entire project source dir is copied. A new module called interface.cmake is installed in place of the old one. This new module contains the code that imports the installed library. This setup lets me write the example-specific cmake and completely ignore if the subproject is being used through the main library project or standalone, with the library installed.
That said, the CMAKE_INSTALL_PREFIX is indeed set at configure time, but that’s just a default chosen by GNUInstallDirs or cmake itself. The actual install path can be specified, for example, in the cmake --install cli command. When you run that cli command, the configuration, generation and building phases are already done. I was wondering if it is visible at least to an install script.
Turned out it is, and everything works. The question now is:


Where can I find a complete documentation of what’s available to cmake code ran in install script mode? And in standard script mode (cmake -P)? And a comparison between the two?

Also, as a side note:

You can run this script to list all available commands and variables:

message(STATUS "*** All commands ***")
get_cmake_property(all_commands COMMANDS)
foreach(command IN LISTS all_commands)
  message(STATUS "${command}")
endforeach()
message(STATUS "********************")

message(STATUS "*** All variables ***")
get_cmake_property(all_variables VARIABLES)
foreach(variable IN LISTS all_variables)
  message(STATUS "${variable}")
endforeach()
message(STATUS "*********************")

Oh!, very interesting, and that excludes automatically undefined variables like PROJECT_SOURCE_DIR, ecc, and unavailable commands, like install()in install script mode?

When running the first half of the script I shared previously, you’ll see that install is listed as one of the commands. That’s because all commands that are “available only in CMake projects” are actually always defined, even in script mode. This allows CMake to fail with the following error message when calling them

  <command> command is not scriptable

instead of the generic

  Unknown CMake command "<command>".

The easiest way to know which commands are really available in script mode is to look at the documentation here: https://cmake.org/cmake/help/latest/manual/cmake-commands.7.html#scripting-commands

1 Like

That’s exactly what I was looking for :slightly_smiling_face:
It doesn’t appear in a “script mode” search, so I missed it. Maybe it could be useful to add a “see also” link in the CMake language/scripts section. Variables’ script mode behaviour is independently documented instead, but they link to each other so it’s fine.

I think there are still some related key information not mentioned by the docs.
For example, as I mentioned before, I had no idea of what CMAKE_INSTALL_PREFIX could contain in install script mode. I had to take some time to make experiments. I was surprised that it contained system-specific directory separator. I was confused because cmake wasn’t having problems, and I didn’t know you could throw windows-style paths to commands without them breaking. And even if they don’t, using the variable to configure a file (see above) causes errors.

CMAKE_INSTALL_PREFIX is just one example I came across, I would like to have a place to learn the nuances of install script mode in advance.