cmExportPackageInfoGenerator.cxx 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614
  1. /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
  2. file LICENSE.rst or https://cmake.org/licensing for details. */
  3. #include "cmExportPackageInfoGenerator.h"
  4. #include <cstddef>
  5. #include <memory>
  6. #include <set>
  7. #include <utility>
  8. #include <vector>
  9. #include <cm/optional>
  10. #include <cm/string_view>
  11. #include <cmext/algorithm>
  12. #include <cmext/string_view>
  13. #include <cm3p/json/value.h>
  14. #include <cm3p/json/writer.h>
  15. #include "cmsys/RegularExpression.hxx"
  16. #include "cmArgumentParserTypes.h"
  17. #include "cmExportSet.h"
  18. #include "cmFindPackageStack.h"
  19. #include "cmGeneratorExpression.h"
  20. #include "cmGeneratorTarget.h"
  21. #include "cmList.h"
  22. #include "cmMakefile.h"
  23. #include "cmMessageType.h"
  24. #include "cmPackageInfoArguments.h"
  25. #include "cmStringAlgorithms.h"
  26. #include "cmSystemTools.h"
  27. #include "cmTarget.h"
  28. static std::string const kCPS_VERSION_STR = "0.13.0";
  29. cmExportPackageInfoGenerator::cmExportPackageInfoGenerator(
  30. cmPackageInfoArguments arguments)
  31. : PackageName(std::move(arguments.PackageName))
  32. , PackageVersion(std::move(arguments.Version))
  33. , PackageVersionCompat(std::move(arguments.VersionCompat))
  34. , PackageVersionSchema(std::move(arguments.VersionSchema))
  35. , PackageDescription(std::move(arguments.Description))
  36. , PackageWebsite(std::move(arguments.Website))
  37. , PackageLicense(std::move(arguments.License))
  38. , DefaultLicense(std::move(arguments.DefaultLicense))
  39. , DefaultTargets(std::move(arguments.DefaultTargets))
  40. , DefaultConfigurations(std::move(arguments.DefaultConfigs))
  41. {
  42. }
  43. cm::string_view cmExportPackageInfoGenerator::GetImportPrefixWithSlash() const
  44. {
  45. return "@prefix@/"_s;
  46. }
  47. bool cmExportPackageInfoGenerator::GenerateImportFile(std::ostream& os)
  48. {
  49. return this->GenerateMainFile(os);
  50. }
  51. void cmExportPackageInfoGenerator::WritePackageInfo(
  52. Json::Value const& packageInfo, std::ostream& os) const
  53. {
  54. Json::StreamWriterBuilder builder;
  55. builder["indentation"] = " ";
  56. builder["commentStyle"] = "None";
  57. std::unique_ptr<Json::StreamWriter> const writer(builder.newStreamWriter());
  58. writer->write(packageInfo, &os);
  59. }
  60. namespace {
  61. bool SetProperty(Json::Value& object, std::string const& property,
  62. std::string const& value)
  63. {
  64. if (!value.empty()) {
  65. object[property] = value;
  66. return true;
  67. }
  68. return false;
  69. }
  70. template <typename T>
  71. void BuildArray(Json::Value& object, std::string const& property,
  72. T const& values)
  73. {
  74. if (!values.empty()) {
  75. Json::Value& array = object[property];
  76. for (auto const& item : values) {
  77. array.append(item);
  78. }
  79. }
  80. }
  81. bool CheckSimpleVersion(std::string const& version)
  82. {
  83. cmsys::RegularExpression regex("^[0-9]+([.][0-9]+)*([-+].*)?$");
  84. return regex.find(version);
  85. }
  86. }
  87. bool cmExportPackageInfoGenerator::CheckVersion() const
  88. {
  89. if (!this->PackageVersion.empty()) {
  90. std::string const& schema = [&] {
  91. if (this->PackageVersionSchema.empty()) {
  92. return std::string{ "simple" };
  93. }
  94. return cmSystemTools::LowerCase(this->PackageVersionSchema);
  95. }();
  96. bool (*validator)(std::string const&) = nullptr;
  97. bool result = true;
  98. if (schema == "simple"_s) {
  99. validator = &CheckSimpleVersion;
  100. } else if (schema == "dpkg"_s || schema == "rpm"_s ||
  101. schema == "pep440"_s) {
  102. // TODO
  103. // We don't validate these at this time. Eventually, we would like to do
  104. // so, but will probably need to introduce a policy whether to treat
  105. // invalid versions as an error.
  106. } else if (schema != "custom"_s) {
  107. this->IssueMessage(MessageType::AUTHOR_WARNING,
  108. cmStrCat("Package \""_s, this->GetPackageName(),
  109. "\" uses unrecognized version schema \""_s,
  110. this->PackageVersionSchema, "\"."_s));
  111. }
  112. if (validator) {
  113. if (!(*validator)(this->PackageVersion)) {
  114. this->ReportError(cmStrCat("Package \""_s, this->GetPackageName(),
  115. "\" version \""_s, this->PackageVersion,
  116. "\" does not conform to the \""_s, schema,
  117. "\" schema."_s));
  118. result = false;
  119. }
  120. if (!this->PackageVersionCompat.empty() &&
  121. !(*validator)(this->PackageVersionCompat)) {
  122. this->ReportError(
  123. cmStrCat("Package \""_s, this->GetPackageName(),
  124. "\" compatibility version \""_s, this->PackageVersionCompat,
  125. "\" does not conform to the \""_s, schema, "\" schema."_s));
  126. result = false;
  127. }
  128. }
  129. return result;
  130. }
  131. return true;
  132. }
  133. bool cmExportPackageInfoGenerator::CheckDefaultTargets() const
  134. {
  135. bool result = true;
  136. std::set<std::string> exportedTargetNames;
  137. for (auto const* te : this->ExportedTargets) {
  138. exportedTargetNames.emplace(te->GetExportName());
  139. }
  140. for (auto const& name : this->DefaultTargets) {
  141. if (!cm::contains(exportedTargetNames, name)) {
  142. this->ReportError(
  143. cmStrCat("Package \"", this->GetPackageName(),
  144. "\" specifies DEFAULT_TARGETS \"", name,
  145. "\", which is not a target in the export set \"",
  146. this->GetExportSet()->GetName(), "\"."));
  147. result = false;
  148. }
  149. }
  150. return result;
  151. }
  152. Json::Value cmExportPackageInfoGenerator::GeneratePackageInfo() const
  153. {
  154. Json::Value package;
  155. package["name"] = this->GetPackageName();
  156. package["cps_version"] = std::string(kCPS_VERSION_STR);
  157. if (SetProperty(package, "version", this->PackageVersion)) {
  158. SetProperty(package, "compat_version", this->PackageVersionCompat);
  159. SetProperty(package, "version_schema", this->PackageVersionSchema);
  160. }
  161. BuildArray(package, "default_components", this->DefaultTargets);
  162. BuildArray(package, "configurations", this->DefaultConfigurations);
  163. SetProperty(package, "description", this->PackageDescription);
  164. SetProperty(package, "website", this->PackageWebsite);
  165. SetProperty(package, "license", this->PackageLicense);
  166. SetProperty(package, "default_license", this->DefaultLicense);
  167. return package;
  168. }
  169. void cmExportPackageInfoGenerator::GeneratePackageRequires(
  170. Json::Value& package) const
  171. {
  172. if (!this->Requirements.empty()) {
  173. Json::Value& requirements = package["requires"];
  174. // Build description for each requirement.
  175. for (auto const& requirement : this->Requirements) {
  176. auto data = Json::Value{ Json::objectValue };
  177. // Add required components.
  178. if (!requirement.second.Components.empty()) {
  179. auto components = Json::Value{ Json::arrayValue };
  180. for (std::string const& component : requirement.second.Components) {
  181. components.append(component);
  182. }
  183. data["components"] = components;
  184. }
  185. // Add additional dependency information.
  186. if (requirement.second.Directory) {
  187. auto hints = Json::Value{ Json::arrayValue };
  188. hints.append(*requirement.second.Directory);
  189. data["hints"] = hints;
  190. }
  191. if (requirement.second.Version) {
  192. data["version"] = *requirement.second.Version;
  193. }
  194. requirements[requirement.first] = data;
  195. }
  196. }
  197. }
  198. Json::Value* cmExportPackageInfoGenerator::GenerateImportTarget(
  199. Json::Value& components, cmGeneratorTarget const* target,
  200. cmStateEnums::TargetType targetType) const
  201. {
  202. auto const& name = target->GetExportName();
  203. if (name.empty()) {
  204. return nullptr;
  205. }
  206. Json::Value& component = components[name];
  207. Json::Value& type = component["type"];
  208. switch (targetType) {
  209. case cmStateEnums::EXECUTABLE:
  210. type = "executable";
  211. break;
  212. case cmStateEnums::STATIC_LIBRARY:
  213. type = "archive";
  214. break;
  215. case cmStateEnums::SHARED_LIBRARY:
  216. type = "dylib";
  217. break;
  218. case cmStateEnums::MODULE_LIBRARY:
  219. type = "module";
  220. break;
  221. case cmStateEnums::INTERFACE_LIBRARY:
  222. type = target->IsSymbolic() ? "symbolic" : "interface";
  223. break;
  224. default:
  225. type = "unknown";
  226. break;
  227. }
  228. return &component;
  229. }
  230. bool cmExportPackageInfoGenerator::GenerateInterfaceProperties(
  231. Json::Value& component, cmGeneratorTarget const* target,
  232. ImportPropertyMap const& properties) const
  233. {
  234. bool result = true;
  235. this->GenerateInterfaceLinkProperties(result, component, target, properties);
  236. this->GenerateInterfaceCompileFeatures(result, component, target,
  237. properties);
  238. this->GenerateInterfaceCompileDefines(result, component, target, properties);
  239. this->GenerateInterfaceListProperty(result, component, target,
  240. "compile_flags", "COMPILE_OPTIONS"_s,
  241. properties);
  242. this->GenerateInterfaceListProperty(result, component, target, "link_flags",
  243. "LINK_OPTIONS"_s, properties);
  244. this->GenerateInterfaceListProperty(result, component, target,
  245. "link_directories", "LINK_DIRECTORIES"_s,
  246. properties);
  247. this->GenerateInterfaceListProperty(result, component, target, "includes",
  248. "INCLUDE_DIRECTORIES"_s, properties);
  249. this->GenerateProperty(result, component, target, "license", "SPDX_LICENSE",
  250. properties);
  251. // TODO: description
  252. return result;
  253. }
  254. namespace {
  255. bool ForbidGeneratorExpressions(
  256. cmGeneratorTarget const* target, std::string const& propertyName,
  257. std::string const& propertyValue, std::string& evaluatedValue,
  258. std::map<std::string, std::vector<std::string>>& allowList)
  259. {
  260. size_t const allowedExpressions = allowList.size();
  261. evaluatedValue = cmGeneratorExpression::Collect(propertyValue, allowList);
  262. if (evaluatedValue != propertyValue &&
  263. allowList.size() > allowedExpressions) {
  264. target->Makefile->IssueMessage(
  265. MessageType::FATAL_ERROR,
  266. cmStrCat("Property \"", propertyName, "\" of target \"",
  267. target->GetName(),
  268. "\" contains a generator expression. This is not allowed."));
  269. return false;
  270. }
  271. // Forbid Nested Generator Expressions
  272. for (auto const& genexp : allowList) {
  273. for (auto const& value : genexp.second) {
  274. if (value.find("$<") != std::string::npos) {
  275. target->Makefile->IssueMessage(
  276. MessageType::FATAL_ERROR,
  277. cmStrCat(
  278. "$<", genexp.first, ":...> expression in \"", propertyName,
  279. "\" of target \"", target->GetName(),
  280. "\" contains a generator expression. This is not allowed."));
  281. return false;
  282. }
  283. }
  284. }
  285. return true;
  286. }
  287. bool ForbidGeneratorExpressions(cmGeneratorTarget const* target,
  288. std::string const& propertyName,
  289. std::string const& propertyValue)
  290. {
  291. std::map<std::string, std::vector<std::string>> allowList;
  292. std::string evaluatedValue;
  293. return ForbidGeneratorExpressions(target, propertyName, propertyValue,
  294. evaluatedValue, allowList);
  295. }
  296. }
  297. bool cmExportPackageInfoGenerator::NoteLinkedTarget(
  298. cmGeneratorTarget const* target, std::string const& linkedName,
  299. cmGeneratorTarget const* linkedTarget)
  300. {
  301. if (cm::contains(this->ExportedTargets, linkedTarget)) {
  302. // Target is internal to this package.
  303. this->LinkTargets.emplace(linkedName,
  304. cmStrCat(':', linkedTarget->GetExportName()));
  305. return true;
  306. }
  307. if (linkedTarget->IsImported()) {
  308. // Target is imported from a found package.
  309. using Package = cm::optional<std::pair<std::string, cmPackageInformation>>;
  310. auto pkgInfo = [](cmTarget* t) -> Package {
  311. cmFindPackageStack pkgStack = t->GetFindPackageStack();
  312. if (!pkgStack.Empty()) {
  313. return std::make_pair(pkgStack.Top().Name, pkgStack.Top().PackageInfo);
  314. }
  315. cmPackageInformation package;
  316. std::string const pkgName =
  317. t->GetSafeProperty("EXPORT_FIND_PACKAGE_NAME");
  318. if (pkgName.empty()) {
  319. return cm::nullopt;
  320. }
  321. return std::make_pair(pkgName, package);
  322. }(linkedTarget->Target);
  323. if (!pkgInfo) {
  324. target->Makefile->IssueMessage(
  325. MessageType::FATAL_ERROR,
  326. cmStrCat("Target \"", target->GetName(),
  327. "\" references imported target \"", linkedName,
  328. "\" which does not come from any known package."));
  329. return false;
  330. }
  331. std::string const& pkgName = pkgInfo->first;
  332. auto const& prefix = cmStrCat(pkgName, "::");
  333. if (!cmHasPrefix(linkedName, prefix)) {
  334. target->Makefile->IssueMessage(
  335. MessageType::FATAL_ERROR,
  336. cmStrCat("Target \"", target->GetName(), "\" references target \"",
  337. linkedName, "\", which comes from the \"", pkgName,
  338. "\" package, but does not belong to the package's "
  339. "canonical namespace (\"",
  340. prefix, "\"). This is not allowed."));
  341. return false;
  342. }
  343. std::string component = linkedName.substr(prefix.length());
  344. this->LinkTargets.emplace(linkedName, cmStrCat(pkgName, ':', component));
  345. cmPackageInformation& req =
  346. this->Requirements.insert(std::move(*pkgInfo)).first->second;
  347. req.Components.emplace(std::move(component));
  348. return true;
  349. }
  350. // Target belongs to another export from this build.
  351. auto const& exportInfo = this->FindExportInfo(linkedTarget);
  352. if (exportInfo.Namespaces.size() == 1 && exportInfo.Sets.size() == 1) {
  353. auto const& linkNamespace = *exportInfo.Namespaces.begin();
  354. if (!cmHasSuffix(linkNamespace, "::")) {
  355. target->Makefile->IssueMessage(
  356. MessageType::FATAL_ERROR,
  357. cmStrCat("Target \"", target->GetName(), "\" references target \"",
  358. linkedName,
  359. "\", which does not use the standard namespace separator. "
  360. "This is not allowed."));
  361. return false;
  362. }
  363. std::string pkgName{ linkNamespace.data(), linkNamespace.size() - 2 };
  364. std::string component = linkedTarget->GetExportName();
  365. if (pkgName == this->GetPackageName()) {
  366. this->LinkTargets.emplace(linkedName, cmStrCat(':', component));
  367. } else {
  368. this->LinkTargets.emplace(linkedName, cmStrCat(pkgName, ':', component));
  369. this->Requirements[pkgName].Components.emplace(std::move(component));
  370. }
  371. return true;
  372. }
  373. // Target belongs to multiple namespaces or multiple export sets.
  374. // cmExportFileGenerator::HandleMissingTarget should have complained about
  375. // this already.
  376. return false;
  377. }
  378. void cmExportPackageInfoGenerator::GenerateInterfaceLinkProperties(
  379. bool& result, Json::Value& component, cmGeneratorTarget const* target,
  380. ImportPropertyMap const& properties) const
  381. {
  382. auto const& iter = properties.find("INTERFACE_LINK_LIBRARIES");
  383. if (iter == properties.end()) {
  384. return;
  385. }
  386. // Extract any $<LINK_ONLY:...> from the link libraries, and assert that no
  387. // other generator expressions are present.
  388. std::map<std::string, std::vector<std::string>> allowList = { { "LINK_ONLY",
  389. {} } };
  390. std::string interfaceLinkLibraries;
  391. if (!ForbidGeneratorExpressions(target, iter->first, iter->second,
  392. interfaceLinkLibraries, allowList)) {
  393. result = false;
  394. return;
  395. }
  396. std::vector<std::string> linkLibraries;
  397. std::vector<std::string> linkRequires;
  398. std::vector<std::string> buildRequires;
  399. auto addLibraries = [this, &linkLibraries,
  400. &result](std::vector<std::string> const& names,
  401. std::vector<std::string>& output) -> void {
  402. for (auto const& name : names) {
  403. auto const& ti = this->LinkTargets.find(name);
  404. if (ti != this->LinkTargets.end()) {
  405. if (ti->second.empty()) {
  406. result = false;
  407. } else {
  408. output.emplace_back(ti->second);
  409. }
  410. } else {
  411. linkLibraries.emplace_back(name);
  412. }
  413. }
  414. };
  415. addLibraries(allowList["LINK_ONLY"], linkRequires);
  416. addLibraries(cmList{ interfaceLinkLibraries }, buildRequires);
  417. BuildArray(component, "requires", buildRequires);
  418. BuildArray(component, "link_requires", linkRequires);
  419. BuildArray(component, "link_libraries", linkLibraries);
  420. }
  421. void cmExportPackageInfoGenerator::GenerateInterfaceCompileFeatures(
  422. bool& result, Json::Value& component, cmGeneratorTarget const* target,
  423. ImportPropertyMap const& properties) const
  424. {
  425. auto const& iter = properties.find("INTERFACE_COMPILE_FEATURES");
  426. if (iter == properties.end()) {
  427. return;
  428. }
  429. if (!ForbidGeneratorExpressions(target, iter->first, iter->second)) {
  430. result = false;
  431. return;
  432. }
  433. std::set<std::string> features;
  434. for (auto const& value : cmList{ iter->second }) {
  435. if (cmHasLiteralPrefix(value, "c_std_")) {
  436. auto suffix = cm::string_view{ value }.substr(6, 2);
  437. features.emplace(cmStrCat("cxx", suffix));
  438. } else if (cmHasLiteralPrefix(value, "cxx_std_")) {
  439. auto suffix = cm::string_view{ value }.substr(8, 2);
  440. features.emplace(cmStrCat("c++", suffix));
  441. }
  442. }
  443. BuildArray(component, "compile_features", features);
  444. }
  445. void cmExportPackageInfoGenerator::GenerateInterfaceCompileDefines(
  446. bool& result, Json::Value& component, cmGeneratorTarget const* target,
  447. ImportPropertyMap const& properties) const
  448. {
  449. auto const& iter = properties.find("INTERFACE_COMPILE_DEFINITIONS");
  450. if (iter == properties.end()) {
  451. return;
  452. }
  453. // TODO: Support language-specific defines.
  454. if (!ForbidGeneratorExpressions(target, iter->first, iter->second)) {
  455. result = false;
  456. return;
  457. }
  458. Json::Value defines;
  459. for (auto const& def : cmList{ iter->second }) {
  460. auto const n = def.find('=');
  461. if (n == std::string::npos) {
  462. defines[def] = Json::Value{};
  463. } else {
  464. defines[def.substr(0, n)] = def.substr(n + 1);
  465. }
  466. }
  467. if (!defines.empty()) {
  468. component["definitions"]["*"] = std::move(defines);
  469. }
  470. }
  471. void cmExportPackageInfoGenerator::GenerateInterfaceListProperty(
  472. bool& result, Json::Value& component, cmGeneratorTarget const* target,
  473. std::string const& outName, cm::string_view inName,
  474. ImportPropertyMap const& properties) const
  475. {
  476. auto const& prop = cmStrCat("INTERFACE_", inName);
  477. auto const& iter = properties.find(prop);
  478. if (iter == properties.end()) {
  479. return;
  480. }
  481. if (!ForbidGeneratorExpressions(target, prop, iter->second)) {
  482. result = false;
  483. return;
  484. }
  485. Json::Value& array = component[outName];
  486. for (auto const& value : cmList{ iter->second }) {
  487. array.append(value);
  488. }
  489. }
  490. void cmExportPackageInfoGenerator::GenerateProperty(
  491. bool& result, Json::Value& component, cmGeneratorTarget const* target,
  492. std::string const& outName, std::string const& inName,
  493. ImportPropertyMap const& properties) const
  494. {
  495. auto const& iter = properties.find(inName);
  496. if (iter == properties.end()) {
  497. return;
  498. }
  499. if (!ForbidGeneratorExpressions(target, inName, iter->second)) {
  500. result = false;
  501. return;
  502. }
  503. component[outName] = iter->second;
  504. }
  505. Json::Value cmExportPackageInfoGenerator::GenerateInterfaceConfigProperties(
  506. std::string const& suffix, ImportPropertyMap const& properties) const
  507. {
  508. Json::Value component;
  509. auto const suffixLength = suffix.length();
  510. for (auto const& p : properties) {
  511. if (!cmHasSuffix(p.first, suffix)) {
  512. continue;
  513. }
  514. auto const n = p.first.length() - suffixLength - 9;
  515. auto const prop = cm::string_view{ p.first }.substr(9, n);
  516. if (prop == "LOCATION") {
  517. component["location"] = p.second;
  518. } else if (prop == "IMPLIB") {
  519. component["link_location"] = p.second;
  520. } else if (prop == "LINK_INTERFACE_LANGUAGES") {
  521. std::vector<std::string> languages;
  522. for (auto const& lang : cmList{ p.second }) {
  523. auto ll = cmSystemTools::LowerCase(lang);
  524. if (ll == "cxx") {
  525. languages.emplace_back("cpp");
  526. } else {
  527. languages.emplace_back(std::move(ll));
  528. }
  529. }
  530. BuildArray(component, "link_languages", languages);
  531. }
  532. }
  533. return component;
  534. }