cmExportPackageInfoGenerator.cxx 14 KB

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