From 3d52d70b84b114adf36a176c6f05aa0f42b1326f Mon Sep 17 00:00:00 2001 From: Matthew Woehlke Date: Fri, 26 Jul 2024 16:22:35 -0400 Subject: export: Add initial CPS support Add initial support for exporting (install only, for now) Common Package Specification (https://cps-org.github.io/cps/) format package descriptions. This has some limitations, such as not supporting generator expressions (as these cannot be portably exported), and only partially supporting transitive dependencies, but should be usable for at least some simple cases. (Actually, $ is theoretically supportable, but is not yet implemented.) This still needs tests; these will be added in the next commit. Other potential improvements include support for language-specific compile definitions and inferring some package properties from project properties. Additionally, there is no module support yet; this is partly pending on having a tool agnostic format for providing the necessary information. --- Help/command/install.rst | 59 ++++ Help/dev/experimental.rst | 20 ++ Source/CMakeLists.txt | 6 + Source/cmExperimental.cxx | 10 + Source/cmExperimental.h | 1 + Source/cmExportFileGenerator.cxx | 14 +- Source/cmExportFileGenerator.h | 5 + Source/cmExportInstallPackageInfoGenerator.cxx | 197 +++++++++++ Source/cmExportInstallPackageInfoGenerator.h | 66 ++++ Source/cmExportPackageInfoGenerator.cxx | 452 +++++++++++++++++++++++++ Source/cmExportPackageInfoGenerator.h | 116 +++++++ Source/cmInstallCommand.cxx | 139 ++++++++ Source/cmInstallPackageInfoExportGenerator.cxx | 36 ++ Source/cmInstallPackageInfoExportGenerator.h | 36 ++ 14 files changed, 1155 insertions(+), 2 deletions(-) create mode 100644 Source/cmExportInstallPackageInfoGenerator.cxx create mode 100644 Source/cmExportInstallPackageInfoGenerator.h create mode 100644 Source/cmExportPackageInfoGenerator.cxx create mode 100644 Source/cmExportPackageInfoGenerator.h create mode 100644 Source/cmInstallPackageInfoExportGenerator.cxx create mode 100644 Source/cmInstallPackageInfoExportGenerator.h diff --git a/Help/command/install.rst b/Help/command/install.rst index ffc1926..bbb70b8 100644 --- a/Help/command/install.rst +++ b/Help/command/install.rst @@ -19,6 +19,7 @@ Synopsis install(`SCRIPT`_ [...]) install(`CODE`_ [...]) install(`EXPORT`_ [...]) + install(`PACKAGE_INFO`_ [...]) install(`RUNTIME_DEPENDENCY_SET`_ [...]) Introduction @@ -906,6 +907,61 @@ Signatures ``mp_myexe`` as if the target were built in its own tree. .. signature:: + install(PACKAGE_INFO [...]) + + .. versionadded:: 3.31 + .. note:: + + Experimental. Gated by ``CMAKE_EXPERIMENTAL_EXPORT_PACKAGE_INFO``. + + Installs a |CPS|_ file exporting targets for dependent projects: + + .. code-block:: cmake + + install(PACKAGE_INFO EXPORT + [APPENDIX ] + [DESTINATION ] + [LOWER_CASE_FILE] + [VERSION + [COMPAT_VERSION ] + [VERSION_SCHEMA ]] + [DEFAULT_TARGETS ...] + [DEFAULT_CONFIGURATIONS ...] + [PERMISSIONS ...] + [CONFIGURATIONS ...] + [COMPONENT ] + [EXCLUDE_FROM_ALL]) + + The ``PACKAGE_INFO`` form generates and installs a |CPS| file which describes + installed targets such that they can be consumed by another project. + Target installations are associated with the export ```` + using the ``EXPORT`` option of the :command:`install(TARGETS)` signature + documented above. Unlike :command:`install(EXPORT)`, this information is not + expressed in CMake code, and can be consumed by tools other than CMake. When + imported into another CMake project, the imported targets will be prefixed + with ``::``. By default, the generated file will be called + ``[-].cps``. If ``LOWER_CASE_FILE`` is given, + the package name as it appears on disk (in both the file name and install + destination) will be first converted to lower case. + + If ``DESTINATION`` is not specified, a platform-specific default is used. + + If ``APPENDIX`` is specified, rather than generating a top level package + specification, the specified targets will be exported as an appendix to the + named package. Appendices may be used to separate less commonly used targets + (along with their external dependencies) from the rest of a package. This + enables consumers to ignore transitive dependencies for targets that they + don't use, and also allows a single logical "package" to be composed of + artifacts produced by multiple build trees. + + Appendices are not permitted to change basic package metadata; therefore, + none of ``VERSION``, ``COMPAT_VERSION``, ``VERSION_SCHEMA``, + ``DEFAULT_TARGETS`` or ``DEFAULT_CONFIGURATIONS`` may be specified in + combination with ``APPENDIX``. Additionally, it is strongly recommended that + use of ``LOWER_CASE_FILE`` should be consistent between the main package and + any appendices. + +.. signature:: install(RUNTIME_DEPENDENCY_SET [...]) .. versionadded:: 3.21 @@ -1097,3 +1153,6 @@ and by CPack. You can also invoke this script manually with This is an environment variable rather than a CMake variable. It allows you to change the installation prefix on UNIX systems. See :envvar:`DESTDIR` for details. + +.. _CPS: https://cps-org.github.io/cps/ +.. |CPS| replace:: Common Package Specification diff --git a/Help/dev/experimental.rst b/Help/dev/experimental.rst index 35ea34f..fb33112 100644 --- a/Help/dev/experimental.rst +++ b/Help/dev/experimental.rst @@ -39,6 +39,23 @@ When activated, this experimental feature provides the following: using the ``CMAKE_EXPORT_FIND_PACKAGE_NAME`` variable and/or ``EXPORT_FIND_PACKAGE_NAME`` target property. +Export |CPS| Package Information +================================ + +In order to activate support for this experimental feature, set + +* variable ``CMAKE_EXPERIMENTAL_EXPORT_PACKAGE_INFO`` to +* value ``b80be207-778e-46ba-8080-b23bba22639e``. + +This UUID may change in future versions of CMake. Be sure to use the value +documented here by the source tree of the version of CMake with which you are +experimenting. + +When activated, this experimental feature provides the following: + +* The experimental ``install(PACKAGE_INFO)`` command is available to export + package information in the |CPS|_ format. + C++ ``import std`` support ========================== @@ -60,3 +77,6 @@ When activated, this experimental feature provides the following: * Targets with the property set to a true value and at least ``cxx_std_23`` may use ``import std;`` in any scanned C++ source file. + +.. _CPS: https://cps-org.github.io/cps/ +.. |CPS| replace:: Common Package Specification diff --git a/Source/CMakeLists.txt b/Source/CMakeLists.txt index 6841f1d..67360bc 100644 --- a/Source/CMakeLists.txt +++ b/Source/CMakeLists.txt @@ -217,6 +217,10 @@ add_library( cmExportInstallCMakeConfigGenerator.cxx cmExportInstallFileGenerator.h cmExportInstallFileGenerator.cxx + cmExportInstallPackageInfoGenerator.h + cmExportInstallPackageInfoGenerator.cxx + cmExportPackageInfoGenerator.h + cmExportPackageInfoGenerator.cxx cmExportTryCompileFileGenerator.h cmExportTryCompileFileGenerator.cxx cmExportSet.h @@ -336,6 +340,8 @@ add_library( cmInstallFilesGenerator.cxx cmInstallImportedRuntimeArtifactsGenerator.h cmInstallImportedRuntimeArtifactsGenerator.cxx + cmInstallPackageInfoExportGenerator.h + cmInstallPackageInfoExportGenerator.cxx cmInstallRuntimeDependencySet.h cmInstallRuntimeDependencySet.cxx cmInstallRuntimeDependencySetGenerator.h diff --git a/Source/cmExperimental.cxx b/Source/cmExperimental.cxx index a2e6e70..4504c07 100644 --- a/Source/cmExperimental.cxx +++ b/Source/cmExperimental.cxx @@ -46,6 +46,16 @@ cmExperimental::FeatureData LookupTable[] = { {}, cmExperimental::TryCompileCondition::Always, false }, + // ExportPackageInfo + { "ExportPackageInfo", + "b80be207-778e-46ba-8080-b23bba22639e", + "CMAKE_EXPERIMENTAL_EXPORT_PACKAGE_INFO", + "CMake's support for exporting package information in the Common Package " + "Specification format. It is meant only for experimentation and feedback " + "to CMake developers.", + {}, + cmExperimental::TryCompileCondition::Always, + false }, }; static_assert(sizeof(LookupTable) / sizeof(LookupTable[0]) == static_cast(cmExperimental::Feature::Sentinel), diff --git a/Source/cmExperimental.h b/Source/cmExperimental.h index 05764f9..46a9bc4 100644 --- a/Source/cmExperimental.h +++ b/Source/cmExperimental.h @@ -20,6 +20,7 @@ public: ExportPackageDependencies, WindowsKernelModeDriver, CxxImportStd, + ExportPackageInfo, Sentinel, }; diff --git a/Source/cmExportFileGenerator.cxx b/Source/cmExportFileGenerator.cxx index 24ed273..5e9461d 100644 --- a/Source/cmExportFileGenerator.cxx +++ b/Source/cmExportFileGenerator.cxx @@ -331,6 +331,14 @@ void cmExportFileGenerator::PopulateCustomTransitiveInterfaceProperties( } } +bool cmExportFileGenerator::NoteLinkedTarget( + cmGeneratorTarget const* /*target*/, std::string const& /*linkedName*/, + cmGeneratorTarget const* /*linkedTarget*/) +{ + // Default implementation does nothing; only needed by some generators. + return true; +} + bool cmExportFileGenerator::AddTargetNamespace(std::string& input, cmGeneratorTarget const* target, cmLocalGenerator const* lg) @@ -352,8 +360,9 @@ bool cmExportFileGenerator::AddTargetNamespace(std::string& input, if (tgt->IsImported()) { input = tgt->GetName(); - return true; + return this->NoteLinkedTarget(target, input, tgt); } + if (this->ExportedTargets.find(tgt) != this->ExportedTargets.end()) { input = this->Namespace + tgt->GetExportName(); } else { @@ -365,7 +374,8 @@ bool cmExportFileGenerator::AddTargetNamespace(std::string& input, input = tgt->GetName(); } } - return true; + + return this->NoteLinkedTarget(target, input, tgt); } void cmExportFileGenerator::ResolveTargetsInGeneratorExpressions( diff --git a/Source/cmExportFileGenerator.h b/Source/cmExportFileGenerator.h index f765493..b1c9ce3 100644 --- a/Source/cmExportFileGenerator.h +++ b/Source/cmExportFileGenerator.h @@ -95,6 +95,11 @@ protected: std::string const& config, std::string const& suffix) = 0; + /** Record a target referenced by an exported target. */ + virtual bool NoteLinkedTarget(cmGeneratorTarget const* target, + std::string const& linkedName, + cmGeneratorTarget const* linkedTarget); + /** Each subclass knows how to deal with a target that is missing from an * export set. */ virtual void HandleMissingTarget(std::string& link_libs, diff --git a/Source/cmExportInstallPackageInfoGenerator.cxx b/Source/cmExportInstallPackageInfoGenerator.cxx new file mode 100644 index 0000000..b9b715e --- /dev/null +++ b/Source/cmExportInstallPackageInfoGenerator.cxx @@ -0,0 +1,197 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ +#include "cmExportInstallPackageInfoGenerator.h" + +#include +#include +#include +#include + +#include + +#include "cmExportSet.h" +#include "cmGeneratorExpression.h" +#include "cmGeneratorTarget.h" +#include "cmInstallExportGenerator.h" +#include "cmLocalGenerator.h" +#include "cmMakefile.h" +#include "cmStateTypes.h" +#include "cmStringAlgorithms.h" +#include "cmSystemTools.h" +#include "cmTarget.h" +#include "cmTargetExport.h" + +cmExportInstallPackageInfoGenerator::cmExportInstallPackageInfoGenerator( + cmInstallExportGenerator* iegen, std::string packageName, + std::string version, std::string versionCompat, std::string versionSchema, + std::vector defaultTargets, + std::vector defaultConfigurations) + : cmExportPackageInfoGenerator( + std::move(packageName), std::move(version), std::move(versionCompat), + std::move(versionSchema), std::move(defaultTargets), + std::move(defaultConfigurations)) + , cmExportInstallFileGenerator(iegen) +{ +} + +std::string cmExportInstallPackageInfoGenerator::GetConfigImportFileGlob() + const +{ + std::string glob = cmStrCat(this->FileBase, "@*", this->FileExt); + return glob; +} + +std::string const& cmExportInstallPackageInfoGenerator::GetExportName() const +{ + return this->GetPackageName(); +} + +bool cmExportInstallPackageInfoGenerator::GenerateMainFile(std::ostream& os) +{ + std::vector allTargets; + { + auto visitor = [&](cmTargetExport const* te) { allTargets.push_back(te); }; + + if (!this->CollectExports(visitor)) { + return false; + } + } + + if (!this->CheckDefaultTargets()) { + return false; + } + + Json::Value root = this->GeneratePackageInfo(); + Json::Value& components = root["components"]; + + // Compute the relative import prefix for the file + std::string const& packagePath = this->GenerateImportPrefix(); + if (packagePath.empty()) { + return false; + } + root["cps_path"] = packagePath; + + bool requiresConfigFiles = false; + // Create all the imported targets. + for (cmTargetExport const* te : allTargets) { + cmGeneratorTarget* gt = te->Target; + cmStateEnums::TargetType targetType = this->GetExportTargetType(te); + + Json::Value* const component = + this->GenerateImportTarget(components, gt, targetType); + if (!component) { + return false; + } + + ImportPropertyMap properties; + if (!this->PopulateInterfaceProperties(te, properties)) { + return false; + } + this->PopulateInterfaceLinkLibrariesProperty( + gt, cmGeneratorExpression::InstallInterface, properties); + + if (targetType != cmStateEnums::INTERFACE_LIBRARY) { + requiresConfigFiles = true; + } + + // Set configuration-agnostic properties for component. + this->GenerateInterfaceProperties(*component, gt, properties); + } + + this->GeneratePackageRequires(root); + + // Write the primary packing information file. + this->WritePackageInfo(root, os); + + bool result = true; + + // Generate an import file for each configuration. + if (requiresConfigFiles) { + for (std::string const& c : this->Configurations) { + if (!this->GenerateImportFileConfig(c)) { + result = false; + } + } + } + + return result; +} + +void cmExportInstallPackageInfoGenerator::GenerateImportTargetsConfig( + std::ostream& os, std::string const& config, std::string const& suffix) +{ + Json::Value root; + root["name"] = this->GetPackageName(); + root["configuration"] = config; + + Json::Value& components = root["components"]; + + for (auto const& te : this->GetExportSet()->GetTargetExports()) { + // Collect import properties for this target. + if (this->GetExportTargetType(te.get()) == + cmStateEnums::INTERFACE_LIBRARY) { + continue; + } + + ImportPropertyMap properties; + std::set importedLocations; + + this->PopulateImportProperties(config, suffix, te.get(), properties, + importedLocations); + + this->GenerateInterfaceConfigProperties(components, te->Target, suffix, + properties); + } + + this->WritePackageInfo(root, os); +} + +std::string cmExportInstallPackageInfoGenerator::GenerateImportPrefix() const +{ + std::string expDest = this->IEGen->GetDestination(); + if (cmSystemTools::FileIsFullPath(expDest)) { + std::string const& installPrefix = + this->IEGen->GetLocalGenerator()->GetMakefile()->GetSafeDefinition( + "CMAKE_INSTALL_PREFIX"); + if (cmHasPrefix(expDest, installPrefix)) { + auto n = installPrefix.length(); + while (n < expDest.length() && expDest[n] == '/') { + ++n; + } + expDest = expDest.substr(n); + } else { + this->ReportError( + cmStrCat("install(PACKAGE_INFO \"", this->GetExportName(), + "\" ...) specifies DESTINATION \"", expDest, + "\" which is not a subdirectory of the install prefix.")); + return {}; + } + } + + if (expDest.empty()) { + return this->GetInstallPrefix(); + } + return cmStrCat(this->GetImportPrefixWithSlash(), expDest); +} + +std::string cmExportInstallPackageInfoGenerator::InstallNameDir( + cmGeneratorTarget const* target, std::string const& config) +{ + std::string install_name_dir; + + cmMakefile* mf = target->Target->GetMakefile(); + if (mf->IsOn("CMAKE_PLATFORM_HAS_INSTALLNAME")) { + install_name_dir = + target->GetInstallNameDirForInstallTree(config, "@prefix@"); + } + + return install_name_dir; +} + +std::string cmExportInstallPackageInfoGenerator::GetCxxModulesDirectory() const +{ + // TODO: Implement a not-CMake-specific mechanism for providing module + // information. + // return IEGen->GetCxxModuleDirectory(); + return {}; +} diff --git a/Source/cmExportInstallPackageInfoGenerator.h b/Source/cmExportInstallPackageInfoGenerator.h new file mode 100644 index 0000000..5861b05 --- /dev/null +++ b/Source/cmExportInstallPackageInfoGenerator.h @@ -0,0 +1,66 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ +#pragma once + +#include "cmConfigure.h" // IWYU pragma: keep + +#include +#include +#include + +#include "cmExportInstallFileGenerator.h" +#include "cmExportPackageInfoGenerator.h" + +class cmGeneratorTarget; +class cmInstallExportGenerator; + +/** \class cmExportInstallPackageInfoGenerator + * \brief Generate files exporting targets from an install tree. + * + * cmExportInstallPackageInfoGenerator generates files exporting targets from + * an installation tree. The files are placed in a temporary location for + * installation by cmInstallExportGenerator. The file format is the Common + * Package Specification (https://cps-org.github.io/cps/). + * + * One main file is generated that describes the imported targets. Additional, + * per-configuration files describe target locations and settings for each + * configuration. + * + * This is used to implement the INSTALL(PACKAGE_INFO) command. + */ +class cmExportInstallPackageInfoGenerator + : public cmExportPackageInfoGenerator + , public cmExportInstallFileGenerator +{ +public: + /** Construct with the export installer that will install the + files. */ + cmExportInstallPackageInfoGenerator( + cmInstallExportGenerator* iegen, std::string packageName, + std::string version, std::string versionCompat, std::string versionSchema, + std::vector defaultTargets, + std::vector defaultConfigurations); + + /** Compute the globbing expression used to load per-config import + files from the main file. */ + std::string GetConfigImportFileGlob() const override; + +protected: + std::string const& GetExportName() const override; + + // Implement virtual methods from the superclass. + bool GenerateMainFile(std::ostream& os) override; + void GenerateImportTargetsConfig(std::ostream& os, std::string const& config, + std::string const& suffix) override; + + char GetConfigFileNameSeparator() const override { return '@'; } + + /** Generate the cps_path, which determines the import prefix. */ + std::string GenerateImportPrefix() const; + + std::string InstallNameDir(cmGeneratorTarget const* target, + std::string const& config) override; + + std::string GetCxxModulesDirectory() const override; + // TODO: Generate C++ module info in a not-CMake-specific format. +}; diff --git a/Source/cmExportPackageInfoGenerator.cxx b/Source/cmExportPackageInfoGenerator.cxx new file mode 100644 index 0000000..7625953 --- /dev/null +++ b/Source/cmExportPackageInfoGenerator.cxx @@ -0,0 +1,452 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ +#include "cmExportPackageInfoGenerator.h" + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +#include "cmExportSet.h" +#include "cmFindPackageStack.h" +#include "cmGeneratorExpression.h" +#include "cmGeneratorTarget.h" +#include "cmList.h" +#include "cmMakefile.h" +#include "cmMessageType.h" +#include "cmStringAlgorithms.h" +#include "cmSystemTools.h" +#include "cmTarget.h" +#include "cmValue.h" + +constexpr char const* cmExportPackageInfoGenerator::CPS_VERSION_STR; + +cmExportPackageInfoGenerator::cmExportPackageInfoGenerator( + std::string packageName, std::string version, std::string versionCompat, + std::string versionSchema, std::vector defaultTargets, + std::vector defaultConfigurations) + : PackageName(std::move(packageName)) + , PackageVersion(std::move(version)) + , PackageVersionCompat(std::move(versionCompat)) + , PackageVersionSchema(std::move(versionSchema)) + , DefaultTargets(std::move(defaultTargets)) + , DefaultConfigurations(std::move(defaultConfigurations)) +{ +} + +cm::string_view cmExportPackageInfoGenerator::GetImportPrefixWithSlash() const +{ + return "@prefix@/"_s; +} + +bool cmExportPackageInfoGenerator::GenerateImportFile(std::ostream& os) +{ + return this->GenerateMainFile(os); +} + +void cmExportPackageInfoGenerator::WritePackageInfo( + Json::Value const& packageInfo, std::ostream& os) const +{ + Json::StreamWriterBuilder builder; + builder["indentation"] = " "; + builder["commentStyle"] = "None"; + std::unique_ptr const writer(builder.newStreamWriter()); + writer->write(packageInfo, &os); +} + +namespace { +template +void buildArray(Json::Value& object, std::string const& property, + T const& values) +{ + if (!values.empty()) { + Json::Value& array = object[property]; + for (auto const& item : values) { + array.append(item); + } + } +} +} + +bool cmExportPackageInfoGenerator::CheckDefaultTargets() const +{ + bool result = true; + std::set exportedTargetNames; + for (auto const* te : this->ExportedTargets) { + exportedTargetNames.emplace(te->GetExportName()); + } + + for (auto const& name : this->DefaultTargets) { + if (!cm::contains(exportedTargetNames, name)) { + this->ReportError( + cmStrCat("Package \"", this->GetPackageName(), + "\" specifies DEFAULT_TARGETS \"", name, + "\", which is not a target in the export set \"", + this->GetExportSet()->GetName(), "\".")); + result = false; + } + } + + return result; +} + +Json::Value cmExportPackageInfoGenerator::GeneratePackageInfo() const +{ + Json::Value package; + + package["name"] = this->GetPackageName(); + package["cps_version"] = this->CPS_VERSION_STR; + + if (!this->PackageVersion.empty()) { + package["version"] = this->PackageVersion; + if (!this->PackageVersion.empty()) { + package["compat_version"] = this->PackageVersionCompat; + } + if (!this->PackageVersion.empty()) { + package["version_schema"] = this->PackageVersionSchema; + } + } + + buildArray(package, "default_components", this->DefaultTargets); + buildArray(package, "configurations", this->DefaultConfigurations); + + // TODO: description, website, license + + return package; +} + +void cmExportPackageInfoGenerator::GeneratePackageRequires( + Json::Value& package) const +{ + if (!this->Requirements.empty()) { + Json::Value& requirements = package["requires"]; + for (auto const& requirement : this->Requirements) { + // TODO: version, hint + requirements[requirement] = Json::Value{}; + } + } +} + +Json::Value* cmExportPackageInfoGenerator::GenerateImportTarget( + Json::Value& components, cmGeneratorTarget const* target, + cmStateEnums::TargetType targetType) const +{ + auto const& name = target->GetExportName(); + if (name.empty()) { + return nullptr; + } + + Json::Value& component = components[name]; + Json::Value& type = component["type"]; + switch (targetType) { + case cmStateEnums::EXECUTABLE: + type = "executable"; + break; + case cmStateEnums::STATIC_LIBRARY: + type = "archive"; + break; + case cmStateEnums::SHARED_LIBRARY: + type = "dylib"; + break; + case cmStateEnums::MODULE_LIBRARY: + type = "module"; + break; + case cmStateEnums::INTERFACE_LIBRARY: + type = "interface"; + break; + default: + type = "unknown"; + break; + } + return &component; +} + +bool cmExportPackageInfoGenerator::GenerateInterfaceProperties( + Json::Value& component, cmGeneratorTarget const* target, + ImportPropertyMap const& properties) const +{ + bool result = true; + + this->GenerateInterfaceLinkProperties(result, component, target, properties); + + this->GenerateInterfaceCompileFeatures(result, component, target, + properties); + this->GenerateInterfaceCompileDefines(result, component, target, properties); + + this->GenerateInterfaceListProperty(result, component, target, + "compile_flags", "COMPILE_OPTIONS"_s, + properties); + this->GenerateInterfaceListProperty(result, component, target, "link_flags", + "LINK_OPTIONS"_s, properties); + this->GenerateInterfaceListProperty(result, component, target, + "link_directories", "LINK_DIRECTORIES"_s, + properties); + this->GenerateInterfaceListProperty(result, component, target, "includes", + "INCLUDE_DIRECTORIES"_s, properties); + + // TODO: description, license + + return result; +} + +namespace { +bool forbidGeneratorExpressions(std::string const& propertyName, + std::string const& propertyValue, + cmGeneratorTarget const* target) +{ + std::string const& evaluatedValue = cmGeneratorExpression::Preprocess( + propertyValue, cmGeneratorExpression::StripAllGeneratorExpressions); + if (evaluatedValue != propertyValue) { + target->Makefile->IssueMessage( + MessageType::FATAL_ERROR, + cmStrCat("Property \"", propertyName, "\" of target \"", + target->GetName(), + "\" contains a generator expression. This is not allowed.")); + return false; + } + return true; +} +} + +bool cmExportPackageInfoGenerator::NoteLinkedTarget( + cmGeneratorTarget const* target, std::string const& linkedName, + cmGeneratorTarget const* linkedTarget) +{ + if (cm::contains(this->ExportedTargets, linkedTarget)) { + // Target is internal to this package. + this->LinkTargets.emplace(linkedName, + cmStrCat(':', linkedTarget->GetExportName())); + return true; + } + + if (linkedTarget->IsImported()) { + // Target is imported from a found package. + auto pkgName = [linkedTarget]() -> std::string { + auto const& pkgStack = linkedTarget->Target->GetFindPackageStack(); + if (!pkgStack.Empty()) { + return pkgStack.Top().Name; + } + + return linkedTarget->Target->GetProperty("EXPORT_FIND_PACKAGE_NAME"); + }(); + + if (pkgName.empty()) { + target->Makefile->IssueMessage( + MessageType::FATAL_ERROR, + cmStrCat("Target \"", target->GetName(), + "\" references imported target \"", linkedName, + "\" which does not come from any known package.")); + return false; + } + + auto const& prefix = cmStrCat(pkgName, "::"); + if (!cmHasPrefix(linkedName, prefix)) { + target->Makefile->IssueMessage( + MessageType::FATAL_ERROR, + cmStrCat("Target \"", target->GetName(), "\" references target \"", + linkedName, "\", which comes from the \"", pkgName, + "\" package, but does not belong to the package's " + "canonical namespace. This is not allowed.")); + return false; + } + + // TODO: Record package version, hint. + this->Requirements.emplace(pkgName); + this->LinkTargets.emplace( + linkedName, cmStrCat(pkgName, ':', linkedName.substr(prefix.length()))); + return true; + } + + // Target belongs to another export from this build. + auto const& exportInfo = this->FindExportInfo(linkedTarget); + if (exportInfo.first.size() == 1) { + auto const& linkNamespace = exportInfo.second; + if (!cmHasSuffix(linkNamespace, "::")) { + target->Makefile->IssueMessage( + MessageType::FATAL_ERROR, + cmStrCat("Target \"", target->GetName(), "\" references target \"", + linkedName, + "\", which does not use the standard namespace separator. " + "This is not allowed.")); + return false; + } + + auto pkgName = + cm::string_view{ linkNamespace.data(), linkNamespace.size() - 2 }; + + if (pkgName == this->GetPackageName()) { + this->LinkTargets.emplace(linkedName, + cmStrCat(':', linkedTarget->GetExportName())); + } else { + this->Requirements.emplace(pkgName); + this->LinkTargets.emplace( + linkedName, cmStrCat(pkgName, ':', linkedTarget->GetExportName())); + } + return true; + } + + // cmExportFileGenerator::HandleMissingTarget should have complained about + // this already. (In fact, we probably shouldn't ever get here.) + return false; +} + +void cmExportPackageInfoGenerator::GenerateInterfaceLinkProperties( + bool& result, Json::Value& component, cmGeneratorTarget const* target, + ImportPropertyMap const& properties) const +{ + auto const& iter = properties.find("INTERFACE_LINK_LIBRARIES"); + if (iter == properties.end()) { + return; + } + + // TODO: Support $. + if (!forbidGeneratorExpressions(iter->first, iter->second, target)) { + result = false; + return; + } + + std::vector buildRequires; + // std::vector linkRequires; TODO + std::vector linkLibraries; + + for (auto const& name : cmList{ iter->second }) { + auto const& ti = this->LinkTargets.find(name); + if (ti != this->LinkTargets.end()) { + if (ti->second.empty()) { + result = false; + } else { + buildRequires.emplace_back(ti->second); + } + } else { + linkLibraries.emplace_back(name); + } + } + + buildArray(component, "requires", buildRequires); + // buildArray(component, "link_requires", linkRequires); TODO + buildArray(component, "link_libraries", linkLibraries); +} + +void cmExportPackageInfoGenerator::GenerateInterfaceCompileFeatures( + bool& result, Json::Value& component, cmGeneratorTarget const* target, + ImportPropertyMap const& properties) const +{ + auto const& iter = properties.find("INTERFACE_COMPILE_FEATURES"); + if (iter == properties.end()) { + return; + } + + if (!forbidGeneratorExpressions(iter->first, iter->second, target)) { + result = false; + return; + } + + std::set features; + for (auto const& value : cmList{ iter->second }) { + if (cmHasLiteralPrefix(value, "c_std_")) { + auto suffix = cm::string_view{ value }.substr(6, 2); + features.emplace(cmStrCat("cxx", suffix)); + } else if (cmHasLiteralPrefix(value, "cxx_std_")) { + auto suffix = cm::string_view{ value }.substr(8, 2); + features.emplace(cmStrCat("c++", suffix)); + } + } + + buildArray(component, "compile_features", features); +} + +void cmExportPackageInfoGenerator::GenerateInterfaceCompileDefines( + bool& result, Json::Value& component, cmGeneratorTarget const* target, + ImportPropertyMap const& properties) const +{ + auto const& iter = properties.find("INTERFACE_COMPILE_DEFINITIONS"); + if (iter == properties.end()) { + return; + } + + // TODO: Support language-specific defines. + if (!forbidGeneratorExpressions(iter->first, iter->second, target)) { + result = false; + return; + } + + Json::Value defines; + for (auto const& def : cmList{ iter->second }) { + auto const n = def.find('='); + if (n == std::string::npos) { + defines[def] = Json::Value{}; + } else { + defines[def.substr(0, n)] = def.substr(n + 1); + } + } + + if (!defines.empty()) { + component["compile_definitions"]["*"] = std::move(defines); + } +} + +void cmExportPackageInfoGenerator::GenerateInterfaceListProperty( + bool& result, Json::Value& component, cmGeneratorTarget const* target, + std::string const& outName, cm::string_view inName, + ImportPropertyMap const& properties) const +{ + auto const& prop = cmStrCat("INTERFACE_", inName); + auto const& iter = properties.find(prop); + if (iter == properties.end()) { + return; + } + + if (!forbidGeneratorExpressions(prop, iter->second, target)) { + result = false; + return; + } + + Json::Value& array = component[outName]; + for (auto const& value : cmList{ iter->second }) { + array.append(value); + } +} + +void cmExportPackageInfoGenerator::GenerateInterfaceConfigProperties( + Json::Value& components, cmGeneratorTarget const* target, + std::string const& suffix, ImportPropertyMap const& properties) const +{ + Json::Value component; + auto const suffixLength = suffix.length(); + + for (auto const& p : properties) { + if (!cmHasSuffix(p.first, suffix)) { + continue; + } + auto const n = p.first.length() - suffixLength - 9; + auto const prop = cm::string_view{ p.first }.substr(9, n); + + if (prop == "LOCATION") { + component["location"] = p.second; + } else if (prop == "IMPLIB") { + component["link_location"] = p.second; + } else if (prop == "LINK_INTERFACE_LANGUAGES") { + std::vector languages; + for (auto const& lang : cmList{ p.second }) { + auto ll = cmSystemTools::LowerCase(lang); + if (ll == "cxx") { + languages.emplace_back("cpp"); + } else { + languages.emplace_back(std::move(ll)); + } + } + buildArray(component, "link_languages", languages); + } + } + + if (!component.empty()) { + components[target->GetExportName()] = component; + } +} diff --git a/Source/cmExportPackageInfoGenerator.h b/Source/cmExportPackageInfoGenerator.h new file mode 100644 index 0000000..1fb1703 --- /dev/null +++ b/Source/cmExportPackageInfoGenerator.h @@ -0,0 +1,116 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ +#pragma once + +#include "cmConfigure.h" // IWYU pragma: keep + +#include +#include +#include +#include +#include + +#include + +#include "cmExportFileGenerator.h" +#include "cmStateTypes.h" + +class cmGeneratorTarget; +namespace Json { +class Value; +} + +/** \class cmExportPackageInfoGenerator + * \brief Generate Common Package Specification package information files + * exporting targets from a build or install tree. + * + * cmExportPackageInfoGenerator is the superclass for + * cmExportBuildPackageInfoGenerator and cmExportInstallPackageInfoGenerator. + * It contains common code generation routines for the two kinds of export + * implementations. + */ +class cmExportPackageInfoGenerator : virtual public cmExportFileGenerator +{ +public: + cmExportPackageInfoGenerator(std::string packageName, std::string version, + std::string versionCompat, + std::string versionSchema, + std::vector defaultTargets, + std::vector defaultConfigurations); + + using cmExportFileGenerator::GenerateImportFile; + +protected: + std::string const& GetPackageName() const { return this->PackageName; } + + void WritePackageInfo(Json::Value const& packageInfo, + std::ostream& os) const; + + // Methods to implement export file code generation. + bool GenerateImportFile(std::ostream& os) override; + + bool CheckDefaultTargets() const; + + Json::Value GeneratePackageInfo() const; + Json::Value* GenerateImportTarget(Json::Value& components, + cmGeneratorTarget const* target, + cmStateEnums::TargetType targetType) const; + + void GeneratePackageRequires(Json::Value& package) const; + + using ImportPropertyMap = std::map; + bool GenerateInterfaceProperties(Json::Value& component, + cmGeneratorTarget const* target, + ImportPropertyMap const& properties) const; + void GenerateInterfaceConfigProperties( + Json::Value& components, cmGeneratorTarget const* target, + std::string const& suffix, ImportPropertyMap const& properties) const; + + cm::string_view GetImportPrefixWithSlash() const override; + + std::string GetCxxModuleFile(std::string const& /*name*/) const override + { + // TODO + return {}; + } + + void GenerateCxxModuleConfigInformation(std::string const& /*name*/, + std::ostream& /*os*/) const override + { + // TODO + } + + bool NoteLinkedTarget(cmGeneratorTarget const* target, + std::string const& linkedName, + cmGeneratorTarget const* linkedTarget) override; + +private: + void GenerateInterfaceLinkProperties( + bool& result, Json::Value& component, cmGeneratorTarget const* target, + ImportPropertyMap const& properties) const; + + void GenerateInterfaceCompileFeatures( + bool& result, Json::Value& component, cmGeneratorTarget const* target, + ImportPropertyMap const& properties) const; + + void GenerateInterfaceCompileDefines( + bool& result, Json::Value& component, cmGeneratorTarget const* target, + ImportPropertyMap const& properties) const; + + void GenerateInterfaceListProperty( + bool& result, Json::Value& component, cmGeneratorTarget const* target, + std::string const& outName, cm::string_view inName, + ImportPropertyMap const& properties) const; + + std::string const PackageName; + std::string const PackageVersion; + std::string const PackageVersionCompat; + std::string const PackageVersionSchema; + std::vector DefaultTargets; + std::vector DefaultConfigurations; + + std::map LinkTargets; + std::set Requirements; + + static constexpr char const* CPS_VERSION_STR = "0.12.0"; +}; diff --git a/Source/cmInstallCommand.cxx b/Source/cmInstallCommand.cxx index 3160585..f4cc4c3 100644 --- a/Source/cmInstallCommand.cxx +++ b/Source/cmInstallCommand.cxx @@ -36,6 +36,7 @@ #include "cmInstallGenerator.h" #include "cmInstallGetRuntimeDependenciesGenerator.h" #include "cmInstallImportedRuntimeArtifactsGenerator.h" +#include "cmInstallPackageInfoExportGenerator.h" #include "cmInstallRuntimeDependencySet.h" #include "cmInstallRuntimeDependencySetGenerator.h" #include "cmInstallScriptGenerator.h" @@ -2162,6 +2163,143 @@ bool HandleExportMode(std::vector const& args, return true; } +bool HandlePackageInfoMode(std::vector const& args, + cmExecutionStatus& status) +{ +#ifndef CMAKE_BOOTSTRAP + if (!cmExperimental::HasSupportEnabled( + status.GetMakefile(), cmExperimental::Feature::ExportPackageInfo)) { + status.SetError("does not recognize sub-command PACKAGE_INFO"); + return false; + } + + Helper helper(status); + + // This is the PACKAGE_INFO mode. + cmInstallCommandArguments ica(helper.DefaultComponentName); + + ArgumentParser::NonEmpty pkg; + ArgumentParser::NonEmpty appendix; + ArgumentParser::NonEmpty exportName; + bool lowerCase = false; + ArgumentParser::NonEmpty version; + ArgumentParser::NonEmpty versionCompat; + ArgumentParser::NonEmpty versionSchema; + ArgumentParser::NonEmpty> defaultTargets; + ArgumentParser::NonEmpty> defaultConfigs; + ArgumentParser::NonEmpty cxxModulesDirectory; + + // TODO: Support DESTINATION. + ica.Bind("PACKAGE_INFO"_s, pkg); + ica.Bind("EXPORT"_s, exportName); + ica.Bind("APPENDIX"_s, appendix); + ica.Bind("LOWER_CASE_FILE"_s, lowerCase); + ica.Bind("VERSION"_s, version); + ica.Bind("COMPAT_VERSION"_s, versionCompat); + ica.Bind("VERSION_SCHEMA"_s, versionSchema); + ica.Bind("DEFAULT_TARGETS"_s, defaultTargets); + ica.Bind("DEFAULT_CONFIGURATIONS"_s, defaultConfigs); + // ica.Bind("CXX_MODULES_DIRECTORY"_s, cxxModulesDirectory); TODO? + + std::vector unknownArgs; + ica.Parse(args, &unknownArgs); + + if (!unknownArgs.empty()) { + // Unknown argument. + status.SetError( + cmStrCat(args[0], " given unknown argument \"", unknownArgs[0], "\".")); + return false; + } + + if (!ica.Finalize()) { + return false; + } + + if (exportName.empty()) { + status.SetError(cmStrCat(args[0], " missing EXPORT.")); + return false; + } + + if (version.empty()) { + if (!versionCompat.empty()) { + status.SetError("COMPAT_VERSION requires VERSION."); + return false; + } + if (!versionSchema.empty()) { + status.SetError("VERSION_SCHEMA requires VERSION."); + return false; + } + } else { + if (!appendix.empty()) { + status.SetError("APPENDIX and VERSION are mutually exclusive."); + return false; + } + } + if (!appendix.empty()) { + if (!defaultTargets.empty()) { + status.SetError("APPENDIX and DEFAULT_TARGETS are mutually exclusive."); + return false; + } + if (!defaultConfigs.empty()) { + status.SetError("APPENDIX and DEFAULT_CONFIGURATIONS " + "are mutually exclusive."); + return false; + } + } + + // Validate the package name. + if (!cmGeneratorExpression::IsValidTargetName(pkg) || + pkg.find(':') != std::string::npos) { + status.SetError( + cmStrCat(args[0], " given invalid package name \"", pkg, "\".")); + return false; + } + + // Construct the case-normalized package name and the file name. + std::string const pkgNameOnDisk = + (lowerCase ? cmSystemTools::LowerCase(pkg) : pkg); + std::string pkgFileName = [&]() -> std::string { + if (appendix.empty()) { + return cmStrCat(pkgNameOnDisk, ".cps"); + } + return cmStrCat(pkgNameOnDisk, '-', appendix, ".cps"); + }(); + + // Get or construct the destination path. + std::string dest = ica.GetDestination(); + if (dest.empty()) { + if (helper.Makefile->GetSafeDefinition("CMAKE_SYSTEM_NAME") == "Windows") { + dest = std::string{ "cps"_s }; + } else { + dest = cmStrCat(helper.GetLibraryDestination(nullptr), "/cps/", + pkgNameOnDisk); + } + } + + cmExportSet& exportSet = + helper.Makefile->GetGlobalGenerator()->GetExportSets()[exportName]; + + cmInstallGenerator::MessageLevel message = + cmInstallGenerator::SelectMessageLevel(helper.Makefile); + + // Create the export install generator. + helper.Makefile->AddInstallGenerator( + cm::make_unique( + &exportSet, dest, ica.GetPermissions(), ica.GetConfigurations(), + ica.GetComponent(), message, ica.GetExcludeFromAll(), + std::move(pkgFileName), std::move(pkg), std::move(version), + std::move(versionCompat), std::move(versionSchema), + std::move(defaultTargets), std::move(defaultConfigs), + std::move(cxxModulesDirectory), helper.Makefile->GetBacktrace())); + + return true; +#else + static_cast(args); + status.SetError("PACKAGE_INFO not supported in bootstrap cmake"); + return false; +#endif +} + bool HandleRuntimeDependencySetMode(std::vector const& args, cmExecutionStatus& status) { @@ -2525,6 +2663,7 @@ bool cmInstallCommand(std::vector const& args, { "DIRECTORY"_s, HandleDirectoryMode }, { "EXPORT"_s, HandleExportMode }, { "EXPORT_ANDROID_MK"_s, HandleExportAndroidMKMode }, + { "PACKAGE_INFO"_s, HandlePackageInfoMode }, { "RUNTIME_DEPENDENCY_SET"_s, HandleRuntimeDependencySetMode }, }; diff --git a/Source/cmInstallPackageInfoExportGenerator.cxx b/Source/cmInstallPackageInfoExportGenerator.cxx new file mode 100644 index 0000000..4ff045b --- /dev/null +++ b/Source/cmInstallPackageInfoExportGenerator.cxx @@ -0,0 +1,36 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ +#include "cmInstallPackageInfoExportGenerator.h" + +#include + +#include + +#include "cmExportInstallFileGenerator.h" +#include "cmExportInstallPackageInfoGenerator.h" +#include "cmListFileCache.h" + +class cmExportSet; + +cmInstallPackageInfoExportGenerator::cmInstallPackageInfoExportGenerator( + cmExportSet* exportSet, std::string destination, std::string filePermissions, + std::vector const& configurations, std::string component, + MessageLevel message, bool excludeFromAll, std::string filename, + std::string packageName, std::string version, std::string versionCompat, + std::string versionSchema, std::vector defaultTargets, + std::vector defaultConfigurations, + std::string cxxModulesDirectory, cmListFileBacktrace backtrace) + : cmInstallExportGenerator( + exportSet, std::move(destination), std::move(filePermissions), + configurations, std::move(component), message, excludeFromAll, + std::move(filename), packageName + "::", std::move(cxxModulesDirectory), + std::move(backtrace)) +{ + this->EFGen = cm::make_unique( + this, std::move(packageName), std::move(version), std::move(versionCompat), + std::move(versionSchema), std::move(defaultTargets), + std::move(defaultConfigurations)); +} + +cmInstallPackageInfoExportGenerator::~cmInstallPackageInfoExportGenerator() = + default; diff --git a/Source/cmInstallPackageInfoExportGenerator.h b/Source/cmInstallPackageInfoExportGenerator.h new file mode 100644 index 0000000..c79df84 --- /dev/null +++ b/Source/cmInstallPackageInfoExportGenerator.h @@ -0,0 +1,36 @@ +/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying + file Copyright.txt or https://cmake.org/licensing for details. */ +#pragma once + +#include +#include + +#include "cmInstallExportGenerator.h" + +class cmExportSet; +class cmListFileBacktrace; + +/** \class cmInstallPackageInfoGenerator + * \brief Generate rules for creating CPS package info files. + */ +class cmInstallPackageInfoExportGenerator : public cmInstallExportGenerator +{ +public: + cmInstallPackageInfoExportGenerator( + cmExportSet* exportSet, std::string destination, + std::string filePermissions, + std::vector const& configurations, std::string component, + MessageLevel message, bool excludeFromAll, std::string filename, + std::string packageName, std::string version, std::string versionCompat, + std::string versionSchema, std::vector defaultTargets, + std::vector defaultConfigurations, + std::string cxxModulesDirectory, cmListFileBacktrace backtrace); + cmInstallPackageInfoExportGenerator( + cmInstallPackageInfoExportGenerator const&) = delete; + ~cmInstallPackageInfoExportGenerator() override; + + cmInstallPackageInfoExportGenerator& operator=( + cmInstallPackageInfoExportGenerator const&) = delete; + + char const* InstallSubcommand() const override { return "PACKAGE_INFO"; } +}; -- cgit v0.12 From 1a846c8849c47f5871afaed68468de26d49af29e Mon Sep 17 00:00:00 2001 From: Matthew Woehlke Date: Thu, 1 Aug 2024 14:59:47 -0400 Subject: Tests: Add tests for install(PACKAGE_INFO) Add several test cases to verify that install(PACKAGE_INFO) is working as expected. These cover validating the output for several scenarios, improper use of the command, effects of command arguments, and a handful of improper uses. Tests that should still be added include one covering DESTINATION (both the default and user-specified) and the CPS location and link_location attributes. These have been skipped for now because the former requires actually installing a test project, and the latter involves validating attributes whose values are platform-dependent. (In particular, I don't think link_location will appear on not-Windows.) Additionally, there is no coverage of (other) common install-command arguments such as PERMISSIONS, COMPONENT, etc.; however, because the logic implementing these is shared, the tests covering them for other sub-commands is probably sufficient. Note that, because the files are generated in a location that includes a hash of the install destination, and because the default destination is platform dependent, the tests need to specify a fixed DESTINATION so that the location of generated files (which we need to inspect and validate) is predictable. --- Tests/RunCMake/CMakeLists.txt | 1 + Tests/RunCMake/PackageInfo/Appendix-check.cmake | 16 ++++++++++ Tests/RunCMake/PackageInfo/Appendix.cmake | 9 ++++++ Tests/RunCMake/PackageInfo/Assertions.cmake | 34 ++++++++++++++++++++++ Tests/RunCMake/PackageInfo/BadArgs1-result.txt | 1 + Tests/RunCMake/PackageInfo/BadArgs1-stderr.txt | 2 ++ Tests/RunCMake/PackageInfo/BadArgs1.cmake | 3 ++ Tests/RunCMake/PackageInfo/BadArgs2-result.txt | 1 + Tests/RunCMake/PackageInfo/BadArgs2-stderr.txt | 2 ++ Tests/RunCMake/PackageInfo/BadArgs2.cmake | 3 ++ Tests/RunCMake/PackageInfo/BadArgs3-result.txt | 1 + Tests/RunCMake/PackageInfo/BadArgs3-stderr.txt | 2 ++ Tests/RunCMake/PackageInfo/BadArgs3.cmake | 3 ++ Tests/RunCMake/PackageInfo/BadArgs4-result.txt | 1 + Tests/RunCMake/PackageInfo/BadArgs4-stderr.txt | 2 ++ Tests/RunCMake/PackageInfo/BadArgs4.cmake | 3 ++ Tests/RunCMake/PackageInfo/BadArgs5-result.txt | 1 + Tests/RunCMake/PackageInfo/BadArgs5-stderr.txt | 2 ++ Tests/RunCMake/PackageInfo/BadArgs5.cmake | 3 ++ .../PackageInfo/BadDefaultTarget-result.txt | 1 + .../PackageInfo/BadDefaultTarget-stderr.txt | 2 ++ Tests/RunCMake/PackageInfo/BadDefaultTarget.cmake | 5 ++++ Tests/RunCMake/PackageInfo/CMakeLists.txt | 3 ++ .../PackageInfo/ExperimentalGate-result.txt | 1 + .../PackageInfo/ExperimentalGate-stderr.txt | 2 ++ Tests/RunCMake/PackageInfo/ExperimentalGate.cmake | 5 ++++ .../PackageInfo/ExperimentalWarning-stderr.txt | 7 +++++ .../RunCMake/PackageInfo/ExperimentalWarning.cmake | 8 +++++ .../PackageInfo/InterfaceProperties-check.cmake | 24 +++++++++++++++ .../RunCMake/PackageInfo/InterfaceProperties.cmake | 15 ++++++++++ .../RunCMake/PackageInfo/LowerCaseFile-check.cmake | 9 ++++++ Tests/RunCMake/PackageInfo/LowerCaseFile.cmake | 4 +++ Tests/RunCMake/PackageInfo/Metadata-check.cmake | 16 ++++++++++ Tests/RunCMake/PackageInfo/Metadata.cmake | 12 ++++++++ Tests/RunCMake/PackageInfo/Minimal-check.cmake | 18 ++++++++++++ Tests/RunCMake/PackageInfo/Minimal.cmake | 3 ++ .../ReferencesNonExportedTarget-result.txt | 1 + .../ReferencesNonExportedTarget-stderr.txt | 1 + .../PackageInfo/ReferencesNonExportedTarget.cmake | 6 ++++ .../ReferencesWronglyExportedTarget-result.txt | 1 + .../ReferencesWronglyExportedTarget-stderr.txt | 7 +++++ .../ReferencesWronglyExportedTarget.cmake | 14 +++++++++ .../ReferencesWronglyImportedTarget-result.txt | 1 + .../ReferencesWronglyImportedTarget-stderr.txt | 3 ++ .../ReferencesWronglyImportedTarget.cmake | 7 +++++ .../ReferencesWronglyNamespacedTarget-result.txt | 1 + .../ReferencesWronglyNamespacedTarget-stderr.txt | 4 +++ .../ReferencesWronglyNamespacedTarget.cmake | 11 +++++++ .../RunCMake/PackageInfo/Requirements-check.cmake | 20 +++++++++++++ Tests/RunCMake/PackageInfo/Requirements.cmake | 20 +++++++++++++ Tests/RunCMake/PackageInfo/RunCMakeTest.cmake | 32 ++++++++++++++++++++ Tests/RunCMake/PackageInfo/TargetTypes-check.cmake | 11 +++++++ Tests/RunCMake/PackageInfo/TargetTypes.cmake | 20 +++++++++++++ Tests/RunCMake/PackageInfo/broken-config.cmake | 1 + Tests/RunCMake/PackageInfo/foo.cxx | 3 ++ Tests/RunCMake/PackageInfo/test-config.cmake | 1 + Tests/RunCMake/PackageInfo/test.cxx | 4 +++ 57 files changed, 394 insertions(+) create mode 100644 Tests/RunCMake/PackageInfo/Appendix-check.cmake create mode 100644 Tests/RunCMake/PackageInfo/Appendix.cmake create mode 100644 Tests/RunCMake/PackageInfo/Assertions.cmake create mode 100644 Tests/RunCMake/PackageInfo/BadArgs1-result.txt create mode 100644 Tests/RunCMake/PackageInfo/BadArgs1-stderr.txt create mode 100644 Tests/RunCMake/PackageInfo/BadArgs1.cmake create mode 100644 Tests/RunCMake/PackageInfo/BadArgs2-result.txt create mode 100644 Tests/RunCMake/PackageInfo/BadArgs2-stderr.txt create mode 100644 Tests/RunCMake/PackageInfo/BadArgs2.cmake create mode 100644 Tests/RunCMake/PackageInfo/BadArgs3-result.txt create mode 100644 Tests/RunCMake/PackageInfo/BadArgs3-stderr.txt create mode 100644 Tests/RunCMake/PackageInfo/BadArgs3.cmake create mode 100644 Tests/RunCMake/PackageInfo/BadArgs4-result.txt create mode 100644 Tests/RunCMake/PackageInfo/BadArgs4-stderr.txt create mode 100644 Tests/RunCMake/PackageInfo/BadArgs4.cmake create mode 100644 Tests/RunCMake/PackageInfo/BadArgs5-result.txt create mode 100644 Tests/RunCMake/PackageInfo/BadArgs5-stderr.txt create mode 100644 Tests/RunCMake/PackageInfo/BadArgs5.cmake create mode 100644 Tests/RunCMake/PackageInfo/BadDefaultTarget-result.txt create mode 100644 Tests/RunCMake/PackageInfo/BadDefaultTarget-stderr.txt create mode 100644 Tests/RunCMake/PackageInfo/BadDefaultTarget.cmake create mode 100644 Tests/RunCMake/PackageInfo/CMakeLists.txt create mode 100644 Tests/RunCMake/PackageInfo/ExperimentalGate-result.txt create mode 100644 Tests/RunCMake/PackageInfo/ExperimentalGate-stderr.txt create mode 100644 Tests/RunCMake/PackageInfo/ExperimentalGate.cmake create mode 100644 Tests/RunCMake/PackageInfo/ExperimentalWarning-stderr.txt create mode 100644 Tests/RunCMake/PackageInfo/ExperimentalWarning.cmake create mode 100644 Tests/RunCMake/PackageInfo/InterfaceProperties-check.cmake create mode 100644 Tests/RunCMake/PackageInfo/InterfaceProperties.cmake create mode 100644 Tests/RunCMake/PackageInfo/LowerCaseFile-check.cmake create mode 100644 Tests/RunCMake/PackageInfo/LowerCaseFile.cmake create mode 100644 Tests/RunCMake/PackageInfo/Metadata-check.cmake create mode 100644 Tests/RunCMake/PackageInfo/Metadata.cmake create mode 100644 Tests/RunCMake/PackageInfo/Minimal-check.cmake create mode 100644 Tests/RunCMake/PackageInfo/Minimal.cmake create mode 100644 Tests/RunCMake/PackageInfo/ReferencesNonExportedTarget-result.txt create mode 100644 Tests/RunCMake/PackageInfo/ReferencesNonExportedTarget-stderr.txt create mode 100644 Tests/RunCMake/PackageInfo/ReferencesNonExportedTarget.cmake create mode 100644 Tests/RunCMake/PackageInfo/ReferencesWronglyExportedTarget-result.txt create mode 100644 Tests/RunCMake/PackageInfo/ReferencesWronglyExportedTarget-stderr.txt create mode 100644 Tests/RunCMake/PackageInfo/ReferencesWronglyExportedTarget.cmake create mode 100644 Tests/RunCMake/PackageInfo/ReferencesWronglyImportedTarget-result.txt create mode 100644 Tests/RunCMake/PackageInfo/ReferencesWronglyImportedTarget-stderr.txt create mode 100644 Tests/RunCMake/PackageInfo/ReferencesWronglyImportedTarget.cmake create mode 100644 Tests/RunCMake/PackageInfo/ReferencesWronglyNamespacedTarget-result.txt create mode 100644 Tests/RunCMake/PackageInfo/ReferencesWronglyNamespacedTarget-stderr.txt create mode 100644 Tests/RunCMake/PackageInfo/ReferencesWronglyNamespacedTarget.cmake create mode 100644 Tests/RunCMake/PackageInfo/Requirements-check.cmake create mode 100644 Tests/RunCMake/PackageInfo/Requirements.cmake create mode 100644 Tests/RunCMake/PackageInfo/RunCMakeTest.cmake create mode 100644 Tests/RunCMake/PackageInfo/TargetTypes-check.cmake create mode 100644 Tests/RunCMake/PackageInfo/TargetTypes.cmake create mode 100644 Tests/RunCMake/PackageInfo/broken-config.cmake create mode 100644 Tests/RunCMake/PackageInfo/foo.cxx create mode 100644 Tests/RunCMake/PackageInfo/test-config.cmake create mode 100644 Tests/RunCMake/PackageInfo/test.cxx diff --git a/Tests/RunCMake/CMakeLists.txt b/Tests/RunCMake/CMakeLists.txt index 2f4554f..e7bc87f 100644 --- a/Tests/RunCMake/CMakeLists.txt +++ b/Tests/RunCMake/CMakeLists.txt @@ -1175,6 +1175,7 @@ add_RunCMake_test(AutoExportDll ) add_RunCMake_test(AndroidMK) +add_RunCMake_test(PackageInfo) 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/PackageInfo/Appendix-check.cmake b/Tests/RunCMake/PackageInfo/Appendix-check.cmake new file mode 100644 index 0000000..864e731 --- /dev/null +++ b/Tests/RunCMake/PackageInfo/Appendix-check.cmake @@ -0,0 +1,16 @@ +include(${CMAKE_CURRENT_LIST_DIR}/Assertions.cmake) + +set(out_dir "${RunCMake_BINARY_DIR}/Appendix-build/CMakeFiles/Export/510c5684a4a8a792eadfb55bc9744983") + +file(READ "${out_dir}/foo.cps" content) +expect_value("${content}" "foo" "name") +expect_value("${content}" "interface" "components" "mammal" "type") +expect_value("${content}" "1.0" "version") + +file(READ "${out_dir}/foo-dog.cps" content) +expect_value("${content}" "foo" "name") +expect_value("${content}" "interface" "components" "canine" "type") +expect_missing("${content}" "version") + +expect_array("${content}" 1 "components" "canine" "requires") +expect_value("${content}" ":mammal" "components" "canine" "requires" 0) diff --git a/Tests/RunCMake/PackageInfo/Appendix.cmake b/Tests/RunCMake/PackageInfo/Appendix.cmake new file mode 100644 index 0000000..fe67778 --- /dev/null +++ b/Tests/RunCMake/PackageInfo/Appendix.cmake @@ -0,0 +1,9 @@ +add_library(mammal INTERFACE) +add_library(canine INTERFACE) +target_link_libraries(canine INTERFACE mammal) + +install(TARGETS mammal EXPORT mammal DESTINATION .) +install(TARGETS canine EXPORT canine DESTINATION .) + +install(PACKAGE_INFO foo DESTINATION cps EXPORT mammal VERSION 1.0) +install(PACKAGE_INFO foo DESTINATION cps EXPORT canine APPENDIX dog) diff --git a/Tests/RunCMake/PackageInfo/Assertions.cmake b/Tests/RunCMake/PackageInfo/Assertions.cmake new file mode 100644 index 0000000..8157618 --- /dev/null +++ b/Tests/RunCMake/PackageInfo/Assertions.cmake @@ -0,0 +1,34 @@ +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_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/PackageInfo/BadArgs1-result.txt b/Tests/RunCMake/PackageInfo/BadArgs1-result.txt new file mode 100644 index 0000000..d00491f --- /dev/null +++ b/Tests/RunCMake/PackageInfo/BadArgs1-result.txt @@ -0,0 +1 @@ +1 diff --git a/Tests/RunCMake/PackageInfo/BadArgs1-stderr.txt b/Tests/RunCMake/PackageInfo/BadArgs1-stderr.txt new file mode 100644 index 0000000..92ba6fb --- /dev/null +++ b/Tests/RunCMake/PackageInfo/BadArgs1-stderr.txt @@ -0,0 +1,2 @@ +CMake Error at BadArgs1.cmake:3 \(install\): + install COMPAT_VERSION requires VERSION. diff --git a/Tests/RunCMake/PackageInfo/BadArgs1.cmake b/Tests/RunCMake/PackageInfo/BadArgs1.cmake new file mode 100644 index 0000000..b99997c --- /dev/null +++ b/Tests/RunCMake/PackageInfo/BadArgs1.cmake @@ -0,0 +1,3 @@ +add_library(foo INTERFACE) +install(TARGETS foo EXPORT foo DESTINATION .) +install(PACKAGE_INFO test EXPORT foo COMPAT_VERSION 1.0) diff --git a/Tests/RunCMake/PackageInfo/BadArgs2-result.txt b/Tests/RunCMake/PackageInfo/BadArgs2-result.txt new file mode 100644 index 0000000..d00491f --- /dev/null +++ b/Tests/RunCMake/PackageInfo/BadArgs2-result.txt @@ -0,0 +1 @@ +1 diff --git a/Tests/RunCMake/PackageInfo/BadArgs2-stderr.txt b/Tests/RunCMake/PackageInfo/BadArgs2-stderr.txt new file mode 100644 index 0000000..636335c --- /dev/null +++ b/Tests/RunCMake/PackageInfo/BadArgs2-stderr.txt @@ -0,0 +1,2 @@ +CMake Error at BadArgs2.cmake:3 \(install\): + install VERSION_SCHEMA requires VERSION. diff --git a/Tests/RunCMake/PackageInfo/BadArgs2.cmake b/Tests/RunCMake/PackageInfo/BadArgs2.cmake new file mode 100644 index 0000000..265d93c --- /dev/null +++ b/Tests/RunCMake/PackageInfo/BadArgs2.cmake @@ -0,0 +1,3 @@ +add_library(foo INTERFACE) +install(TARGETS foo EXPORT foo DESTINATION .) +install(PACKAGE_INFO test EXPORT foo VERSION_SCHEMA simple) diff --git a/Tests/RunCMake/PackageInfo/BadArgs3-result.txt b/Tests/RunCMake/PackageInfo/BadArgs3-result.txt new file mode 100644 index 0000000..d00491f --- /dev/null +++ b/Tests/RunCMake/PackageInfo/BadArgs3-result.txt @@ -0,0 +1 @@ +1 diff --git a/Tests/RunCMake/PackageInfo/BadArgs3-stderr.txt b/Tests/RunCMake/PackageInfo/BadArgs3-stderr.txt new file mode 100644 index 0000000..11e1a8c --- /dev/null +++ b/Tests/RunCMake/PackageInfo/BadArgs3-stderr.txt @@ -0,0 +1,2 @@ +CMake Error at BadArgs3.cmake:3 \(install\): + install APPENDIX and VERSION are mutually exclusive. diff --git a/Tests/RunCMake/PackageInfo/BadArgs3.cmake b/Tests/RunCMake/PackageInfo/BadArgs3.cmake new file mode 100644 index 0000000..5f57f6a --- /dev/null +++ b/Tests/RunCMake/PackageInfo/BadArgs3.cmake @@ -0,0 +1,3 @@ +add_library(foo INTERFACE) +install(TARGETS foo EXPORT foo DESTINATION .) +install(PACKAGE_INFO test EXPORT foo APPENDIX test VERSION 1.0) diff --git a/Tests/RunCMake/PackageInfo/BadArgs4-result.txt b/Tests/RunCMake/PackageInfo/BadArgs4-result.txt new file mode 100644 index 0000000..d00491f --- /dev/null +++ b/Tests/RunCMake/PackageInfo/BadArgs4-result.txt @@ -0,0 +1 @@ +1 diff --git a/Tests/RunCMake/PackageInfo/BadArgs4-stderr.txt b/Tests/RunCMake/PackageInfo/BadArgs4-stderr.txt new file mode 100644 index 0000000..067a07b --- /dev/null +++ b/Tests/RunCMake/PackageInfo/BadArgs4-stderr.txt @@ -0,0 +1,2 @@ +CMake Error at BadArgs4.cmake:3 \(install\): + install APPENDIX and DEFAULT_TARGETS are mutually exclusive. diff --git a/Tests/RunCMake/PackageInfo/BadArgs4.cmake b/Tests/RunCMake/PackageInfo/BadArgs4.cmake new file mode 100644 index 0000000..426d10b --- /dev/null +++ b/Tests/RunCMake/PackageInfo/BadArgs4.cmake @@ -0,0 +1,3 @@ +add_library(foo INTERFACE) +install(TARGETS foo EXPORT foo DESTINATION .) +install(PACKAGE_INFO test EXPORT foo APPENDIX test DEFAULT_TARGETS foo) diff --git a/Tests/RunCMake/PackageInfo/BadArgs5-result.txt b/Tests/RunCMake/PackageInfo/BadArgs5-result.txt new file mode 100644 index 0000000..d00491f --- /dev/null +++ b/Tests/RunCMake/PackageInfo/BadArgs5-result.txt @@ -0,0 +1 @@ +1 diff --git a/Tests/RunCMake/PackageInfo/BadArgs5-stderr.txt b/Tests/RunCMake/PackageInfo/BadArgs5-stderr.txt new file mode 100644 index 0000000..4f7d285 --- /dev/null +++ b/Tests/RunCMake/PackageInfo/BadArgs5-stderr.txt @@ -0,0 +1,2 @@ +CMake Error at BadArgs5.cmake:3 \(install\): + install APPENDIX and DEFAULT_CONFIGURATIONS are mutually exclusive. diff --git a/Tests/RunCMake/PackageInfo/BadArgs5.cmake b/Tests/RunCMake/PackageInfo/BadArgs5.cmake new file mode 100644 index 0000000..356c856 --- /dev/null +++ b/Tests/RunCMake/PackageInfo/BadArgs5.cmake @@ -0,0 +1,3 @@ +add_library(foo INTERFACE) +install(TARGETS foo EXPORT foo DESTINATION .) +install(PACKAGE_INFO test EXPORT foo APPENDIX test DEFAULT_CONFIGURATIONS test) diff --git a/Tests/RunCMake/PackageInfo/BadDefaultTarget-result.txt b/Tests/RunCMake/PackageInfo/BadDefaultTarget-result.txt new file mode 100644 index 0000000..d00491f --- /dev/null +++ b/Tests/RunCMake/PackageInfo/BadDefaultTarget-result.txt @@ -0,0 +1 @@ +1 diff --git a/Tests/RunCMake/PackageInfo/BadDefaultTarget-stderr.txt b/Tests/RunCMake/PackageInfo/BadDefaultTarget-stderr.txt new file mode 100644 index 0000000..6467a14 --- /dev/null +++ b/Tests/RunCMake/PackageInfo/BadDefaultTarget-stderr.txt @@ -0,0 +1,2 @@ +CMake Error: Package "test" specifies DEFAULT_TARGETS "dog", which is not a target in the export set "foo". +CMake Error: Package "test" specifies DEFAULT_TARGETS "cat", which is not a target in the export set "foo". diff --git a/Tests/RunCMake/PackageInfo/BadDefaultTarget.cmake b/Tests/RunCMake/PackageInfo/BadDefaultTarget.cmake new file mode 100644 index 0000000..d3d993a --- /dev/null +++ b/Tests/RunCMake/PackageInfo/BadDefaultTarget.cmake @@ -0,0 +1,5 @@ +add_library(foo INTERFACE) +add_library(dog INTERFACE) +install(TARGETS foo EXPORT foo DESTINATION .) +install(TARGETS dog EXPORT dog DESTINATION .) +install(PACKAGE_INFO test EXPORT foo DEFAULT_TARGETS dog cat) diff --git a/Tests/RunCMake/PackageInfo/CMakeLists.txt b/Tests/RunCMake/PackageInfo/CMakeLists.txt new file mode 100644 index 0000000..dda37d8 --- /dev/null +++ b/Tests/RunCMake/PackageInfo/CMakeLists.txt @@ -0,0 +1,3 @@ +cmake_minimum_required(VERSION 3.30) +project(${RunCMake_TEST} NONE) +include(${RunCMake_TEST}.cmake) diff --git a/Tests/RunCMake/PackageInfo/ExperimentalGate-result.txt b/Tests/RunCMake/PackageInfo/ExperimentalGate-result.txt new file mode 100644 index 0000000..d00491f --- /dev/null +++ b/Tests/RunCMake/PackageInfo/ExperimentalGate-result.txt @@ -0,0 +1 @@ +1 diff --git a/Tests/RunCMake/PackageInfo/ExperimentalGate-stderr.txt b/Tests/RunCMake/PackageInfo/ExperimentalGate-stderr.txt new file mode 100644 index 0000000..40799b7 --- /dev/null +++ b/Tests/RunCMake/PackageInfo/ExperimentalGate-stderr.txt @@ -0,0 +1,2 @@ +CMake Error at ExperimentalGate.cmake:5 \(install\): + install does not recognize sub-command PACKAGE_INFO diff --git a/Tests/RunCMake/PackageInfo/ExperimentalGate.cmake b/Tests/RunCMake/PackageInfo/ExperimentalGate.cmake new file mode 100644 index 0000000..327d3bb --- /dev/null +++ b/Tests/RunCMake/PackageInfo/ExperimentalGate.cmake @@ -0,0 +1,5 @@ +unset(CMAKE_EXPERIMENTAL_EXPORT_PACKAGE_INFO) + +add_library(foo INTERFACE) +install(TARGETS foo EXPORT foo DESTINATION .) +install(PACKAGE_INFO foo DESTINATION cps EXPORT foo) diff --git a/Tests/RunCMake/PackageInfo/ExperimentalWarning-stderr.txt b/Tests/RunCMake/PackageInfo/ExperimentalWarning-stderr.txt new file mode 100644 index 0000000..960d541 --- /dev/null +++ b/Tests/RunCMake/PackageInfo/ExperimentalWarning-stderr.txt @@ -0,0 +1,7 @@ +CMake Warning \(dev\) at ExperimentalWarning.cmake:8 \(install\): + CMake's support for exporting package information in the Common Package + Specification format. 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/PackageInfo/ExperimentalWarning.cmake b/Tests/RunCMake/PackageInfo/ExperimentalWarning.cmake new file mode 100644 index 0000000..df6604c --- /dev/null +++ b/Tests/RunCMake/PackageInfo/ExperimentalWarning.cmake @@ -0,0 +1,8 @@ +set( + CMAKE_EXPERIMENTAL_EXPORT_PACKAGE_INFO + "b80be207-778e-46ba-8080-b23bba22639e" + ) + +add_library(foo INTERFACE) +install(TARGETS foo EXPORT foo DESTINATION .) +install(PACKAGE_INFO foo DESTINATION cps EXPORT foo) diff --git a/Tests/RunCMake/PackageInfo/InterfaceProperties-check.cmake b/Tests/RunCMake/PackageInfo/InterfaceProperties-check.cmake new file mode 100644 index 0000000..2c3272e --- /dev/null +++ b/Tests/RunCMake/PackageInfo/InterfaceProperties-check.cmake @@ -0,0 +1,24 @@ +include(${CMAKE_CURRENT_LIST_DIR}/Assertions.cmake) + +set(out_dir "${RunCMake_BINARY_DIR}/InterfaceProperties-build/CMakeFiles/Export/510c5684a4a8a792eadfb55bc9744983") + +file(READ "${out_dir}/foo.cps" content) +expect_value("${content}" "foo" "name") + +string(JSON component GET "${content}" "components" "foo") + +expect_value("${component}" "interface" "type") +expect_array("${component}" 1 "includes") +expect_value("${component}" "@prefix@/include/foo" "includes" 0) +expect_array("${component}" 1 "compile_features") +expect_value("${component}" "c++23" "compile_features" 0) +expect_array("${component}" 1 "compile_flags") +expect_value("${component}" "-ffast-math" "compile_flags" 0) +expect_null("${component}" "compile_definitions" "*" "FOO") +expect_value("${component}" "BAR" "compile_definitions" "*" "BAR") +expect_array("${component}" 1 "link_directories") +expect_value("${component}" "/opt/foo/lib" "link_directories" 0) +expect_array("${component}" 1 "link_flags") +expect_value("${component}" "--needed" "link_flags" 0) +expect_array("${component}" 1 "link_libraries") +expect_value("${component}" "/usr/lib/libm.so" "link_libraries" 0) diff --git a/Tests/RunCMake/PackageInfo/InterfaceProperties.cmake b/Tests/RunCMake/PackageInfo/InterfaceProperties.cmake new file mode 100644 index 0000000..42edc21 --- /dev/null +++ b/Tests/RunCMake/PackageInfo/InterfaceProperties.cmake @@ -0,0 +1,15 @@ +add_library(foo INTERFACE) + +target_compile_features(foo INTERFACE cxx_std_23) +target_compile_options(foo INTERFACE -ffast-math) +target_compile_definitions(foo INTERFACE -DFOO -DBAR=BAR) +target_include_directories( + foo INTERFACE + $ + ) +target_link_directories(foo INTERFACE /opt/foo/lib) +target_link_options(foo INTERFACE --needed) +target_link_libraries(foo INTERFACE /usr/lib/libm.so) + +install(TARGETS foo EXPORT foo DESTINATION .) +install(PACKAGE_INFO foo DESTINATION cps EXPORT foo) diff --git a/Tests/RunCMake/PackageInfo/LowerCaseFile-check.cmake b/Tests/RunCMake/PackageInfo/LowerCaseFile-check.cmake new file mode 100644 index 0000000..d8de372 --- /dev/null +++ b/Tests/RunCMake/PackageInfo/LowerCaseFile-check.cmake @@ -0,0 +1,9 @@ +include(${CMAKE_CURRENT_LIST_DIR}/Assertions.cmake) + +set(out_dir "${RunCMake_BINARY_DIR}/LowerCaseFile-build/CMakeFiles/Export/510c5684a4a8a792eadfb55bc9744983") + +file(READ "${out_dir}/lowercase.cps" content) +expect_value("${content}" "LowerCase" "name") + +file(READ "${out_dir}/PreserveCase.cps" content) +expect_value("${content}" "PreserveCase" "name") diff --git a/Tests/RunCMake/PackageInfo/LowerCaseFile.cmake b/Tests/RunCMake/PackageInfo/LowerCaseFile.cmake new file mode 100644 index 0000000..dc6827f --- /dev/null +++ b/Tests/RunCMake/PackageInfo/LowerCaseFile.cmake @@ -0,0 +1,4 @@ +add_library(foo INTERFACE) +install(TARGETS foo EXPORT foo DESTINATION .) +install(PACKAGE_INFO LowerCase DESTINATION cps EXPORT foo LOWER_CASE_FILE) +install(PACKAGE_INFO PreserveCase DESTINATION cps EXPORT foo) diff --git a/Tests/RunCMake/PackageInfo/Metadata-check.cmake b/Tests/RunCMake/PackageInfo/Metadata-check.cmake new file mode 100644 index 0000000..8db8c29 --- /dev/null +++ b/Tests/RunCMake/PackageInfo/Metadata-check.cmake @@ -0,0 +1,16 @@ +include(${CMAKE_CURRENT_LIST_DIR}/Assertions.cmake) + +set(out_dir "${RunCMake_BINARY_DIR}/Metadata-build/CMakeFiles/Export/510c5684a4a8a792eadfb55bc9744983") + +file(READ "${out_dir}/foo.cps" content) +expect_value("${content}" "foo" "name") +expect_value("${content}" "1.2.3" "version") +expect_value("${content}" "1.2.0" "compat_version") +expect_value("${content}" "simple" "version_schema") + +expect_array("${content}" 1 "default_components") +expect_value("${content}" "foo" "default_components" 0) + +expect_array("${content}" 2 "configurations") +expect_value("${content}" "release" "configurations" 0) +expect_value("${content}" "debug" "configurations" 1) diff --git a/Tests/RunCMake/PackageInfo/Metadata.cmake b/Tests/RunCMake/PackageInfo/Metadata.cmake new file mode 100644 index 0000000..f8fc9b8 --- /dev/null +++ b/Tests/RunCMake/PackageInfo/Metadata.cmake @@ -0,0 +1,12 @@ +add_library(foo INTERFACE) +install(TARGETS foo EXPORT foo DESTINATION .) +install( + PACKAGE_INFO foo + DESTINATION cps + EXPORT foo + VERSION 1.2.3 + VERSION_SCHEMA simple + COMPAT_VERSION 1.2.0 + DEFAULT_TARGETS foo + DEFAULT_CONFIGURATIONS release debug + ) diff --git a/Tests/RunCMake/PackageInfo/Minimal-check.cmake b/Tests/RunCMake/PackageInfo/Minimal-check.cmake new file mode 100644 index 0000000..9608ed8 --- /dev/null +++ b/Tests/RunCMake/PackageInfo/Minimal-check.cmake @@ -0,0 +1,18 @@ +include(${CMAKE_CURRENT_LIST_DIR}/Assertions.cmake) + +set(out_dir "${RunCMake_BINARY_DIR}/Minimal-build/CMakeFiles/Export/510c5684a4a8a792eadfb55bc9744983") + +file(READ "${out_dir}/foo.cps" content) +expect_value("${content}" "foo" "name") +expect_value("${content}" "interface" "components" "foo" "type") +expect_missing("${content}" "version") +expect_missing("${content}" "configurations") +expect_missing("${content}" "default_targets") +expect_missing("${content}" "components" "foo" "compile_definitions") +expect_missing("${content}" "components" "foo" "compile_features") +expect_missing("${content}" "components" "foo" "compile_flags") +expect_missing("${content}" "components" "foo" "link_directories") +expect_missing("${content}" "components" "foo" "link_features") +expect_missing("${content}" "components" "foo" "link_flags") +expect_missing("${content}" "components" "foo" "link_libraries") +expect_missing("${content}" "components" "foo" "requires") diff --git a/Tests/RunCMake/PackageInfo/Minimal.cmake b/Tests/RunCMake/PackageInfo/Minimal.cmake new file mode 100644 index 0000000..6c060b9 --- /dev/null +++ b/Tests/RunCMake/PackageInfo/Minimal.cmake @@ -0,0 +1,3 @@ +add_library(foo INTERFACE) +install(TARGETS foo EXPORT foo DESTINATION .) +install(PACKAGE_INFO foo DESTINATION cps EXPORT foo) diff --git a/Tests/RunCMake/PackageInfo/ReferencesNonExportedTarget-result.txt b/Tests/RunCMake/PackageInfo/ReferencesNonExportedTarget-result.txt new file mode 100644 index 0000000..d00491f --- /dev/null +++ b/Tests/RunCMake/PackageInfo/ReferencesNonExportedTarget-result.txt @@ -0,0 +1 @@ +1 diff --git a/Tests/RunCMake/PackageInfo/ReferencesNonExportedTarget-stderr.txt b/Tests/RunCMake/PackageInfo/ReferencesNonExportedTarget-stderr.txt new file mode 100644 index 0000000..c68d518 --- /dev/null +++ b/Tests/RunCMake/PackageInfo/ReferencesNonExportedTarget-stderr.txt @@ -0,0 +1 @@ +CMake Error: install\(PACKAGE_INFO "dog" \.\.\.\) includes target "canine" which requires target "mammal" that is not in any export set. diff --git a/Tests/RunCMake/PackageInfo/ReferencesNonExportedTarget.cmake b/Tests/RunCMake/PackageInfo/ReferencesNonExportedTarget.cmake new file mode 100644 index 0000000..a835582 --- /dev/null +++ b/Tests/RunCMake/PackageInfo/ReferencesNonExportedTarget.cmake @@ -0,0 +1,6 @@ +add_library(mammal INTERFACE) +add_library(canine INTERFACE) +target_link_libraries(canine INTERFACE mammal) + +install(TARGETS canine EXPORT dog DESTINATION .) +install(PACKAGE_INFO dog EXPORT dog) diff --git a/Tests/RunCMake/PackageInfo/ReferencesWronglyExportedTarget-result.txt b/Tests/RunCMake/PackageInfo/ReferencesWronglyExportedTarget-result.txt new file mode 100644 index 0000000..d00491f --- /dev/null +++ b/Tests/RunCMake/PackageInfo/ReferencesWronglyExportedTarget-result.txt @@ -0,0 +1 @@ +1 diff --git a/Tests/RunCMake/PackageInfo/ReferencesWronglyExportedTarget-stderr.txt b/Tests/RunCMake/PackageInfo/ReferencesWronglyExportedTarget-stderr.txt new file mode 100644 index 0000000..0a74e18 --- /dev/null +++ b/Tests/RunCMake/PackageInfo/ReferencesWronglyExportedTarget-stderr.txt @@ -0,0 +1,7 @@ +CMake Error in CMakeLists.txt: + Target "test" references target "foo", which does not use the standard + namespace separator. This is not allowed. +.* +CMake Error in CMakeLists.txt: + Target "test" references target "bar_bar", which does not use the standard + namespace separator. This is not allowed. diff --git a/Tests/RunCMake/PackageInfo/ReferencesWronglyExportedTarget.cmake b/Tests/RunCMake/PackageInfo/ReferencesWronglyExportedTarget.cmake new file mode 100644 index 0000000..3e3d21d --- /dev/null +++ b/Tests/RunCMake/PackageInfo/ReferencesWronglyExportedTarget.cmake @@ -0,0 +1,14 @@ +add_library(foo INTERFACE) +add_library(bar INTERFACE) + +add_library(test INTERFACE) +target_link_libraries(test INTERFACE foo bar) + +install(TARGETS foo EXPORT foo DESTINATION .) +install(TARGETS bar EXPORT bar DESTINATION .) + +install(EXPORT foo DESTINATION .) +install(EXPORT bar DESTINATION . NAMESPACE bar_) + +install(TARGETS test EXPORT test DESTINATION .) +install(PACKAGE_INFO test EXPORT test) diff --git a/Tests/RunCMake/PackageInfo/ReferencesWronglyImportedTarget-result.txt b/Tests/RunCMake/PackageInfo/ReferencesWronglyImportedTarget-result.txt new file mode 100644 index 0000000..d00491f --- /dev/null +++ b/Tests/RunCMake/PackageInfo/ReferencesWronglyImportedTarget-result.txt @@ -0,0 +1 @@ +1 diff --git a/Tests/RunCMake/PackageInfo/ReferencesWronglyImportedTarget-stderr.txt b/Tests/RunCMake/PackageInfo/ReferencesWronglyImportedTarget-stderr.txt new file mode 100644 index 0000000..cc4e824 --- /dev/null +++ b/Tests/RunCMake/PackageInfo/ReferencesWronglyImportedTarget-stderr.txt @@ -0,0 +1,3 @@ +CMake Error in CMakeLists.txt: + Target "foo" references imported target "bar" which does not come from any + known package. diff --git a/Tests/RunCMake/PackageInfo/ReferencesWronglyImportedTarget.cmake b/Tests/RunCMake/PackageInfo/ReferencesWronglyImportedTarget.cmake new file mode 100644 index 0000000..8addd64 --- /dev/null +++ b/Tests/RunCMake/PackageInfo/ReferencesWronglyImportedTarget.cmake @@ -0,0 +1,7 @@ +add_library(bar INTERFACE IMPORTED) + +add_library(foo INTERFACE) +target_link_libraries(foo INTERFACE bar) + +install(TARGETS foo EXPORT foo DESTINATION .) +install(PACKAGE_INFO foo EXPORT foo) diff --git a/Tests/RunCMake/PackageInfo/ReferencesWronglyNamespacedTarget-result.txt b/Tests/RunCMake/PackageInfo/ReferencesWronglyNamespacedTarget-result.txt new file mode 100644 index 0000000..d00491f --- /dev/null +++ b/Tests/RunCMake/PackageInfo/ReferencesWronglyNamespacedTarget-result.txt @@ -0,0 +1 @@ +1 diff --git a/Tests/RunCMake/PackageInfo/ReferencesWronglyNamespacedTarget-stderr.txt b/Tests/RunCMake/PackageInfo/ReferencesWronglyNamespacedTarget-stderr.txt new file mode 100644 index 0000000..0a6872e --- /dev/null +++ b/Tests/RunCMake/PackageInfo/ReferencesWronglyNamespacedTarget-stderr.txt @@ -0,0 +1,4 @@ +CMake Error in CMakeLists.txt: + Target "foo" references target "wrong::lib", which comes from the "broken" + package, but does not belong to the package's canonical namespace. This is + not allowed. diff --git a/Tests/RunCMake/PackageInfo/ReferencesWronglyNamespacedTarget.cmake b/Tests/RunCMake/PackageInfo/ReferencesWronglyNamespacedTarget.cmake new file mode 100644 index 0000000..7a4f9c0 --- /dev/null +++ b/Tests/RunCMake/PackageInfo/ReferencesWronglyNamespacedTarget.cmake @@ -0,0 +1,11 @@ +find_package( + broken REQUIRED CONFIG + NO_DEFAULT_PATH + PATHS ${CMAKE_CURRENT_LIST_DIR} + ) + +add_library(foo INTERFACE) +target_link_libraries(foo INTERFACE wrong::lib) + +install(TARGETS foo EXPORT foo DESTINATION .) +install(PACKAGE_INFO foo EXPORT foo) diff --git a/Tests/RunCMake/PackageInfo/Requirements-check.cmake b/Tests/RunCMake/PackageInfo/Requirements-check.cmake new file mode 100644 index 0000000..59a212f --- /dev/null +++ b/Tests/RunCMake/PackageInfo/Requirements-check.cmake @@ -0,0 +1,20 @@ +include(${CMAKE_CURRENT_LIST_DIR}/Assertions.cmake) + +set(out_dir "${RunCMake_BINARY_DIR}/Requirements-build/CMakeFiles/Export/510c5684a4a8a792eadfb55bc9744983") + +file(READ "${out_dir}/foo.cps" content) +expect_value("${content}" "foo" "name") +expect_value("${content}" "interface" "components" "libb" "type") + +file(READ "${out_dir}/bar.cps" content) +expect_value("${content}" "bar" "name") +expect_null("${content}" "requires" "foo") +expect_null("${content}" "requires" "test") +expect_value("${content}" "interface" "components" "libc" "type") +expect_value("${content}" "interface" "components" "libd" "type") + +string(JSON component GET "${content}" "components" "libd") +expect_array("${component}" 3 "requires") +expect_value("${component}" "test:liba" "requires" 0) +expect_value("${component}" "foo:libb" "requires" 1) +expect_value("${component}" ":libc" "requires" 2) diff --git a/Tests/RunCMake/PackageInfo/Requirements.cmake b/Tests/RunCMake/PackageInfo/Requirements.cmake new file mode 100644 index 0000000..a4e947c --- /dev/null +++ b/Tests/RunCMake/PackageInfo/Requirements.cmake @@ -0,0 +1,20 @@ +find_package( + test REQUIRED CONFIG + NO_DEFAULT_PATH + PATHS ${CMAKE_CURRENT_LIST_DIR} + ) + +add_library(libb INTERFACE) +add_library(libc INTERFACE) +add_library(libd INTERFACE) + +add_library(foo ALIAS libb) +add_library(bar ALIAS libc) + +target_link_libraries(libd INTERFACE test::liba foo bar) + +install(TARGETS libb EXPORT foo DESTINATION .) +install(PACKAGE_INFO foo DESTINATION cps EXPORT foo) + +install(TARGETS libc libd EXPORT bar DESTINATION .) +install(PACKAGE_INFO bar DESTINATION cps EXPORT bar) diff --git a/Tests/RunCMake/PackageInfo/RunCMakeTest.cmake b/Tests/RunCMake/PackageInfo/RunCMakeTest.cmake new file mode 100644 index 0000000..54a32d5 --- /dev/null +++ b/Tests/RunCMake/PackageInfo/RunCMakeTest.cmake @@ -0,0 +1,32 @@ +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" + ) + +# Test incorrect usage +run_cmake(BadArgs1) +run_cmake(BadArgs2) +run_cmake(BadArgs3) +run_cmake(BadArgs4) +run_cmake(BadArgs5) +run_cmake(BadDefaultTarget) +run_cmake(ReferencesNonExportedTarget) +run_cmake(ReferencesWronglyExportedTarget) +run_cmake(ReferencesWronglyImportedTarget) +run_cmake(ReferencesWronglyNamespacedTarget) + +# Test functionality +run_cmake(Appendix) +run_cmake(InterfaceProperties) +run_cmake(Metadata) +run_cmake(Minimal) +run_cmake(LowerCaseFile) +run_cmake(Requirements) +run_cmake(TargetTypes) diff --git a/Tests/RunCMake/PackageInfo/TargetTypes-check.cmake b/Tests/RunCMake/PackageInfo/TargetTypes-check.cmake new file mode 100644 index 0000000..34ca5ab --- /dev/null +++ b/Tests/RunCMake/PackageInfo/TargetTypes-check.cmake @@ -0,0 +1,11 @@ +include(${CMAKE_CURRENT_LIST_DIR}/Assertions.cmake) + +set(out_dir "${RunCMake_BINARY_DIR}/TargetTypes-build/CMakeFiles/Export/510c5684a4a8a792eadfb55bc9744983") + +file(READ "${out_dir}/foo.cps" content) +expect_value("${content}" "foo" "name") +expect_value("${content}" "archive" "components" "foo-static" "type") +expect_value("${content}" "dylib" "components" "foo-shared" "type") +expect_value("${content}" "module" "components" "foo-module" "type") +expect_value("${content}" "interface" "components" "bar" "type") +expect_value("${content}" "executable" "components" "test" "type") diff --git a/Tests/RunCMake/PackageInfo/TargetTypes.cmake b/Tests/RunCMake/PackageInfo/TargetTypes.cmake new file mode 100644 index 0000000..755c3a3 --- /dev/null +++ b/Tests/RunCMake/PackageInfo/TargetTypes.cmake @@ -0,0 +1,20 @@ +project(TargetTypes CXX) + +add_library(foo-static STATIC foo.cxx) +add_library(foo-shared SHARED foo.cxx) +add_library(foo-module MODULE foo.cxx) +add_library(bar INTERFACE) +add_executable(test test.cxx) + +install( + TARGETS + foo-static + foo-shared + foo-module + bar + test + EXPORT foo + DESTINATION . + ) + +install(PACKAGE_INFO foo DESTINATION cps EXPORT foo) diff --git a/Tests/RunCMake/PackageInfo/broken-config.cmake b/Tests/RunCMake/PackageInfo/broken-config.cmake new file mode 100644 index 0000000..09e40df --- /dev/null +++ b/Tests/RunCMake/PackageInfo/broken-config.cmake @@ -0,0 +1 @@ +add_library(wrong::lib INTERFACE IMPORTED) diff --git a/Tests/RunCMake/PackageInfo/foo.cxx b/Tests/RunCMake/PackageInfo/foo.cxx new file mode 100644 index 0000000..3695dc9 --- /dev/null +++ b/Tests/RunCMake/PackageInfo/foo.cxx @@ -0,0 +1,3 @@ +void foo() +{ +} diff --git a/Tests/RunCMake/PackageInfo/test-config.cmake b/Tests/RunCMake/PackageInfo/test-config.cmake new file mode 100644 index 0000000..3c46ea6 --- /dev/null +++ b/Tests/RunCMake/PackageInfo/test-config.cmake @@ -0,0 +1 @@ +add_library(test::liba INTERFACE IMPORTED) diff --git a/Tests/RunCMake/PackageInfo/test.cxx b/Tests/RunCMake/PackageInfo/test.cxx new file mode 100644 index 0000000..f8b643a --- /dev/null +++ b/Tests/RunCMake/PackageInfo/test.cxx @@ -0,0 +1,4 @@ +int main() +{ + return 0; +} -- cgit v0.12 From b2dc1bd1498dc47fa1d611813c6983921eb6a71b Mon Sep 17 00:00:00 2001 From: Matthew Woehlke Date: Wed, 21 Aug 2024 14:46:42 -0400 Subject: CMake: Suppress IBM XL optimization warning `xlC` now warns "Additional optimization may be attained by recompiling and specifying MAXMEM option" on `cmExportPackageInfoGenerator`. Add the suggested option. --- CompileFlags.cmake | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/CompileFlags.cmake b/CompileFlags.cmake index f94e079..3080daa 100644 --- a/CompileFlags.cmake +++ b/CompileFlags.cmake @@ -37,7 +37,13 @@ elseif(_CLANG_MSVC_WINDOWS AND "x${CMAKE_CXX_COMPILER_FRONTEND_VARIANT}" STREQUA string(APPEND CMAKE_EXE_LINKER_FLAGS " -Xlinker -stack:20000000") endif() -#silence duplicate symbol warnings on AIX +# Silence "Additional optimization may be attained by recompiling and +# specifying MAXMEM option" warning on XLC (AIX) +if(CMAKE_CXX_COMPILER_ID MATCHES "^(XL|XLClang)$") + string(APPEND CMAKE_CXX_FLAGS " -qmaxmem=-1") +endif() + +# Silence duplicate symbol warnings on AIX if(CMAKE_SYSTEM_NAME MATCHES "AIX") if(NOT CMAKE_COMPILER_IS_GNUCXX) set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -bhalt:5 ") -- cgit v0.12