cmExportPackageInfoGenerator.cxx 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452
  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 const std::string kCPS_VERSION_STR = "0.12.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. for (auto const& requirement : this->Requirements) {
  111. // TODO: version, hint
  112. requirements[requirement] = Json::Value{};
  113. }
  114. }
  115. }
  116. Json::Value* cmExportPackageInfoGenerator::GenerateImportTarget(
  117. Json::Value& components, cmGeneratorTarget const* target,
  118. cmStateEnums::TargetType targetType) const
  119. {
  120. auto const& name = target->GetExportName();
  121. if (name.empty()) {
  122. return nullptr;
  123. }
  124. Json::Value& component = components[name];
  125. Json::Value& type = component["type"];
  126. switch (targetType) {
  127. case cmStateEnums::EXECUTABLE:
  128. type = "executable";
  129. break;
  130. case cmStateEnums::STATIC_LIBRARY:
  131. type = "archive";
  132. break;
  133. case cmStateEnums::SHARED_LIBRARY:
  134. type = "dylib";
  135. break;
  136. case cmStateEnums::MODULE_LIBRARY:
  137. type = "module";
  138. break;
  139. case cmStateEnums::INTERFACE_LIBRARY:
  140. type = "interface";
  141. break;
  142. default:
  143. type = "unknown";
  144. break;
  145. }
  146. return &component;
  147. }
  148. bool cmExportPackageInfoGenerator::GenerateInterfaceProperties(
  149. Json::Value& component, cmGeneratorTarget const* target,
  150. ImportPropertyMap const& properties) const
  151. {
  152. bool result = true;
  153. this->GenerateInterfaceLinkProperties(result, component, target, properties);
  154. this->GenerateInterfaceCompileFeatures(result, component, target,
  155. properties);
  156. this->GenerateInterfaceCompileDefines(result, component, target, properties);
  157. this->GenerateInterfaceListProperty(result, component, target,
  158. "compile_flags", "COMPILE_OPTIONS"_s,
  159. properties);
  160. this->GenerateInterfaceListProperty(result, component, target, "link_flags",
  161. "LINK_OPTIONS"_s, properties);
  162. this->GenerateInterfaceListProperty(result, component, target,
  163. "link_directories", "LINK_DIRECTORIES"_s,
  164. properties);
  165. this->GenerateInterfaceListProperty(result, component, target, "includes",
  166. "INCLUDE_DIRECTORIES"_s, properties);
  167. // TODO: description, license
  168. return result;
  169. }
  170. namespace {
  171. bool forbidGeneratorExpressions(std::string const& propertyName,
  172. std::string const& propertyValue,
  173. cmGeneratorTarget const* target)
  174. {
  175. std::string const& evaluatedValue = cmGeneratorExpression::Preprocess(
  176. propertyValue, cmGeneratorExpression::StripAllGeneratorExpressions);
  177. if (evaluatedValue != propertyValue) {
  178. target->Makefile->IssueMessage(
  179. MessageType::FATAL_ERROR,
  180. cmStrCat("Property \"", propertyName, "\" of target \"",
  181. target->GetName(),
  182. "\" contains a generator expression. This is not allowed."));
  183. return false;
  184. }
  185. return true;
  186. }
  187. }
  188. bool cmExportPackageInfoGenerator::NoteLinkedTarget(
  189. cmGeneratorTarget const* target, std::string const& linkedName,
  190. cmGeneratorTarget const* linkedTarget)
  191. {
  192. if (cm::contains(this->ExportedTargets, linkedTarget)) {
  193. // Target is internal to this package.
  194. this->LinkTargets.emplace(linkedName,
  195. cmStrCat(':', linkedTarget->GetExportName()));
  196. return true;
  197. }
  198. if (linkedTarget->IsImported()) {
  199. // Target is imported from a found package.
  200. auto pkgName = [linkedTarget]() -> std::string {
  201. auto const& pkgStack = linkedTarget->Target->GetFindPackageStack();
  202. if (!pkgStack.Empty()) {
  203. return pkgStack.Top().Name;
  204. }
  205. return linkedTarget->Target->GetProperty("EXPORT_FIND_PACKAGE_NAME");
  206. }();
  207. if (pkgName.empty()) {
  208. target->Makefile->IssueMessage(
  209. MessageType::FATAL_ERROR,
  210. cmStrCat("Target \"", target->GetName(),
  211. "\" references imported target \"", linkedName,
  212. "\" which does not come from any known package."));
  213. return false;
  214. }
  215. auto const& prefix = cmStrCat(pkgName, "::");
  216. if (!cmHasPrefix(linkedName, prefix)) {
  217. target->Makefile->IssueMessage(
  218. MessageType::FATAL_ERROR,
  219. cmStrCat("Target \"", target->GetName(), "\" references target \"",
  220. linkedName, "\", which comes from the \"", pkgName,
  221. "\" package, but does not belong to the package's "
  222. "canonical namespace. This is not allowed."));
  223. return false;
  224. }
  225. // TODO: Record package version, hint.
  226. this->Requirements.emplace(pkgName);
  227. this->LinkTargets.emplace(
  228. linkedName, cmStrCat(pkgName, ':', linkedName.substr(prefix.length())));
  229. return true;
  230. }
  231. // Target belongs to multiple namespaces or multiple export sets.
  232. auto const& exportInfo = this->FindExportInfo(linkedTarget);
  233. if (exportInfo.Namespaces.size() == 1 && exportInfo.Sets.size() == 1) {
  234. auto const& linkNamespace = *exportInfo.Namespaces.begin();
  235. if (!cmHasSuffix(linkNamespace, "::")) {
  236. target->Makefile->IssueMessage(
  237. MessageType::FATAL_ERROR,
  238. cmStrCat("Target \"", target->GetName(), "\" references target \"",
  239. linkedName,
  240. "\", which does not use the standard namespace separator. "
  241. "This is not allowed."));
  242. return false;
  243. }
  244. auto pkgName =
  245. cm::string_view{ linkNamespace.data(), linkNamespace.size() - 2 };
  246. if (pkgName == this->GetPackageName()) {
  247. this->LinkTargets.emplace(linkedName,
  248. cmStrCat(':', linkedTarget->GetExportName()));
  249. } else {
  250. this->Requirements.emplace(pkgName);
  251. this->LinkTargets.emplace(
  252. linkedName, cmStrCat(pkgName, ':', linkedTarget->GetExportName()));
  253. }
  254. return true;
  255. }
  256. // cmExportFileGenerator::HandleMissingTarget should have complained about
  257. // this already.
  258. return false;
  259. }
  260. void cmExportPackageInfoGenerator::GenerateInterfaceLinkProperties(
  261. bool& result, Json::Value& component, cmGeneratorTarget const* target,
  262. ImportPropertyMap const& properties) const
  263. {
  264. auto const& iter = properties.find("INTERFACE_LINK_LIBRARIES");
  265. if (iter == properties.end()) {
  266. return;
  267. }
  268. // TODO: Support $<LINK_ONLY>.
  269. if (!forbidGeneratorExpressions(iter->first, iter->second, target)) {
  270. result = false;
  271. return;
  272. }
  273. std::vector<std::string> buildRequires;
  274. // std::vector<std::string> linkRequires; TODO
  275. std::vector<std::string> linkLibraries;
  276. for (auto const& name : cmList{ iter->second }) {
  277. auto const& ti = this->LinkTargets.find(name);
  278. if (ti != this->LinkTargets.end()) {
  279. if (ti->second.empty()) {
  280. result = false;
  281. } else {
  282. buildRequires.emplace_back(ti->second);
  283. }
  284. } else {
  285. linkLibraries.emplace_back(name);
  286. }
  287. }
  288. buildArray(component, "requires", buildRequires);
  289. // buildArray(component, "link_requires", linkRequires); TODO
  290. buildArray(component, "link_libraries", linkLibraries);
  291. }
  292. void cmExportPackageInfoGenerator::GenerateInterfaceCompileFeatures(
  293. bool& result, Json::Value& component, cmGeneratorTarget const* target,
  294. ImportPropertyMap const& properties) const
  295. {
  296. auto const& iter = properties.find("INTERFACE_COMPILE_FEATURES");
  297. if (iter == properties.end()) {
  298. return;
  299. }
  300. if (!forbidGeneratorExpressions(iter->first, iter->second, target)) {
  301. result = false;
  302. return;
  303. }
  304. std::set<std::string> features;
  305. for (auto const& value : cmList{ iter->second }) {
  306. if (cmHasLiteralPrefix(value, "c_std_")) {
  307. auto suffix = cm::string_view{ value }.substr(6, 2);
  308. features.emplace(cmStrCat("cxx", suffix));
  309. } else if (cmHasLiteralPrefix(value, "cxx_std_")) {
  310. auto suffix = cm::string_view{ value }.substr(8, 2);
  311. features.emplace(cmStrCat("c++", suffix));
  312. }
  313. }
  314. buildArray(component, "compile_features", features);
  315. }
  316. void cmExportPackageInfoGenerator::GenerateInterfaceCompileDefines(
  317. bool& result, Json::Value& component, cmGeneratorTarget const* target,
  318. ImportPropertyMap const& properties) const
  319. {
  320. auto const& iter = properties.find("INTERFACE_COMPILE_DEFINITIONS");
  321. if (iter == properties.end()) {
  322. return;
  323. }
  324. // TODO: Support language-specific defines.
  325. if (!forbidGeneratorExpressions(iter->first, iter->second, target)) {
  326. result = false;
  327. return;
  328. }
  329. Json::Value defines;
  330. for (auto const& def : cmList{ iter->second }) {
  331. auto const n = def.find('=');
  332. if (n == std::string::npos) {
  333. defines[def] = Json::Value{};
  334. } else {
  335. defines[def.substr(0, n)] = def.substr(n + 1);
  336. }
  337. }
  338. if (!defines.empty()) {
  339. component["compile_definitions"]["*"] = std::move(defines);
  340. }
  341. }
  342. void cmExportPackageInfoGenerator::GenerateInterfaceListProperty(
  343. bool& result, Json::Value& component, cmGeneratorTarget const* target,
  344. std::string const& outName, cm::string_view inName,
  345. ImportPropertyMap const& properties) const
  346. {
  347. auto const& prop = cmStrCat("INTERFACE_", inName);
  348. auto const& iter = properties.find(prop);
  349. if (iter == properties.end()) {
  350. return;
  351. }
  352. if (!forbidGeneratorExpressions(prop, iter->second, target)) {
  353. result = false;
  354. return;
  355. }
  356. Json::Value& array = component[outName];
  357. for (auto const& value : cmList{ iter->second }) {
  358. array.append(value);
  359. }
  360. }
  361. void cmExportPackageInfoGenerator::GenerateInterfaceConfigProperties(
  362. Json::Value& components, cmGeneratorTarget const* target,
  363. std::string const& suffix, ImportPropertyMap const& properties) const
  364. {
  365. Json::Value component;
  366. auto const suffixLength = suffix.length();
  367. for (auto const& p : properties) {
  368. if (!cmHasSuffix(p.first, suffix)) {
  369. continue;
  370. }
  371. auto const n = p.first.length() - suffixLength - 9;
  372. auto const prop = cm::string_view{ p.first }.substr(9, n);
  373. if (prop == "LOCATION") {
  374. component["location"] = p.second;
  375. } else if (prop == "IMPLIB") {
  376. component["link_location"] = p.second;
  377. } else if (prop == "LINK_INTERFACE_LANGUAGES") {
  378. std::vector<std::string> languages;
  379. for (auto const& lang : cmList{ p.second }) {
  380. auto ll = cmSystemTools::LowerCase(lang);
  381. if (ll == "cxx") {
  382. languages.emplace_back("cpp");
  383. } else {
  384. languages.emplace_back(std::move(ll));
  385. }
  386. }
  387. buildArray(component, "link_languages", languages);
  388. }
  389. }
  390. if (!component.empty()) {
  391. components[target->GetExportName()] = component;
  392. }
  393. }