cmExportPackageInfoGenerator.cxx 16 KB

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