diff --git a/CHANGELOG.md b/CHANGELOG.md index 02174e280..015457325 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - jrl_release: add `ConanFileVersionExtractor` by @arntanguy - jrl_release: add `--check-version --check-tag vX.Y.Z` by @arntanguy - jrl_release: add `DebianChangelogVersionExtractor` by @arntanguy +- jrl: change jrl_configure_default_install_dirs() to macro to make GNUInstallDirs in caller's scope by @ahoarau ## [2.1.0] - 2026-07-03 diff --git a/v2/docs/api.md b/v2/docs/api.md index 90693e850..861a38a68 100644 --- a/v2/docs/api.md +++ b/v2/docs/api.md @@ -219,13 +219,17 @@ jrl_target_set_output_directory(my_python_module_target OUTPUT_DIRECTORY ${CMAKE jrl_configure_default_install_dirs() ``` -**Type:** function +**Type:** macro ### Description Configures the default install directories using GNUInstallDirs (bin, lib, include, etc.). Works on all platforms. + Must be called directly from a project's `CMakeLists.txt`, not wrapped in a `function()`: + `CMAKE_INSTALL_DOCDIR`, `DATADIR`, `MANDIR`, `INFODIR`, `LOCALEDIR` and every + `CMAKE_INSTALL_FULL_*` are not cache entries and would be lost when the function returns. + ### Arguments None diff --git a/v2/modules/jrl.cmake b/v2/modules/jrl.cmake index 6caea223b..cb4c62ea4 100644 --- a/v2/modules/jrl.cmake +++ b/v2/modules/jrl.cmake @@ -796,13 +796,17 @@ endfunction() jrl_configure_default_install_dirs() ``` -**Type:** function +**Type:** macro ### Description Configures the default install directories using GNUInstallDirs (bin, lib, include, etc.). Works on all platforms. + Must be called directly from a project's `CMakeLists.txt`, not wrapped in a `function()`: + `CMAKE_INSTALL_DOCDIR`, `DATADIR`, `MANDIR`, `INFODIR`, `LOCALEDIR` and every + `CMAKE_INSTALL_FULL_*` are not cache entries and would be lost when the function returns. + ### Arguments None @@ -813,19 +817,37 @@ jrl_configure_default_install_dirs() jrl_configure_default_install_dirs() ``` #]============================================================================] -function(jrl_configure_default_install_dirs) +macro(jrl_configure_default_install_dirs) + if(DEFINED CMAKE_CURRENT_FUNCTION) + message( + FATAL_ERROR + "jrl_configure_default_install_dirs() must be called directly from a CMakeLists.txt, not from inside function '${CMAKE_CURRENT_FUNCTION}()'. + The GNUInstallDirs variables that are not cache entries (CMAKE_INSTALL_DOCDIR, DATADIR, MANDIR, + INFODIR, LOCALEDIR and all CMAKE_INSTALL_FULL_*) would be lost when that function returns. + " + ) + endif() + # Prevent the following warning on pure-cmake projects (i.e. defined with LANGUAGES NONE, like jrl-cmakemodules): # "Unable to determine default CMAKE_INSTALL_LIBDIR directory because no target architecture is known. # Please enable at least one language before including GNUInstallDirs" # ref: https://github.com/Kitware/CMake/blob/v4.2.3/Modules/GNUInstallDirs.cmake#L434C8-L435C75 # issue: https://gitlab.kitware.com/cmake/cmake/-/issues/23461 - - if(NOT DEFINED CMAKE_SIZEOF_VOID_P) + if(DEFINED CMAKE_SIZEOF_VOID_P) + set(_cmake_sizeof_already_defined false) + else() + set(_cmake_sizeof_already_defined true) set(CMAKE_SIZEOF_VOID_P 0) endif() include(GNUInstallDirs) -endfunction() + + # Do not leak the placeholder: a defined-but-0 value is worse than an undefined one. + if(_cmake_sizeof_already_defined) + unset(CMAKE_SIZEOF_VOID_P) + endif() + unset(_cmake_sizeof_already_defined) +endmacro() #[============================================================================[ # `jrl_configure_default_install_prefix` @@ -939,8 +961,9 @@ jrl_configure_defaults() macro(jrl_configure_defaults) jrl_configure_default_build_type(Release) jrl_configure_default_binary_dirs() - jrl_configure_default_install_dirs() + # Before install_dirs: GNUInstallDirs derives CMAKE_INSTALL_FULL_* from the prefix, once. jrl_configure_default_install_prefix(${CMAKE_BINARY_DIR}/install) + jrl_configure_default_install_dirs() jrl_configure_copy_compile_commands_in_source_dir() jrl_configure_uninstall_target() endmacro() diff --git a/v2/tests/CMakeLists.txt b/v2/tests/CMakeLists.txt index 89ede6354..b5add8aec 100644 --- a/v2/tests/CMakeLists.txt +++ b/v2/tests/CMakeLists.txt @@ -39,3 +39,4 @@ add_subdirectory(print_dependencies_summary) add_subdirectory(export_package_control) add_subdirectory(target_headers_relative) add_subdirectory(output_dirs) +add_subdirectory(gnu_install_dirs) diff --git a/v2/tests/gnu_install_dirs/CMakeLists.txt b/v2/tests/gnu_install_dirs/CMakeLists.txt new file mode 100644 index 000000000..14bb1a66f --- /dev/null +++ b/v2/tests/gnu_install_dirs/CMakeLists.txt @@ -0,0 +1 @@ +add_cmake_test(NAME gid-project DEPENDS jrl-cmakemodules) diff --git a/v2/tests/gnu_install_dirs/README.md b/v2/tests/gnu_install_dirs/README.md new file mode 100644 index 000000000..4ea558eb9 --- /dev/null +++ b/v2/tests/gnu_install_dirs/README.md @@ -0,0 +1,6 @@ +# gnu_install_dirs + +Verifies that `jrl_configure_defaults()` leaves every `GNUInstallDirs` variable usable in the +caller's scope, including `CMAKE_INSTALL_DOCDIR`, `DATADIR`, `MANDIR`, `INFODIR`, `LOCALEDIR` +and all the `CMAKE_INSTALL_FULL_*`, which are not cache entries and are therefore lost if +`GNUInstallDirs` is included from inside a `function()`. diff --git a/v2/tests/gnu_install_dirs/gid-project/CMakeLists.txt b/v2/tests/gnu_install_dirs/gid-project/CMakeLists.txt new file mode 100644 index 000000000..3f0654346 --- /dev/null +++ b/v2/tests/gnu_install_dirs/gid-project/CMakeLists.txt @@ -0,0 +1,61 @@ +cmake_minimum_required(VERSION 3.22) + +project(gid-project VERSION 1.0.0 LANGUAGES CXX) + +find_package(jrl-cmakemodules 2.0.0 CONFIG REQUIRED) + +jrl_configure_defaults() + +# GNUInstallDirs only creates cache entries for its root dirs (BINDIR, LIBDIR, ...). +# The derived ones (DATADIR, DOCDIR, MANDIR, INFODIR, LOCALEDIR) and every +# CMAKE_INSTALL_FULL_* are plain variables, lost if GNUInstallDirs is included +# from inside a function(). They must all be usable in the caller's scope. +set(expected_install_dirs + BINDIR + SBINDIR + LIBEXECDIR + SYSCONFDIR + SHAREDSTATEDIR + LOCALSTATEDIR + LIBDIR + INCLUDEDIR + OLDINCLUDEDIR + DATAROOTDIR + DATADIR + INFODIR + LOCALEDIR + MANDIR + DOCDIR +) + +foreach(dir IN LISTS expected_install_dirs) + foreach(var CMAKE_INSTALL_${dir} CMAKE_INSTALL_FULL_${dir}) + if(NOT DEFINED ${var}) + message(FATAL_ERROR "${var} is not defined after jrl_configure_defaults()") + endif() + if(${var} STREQUAL "") + message(FATAL_ERROR "${var} is empty after jrl_configure_defaults()") + endif() + endforeach() + message( + STATUS + "CMAKE_INSTALL_${dir}=${CMAKE_INSTALL_${dir}} (full: ${CMAKE_INSTALL_FULL_${dir}})" + ) +endforeach() + +if(NOT CMAKE_INSTALL_DOCDIR STREQUAL "${CMAKE_INSTALL_DATAROOTDIR}/doc/${PROJECT_NAME}") + message( + FATAL_ERROR + "Expected CMAKE_INSTALL_DOCDIR to be '${CMAKE_INSTALL_DATAROOTDIR}/doc/${PROJECT_NAME}', got: '${CMAKE_INSTALL_DOCDIR}'" + ) +endif() + +file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/test.txt" "OK\n") +install(FILES "${CMAKE_CURRENT_BINARY_DIR}/test.txt" DESTINATION ${CMAKE_INSTALL_DOCDIR}) + +enable_testing() + +add_test( + NAME "gnu_install_dirs: test.txt is installed in CMAKE_INSTALL_DOCDIR" + COMMAND ${CMAKE_COMMAND} -E cat "${CMAKE_INSTALL_FULL_DOCDIR}/test.txt" +) diff --git a/v2/tests/gnu_install_dirs/gid-project/pixi.toml b/v2/tests/gnu_install_dirs/gid-project/pixi.toml new file mode 100644 index 000000000..d5fabe591 --- /dev/null +++ b/v2/tests/gnu_install_dirs/gid-project/pixi.toml @@ -0,0 +1,30 @@ +[workspace] +name = "gid-project" +version = "1.0.0" +channels = ["conda-forge"] +platforms = ["osx-arm64", "osx-64", "linux-64", "linux-aarch64", "win-64"] +preview = ["pixi-build"] + +[dependencies] +cxx-compiler = "*" +cmake = ">=3.22" +ninja = "*" +jrl-cmakemodules = { path = "../../../../" } + +[tasks] +clear = { cmd = "rm -rf build" } +configure = { cmd = "cmake --log-level=DEBUG -G Ninja -S . -B build" } +build = { cmd = "cmake --build build" } +install = { cmd = "cmake --install build" } +ctest = { cmd = "ctest --test-dir build --output-on-failure --no-tests=ignore" } +test = { depends-on = ["clear", "configure", "build", "install", "ctest"] } + +[package] +name = { workspace = true } +version = { workspace = true } + +[package.host-dependencies] +jrl-cmakemodules = { path = "../../../.." } + +[package.build] +backend = { name = "pixi-build-cmake", version = "*" } diff --git a/v2/tests/gnu_install_dirs/pixi.toml b/v2/tests/gnu_install_dirs/pixi.toml new file mode 100644 index 000000000..e0904c319 --- /dev/null +++ b/v2/tests/gnu_install_dirs/pixi.toml @@ -0,0 +1,6 @@ +[workspace] +channels = ["conda-forge"] +platforms = ["linux-64", "linux-aarch64", "osx-arm64", "osx-64", "win-64"] + +[tasks] +test = { cmd = "pixi run test", cwd = "gid-project" } diff --git a/v2/tests/pixi.toml b/v2/tests/pixi.toml index 089c688eb..216457101 100644 --- a/v2/tests/pixi.toml +++ b/v2/tests/pixi.toml @@ -25,6 +25,7 @@ test_header_visibility = { cmd = "pixi run test", cwd = "header_visibility" } test_export_package_control = { cmd = "pixi run test", cwd = "export_package_control" } test_target_headers_relative = { cmd = "pixi run test", cwd = "target_headers_relative" } test_output_dirs = { cmd = "pixi run test", cwd = "output_dirs" } +test_gnu_install_dirs = { cmd = "pixi run test", cwd = "gnu_install_dirs" } test = { depends-on = [ "test_find_modules", @@ -47,4 +48,5 @@ test = { depends-on = [ "test_export_package_control", "test_target_headers_relative", "test_output_dirs", + "test_gnu_install_dirs", ] }