cmExportPackageInfoGenerator.cxx 18 KB

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