diff --git a/Source/cmCMakePkgConfigCommand.cxx b/Source/cmCMakePkgConfigCommand.cxx index e6b1a52e7f..9d35cabd03 100644 --- a/Source/cmCMakePkgConfigCommand.cxx +++ b/Source/cmCMakePkgConfigCommand.cxx @@ -861,6 +861,7 @@ cmTarget* CreateCMakeTarget(std::string const& name, std::string const& prefix, { auto* tgt = mf.AddForeignTarget("pkgcfg", cmStrCat(prefix, name)); + tgt->SetForeignPackageName(name); tgt->AppendProperty("VERSION", pkg.Version()); auto libs = pkg.Libs(); @@ -1097,11 +1098,89 @@ bool HandleImportCommand(std::vector const& args, mf.AddImportedTarget(local_name, cm::TargetType::INTERFACE_LIBRARY, cm::ImportedTargetScope::Local); tgt->AppendProperty("INTERFACE_LINK_LIBRARIES", foreign_name); + if (auto* ft = mf.FindTargetToUse(foreign_name, cm::TargetDomain::FOREIGN)) { + tgt->SetForeignTarget(ft); + } + return true; +} + +bool PkgConfigImportPackageImpl(cmMakefile& mf, cmExecutionStatus& status, + std::string const& name) +{ + std::string const prefix; + + // A pkg-config package is referenced in CPS as ":" and linked + // directly as the foreign target which provides it, so there is no + // interface target to create here. + auto const foreignName = cmStrCat("@foreign_pkgcfg::", prefix, name); + if (mf.FindTargetToUse(foreignName, cm::TargetDomain::FOREIGN)) { + return true; + } + + cmPkgConfigEnv pcEnv; + CollectEnv(mf, pcEnv, CommonArguments::EnvModeType::ENVMODE_PKGCONF); + if (pcEnv.Path) { + pcEnv.search = *pcEnv.Path; + if (pcEnv.LibDirs) { + pcEnv.search += *pcEnv.LibDirs; + } + } else if (pcEnv.LibDirs) { + pcEnv.search = *pcEnv.LibDirs; + } + + ImportEnv imEnv{ false, true, false, + CommonArguments::StrictnessType::STRICTNESS_PERMISSIVE }; + ImportState state{ status, false }; + + cm::optional maybePackage = + ImportPackage(name, cm::optional{}, imEnv, pcEnv, state); + if (!maybePackage) { + return false; + } + + pkgProviders providers; + pkgStack inStack; + std::unordered_map outStack; + + ImportEnv depEnv = imEnv; + depEnv.exact = false; + + if (!CheckPackageDependencies(name, prefix, *maybePackage, inStack, outStack, + providers, depEnv, state)) { + return false; + } + outStack.emplace(name, std::move(*maybePackage)); + + while (!inStack.empty()) { + auto depName = inStack.begin()->first; + auto reqs = inStack.begin()->second; + maybePackage = ImportPackage(depName, reqs, depEnv, pcEnv, state); + if (!maybePackage) { + return false; + } + if (!CheckPackageDependencies(depName, prefix, *maybePackage, inStack, + outStack, providers, depEnv, state)) { + return false; + } + inStack.erase(depName); + outStack.emplace(std::move(depName), std::move(*maybePackage)); + } + + for (auto& entry : outStack) { + CreateCMakeTarget(entry.first, prefix, entry.second, providers, mf); + } + return true; } } // namespace +bool cmImportPkgConfigPackage(cmMakefile& mf, cmExecutionStatus& status, + std::string const& name) +{ + return PkgConfigImportPackageImpl(mf, status, name); +} + bool cmCMakePkgConfigCommand(std::vector const& args, cmExecutionStatus& status) { diff --git a/Source/cmCMakePkgConfigCommand.h b/Source/cmCMakePkgConfigCommand.h index 49ae19b901..7360620ca5 100644 --- a/Source/cmCMakePkgConfigCommand.h +++ b/Source/cmCMakePkgConfigCommand.h @@ -8,6 +8,10 @@ #include class cmExecutionStatus; +class cmMakefile; bool cmCMakePkgConfigCommand(std::vector const& args, cmExecutionStatus& status); + +bool cmImportPkgConfigPackage(cmMakefile& mf, cmExecutionStatus& status, + std::string const& name); diff --git a/Source/cmExportPackageInfoGenerator.cxx b/Source/cmExportPackageInfoGenerator.cxx index e994eb7a77..6c76a5849a 100644 --- a/Source/cmExportPackageInfoGenerator.cxx +++ b/Source/cmExportPackageInfoGenerator.cxx @@ -228,6 +228,19 @@ void cmExportPackageInfoGenerator::GeneratePackageRequires( data["version"] = *requirement.second.Version; } + if (!requirement.second.Domains.empty()) { + auto requirementDomains = Json::Value{ Json::arrayValue }; + for (cm::PackageDomain domain : requirement.second.Domains) { + if (domain == cm::PackageDomain::PkgConfig) { + requirementDomains.append("pkg-config"); + } + } + if (!requirementDomains.empty()) { + data["extensions"]["cmake"]["domains@v1"] = + std::move(requirementDomains); + } + } + requirements[requirement.first] = data; } } @@ -358,6 +371,25 @@ bool cmExportPackageInfoGenerator::NoteLinkedTarget( return true; } + auto getRequirement = + [&](std::string const& name, + cmPackageInformation&& requirement) -> cmPackageInformation* { + // emplace may move from requirement even when the key already exists. + auto const domains = requirement.Domains; + auto record = this->Requirements.emplace(name, std::move(requirement)); + if (!record.second) { + if (record.first->second.Domains != domains) { + target->Makefile->IssueMessage( + MessageType::FATAL_ERROR, + cmStrCat("Package \"", this->GetPackageName(), + "\" requires package \"", name, + "\" in multiple domain sets. This is not supported.")); + return nullptr; + } + } + return &record.first->second; + }; + if (linkedTarget->IsImported()) { // Target is imported from a found package. using Package = cm::optional>; @@ -379,6 +411,26 @@ bool cmExportPackageInfoGenerator::NoteLinkedTarget( }(linkedTarget->Target); if (!pkgInfo) { + if (cmTarget* foreignTarget = linkedTarget->Target->GetForeignTarget()) { + cmPackageInformation reqInit; + reqInit.Domains = { cm::PackageDomain::PkgConfig }; + + std::string foreignPkgName = foreignTarget->GetForeignPackageName(); + cmPackageInformation* req = + getRequirement(foreignPkgName, std::move(reqInit)); + if (!req) { + return false; + } + + // A pkg-config package has no components of its own; expose it as a + // component named after the package itself. + this->LinkTargets.emplace( + linkedName, cmStrCat(foreignPkgName, ':', foreignPkgName)); + + req->Components.emplace(std::move(foreignPkgName)); + return true; + } + target->Makefile->IssueMessage( MessageType::FATAL_ERROR, cmStrCat("Target \"", target->GetName(), @@ -402,10 +454,13 @@ bool cmExportPackageInfoGenerator::NoteLinkedTarget( } std::string component = linkedName.substr(prefix.length()); + cmPackageInformation* req = + getRequirement(pkgName, std::move(pkgInfo->second)); + if (!req) { + return false; + } this->LinkTargets.emplace(linkedName, cmStrCat(pkgName, ':', component)); - cmPackageInformation& req = - this->Requirements.insert(std::move(*pkgInfo)).first->second; - req.Components.emplace(std::move(component)); + req->Components.emplace(std::move(component)); return true; } @@ -428,8 +483,12 @@ bool cmExportPackageInfoGenerator::NoteLinkedTarget( if (pkgName == this->GetPackageName()) { this->LinkTargets.emplace(linkedName, cmStrCat(':', component)); } else { + cmPackageInformation* req = getRequirement(pkgName, {}); + if (!req) { + return false; + } this->LinkTargets.emplace(linkedName, cmStrCat(pkgName, ':', component)); - this->Requirements[pkgName].Components.emplace(std::move(component)); + req->Components.emplace(std::move(component)); } return true; } diff --git a/Source/cmFindPackageCommand.cxx b/Source/cmFindPackageCommand.cxx index 2f7e489d8e..52b66d8060 100644 --- a/Source/cmFindPackageCommand.cxx +++ b/Source/cmFindPackageCommand.cxx @@ -24,6 +24,7 @@ #include "cmsys/String.h" #include "cmAlgorithms.h" +#include "cmCMakePkgConfigCommand.h" #include "cmConfigureLog.h" #include "cmDependencyProvider.h" #include "cmDiagnostics.h" @@ -2193,8 +2194,17 @@ bool cmFindPackageCommand::FindPackageDependencies( std::string const& filePath, cmPackageInfoReader const& reader, RequiredStatus required) { - // Get package requirements. - for (cmPackageRequirement const& dep : reader.GetRequirements()) { + for (auto const& dep : reader.GetRequirements()) { + // Try domain-based resolution first. +#ifndef CMAKE_BOOTSTRAP + if (cm::contains(dep.Domains, cm::PackageDomain::PkgConfig)) { + cmExecutionStatus status{ *this->Makefile }; + if (cmImportPkgConfigPackage(*this->Makefile, status, dep.Name)) { + continue; + } + } +#endif + cmExecutionStatus status{ *this->Makefile }; cmMakefile::CallRAII scope{ this->Makefile, filePath, status }; diff --git a/Source/cmFindPackageStack.h b/Source/cmFindPackageStack.h index e3ac8ca78e..f32f274a72 100644 --- a/Source/cmFindPackageStack.h +++ b/Source/cmFindPackageStack.h @@ -7,11 +7,16 @@ #include #include #include +#include #include #include "cmConstStack.h" +namespace cm { +enum class PackageDomain; +} + /** * This data represents the actual contents of find_package * -Config.cmake or .cps file, and not what is passed @@ -28,6 +33,7 @@ public: cm::optional Website; cm::optional PackageUrl; std::set Components; + std::vector Domains; }; /** diff --git a/Source/cmMakefile.cxx b/Source/cmMakefile.cxx index db21efce2e..66bc595ad1 100644 --- a/Source/cmMakefile.cxx +++ b/Source/cmMakefile.cxx @@ -4084,6 +4084,13 @@ cmTarget* cmMakefile::AddForeignTarget(std::string const& origin, return this->ImportedTargetsOwned.back().get(); } +cmTarget* cmMakefile::FindForeignTarget(std::string const& origin, + std::string const& name) const +{ + return this->FindTargetToUse(cmStrCat("@foreign_", origin, "::", name), + cm::TargetDomain::FOREIGN); +} + cmTarget* cmMakefile::FindTargetToUse(std::string const& name, cm::TargetDomainSet domains) const { diff --git a/Source/cmMakefile.h b/Source/cmMakefile.h index a7b0953690..6ba6ef7910 100644 --- a/Source/cmMakefile.h +++ b/Source/cmMakefile.h @@ -297,6 +297,10 @@ public: cmTarget* AddForeignTarget(std::string const& origin, std::string const& name); + /** Find a foreign target previously created by AddForeignTarget. */ + cmTarget* FindForeignTarget(std::string const& origin, + std::string const& name) const; + std::pair CreateNewTarget( std::string const& name, cm::TargetType type, cmTarget::PerConfig perConfig = cmTarget::PerConfig::Yes, diff --git a/Source/cmPackageInfoReader.cxx b/Source/cmPackageInfoReader.cxx index abc89d701a..9538057747 100644 --- a/Source/cmPackageInfoReader.cxx +++ b/Source/cmPackageInfoReader.cxx @@ -509,6 +509,7 @@ std::unique_ptr cmPackageInfoReader::Read( if (parent) { reader->ComponentTargets = parent->ComponentTargets; reader->DefaultConfigurations = parent->DefaultConfigurations; + reader->RequirementDomains = parent->RequirementDomains; } else { for (std::string const& config : ReadList(reader->Data, "configurations")) { @@ -517,6 +518,12 @@ std::unique_ptr cmPackageInfoReader::Read( } } + // Read package-level requirements. (Configuration supplements are not + // allowed to declare package requirements.) + if (cmSystemTools::GetFilenameName(path).find('@') == std::string::npos) { + reader->ReadRequirements(); + } + // Check for a default license. Json::Value const& defaultLicense = reader->Data["default_license"]; if (!defaultLicense.isNull()) { @@ -590,24 +597,56 @@ cmPackageInfoReader::ParseVersion( return cm::nullopt; } -std::vector cmPackageInfoReader::GetRequirements() const +void cmPackageInfoReader::ReadRequirements() { - std::vector requirements; - auto const& requirementObjects = this->Data["requires"]; if (!requirementObjects.isObject()) { - return {}; + return; } for (auto ri = requirementObjects.begin(), re = requirementObjects.end(); ri != re; ++ri) { - cmPackageRequirement r{ ri.name(), ToString((*ri)["version"]), + cmPackageRequirement r{ ri.name(), + ToString((*ri)["version"]), ReadList(*ri, "components"), - ReadList(*ri, "hints") }; - requirements.emplace_back(std::move(r)); + ReadList(*ri, "hints"), + {} }; + for (std::string const& domain : + ReadList(GetExtensions(*ri), "domains@v1")) { + r.Domains.push_back(domain == "pkg-config" ? cm::PackageDomain::PkgConfig + : cm::PackageDomain::Unknown); + } + if (!r.Domains.empty()) { + this->RequirementDomains[r.Name] = r.Domains; + } else { + // A local declaration without domains clears any inherited domains. + this->RequirementDomains.erase(r.Name); + } + this->Requirements.emplace_back(std::move(r)); + } +} + +std::string cmPackageInfoReader::ResolveTargetName( + cmMakefile* makefile, std::string const& name, + std::string const& context) const +{ + // A pkg-config package is exposed as a single component of the same name. + // Only resolve it to a foreign target if this requirement opts in. + std::string::size_type const n = name.find(':'); + if (n != std::string::npos && n > 0 && + name.substr(0, n) == name.substr(n + 1)) { + std::string const package = name.substr(0, n); + auto const di = this->RequirementDomains.find(package); + if (di != this->RequirementDomains.end() && + cm::contains(di->second, cm::PackageDomain::PkgConfig)) { + if (cmTarget* foreignTarget = + makefile->FindForeignTarget("pkgcfg", package)) { + return foreignTarget->GetName(); + } + } } - return requirements; + return NormalizeTargetName(name, context); } std::vector cmPackageInfoReader::GetComponentNames() const @@ -794,24 +833,26 @@ void cmPackageInfoReader::SetTargetProperties( // Add transitive dependencies. for (std::string const& dep : ReadList(data, "requires")) { AppendProperty(makefile, target, "LINK_LIBRARIES"_s, configuration, - NormalizeTargetName(dep, package)); + this->ResolveTargetName(makefile, dep, package)); } for (std::string const& dep : ReadList(data, "compile_requires")) { std::string const& lib = - cmStrCat("$'); + cmStrCat("$ResolveTargetName(makefile, dep, package), '>'); AppendProperty(makefile, target, "LINK_LIBRARIES"_s, configuration, lib); } for (std::string const& dep : ReadList(data, "link_requires")) { - std::string const& lib = - cmStrCat("$'); + std::string const& lib = cmStrCat( + "$ResolveTargetName(makefile, dep, package), '>'); AppendProperty(makefile, target, "LINK_LIBRARIES"_s, configuration, lib); } for (std::string const& dep : ReadList(data, "dyld_requires")) { AppendImportProperty(makefile, target, "LINK_DEPENDENT_LIBRARIES"_s, - configuration, NormalizeTargetName(dep, package)); + configuration, + this->ResolveTargetName(makefile, dep, package)); } for (std::string const& lib : ReadList(data, "link_libraries")) { diff --git a/Source/cmPackageInfoReader.h b/Source/cmPackageInfoReader.h index c66ad82e5d..68864cdadc 100644 --- a/Source/cmPackageInfoReader.h +++ b/Source/cmPackageInfoReader.h @@ -20,6 +20,7 @@ class cmTarget; namespace cm { enum class ImportedTargetScope; +enum class PackageDomain; enum class TargetType; } // namespace cm @@ -29,6 +30,7 @@ struct cmPackageRequirement std::string Version; std::vector Components; std::vector Hints; + std::vector Domains; }; /** \class cmPackageInfoReader @@ -76,7 +78,11 @@ public: cm::optional ParseVersion( cm::optional const& version) const; - std::vector GetRequirements() const; + /// Requirements declared by this file, excluding inherited requirements. + std::vector const& GetRequirements() const + { + return this->Requirements; + } std::vector GetComponentNames() const; /// Create targets for components specified in the CPS file. @@ -118,10 +124,16 @@ private: Json::Value const& object) const; std::string ResolvePath(std::string path) const; + std::string ResolveTargetName(cmMakefile* makefile, std::string const& name, + std::string const& context) const; + + void ReadRequirements(); std::string Path; Json::Value Data; std::string Prefix; + std::vector Requirements; + std::map> RequirementDomains; std::map ComponentTargets; std::vector DefaultConfigurations; diff --git a/Source/cmTarget.cxx b/Source/cmTarget.cxx index 76e9d2f9a3..93e7c8fc71 100644 --- a/Source/cmTarget.cxx +++ b/Source/cmTarget.cxx @@ -648,6 +648,8 @@ public: bool IsExportPassthrough = false; bool CxxModuleNeedsInterfaceObjects = false; cmTarget::Visibility TargetVisibility; + std::string ForeignPackageName; + cmTarget* ForeignTarget = nullptr; std::set>> Utilities; std::set CodegenDependencies; std::vector PreBuildCommands; @@ -3339,6 +3341,26 @@ std::string cmTarget::ImportedGetFullPath( return result; } +void cmTarget::SetForeignPackageName(std::string const& name) +{ + this->impl->ForeignPackageName = name; +} + +std::string const& cmTarget::GetForeignPackageName() const +{ + return this->impl->ForeignPackageName; +} + +void cmTarget::SetForeignTarget(cmTarget* target) +{ + this->impl->ForeignTarget = target; +} + +cmTarget* cmTarget::GetForeignTarget() const +{ + return this->impl->ForeignTarget; +} + cmFileSet const* cmTarget::GetFileSet(std::string const& name) const { auto it = this->impl->FileSets.find(name); diff --git a/Source/cmTarget.h b/Source/cmTarget.h index 9d022bf85d..f8d0c5e4bf 100644 --- a/Source/cmTarget.h +++ b/Source/cmTarget.h @@ -289,6 +289,11 @@ public: bool HasKnownObjectFileLocation(std::string* reason = nullptr) const; + void SetForeignPackageName(std::string const& name); + std::string const& GetForeignPackageName() const; + void SetForeignTarget(cmTarget* target); + cmTarget* GetForeignTarget() const; + //! Get a backtrace from the creation of the target. cmListFileBacktrace const& GetBacktrace() const; diff --git a/Source/cmTargetTypes.h b/Source/cmTargetTypes.h index da1633d4cd..5a7ef2c649 100644 --- a/Source/cmTargetTypes.h +++ b/Source/cmTargetTypes.h @@ -46,4 +46,11 @@ enum class ImportedTargetScope Local, Global, }; + +enum class PackageDomain +{ + Unknown, + Default, + PkgConfig, +}; }; diff --git a/Tests/FindPackageCpsTest/CMakeLists.txt b/Tests/FindPackageCpsTest/CMakeLists.txt index 94f959cc6c..65b26dc892 100644 --- a/Tests/FindPackageCpsTest/CMakeLists.txt +++ b/Tests/FindPackageCpsTest/CMakeLists.txt @@ -457,3 +457,51 @@ else() "include set 'bar' files" "${CMAKE_SOURCE_DIR}/include/bar.h") endif() + +############################################################################### +# Test CPS + pkg-config interoperability. + +set(ENV{PKG_CONFIG_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/pc") +find_package(pc-link-test) +if(NOT pc-link-test_FOUND) + message(SEND_ERROR "pc-link-test not found !") +elseif(NOT TARGET pc-link-test::Parent) + message(SEND_ERROR "pc-link-test::Parent missing !") +elseif(NOT TARGET pc-link-test::InheritsRequirement) + message(SEND_ERROR "pc-link-test::InheritsRequirement missing !") +elseif(NOT TARGET pc-link-test::ShadowsRequirement) + message(SEND_ERROR "pc-link-test::ShadowsRequirement missing !") +elseif(NOT TARGET pc-link-test::UnknownDomain) + message(SEND_ERROR "pc-link-test::UnknownDomain missing !") +endif() + +expect_property(pc-link-test::Parent INTERFACE_LINK_LIBRARIES + "link libraries (from pkg-config dependency)" + "@foreign_pkgcfg::cps-pkg-test" + "$<$:$>" + "$<$:$>") +expect_property(pc-link-test::Parent IMPORTED_LINK_DEPENDENT_LIBRARIES_DEBUG + "dyld requirements (from pkg-config dependency)" + "@foreign_pkgcfg::cps-pkg-test") + +# Appendices inherit base requirement domains and keep additional requirements +# local. Configuration supplements inherit both levels of requirement domains. +# Their package-level requires blocks are ignored, including attempted +# overrides of the inherited pkg-config domains. +expect_property(pc-link-test::InheritsRequirement INTERFACE_LINK_LIBRARIES + "link libraries (from inherited and local dependencies)" + "@foreign_pkgcfg::cps-pkg-test" + "@foreign_pkgcfg::cps-pkg-appendix" + "$<$:@foreign_pkgcfg::cps-pkg-test>" + "$<$:@foreign_pkgcfg::cps-pkg-appendix>") +expect_property(pc-link-test::ShadowsRequirement INTERFACE_LINK_LIBRARIES + "link libraries (from shadowing dependency)" + "cps-pkg-test::cps-pkg-test" + "$" + "$") +expect_property(pc-link-test::ShadowsRequirement IMPORTED_LINK_DEPENDENT_LIBRARIES + "dyld requirements (from shadowing dependency)" + "cps-pkg-test::cps-pkg-test") +expect_property(pc-link-test::UnknownDomain INTERFACE_LINK_LIBRARIES + "link libraries (from unknown domain dependency)" + "cps-pkg-test::cps-pkg-test") diff --git a/Tests/FindPackageCpsTest/cps/cps-pkg-test.cps b/Tests/FindPackageCpsTest/cps/cps-pkg-test.cps new file mode 100644 index 0000000000..2e8f2f0f55 --- /dev/null +++ b/Tests/FindPackageCpsTest/cps/cps-pkg-test.cps @@ -0,0 +1,10 @@ +{ + "cps_version": "0.15.0", + "name": "cps-pkg-test", + "cps_path": "@prefix@/cps", + "components": { + "cps-pkg-test": { + "type": "interface" + } + } +} diff --git a/Tests/FindPackageCpsTest/cps/pc-link-test-inherit.cps b/Tests/FindPackageCpsTest/cps/pc-link-test-inherit.cps new file mode 100644 index 0000000000..83df377c21 --- /dev/null +++ b/Tests/FindPackageCpsTest/cps/pc-link-test-inherit.cps @@ -0,0 +1,19 @@ +{ + "name": "pc-link-test", + "requires": { + "cps-pkg-appendix": { + "components": ["cps-pkg-appendix"], + "extensions": { + "cmake": { + "domains@v1": ["pkg-config"] + } + } + } + }, + "components": { + "InheritsRequirement": { + "type": "interface", + "requires": ["cps-pkg-test:cps-pkg-test", "cps-pkg-appendix:cps-pkg-appendix"] + } + } +} diff --git a/Tests/FindPackageCpsTest/cps/pc-link-test-inherit@debug.cps b/Tests/FindPackageCpsTest/cps/pc-link-test-inherit@debug.cps new file mode 100644 index 0000000000..5835cddf65 --- /dev/null +++ b/Tests/FindPackageCpsTest/cps/pc-link-test-inherit@debug.cps @@ -0,0 +1,14 @@ +{ + "name": "pc-link-test", + "configuration": "debug", + "requires": { + "cps-pkg-test": {}, + "cps-pkg-appendix": {}, + "cps-pkg-unavailable": {} + }, + "components": { + "InheritsRequirement": { + "requires": ["cps-pkg-test:cps-pkg-test", "cps-pkg-appendix:cps-pkg-appendix"] + } + } +} diff --git a/Tests/FindPackageCpsTest/cps/pc-link-test-shadow.cps b/Tests/FindPackageCpsTest/cps/pc-link-test-shadow.cps new file mode 100644 index 0000000000..f9608eb06c --- /dev/null +++ b/Tests/FindPackageCpsTest/cps/pc-link-test-shadow.cps @@ -0,0 +1,17 @@ +{ + "name": "pc-link-test", + "requires": { + "cps-pkg-test": { + "components": ["cps-pkg-test"] + } + }, + "components": { + "ShadowsRequirement": { + "type": "interface", + "requires": ["cps-pkg-test:cps-pkg-test"], + "compile_requires": ["cps-pkg-test:cps-pkg-test"], + "link_requires": ["cps-pkg-test:cps-pkg-test"], + "dyld_requires": ["cps-pkg-test:cps-pkg-test"] + } + } +} diff --git a/Tests/FindPackageCpsTest/cps/pc-link-test-unknown-domain.cps b/Tests/FindPackageCpsTest/cps/pc-link-test-unknown-domain.cps new file mode 100644 index 0000000000..dbc7a3aeb0 --- /dev/null +++ b/Tests/FindPackageCpsTest/cps/pc-link-test-unknown-domain.cps @@ -0,0 +1,19 @@ +{ + "name": "pc-link-test", + "requires": { + "cps-pkg-test": { + "components": ["cps-pkg-test"], + "extensions": { + "cmake": { + "domains@v1": ["unrecognized-domain"] + } + } + } + }, + "components": { + "UnknownDomain": { + "type": "interface", + "requires": ["cps-pkg-test:cps-pkg-test"] + } + } +} diff --git a/Tests/FindPackageCpsTest/cps/pc-link-test.cps b/Tests/FindPackageCpsTest/cps/pc-link-test.cps new file mode 100644 index 0000000000..47c85739e4 --- /dev/null +++ b/Tests/FindPackageCpsTest/cps/pc-link-test.cps @@ -0,0 +1,21 @@ +{ + "cps_version": "0.15.0", + "name": "pc-link-test", + "cps_path": "@prefix@/cps", + "components": { + "Parent": { + "type": "interface", + "requires": ["cps-pkg-test:cps-pkg-test"] + } + }, + "requires": { + "cps-pkg-test": { + "components": ["cps-pkg-test"], + "extensions": { + "cmake": { + "domains@v1": ["unrecognized-domain", "pkg-config"] + } + } + } + } +} diff --git a/Tests/FindPackageCpsTest/cps/pc-link-test@debug.cps b/Tests/FindPackageCpsTest/cps/pc-link-test@debug.cps new file mode 100644 index 0000000000..6028ba415f --- /dev/null +++ b/Tests/FindPackageCpsTest/cps/pc-link-test@debug.cps @@ -0,0 +1,15 @@ +{ + "name": "pc-link-test", + "configuration": "debug", + "requires": { + "cps-pkg-test": {}, + "cps-pkg-unavailable": {} + }, + "components": { + "Parent": { + "compile_requires": ["cps-pkg-test:cps-pkg-test"], + "link_requires": ["cps-pkg-test:cps-pkg-test"], + "dyld_requires": ["cps-pkg-test:cps-pkg-test"] + } + } +} diff --git a/Tests/FindPackageCpsTest/pc/cps-pkg-appendix.pc b/Tests/FindPackageCpsTest/pc/cps-pkg-appendix.pc new file mode 100644 index 0000000000..701fa88b5c --- /dev/null +++ b/Tests/FindPackageCpsTest/pc/cps-pkg-appendix.pc @@ -0,0 +1,5 @@ +Name: cps-pkg-appendix +Description: Dummy package for an appendix-local CPS requirement +Version: 1.0 +Libs: -L/dummy +Cflags: diff --git a/Tests/FindPackageCpsTest/pc/cps-pkg-test.pc b/Tests/FindPackageCpsTest/pc/cps-pkg-test.pc new file mode 100644 index 0000000000..1d56cd23dd --- /dev/null +++ b/Tests/FindPackageCpsTest/pc/cps-pkg-test.pc @@ -0,0 +1,5 @@ +Name: cps-pkg-test +Description: Dummy package for CPS + pkg-config interoperability test +Version: 1.0 +Libs: -L/dummy +Cflags: diff --git a/Tests/RunCMake/ExportPackageInfo/PkgConfig-check.cmake b/Tests/RunCMake/ExportPackageInfo/PkgConfig-check.cmake new file mode 100644 index 0000000000..ecfe49f08b --- /dev/null +++ b/Tests/RunCMake/ExportPackageInfo/PkgConfig-check.cmake @@ -0,0 +1,14 @@ +include(${CMAKE_CURRENT_LIST_DIR}/Assertions.cmake) + +set(out_dir "${RunCMake_BINARY_DIR}/PkgConfig-build") + +file(READ "${out_dir}/cps/foo/foo.cps" content) +expect_value("${content}" "foo" "name") +expect_array("${content}" 1 "requires" "cps-pkg-test" "components") +expect_value("${content}" "cps-pkg-test" "requires" "cps-pkg-test" "components" 0) +expect_array("${content}" 1 "requires" "cps-pkg-test" "extensions" "cmake" "domains@v1") +expect_value("${content}" "pkg-config" "requires" "cps-pkg-test" "extensions" "cmake" "domains@v1" 0) + +string(JSON component GET "${content}" "components" "foo") +expect_array("${component}" 1 "requires") +expect_value("${component}" "cps-pkg-test:cps-pkg-test" "requires" 0) diff --git a/Tests/RunCMake/ExportPackageInfo/PkgConfig.cmake b/Tests/RunCMake/ExportPackageInfo/PkgConfig.cmake new file mode 100644 index 0000000000..32a59c7b14 --- /dev/null +++ b/Tests/RunCMake/ExportPackageInfo/PkgConfig.cmake @@ -0,0 +1,13 @@ +set(ENV{PKG_CONFIG_PATH} "${CMAKE_CURRENT_LIST_DIR}/pc") + +cmake_pkg_config(IMPORT cps-pkg-test REQUIRED) + +add_library(foo INTERFACE) +target_link_libraries(foo INTERFACE PkgConfig::cps-pkg-test) + +# Multiple components may require the same package in the same domain. +add_library(bar INTERFACE) +target_link_libraries(bar INTERFACE PkgConfig::cps-pkg-test) + +install(TARGETS foo bar EXPORT foo DESTINATION .) +export(PACKAGE_INFO foo EXPORT foo) diff --git a/Tests/RunCMake/ExportPackageInfo/PkgConfigCollision-result.txt b/Tests/RunCMake/ExportPackageInfo/PkgConfigCollision-result.txt new file mode 100644 index 0000000000..d00491fd7e --- /dev/null +++ b/Tests/RunCMake/ExportPackageInfo/PkgConfigCollision-result.txt @@ -0,0 +1 @@ +1 diff --git a/Tests/RunCMake/ExportPackageInfo/PkgConfigCollision-stderr.txt b/Tests/RunCMake/ExportPackageInfo/PkgConfigCollision-stderr.txt new file mode 100644 index 0000000000..8963f8868f --- /dev/null +++ b/Tests/RunCMake/ExportPackageInfo/PkgConfigCollision-stderr.txt @@ -0,0 +1,3 @@ +CMake Error in CMakeLists.txt: + Package "consumer" requires package "cps-pkg-test" in multiple domain sets\. + This is not supported\. diff --git a/Tests/RunCMake/ExportPackageInfo/PkgConfigCollision.cmake b/Tests/RunCMake/ExportPackageInfo/PkgConfigCollision.cmake new file mode 100644 index 0000000000..a9289781c0 --- /dev/null +++ b/Tests/RunCMake/ExportPackageInfo/PkgConfigCollision.cmake @@ -0,0 +1,25 @@ +set(ENV{PKG_CONFIG_PATH} "${CMAKE_CURRENT_LIST_DIR}/pc") +cmake_pkg_config(IMPORT cps-pkg-test REQUIRED) + +if(imported) + add_library(cps-pkg-test::bar INTERFACE IMPORTED) + set_property(TARGET cps-pkg-test::bar PROPERTY + EXPORT_FIND_PACKAGE_NAME cps-pkg-test) +else() + add_library(bar INTERFACE) + add_library(cps-pkg-test::bar ALIAS bar) + install(TARGETS bar EXPORT dependency) + export(PACKAGE_INFO cps-pkg-test EXPORT dependency) +endif() + +add_library(consumer INTERFACE) +if(pkg_config_first) + target_link_libraries(consumer INTERFACE + PkgConfig::cps-pkg-test cps-pkg-test::bar) +else() + target_link_libraries(consumer INTERFACE + cps-pkg-test::bar PkgConfig::cps-pkg-test) +endif() + +install(TARGETS consumer EXPORT consumer) +export(PACKAGE_INFO consumer EXPORT consumer) diff --git a/Tests/RunCMake/ExportPackageInfo/RunCMakeTest.cmake b/Tests/RunCMake/ExportPackageInfo/RunCMakeTest.cmake index 16242ca8a6..3b5a848f6e 100644 --- a/Tests/RunCMake/ExportPackageInfo/RunCMakeTest.cmake +++ b/Tests/RunCMake/ExportPackageInfo/RunCMakeTest.cmake @@ -17,6 +17,16 @@ run_cmake(DependsMultipleDifferentSets) run_cmake(LinkInterfaceGeneratorExpression) run_cmake(CompileOnlyRecursive) run_cmake(LinkOnlyRecursive) +foreach(imported IN ITEMS ON OFF) + foreach(pkg_config_first IN ITEMS ON OFF) + set(RunCMake_TEST_VARIANT_DESCRIPTION + " (imported=${imported}, pkg_config_first=${pkg_config_first})") + run_cmake_with_options(PkgConfigCollision + -Dimported=${imported} + -Dpkg_config_first=${pkg_config_first}) + endforeach() +endforeach() +unset(RunCMake_TEST_VARIANT_DESCRIPTION) # Test functionality run_cmake(Appendix) @@ -28,6 +38,7 @@ run_cmake(Minimal) run_cmake(MinimalVersion) run_cmake(LowerCaseFile) run_cmake(Requirements) +run_cmake(PkgConfig) run_cmake(LinkDependentLibraries) run_cmake(PrivateLinkDependency) run_cmake(ExportSymbolicComponent) diff --git a/Tests/RunCMake/ExportPackageInfo/pc/cps-pkg-test.pc b/Tests/RunCMake/ExportPackageInfo/pc/cps-pkg-test.pc new file mode 100644 index 0000000000..e20fb31bd5 --- /dev/null +++ b/Tests/RunCMake/ExportPackageInfo/pc/cps-pkg-test.pc @@ -0,0 +1,5 @@ +Name: cps-pkg-test +Description: Dummy package for CPS + pkg-config interoperability export test +Version: 1.0 +Libs: -L/dummy +Cflags: diff --git a/Tests/RunCMake/InstallPackageInfo/PkgConfig-check.cmake b/Tests/RunCMake/InstallPackageInfo/PkgConfig-check.cmake new file mode 100644 index 0000000000..153e91fed1 --- /dev/null +++ b/Tests/RunCMake/InstallPackageInfo/PkgConfig-check.cmake @@ -0,0 +1,16 @@ +include(${CMAKE_CURRENT_LIST_DIR}/Assertions.cmake) + +set(out_dir "${RunCMake_BINARY_DIR}/PkgConfig-build/CMakeFiles/Export/510c5684a4a8a792eadfb55bc9744983") + +file(READ "${out_dir}/foo.cps" content) +expect_value("${content}" "foo" "name") +expect_array("${content}" 1 "requires" "cps-pkg-test" "components") +expect_value("${content}" "cps-pkg-test" "requires" "cps-pkg-test" "components" 0) +expect_array("${content}" 1 "requires" "cps-pkg-test" "extensions" "cmake" "domains@v1") +expect_value("${content}" "pkg-config" "requires" "cps-pkg-test" "extensions" "cmake" "domains@v1" 0) + +foreach(name IN ITEMS foo bar) + string(JSON component GET "${content}" "components" "${name}") + expect_array("${component}" 1 "requires") + expect_value("${component}" "cps-pkg-test:cps-pkg-test" "requires" 0) +endforeach() diff --git a/Tests/RunCMake/InstallPackageInfo/PkgConfig.cmake b/Tests/RunCMake/InstallPackageInfo/PkgConfig.cmake new file mode 100644 index 0000000000..032266abd2 --- /dev/null +++ b/Tests/RunCMake/InstallPackageInfo/PkgConfig.cmake @@ -0,0 +1,13 @@ +set(ENV{PKG_CONFIG_PATH} "${CMAKE_CURRENT_LIST_DIR}/pc") + +cmake_pkg_config(IMPORT cps-pkg-test REQUIRED) + +add_library(foo INTERFACE) +target_link_libraries(foo INTERFACE PkgConfig::cps-pkg-test) + +# Multiple components may require the same package in the same domain. +add_library(bar INTERFACE) +target_link_libraries(bar INTERFACE PkgConfig::cps-pkg-test) + +install(TARGETS foo bar EXPORT foo DESTINATION .) +install(PACKAGE_INFO foo EXPORT foo DESTINATION cps) diff --git a/Tests/RunCMake/InstallPackageInfo/PkgConfigCollision-result.txt b/Tests/RunCMake/InstallPackageInfo/PkgConfigCollision-result.txt new file mode 100644 index 0000000000..d00491fd7e --- /dev/null +++ b/Tests/RunCMake/InstallPackageInfo/PkgConfigCollision-result.txt @@ -0,0 +1 @@ +1 diff --git a/Tests/RunCMake/InstallPackageInfo/PkgConfigCollision-stderr.txt b/Tests/RunCMake/InstallPackageInfo/PkgConfigCollision-stderr.txt new file mode 100644 index 0000000000..8963f8868f --- /dev/null +++ b/Tests/RunCMake/InstallPackageInfo/PkgConfigCollision-stderr.txt @@ -0,0 +1,3 @@ +CMake Error in CMakeLists.txt: + Package "consumer" requires package "cps-pkg-test" in multiple domain sets\. + This is not supported\. diff --git a/Tests/RunCMake/InstallPackageInfo/PkgConfigCollision.cmake b/Tests/RunCMake/InstallPackageInfo/PkgConfigCollision.cmake new file mode 100644 index 0000000000..634204c98b --- /dev/null +++ b/Tests/RunCMake/InstallPackageInfo/PkgConfigCollision.cmake @@ -0,0 +1,25 @@ +set(ENV{PKG_CONFIG_PATH} "${CMAKE_CURRENT_LIST_DIR}/pc") +cmake_pkg_config(IMPORT cps-pkg-test REQUIRED) + +if(imported) + add_library(cps-pkg-test::bar INTERFACE IMPORTED) + set_property(TARGET cps-pkg-test::bar PROPERTY + EXPORT_FIND_PACKAGE_NAME cps-pkg-test) +else() + add_library(bar INTERFACE) + add_library(cps-pkg-test::bar ALIAS bar) + install(TARGETS bar EXPORT dependency) + install(PACKAGE_INFO cps-pkg-test EXPORT dependency DESTINATION cps) +endif() + +add_library(consumer INTERFACE) +if(pkg_config_first) + target_link_libraries(consumer INTERFACE + PkgConfig::cps-pkg-test cps-pkg-test::bar) +else() + target_link_libraries(consumer INTERFACE + cps-pkg-test::bar PkgConfig::cps-pkg-test) +endif() + +install(TARGETS consumer EXPORT consumer) +install(PACKAGE_INFO consumer EXPORT consumer DESTINATION cps) diff --git a/Tests/RunCMake/InstallPackageInfo/RunCMakeTest.cmake b/Tests/RunCMake/InstallPackageInfo/RunCMakeTest.cmake index c25c6292ca..6e3b993d0f 100644 --- a/Tests/RunCMake/InstallPackageInfo/RunCMakeTest.cmake +++ b/Tests/RunCMake/InstallPackageInfo/RunCMakeTest.cmake @@ -26,6 +26,16 @@ run_cmake(ReferencesWronglyImportedTarget) run_cmake(ReferencesWronglyNamespacedTarget) run_cmake(DependsMultipleDifferentNamespace) run_cmake(DependsMultipleDifferentSets) +foreach(imported IN ITEMS ON OFF) + foreach(pkg_config_first IN ITEMS ON OFF) + set(RunCMake_TEST_VARIANT_DESCRIPTION + " (imported=${imported}, pkg_config_first=${pkg_config_first})") + run_cmake_with_options(PkgConfigCollision + -Dimported=${imported} + -Dpkg_config_first=${pkg_config_first}) + endforeach() +endforeach() +unset(RunCMake_TEST_VARIANT_DESCRIPTION) # Test functionality run_cmake(Appendix) @@ -38,6 +48,7 @@ run_cmake(Minimal) run_cmake(MinimalVersion) run_cmake(LowerCaseFile) run_cmake(Requirements) +run_cmake(PkgConfig) run_cmake(PrivateLinkDependency) run_cmake(TargetTypes) run_cmake(DependsMultiple) diff --git a/Tests/RunCMake/InstallPackageInfo/pc/cps-pkg-test.pc b/Tests/RunCMake/InstallPackageInfo/pc/cps-pkg-test.pc new file mode 100644 index 0000000000..06a2f851f5 --- /dev/null +++ b/Tests/RunCMake/InstallPackageInfo/pc/cps-pkg-test.pc @@ -0,0 +1,5 @@ +Name: cps-pkg-test +Description: Dummy package for CPS + pkg-config interoperability install test +Version: 1.0 +Libs: -L/dummy +Cflags: