From 34343922a5ca8a19ece9aa048232d0864b599441 Mon Sep 17 00:00:00 2001 From: Matthew Woehlke Date: Tue, 11 Nov 2025 11:51:58 -0500 Subject: [PATCH] install: Add ability to generate CPS from install(EXPORT) Add the ability to generate CPS package information when install(EXPORT) is used. This relies on additional information to be passed to CMake, and is intended as a way of getting CPS out of existing projects without needing to make changes to the projects' CMakeLists.txt. (Particularly, this feature is intended for package distributors, not project authors.) --- Help/command/install.rst | 16 +- Help/manual/cmake-variables.7.rst | 1 + .../CMAKE_INSTALL_EXPORTS_AS_PACKAGE_INFO.rst | 122 +++++++++++++ Source/cmExperimental.cxx | 9 + Source/cmExperimental.h | 1 + Source/cmInstallCommand.cxx | 168 ++++++++++++++++++ Tests/RunCMake/CMakeLists.txt | 1 + .../Assertions.cmake | 39 ++++ .../BadDirective-result.txt | 1 + .../BadDirective-stderr.txt | 5 + .../BadDirective.cmake | 2 + .../CMakeLists.txt | 3 + .../ExperimentalGate.cmake | 6 + .../ExperimentalWarning-stderr.txt | 7 + .../ExperimentalWarning.cmake | 13 ++ .../LowerCase-check.cmake | 27 +++ .../LowerCase.cmake | 12 ++ .../MissingAppendixName-result.txt | 1 + .../MissingAppendixName-stderr.txt | 5 + .../MissingAppendixName.cmake | 2 + .../MissingExport-result.txt | 1 + .../MissingExport-stderr.txt | 3 + .../MissingExport.cmake | 2 + .../MissingPackageName-result.txt | 1 + .../MissingPackageName-stderr.txt | 5 + .../MissingPackageName.cmake | 2 + .../RunCMakeTest.cmake | 22 +++ .../SampleExport-check.cmake | 19 ++ .../SampleExport.cmake | 22 +++ 29 files changed, 517 insertions(+), 1 deletion(-) create mode 100644 Help/variable/CMAKE_INSTALL_EXPORTS_AS_PACKAGE_INFO.rst create mode 100644 Tests/RunCMake/InstallExportsAsPackageInfo/Assertions.cmake create mode 100644 Tests/RunCMake/InstallExportsAsPackageInfo/BadDirective-result.txt create mode 100644 Tests/RunCMake/InstallExportsAsPackageInfo/BadDirective-stderr.txt create mode 100644 Tests/RunCMake/InstallExportsAsPackageInfo/BadDirective.cmake create mode 100644 Tests/RunCMake/InstallExportsAsPackageInfo/CMakeLists.txt create mode 100644 Tests/RunCMake/InstallExportsAsPackageInfo/ExperimentalGate.cmake create mode 100644 Tests/RunCMake/InstallExportsAsPackageInfo/ExperimentalWarning-stderr.txt create mode 100644 Tests/RunCMake/InstallExportsAsPackageInfo/ExperimentalWarning.cmake create mode 100644 Tests/RunCMake/InstallExportsAsPackageInfo/LowerCase-check.cmake create mode 100644 Tests/RunCMake/InstallExportsAsPackageInfo/LowerCase.cmake create mode 100644 Tests/RunCMake/InstallExportsAsPackageInfo/MissingAppendixName-result.txt create mode 100644 Tests/RunCMake/InstallExportsAsPackageInfo/MissingAppendixName-stderr.txt create mode 100644 Tests/RunCMake/InstallExportsAsPackageInfo/MissingAppendixName.cmake create mode 100644 Tests/RunCMake/InstallExportsAsPackageInfo/MissingExport-result.txt create mode 100644 Tests/RunCMake/InstallExportsAsPackageInfo/MissingExport-stderr.txt create mode 100644 Tests/RunCMake/InstallExportsAsPackageInfo/MissingExport.cmake create mode 100644 Tests/RunCMake/InstallExportsAsPackageInfo/MissingPackageName-result.txt create mode 100644 Tests/RunCMake/InstallExportsAsPackageInfo/MissingPackageName-stderr.txt create mode 100644 Tests/RunCMake/InstallExportsAsPackageInfo/MissingPackageName.cmake create mode 100644 Tests/RunCMake/InstallExportsAsPackageInfo/RunCMakeTest.cmake create mode 100644 Tests/RunCMake/InstallExportsAsPackageInfo/SampleExport-check.cmake create mode 100644 Tests/RunCMake/InstallExportsAsPackageInfo/SampleExport.cmake diff --git a/Help/command/install.rst b/Help/command/install.rst index f3c4d2f603..68caf6818a 100644 --- a/Help/command/install.rst +++ b/Help/command/install.rst @@ -979,7 +979,7 @@ Signatures Experimental. Gated by ``CMAKE_EXPERIMENTAL_EXPORT_PACKAGE_INFO``. - Installs a |CPS|_ file exporting targets for dependent projects: + Installs a |CPS|_ ("CPS") file exporting targets for dependent projects: .. code-block:: cmake @@ -1105,6 +1105,20 @@ Signatures use of ``LOWER_CASE_FILE`` should be consistent between the main package and any appendices. + .. note:: + Because it is intended to be portable across multiple build tools, CPS + may not support all features that are allowed in CMake-script exports. In + particular, support for generator expressions in interface properties is + limited at this time to configuration-dependent expressions. + + .. note:: + This is the recommended way to generate |CPS| package information for a + project. For distributors whose users may require CPS package information + when making changes to the project's build files is not practical, the + :variable:`CMAKE_INSTALL_EXPORTS_AS_PACKAGE_INFO` variable may be used to + generate ``.cps`` files from :command:`install(EXPORT)` calls. Refer to + the variable's documentation for usage and caveats. + .. signature:: install(RUNTIME_DEPENDENCY_SET [...]) diff --git a/Help/manual/cmake-variables.7.rst b/Help/manual/cmake-variables.7.rst index 6c127161e3..e958dbdda5 100644 --- a/Help/manual/cmake-variables.7.rst +++ b/Help/manual/cmake-variables.7.rst @@ -263,6 +263,7 @@ Variables that Change Behavior /variable/CMAKE_INCLUDE_PATH /variable/CMAKE_INSTALL_DEFAULT_COMPONENT_NAME /variable/CMAKE_INSTALL_DEFAULT_DIRECTORY_PERMISSIONS + /variable/CMAKE_INSTALL_EXPORTS_AS_PACKAGE_INFO /variable/CMAKE_INSTALL_MESSAGE /variable/CMAKE_INSTALL_PREFIX /variable/CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT diff --git a/Help/variable/CMAKE_INSTALL_EXPORTS_AS_PACKAGE_INFO.rst b/Help/variable/CMAKE_INSTALL_EXPORTS_AS_PACKAGE_INFO.rst new file mode 100644 index 0000000000..f5b7b27241 --- /dev/null +++ b/Help/variable/CMAKE_INSTALL_EXPORTS_AS_PACKAGE_INFO.rst @@ -0,0 +1,122 @@ +CMAKE_INSTALL_EXPORTS_AS_PACKAGE_INFO +------------------------------------- + +.. versionadded:: 4.3 + +.. note:: + + This variable is meaningful only when experimental support has been enabled + by the ``CMAKE_EXPERIMENTAL_FIXME`` gate. + +A list of directives instructing CMake to install |CPS| package information +when exported target information is installed via :command:`install(EXPORT)`. +The value is treated as a list, with each directive having the form +``:[/[l][a][/]]``. +Slashes are used to separate different components of the directive. + +Note that this feature is intended for package distributors, and should +**only** be used when editing a project's CMake script is not feasible. +Developers should use :command:`install(PACKAGE_INFO)` directly. + +Additionally, because ``CMAKE_INSTALL_EXPORTS_AS_PACKAGE_INFO`` functions by +emulating a call to :command:`install(PACKAGE_INFO)`, using it with a project +that is already calling :command:`install(PACKAGE_INFO)` directly may result +in conflicting installation directives, which will usually cause the project's +configure step to fail. + +The meaning of the values is as follows: + +```` + Name of the export for which package information should be installed. + +```` + Name of the package for which to generate package information. This is also + the name that users would use in a :command:`find_package` call. + +``l`` + Optional. Specifies that the name of the package information file on disk + should be lower case. See the ``LOWER_CASE_FILE`` option of + :command:`install(PACKAGE_INFO)`. + +``a`` + Optional. Specifies that an appendix ```` should be created + rather than a root package description. See the ``APPENDIX`` option of + :command:`install(PACKAGE_INFO)`. Note that additional information + (see below) cannot be added to appendices. + +```` + Optional. Specifies the destination to which the package information file + should be installed. See the ``DESTINATION`` option of + :command:`install(PACKAGE_INFO)`. Note that the default is a + platform-specific location that is appropriate for |CPS| files in most + instances, *not* the ``DESTINATION`` of the :command:`install(EXPORT)` + which the directive matched. + +For non-appendices, CMake will also infer additional information from several +CMake variables of the form ``_EXPORT_PACKAGE_INFO_``. The +values of these are first processed as if by :command:`string(CONFIGURE)` with +the ``@ONLY`` option. These are optional, and their effect is equivalent to +passing their value to the ```` option of the +:command:`install(PACKAGE_INFO)` command. + +The additional variables are: + + * ``_EXPORT_PACKAGE_INFO_VERSION`` + * ``_EXPORT_PACKAGE_INFO_COMPAT_VERSION`` + * ``_EXPORT_PACKAGE_INFO_VERSION_SCHEMA`` + * ``_EXPORT_PACKAGE_INFO_LICENSE`` + * ``_EXPORT_PACKAGE_INFO_DEFAULT_LICENSE`` + * ``_EXPORT_PACKAGE_INFO_DEFAULT_CONFIGURATIONS`` + +Ideally, the version should be set to ``@PROJECT_VERSION@``. However, +some projects may not use the ``VERSION`` option of the :command:`project` +command. + +Example +^^^^^^^ + +Consider the following (simplified) project: + +.. code-block:: cmake + + project(Example VERSION 1.2.3 SPDX_LICENSE "BSD-3-Clause") + + add_library(foo ...) + add_library(bar ...) + + install(TARGETS foo EXPORT required-targets) + install(TARGETS bar EXPORT optional-targets) + + install(EXPORT required-targets FILE example-targets.cmake ...) + install(EXPORT optional-targets FILE example-optional-targets.cmake ...) + +In this example, let ``example-targets.cmake`` be a file which is always +installed, and ``example-optional-targets.cmake`` be a file which is +optionally installed (e.g. is distributed as part of a separate package which +depends on the 'base' package). + +Now, imagine we are a distributor that wants to make |CPS| package information +files available to our users, but we do not want to modify the project's build +files. We can do this by passing the following arguments to CMake: + +.. code-block:: + + -DCMAKE_EXPERIMENTAL_FIXME= + -DCMAKE_INSTALL_EXPORTS_AS_PACKAGE_INFO=\ + required-targets:Example/l;\ + optional-targets:Example/laoptional + -Drequired-targets_EXPORT_PACKAGE_INFO_VERSION=@Example_VERSION@ + -Drequired-targets_EXPORT_PACKAGE_INFO_LICENSE=@Example_SPDX_LICENSE@ + +(Whitespace and line continuation characters, added for readability, should +be removed in real usage. Arguments may need to be quoted to prevent being +reinterpreted by the command shell.) + +This will cause CMake to also create and install the files ``example.cps`` and +``example-optional.cps`` which describe the ``Example`` package. We could +also specify the version and license information using substitutions provided +by the package build system (e.g. rpm, dpkg) if the project does not provide +this information via the :command:`project` command. + +.. _CPS: https://cps-org.github.io/cps/ +.. |CPS| replace:: Common Package Specification diff --git a/Source/cmExperimental.cxx b/Source/cmExperimental.cxx index b7aec465d4..fe180b611a 100644 --- a/Source/cmExperimental.cxx +++ b/Source/cmExperimental.cxx @@ -55,6 +55,15 @@ cmExperimental::FeatureData const LookupTable[] = { "experimentation and feedback to CMake developers.", {}, cmExperimental::TryCompileCondition::Always }, + // MappedPackageInfo + { "MappedPackageInfo", + "ababa1b5-7099-495f-a9cd-e22d38f274f2", + "CMAKE_EXPERIMENTAL_MAPPED_PACKAGE_INFO", + "CMake's support for generating package information in the Common Package " + "Specification format from CMake script exports is experimental. It is " + "meant only for experimentation and feedback to CMake developers.", + {}, + cmExperimental::TryCompileCondition::Always }, // ExportBuildDatabase { "ExportBuildDatabase", "73194a1d-c0b5-41b9-9190-a4512925e192", diff --git a/Source/cmExperimental.h b/Source/cmExperimental.h index 54fd691e85..5171e189c6 100644 --- a/Source/cmExperimental.h +++ b/Source/cmExperimental.h @@ -21,6 +21,7 @@ public: CxxImportStd, ImportPackageInfo, ExportPackageInfo, + MappedPackageInfo, ExportBuildDatabase, Instrumentation, diff --git a/Source/cmInstallCommand.cxx b/Source/cmInstallCommand.cxx index e66fa8a767..15267d04a6 100644 --- a/Source/cmInstallCommand.cxx +++ b/Source/cmInstallCommand.cxx @@ -2035,6 +2035,152 @@ bool HandleExportAndroidMKMode(std::vector const& args, #endif } +#ifndef CMAKE_BOOTSTRAP +cm::optional MatchExport(cm::string_view directive, + std::string const& exportName) +{ + std::string::size_type const l = exportName.size(); + if (directive.substr(0, l) == exportName) { + if (directive.size() > l && directive[l] == ':') { + return directive.substr(l + 1); + } + } + return cm::nullopt; +} + +void AssignValue(std::string& dest, std::string const& value) +{ + dest = value; +} + +void AssignValue(std::vector& dest, std::string const& value) +{ + dest = cmList{ value }.data(); +} + +template +void GetExportArgumentFromVariable(cmMakefile const* makefile, + cmExportSet const& exportSet, + cm::string_view suffix, T& variable) +{ + std::string const& name = + cmStrCat(exportSet.GetName(), "_EXPORT_PACKAGE_INFO_"_s, suffix); + if (cmValue const& value = makefile->GetDefinition(name)) { + std::string realValue; + makefile->ConfigureString(value, realValue, true, false); + AssignValue(variable, realValue); + } +} + +bool HandleMappedPackageInfo( + cmExportSet& exportSet, cm::string_view directive, Helper& helper, + cmInstallCommandArguments const& installCommandArgs, + cmExecutionStatus& status, cmInstallGenerator::MessageLevel message, + std::string const& cxxModulesDirectory) +{ + cmPackageInfoArguments arguments; + + // Extract information from the directive. + std::string::size_type const n = directive.find('/'); + if (n != std::string::npos) { + arguments.PackageName = std::string{ directive.substr(0, n) }; + directive = directive.substr(n + 1); + + if (!directive.empty() && directive[0] == 'l') { + arguments.LowerCase = true; + directive = directive.substr(1); + } + + if (!directive.empty() && directive[0] == 'a') { + std::string::size_type const d = directive.find('/'); + if (d != std::string::npos) { + arguments.Appendix = std::string{ directive.substr(1, d - 1) }; + directive = directive.substr(d); + } else { + arguments.Appendix = std::string{ directive.substr(1) }; + directive = {}; + } + + if (arguments.Appendix.empty()) { + status.SetError(cmStrCat( + "CMAKE_INSTALL_EXPORTS_AS_PACKAGE_INFO given APPENDIX " + R"(directive for export ")"_s, + exportSet.GetName(), R"(", but no appendix name was provided.)"_s)); + return false; + } + } + + if (!directive.empty()) { + if (directive[0] != '/') { + status.SetError( + cmStrCat("CMAKE_INSTALL_EXPORTS_AS_PACKAGE_INFO given unrecognized " + R"(directive ")"_s, + directive, R"(".)"_s)); + return false; + } + + directive = directive.substr(1); + } + } else { + arguments.PackageName = std::string{ directive }; + directive = {}; + } + + if (arguments.PackageName.empty()) { + status.SetError( + cmStrCat("CMAKE_INSTALL_EXPORTS_AS_PACKAGE_INFO missing package name " + R"(for export ")"_s, + exportSet.GetName(), R"(".)"_s)); + return false; + } + + // Build destination. + std::string dest = std::string{ directive }; + if (dest.empty()) { + if (helper.Makefile->GetSafeDefinition("CMAKE_SYSTEM_NAME") == "Windows") { + dest = std::string{ "cps"_s }; + } else { + dest = cmStrCat(helper.GetLibraryDestination(nullptr), "/cps/", + arguments.GetPackageDirName()); + } + } + + if (arguments.Appendix.empty()) { + // Get additional export information from variables. + GetExportArgumentFromVariable( // BR + helper.Makefile, exportSet, "VERSION"_s, arguments.Version); + GetExportArgumentFromVariable( // BR + helper.Makefile, exportSet, "COMPAT_VERSION"_s, arguments.VersionCompat); + GetExportArgumentFromVariable( // BR + helper.Makefile, exportSet, "VERSION_SCHEMA"_s, arguments.VersionSchema); + GetExportArgumentFromVariable( // BR + helper.Makefile, exportSet, "LICENSE"_s, arguments.License); + GetExportArgumentFromVariable( // BR + helper.Makefile, exportSet, "DEFAULT_LICENSE"_s, + arguments.DefaultLicense); + GetExportArgumentFromVariable( // BR + helper.Makefile, exportSet, "DEFAULT_CONFIGURATIONS"_s, + arguments.DefaultConfigs); + } + + // Sanity-check export information. + if (!arguments.Check(status)) { + return false; + } + + // Create the package info generator. + helper.Makefile->AddInstallGenerator( + cm::make_unique( + &exportSet, dest, installCommandArgs.GetPermissions(), + installCommandArgs.GetConfigurations(), + installCommandArgs.GetComponent(), message, + installCommandArgs.GetExcludeFromAll(), std::move(arguments), + cxxModulesDirectory, helper.Makefile->GetBacktrace())); + + return true; +} +#endif + bool HandleExportMode(std::vector const& args, cmExecutionStatus& status) { @@ -2135,6 +2281,28 @@ bool HandleExportMode(std::vector const& args, helper.Makefile->GetGlobalGenerator()->AddInstallComponent( ica.GetComponent()); +#ifndef CMAKE_BOOTSTRAP + // Check if PACKAGE_INFO export has been requested for this export set. + if (cmExperimental::HasSupportEnabled( + status.GetMakefile(), cmExperimental::Feature::ExportPackageInfo) && + cmExperimental::HasSupportEnabled( + status.GetMakefile(), cmExperimental::Feature::MappedPackageInfo)) { + if (cmValue const& piExports = helper.Makefile->GetDefinition( + "CMAKE_INSTALL_EXPORTS_AS_PACKAGE_INFO")) { + for (auto const& pie : cmList{ piExports }) { + cm::optional const directive = MatchExport(pie, exp); + if (directive) { + if (!HandleMappedPackageInfo(exportSet, *directive, helper, ica, + status, message, + cxx_modules_directory)) { + return false; + } + } + } + } + } +#endif + // Create the export install generator. helper.Makefile->AddInstallGenerator( cm::make_unique( diff --git a/Tests/RunCMake/CMakeLists.txt b/Tests/RunCMake/CMakeLists.txt index 880776be0d..3c67c3a7b3 100644 --- a/Tests/RunCMake/CMakeLists.txt +++ b/Tests/RunCMake/CMakeLists.txt @@ -1335,6 +1335,7 @@ add_RunCMake_test(AutoExportDll add_RunCMake_test(AndroidMK) add_RunCMake_test(ExportPackageInfo) add_RunCMake_test(InstallPackageInfo) +add_RunCMake_test(InstallExportsAsPackageInfo) if(CMake_TEST_ANDROID_NDK OR CMake_TEST_ANDROID_STANDALONE_TOOLCHAIN) if(NOT "${CMAKE_GENERATOR}" MATCHES "Make|Ninja|Visual Studio 1[456]") diff --git a/Tests/RunCMake/InstallExportsAsPackageInfo/Assertions.cmake b/Tests/RunCMake/InstallExportsAsPackageInfo/Assertions.cmake new file mode 100644 index 0000000000..068074da2a --- /dev/null +++ b/Tests/RunCMake/InstallExportsAsPackageInfo/Assertions.cmake @@ -0,0 +1,39 @@ +macro(_expect entity op actual expected) + if(NOT "${actual}" ${op} "${expected}") + list(JOIN ARGN "." name) + set(RunCMake_TEST_FAILED + "Attribute '${name}' ${entity} '${actual}' does not match expected ${entity} '${expected}'" PARENT_SCOPE) + return() + endif() +endmacro() + +function(expect_value content expected_value) + string(JSON actual_value GET "${content}" ${ARGN}) + _expect("value" STREQUAL "${actual_value}" "${expected_value}" ${ARGN}) +endfunction() + +function(expect_array content expected_length) + string(JSON actual_type TYPE "${content}" ${ARGN}) + _expect("type" STREQUAL "${actual_type}" "ARRAY" ${ARGN}) + + string(JSON actual_length LENGTH "${content}" ${ARGN}) + _expect("length" EQUAL "${actual_length}" "${expected_length}" ${ARGN}) +endfunction() + +function(expect_object content) + string(JSON actual_type TYPE "${content}" ${ARGN}) + _expect("type" STREQUAL "${actual_type}" "OBJECT" ${ARGN}) +endfunction() + +function(expect_null content) + string(JSON actual_type TYPE "${content}" ${ARGN}) + _expect("type" STREQUAL "${actual_type}" "NULL" ${ARGN}) +endfunction() + +function(expect_missing content) + string(JSON value ERROR_VARIABLE error GET "${content}" ${ARGN}) + if(NOT value MATCHES "^(.*-)?NOTFOUND$") + set(RunCMake_TEST_FAILED + "Attribute '${ARGN}' is unexpectedly present" PARENT_SCOPE) + endif() +endfunction() diff --git a/Tests/RunCMake/InstallExportsAsPackageInfo/BadDirective-result.txt b/Tests/RunCMake/InstallExportsAsPackageInfo/BadDirective-result.txt new file mode 100644 index 0000000000..d00491fd7e --- /dev/null +++ b/Tests/RunCMake/InstallExportsAsPackageInfo/BadDirective-result.txt @@ -0,0 +1 @@ +1 diff --git a/Tests/RunCMake/InstallExportsAsPackageInfo/BadDirective-stderr.txt b/Tests/RunCMake/InstallExportsAsPackageInfo/BadDirective-stderr.txt new file mode 100644 index 0000000000..10cead84d8 --- /dev/null +++ b/Tests/RunCMake/InstallExportsAsPackageInfo/BadDirective-stderr.txt @@ -0,0 +1,5 @@ +CMake Error at BadDirective\.cmake:2 \(install\): + install CMAKE_INSTALL_EXPORTS_AS_PACKAGE_INFO given unrecognized directive + "error"\. +Call Stack \(most recent call first\): + CMakeLists\.txt:3 \(include\) diff --git a/Tests/RunCMake/InstallExportsAsPackageInfo/BadDirective.cmake b/Tests/RunCMake/InstallExportsAsPackageInfo/BadDirective.cmake new file mode 100644 index 0000000000..e967511d5e --- /dev/null +++ b/Tests/RunCMake/InstallExportsAsPackageInfo/BadDirective.cmake @@ -0,0 +1,2 @@ +set(CMAKE_INSTALL_EXPORTS_AS_PACKAGE_INFO foo:foo/error) +install(EXPORT foo FILE foo-targets.cmake DESTINATION .) diff --git a/Tests/RunCMake/InstallExportsAsPackageInfo/CMakeLists.txt b/Tests/RunCMake/InstallExportsAsPackageInfo/CMakeLists.txt new file mode 100644 index 0000000000..abd58e2f9a --- /dev/null +++ b/Tests/RunCMake/InstallExportsAsPackageInfo/CMakeLists.txt @@ -0,0 +1,3 @@ +cmake_minimum_required(VERSION 4.2) +project(${RunCMake_TEST} NONE) +include(${RunCMake_TEST}.cmake) diff --git a/Tests/RunCMake/InstallExportsAsPackageInfo/ExperimentalGate.cmake b/Tests/RunCMake/InstallExportsAsPackageInfo/ExperimentalGate.cmake new file mode 100644 index 0000000000..6675f623b7 --- /dev/null +++ b/Tests/RunCMake/InstallExportsAsPackageInfo/ExperimentalGate.cmake @@ -0,0 +1,6 @@ +unset(CMAKE_EXPERIMENTAL_MAPPED_PACKAGE_INFO) +set(CMAKE_INSTALL_EXPORTS_AS_PACKAGE_INFO foo:) + +add_library(foo INTERFACE) +install(TARGETS foo EXPORT foo DESTINATION .) +install(EXPORT foo FILE foo-targets.cmake DESTINATION .) diff --git a/Tests/RunCMake/InstallExportsAsPackageInfo/ExperimentalWarning-stderr.txt b/Tests/RunCMake/InstallExportsAsPackageInfo/ExperimentalWarning-stderr.txt new file mode 100644 index 0000000000..b761f9c80e --- /dev/null +++ b/Tests/RunCMake/InstallExportsAsPackageInfo/ExperimentalWarning-stderr.txt @@ -0,0 +1,7 @@ +CMake Warning \(dev\) at ExperimentalWarning\.cmake:13 \(install\): + CMake's support for generating package information in the Common Package + Specification format from CMake script exports is experimental\. It is + meant only for experimentation and feedback to CMake developers\. +Call Stack \(most recent call first\): + CMakeLists\.txt:3 \(include\) +This warning is for project developers\. Use -Wno-dev to suppress it\. diff --git a/Tests/RunCMake/InstallExportsAsPackageInfo/ExperimentalWarning.cmake b/Tests/RunCMake/InstallExportsAsPackageInfo/ExperimentalWarning.cmake new file mode 100644 index 0000000000..22fcb465b3 --- /dev/null +++ b/Tests/RunCMake/InstallExportsAsPackageInfo/ExperimentalWarning.cmake @@ -0,0 +1,13 @@ +set( + CMAKE_EXPERIMENTAL_EXPORT_PACKAGE_INFO + "b80be207-778e-46ba-8080-b23bba22639e" + ) +set( + CMAKE_EXPERIMENTAL_MAPPED_PACKAGE_INFO + "ababa1b5-7099-495f-a9cd-e22d38f274f2" + ) +unset(CMAKE_INSTALL_EXPORTS_AS_PACKAGE_INFO) + +add_library(foo INTERFACE) +install(TARGETS foo EXPORT foo DESTINATION .) +install(EXPORT foo FILE foo-targets.cmake DESTINATION .) diff --git a/Tests/RunCMake/InstallExportsAsPackageInfo/LowerCase-check.cmake b/Tests/RunCMake/InstallExportsAsPackageInfo/LowerCase-check.cmake new file mode 100644 index 0000000000..09a724620d --- /dev/null +++ b/Tests/RunCMake/InstallExportsAsPackageInfo/LowerCase-check.cmake @@ -0,0 +1,27 @@ +include(${CMAKE_CURRENT_LIST_DIR}/Assertions.cmake) + +set(out_dir "${RunCMake_BINARY_DIR}/LowerCase-build/CMakeFiles/Export/510c5684a4a8a792eadfb55bc9744983") + +function(expect_in_list list value) + list(FIND ${list} "${value}" index) + if(${index} EQUAL -1) + set(RunCMake_TEST_FAILED + "Expected '${value}' in ${list} ('${${list}}'), but it was not found" PARENT_SCOPE) + endif() +endfunction() + +file(GLOB files + LIST_DIRECTORIES false + RELATIVE "${out_dir}" + "${out_dir}/*.cps" +) +expect_in_list(files "farm.cps") +expect_in_list(files "farm-extra.cps") + +file(READ "${out_dir}/farm.cps" content) +expect_value("${content}" "Farm" "name") +expect_value("${content}" "interface" "components" "Cow" "type") + +file(READ "${out_dir}/farm-extra.cps" content) +expect_value("${content}" "Farm" "name") +expect_value("${content}" "interface" "components" "Pig" "type") diff --git a/Tests/RunCMake/InstallExportsAsPackageInfo/LowerCase.cmake b/Tests/RunCMake/InstallExportsAsPackageInfo/LowerCase.cmake new file mode 100644 index 0000000000..f08f39fc4e --- /dev/null +++ b/Tests/RunCMake/InstallExportsAsPackageInfo/LowerCase.cmake @@ -0,0 +1,12 @@ +set(CMAKE_INSTALL_EXPORTS_AS_PACKAGE_INFO + cow:Farm/l/cps + pig:Farm/laextra/cps +) + +add_library(Cow INTERFACE) +add_library(Pig INTERFACE) + +install(TARGETS Cow EXPORT cow) +install(TARGETS Pig EXPORT pig) +install(EXPORT cow FILE farm-targets.cmake DESTINATION .) +install(EXPORT pig FILE farm-targets-extra.cmake DESTINATION .) diff --git a/Tests/RunCMake/InstallExportsAsPackageInfo/MissingAppendixName-result.txt b/Tests/RunCMake/InstallExportsAsPackageInfo/MissingAppendixName-result.txt new file mode 100644 index 0000000000..d00491fd7e --- /dev/null +++ b/Tests/RunCMake/InstallExportsAsPackageInfo/MissingAppendixName-result.txt @@ -0,0 +1 @@ +1 diff --git a/Tests/RunCMake/InstallExportsAsPackageInfo/MissingAppendixName-stderr.txt b/Tests/RunCMake/InstallExportsAsPackageInfo/MissingAppendixName-stderr.txt new file mode 100644 index 0000000000..f63cc68562 --- /dev/null +++ b/Tests/RunCMake/InstallExportsAsPackageInfo/MissingAppendixName-stderr.txt @@ -0,0 +1,5 @@ +CMake Error at MissingAppendixName.cmake:2 \(install\): + install CMAKE_INSTALL_EXPORTS_AS_PACKAGE_INFO given APPENDIX directive for + export "foo", but no appendix name was provided\. +Call Stack \(most recent call first\): + CMakeLists\.txt:3 \(include\) diff --git a/Tests/RunCMake/InstallExportsAsPackageInfo/MissingAppendixName.cmake b/Tests/RunCMake/InstallExportsAsPackageInfo/MissingAppendixName.cmake new file mode 100644 index 0000000000..08147e3313 --- /dev/null +++ b/Tests/RunCMake/InstallExportsAsPackageInfo/MissingAppendixName.cmake @@ -0,0 +1,2 @@ +set(CMAKE_INSTALL_EXPORTS_AS_PACKAGE_INFO foo:foo/a) +install(EXPORT foo FILE foo-targets.cmake DESTINATION .) diff --git a/Tests/RunCMake/InstallExportsAsPackageInfo/MissingExport-result.txt b/Tests/RunCMake/InstallExportsAsPackageInfo/MissingExport-result.txt new file mode 100644 index 0000000000..d00491fd7e --- /dev/null +++ b/Tests/RunCMake/InstallExportsAsPackageInfo/MissingExport-result.txt @@ -0,0 +1 @@ +1 diff --git a/Tests/RunCMake/InstallExportsAsPackageInfo/MissingExport-stderr.txt b/Tests/RunCMake/InstallExportsAsPackageInfo/MissingExport-stderr.txt new file mode 100644 index 0000000000..67e290bb8a --- /dev/null +++ b/Tests/RunCMake/InstallExportsAsPackageInfo/MissingExport-stderr.txt @@ -0,0 +1,3 @@ +CMake Error: INSTALL\(PACKAGE_INFO\) given unknown export "foo" +CMake Error: INSTALL\(EXPORT\) given unknown export "foo" +CMake Generate step failed\. Build files cannot be regenerated correctly\. diff --git a/Tests/RunCMake/InstallExportsAsPackageInfo/MissingExport.cmake b/Tests/RunCMake/InstallExportsAsPackageInfo/MissingExport.cmake new file mode 100644 index 0000000000..517af2217d --- /dev/null +++ b/Tests/RunCMake/InstallExportsAsPackageInfo/MissingExport.cmake @@ -0,0 +1,2 @@ +set(CMAKE_INSTALL_EXPORTS_AS_PACKAGE_INFO foo:foo) +install(EXPORT foo FILE foo-targets.cmake DESTINATION .) diff --git a/Tests/RunCMake/InstallExportsAsPackageInfo/MissingPackageName-result.txt b/Tests/RunCMake/InstallExportsAsPackageInfo/MissingPackageName-result.txt new file mode 100644 index 0000000000..d00491fd7e --- /dev/null +++ b/Tests/RunCMake/InstallExportsAsPackageInfo/MissingPackageName-result.txt @@ -0,0 +1 @@ +1 diff --git a/Tests/RunCMake/InstallExportsAsPackageInfo/MissingPackageName-stderr.txt b/Tests/RunCMake/InstallExportsAsPackageInfo/MissingPackageName-stderr.txt new file mode 100644 index 0000000000..99d4240b73 --- /dev/null +++ b/Tests/RunCMake/InstallExportsAsPackageInfo/MissingPackageName-stderr.txt @@ -0,0 +1,5 @@ +CMake Error at MissingPackageName.cmake:2 \(install\): + install CMAKE_INSTALL_EXPORTS_AS_PACKAGE_INFO missing package name for + export "foo"\. +Call Stack \(most recent call first\): + CMakeLists\.txt:3 \(include\) diff --git a/Tests/RunCMake/InstallExportsAsPackageInfo/MissingPackageName.cmake b/Tests/RunCMake/InstallExportsAsPackageInfo/MissingPackageName.cmake new file mode 100644 index 0000000000..8489637454 --- /dev/null +++ b/Tests/RunCMake/InstallExportsAsPackageInfo/MissingPackageName.cmake @@ -0,0 +1,2 @@ +set(CMAKE_INSTALL_EXPORTS_AS_PACKAGE_INFO foo:) +install(EXPORT foo FILE foo-targets.cmake DESTINATION .) diff --git a/Tests/RunCMake/InstallExportsAsPackageInfo/RunCMakeTest.cmake b/Tests/RunCMake/InstallExportsAsPackageInfo/RunCMakeTest.cmake new file mode 100644 index 0000000000..7f35c13204 --- /dev/null +++ b/Tests/RunCMake/InstallExportsAsPackageInfo/RunCMakeTest.cmake @@ -0,0 +1,22 @@ +include(RunCMake) + +# Test experimental gate +run_cmake(ExperimentalGate) +run_cmake(ExperimentalWarning) + +# Enable experimental feature and suppress warnings +set(RunCMake_TEST_OPTIONS + -Wno-dev + "-DCMAKE_EXPERIMENTAL_EXPORT_PACKAGE_INFO:STRING=b80be207-778e-46ba-8080-b23bba22639e" + "-DCMAKE_EXPERIMENTAL_MAPPED_PACKAGE_INFO:STRING=ababa1b5-7099-495f-a9cd-e22d38f274f2" + ) + +# Test incorrect usage +run_cmake(MissingPackageName) +run_cmake(MissingAppendixName) +run_cmake(MissingExport) +run_cmake(BadDirective) + +# Test functionality +run_cmake(SampleExport) +run_cmake(LowerCase) diff --git a/Tests/RunCMake/InstallExportsAsPackageInfo/SampleExport-check.cmake b/Tests/RunCMake/InstallExportsAsPackageInfo/SampleExport-check.cmake new file mode 100644 index 0000000000..fdeeefa300 --- /dev/null +++ b/Tests/RunCMake/InstallExportsAsPackageInfo/SampleExport-check.cmake @@ -0,0 +1,19 @@ +include(${CMAKE_CURRENT_LIST_DIR}/Assertions.cmake) + +set(out_dir "${RunCMake_BINARY_DIR}/SampleExport-build/CMakeFiles/Export/510c5684a4a8a792eadfb55bc9744983") + +file(READ "${out_dir}/farm.cps" content) +expect_value("${content}" "farm" "name") +expect_value("${content}" "interface" "components" "cow" "type") +expect_value("${content}" "1.2.3" "version") +expect_value("${content}" "1.1.0" "compat_version") +expect_value("${content}" "simple" "version_schema") +expect_value("${content}" "Apache-2.0" "license") +expect_value("${content}" "BSD-3-Clause" "default_license") +expect_array("${content}" 2 "configurations") +expect_value("${content}" "Small" "configurations" 0) +expect_value("${content}" "Large" "configurations" 1) + +file(READ "${out_dir}/farm-extra.cps" content) +expect_value("${content}" "farm" "name") +expect_value("${content}" "interface" "components" "pig" "type") diff --git a/Tests/RunCMake/InstallExportsAsPackageInfo/SampleExport.cmake b/Tests/RunCMake/InstallExportsAsPackageInfo/SampleExport.cmake new file mode 100644 index 0000000000..fedcec0ebc --- /dev/null +++ b/Tests/RunCMake/InstallExportsAsPackageInfo/SampleExport.cmake @@ -0,0 +1,22 @@ +cmake_minimum_required(VERSION 4.2) + +project(farm VERSION 1.2.3 COMPAT_VERSION 1.1.0) + +set(CMAKE_INSTALL_EXPORTS_AS_PACKAGE_INFO + cow:farm//cps + pig:farm/aextra/cps +) +set(cow_EXPORT_PACKAGE_INFO_VERSION @farm_VERSION@) +set(cow_EXPORT_PACKAGE_INFO_COMPAT_VERSION @farm_COMPAT_VERSION@) +set(cow_EXPORT_PACKAGE_INFO_VERSION_SCHEMA "simple") +set(cow_EXPORT_PACKAGE_INFO_LICENSE "Apache-2.0") +set(cow_EXPORT_PACKAGE_INFO_DEFAULT_LICENSE "BSD-3-Clause") +set(cow_EXPORT_PACKAGE_INFO_DEFAULT_CONFIGURATIONS "Small;Large") + +add_library(cow INTERFACE) +add_library(pig INTERFACE) + +install(TARGETS cow EXPORT cow) +install(TARGETS pig EXPORT pig) +install(EXPORT cow FILE farm-targets.cmake DESTINATION .) +install(EXPORT pig FILE farm-targets-extra.cmake DESTINATION .)