cmCxxModuleMetadata.cxx 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541
  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 "cmCxxModuleMetadata.h"
  4. #include <algorithm>
  5. #include <set>
  6. #include <string>
  7. #include <utility>
  8. #include <cmext/string_view>
  9. #include <cm3p/json/value.h>
  10. #include <cm3p/json/writer.h>
  11. #include "cmsys/FStream.hxx"
  12. #include "cmFileSet.h"
  13. #include "cmGeneratedFileStream.h"
  14. #include "cmJSONState.h"
  15. #include "cmListFileCache.h"
  16. #include "cmStringAlgorithms.h"
  17. #include "cmSystemTools.h"
  18. #include "cmTarget.h"
  19. namespace {
  20. bool JsonIsStringArray(Json::Value const& v)
  21. {
  22. return v.isArray() &&
  23. std::all_of(v.begin(), v.end(),
  24. [](Json::Value const& it) { return it.isString(); });
  25. }
  26. bool ParsePreprocessorDefine(Json::Value& dval,
  27. cmCxxModuleMetadata::PreprocessorDefineData& out,
  28. cmJSONState* state)
  29. {
  30. if (!dval.isObject()) {
  31. state->AddErrorAtValue("each entry in 'definitions' must be an object",
  32. &dval);
  33. return false;
  34. }
  35. if (!dval.isMember("name") || !dval["name"].isString() ||
  36. dval["name"].asString().empty()) {
  37. state->AddErrorAtValue(
  38. "preprocessor definition requires a non-empty 'name'", &dval["name"]);
  39. return false;
  40. }
  41. out.Name = dval["name"].asString();
  42. if (dval.isMember("value")) {
  43. if (dval["value"].isString()) {
  44. out.Value = dval["value"].asString();
  45. } else if (!dval["value"].isNull()) {
  46. state->AddErrorAtValue(
  47. "'value' in preprocessor definition must be string or null",
  48. &dval["value"]);
  49. return false;
  50. }
  51. }
  52. if (dval.isMember("undef")) {
  53. if (!dval["undef"].isBool()) {
  54. state->AddErrorAtValue(
  55. "'undef' in preprocessor definition must be boolean", &dval["undef"]);
  56. return false;
  57. }
  58. out.Undef = dval["undef"].asBool();
  59. }
  60. return true;
  61. }
  62. bool ParseCMakeLocalArgumentsVendor(
  63. Json::Value& cmlav, cmCxxModuleMetadata::LocalArgumentsData& out,
  64. cmJSONState* state)
  65. {
  66. if (!cmlav.isObject()) {
  67. state->AddErrorAtValue("'vendor' must be an object", &cmlav);
  68. return false;
  69. }
  70. if (cmlav.isMember("compile-options")) {
  71. if (!JsonIsStringArray(cmlav["compile-options"])) {
  72. state->AddErrorAtValue("'compile-options' must be an array of strings",
  73. &cmlav["compile-options"]);
  74. return false;
  75. }
  76. for (auto const& s : cmlav["compile-options"]) {
  77. out.CompileOptions.push_back(s.asString());
  78. }
  79. }
  80. if (cmlav.isMember("compile-features")) {
  81. if (!JsonIsStringArray(cmlav["compile-features"])) {
  82. state->AddErrorAtValue("'compile-features' must be an array of strings",
  83. &cmlav["compile-features"]);
  84. return false;
  85. }
  86. for (auto const& s : cmlav["compile-features"]) {
  87. out.CompileFeatures.push_back(s.asString());
  88. }
  89. }
  90. return true;
  91. }
  92. bool ParseLocalArguments(Json::Value& lav,
  93. cmCxxModuleMetadata::LocalArgumentsData& out,
  94. cmJSONState* state)
  95. {
  96. if (!lav.isObject()) {
  97. state->AddErrorAtValue("'local-arguments' must be an object", &lav);
  98. return false;
  99. }
  100. if (lav.isMember("include-directories")) {
  101. if (!JsonIsStringArray(lav["include-directories"])) {
  102. state->AddErrorAtValue(
  103. "'include-directories' must be an array of strings",
  104. &lav["include-directories"]);
  105. return false;
  106. }
  107. for (auto const& s : lav["include-directories"]) {
  108. out.IncludeDirectories.push_back(s.asString());
  109. }
  110. }
  111. if (lav.isMember("system-include-directories")) {
  112. if (!JsonIsStringArray(lav["system-include-directories"])) {
  113. state->AddErrorAtValue(
  114. "'system-include-directories' must be an array of strings",
  115. &lav["system-include-directories"]);
  116. return false;
  117. }
  118. for (auto const& s : lav["system-include-directories"]) {
  119. out.SystemIncludeDirectories.push_back(s.asString());
  120. }
  121. }
  122. if (lav.isMember("definitions")) {
  123. if (!lav["definitions"].isArray()) {
  124. state->AddErrorAtValue("'definitions' must be an array",
  125. &lav["definitions"]);
  126. return false;
  127. }
  128. for (Json::Value& dval : lav["definitions"]) {
  129. out.Definitions.emplace_back();
  130. if (!ParsePreprocessorDefine(dval, out.Definitions.back(), state)) {
  131. return false;
  132. }
  133. }
  134. }
  135. if (lav.isMember("vendor")) {
  136. if (!ParseCMakeLocalArgumentsVendor(lav["vendor"], out, state)) {
  137. return false;
  138. }
  139. }
  140. return true;
  141. }
  142. bool ParseModule(Json::Value& mval, cmCxxModuleMetadata::ModuleData& mod,
  143. cmJSONState* state)
  144. {
  145. if (!mval.isObject()) {
  146. state->AddErrorAtValue("each entry in 'modules' must be an object", &mval);
  147. return false;
  148. }
  149. if (!mval.isMember("logical-name") || !mval["logical-name"].isString() ||
  150. mval["logical-name"].asString().empty()) {
  151. state->AddErrorAtValue(
  152. "module entries require a non-empty 'logical-name' string",
  153. &mval["logical-name"]);
  154. return false;
  155. }
  156. mod.LogicalName = mval["logical-name"].asString();
  157. if (!mval.isMember("source-path") || !mval["source-path"].isString() ||
  158. mval["source-path"].asString().empty()) {
  159. state->AddErrorAtValue(
  160. "module entries require a non-empty 'source-path' string",
  161. &mval["source-path"]);
  162. return false;
  163. }
  164. mod.SourcePath = mval["source-path"].asString();
  165. if (mval.isMember("is-interface")) {
  166. if (!mval["is-interface"].isBool()) {
  167. state->AddErrorAtValue("'is-interface' must be boolean",
  168. &mval["is-interface"]);
  169. return false;
  170. }
  171. mod.IsInterface = mval["is-interface"].asBool();
  172. } else {
  173. mod.IsInterface = true;
  174. }
  175. if (mval.isMember("is-std-library")) {
  176. if (!mval["is-std-library"].isBool()) {
  177. state->AddErrorAtValue("'is-std-library' must be boolean",
  178. &mval["is-std-library"]);
  179. return false;
  180. }
  181. mod.IsStdLibrary = mval["is-std-library"].asBool();
  182. } else {
  183. mod.IsStdLibrary = false;
  184. }
  185. if (mval.isMember("local-arguments")) {
  186. mod.LocalArguments.emplace();
  187. if (!ParseLocalArguments(mval["local-arguments"], *mod.LocalArguments,
  188. state)) {
  189. return false;
  190. }
  191. }
  192. return true;
  193. }
  194. bool ParseRoot(Json::Value& root, cmCxxModuleMetadata& meta,
  195. cmJSONState* state)
  196. {
  197. if (!root.isMember("version") || !root["version"].isInt()) {
  198. state->AddErrorAtValue(
  199. "Top-level member 'version' is required and must be an integer", &root);
  200. return false;
  201. }
  202. meta.Version = root["version"].asInt();
  203. if (root.isMember("revision")) {
  204. if (!root["revision"].isInt()) {
  205. state->AddErrorAtValue("'revision' must be an integer",
  206. &root["revision"]);
  207. return false;
  208. }
  209. meta.Revision = root["revision"].asInt();
  210. }
  211. if (meta.Version != 1) {
  212. state->AddErrorAtValue(cmStrCat("Module manifest version number, '",
  213. meta.Version, '.', meta.Revision,
  214. "' is newer than max supported (1.1)"),
  215. &root);
  216. return false;
  217. }
  218. if (root.isMember("modules")) {
  219. if (!root["modules"].isArray()) {
  220. state->AddErrorAtValue("'modules' must be an array", &root["modules"]);
  221. return false;
  222. }
  223. for (Json::Value& mval : root["modules"]) {
  224. meta.Modules.emplace_back();
  225. if (!ParseModule(mval, meta.Modules.back(), state)) {
  226. return false;
  227. }
  228. }
  229. }
  230. return true;
  231. }
  232. } // namespace
  233. cmCxxModuleMetadata::ParseResult cmCxxModuleMetadata::LoadFromFile(
  234. std::string const& path)
  235. {
  236. ParseResult res;
  237. Json::Value root;
  238. cmJSONState parseState(path, &root);
  239. if (!parseState.errors.empty()) {
  240. res.Error = parseState.GetErrorMessage();
  241. return res;
  242. }
  243. cmCxxModuleMetadata meta;
  244. if (!ParseRoot(root, meta, &parseState)) {
  245. res.Error = parseState.GetErrorMessage();
  246. return res;
  247. }
  248. meta.MetadataFilePath = path;
  249. res.Meta = std::move(meta);
  250. return res;
  251. }
  252. namespace {
  253. Json::Value SerializePreprocessorDefine(
  254. cmCxxModuleMetadata::PreprocessorDefineData const& d)
  255. {
  256. Json::Value dv(Json::objectValue);
  257. dv["name"] = d.Name;
  258. if (d.Value) {
  259. dv["value"] = *d.Value;
  260. }
  261. if (d.Undef) {
  262. dv["undef"] = d.Undef;
  263. }
  264. return dv;
  265. }
  266. Json::Value SerializeCMakeLocalArgumentsVendor(
  267. cmCxxModuleMetadata::LocalArgumentsData const& la)
  268. {
  269. Json::Value vend(Json::objectValue);
  270. if (!la.CompileOptions.empty()) {
  271. Json::Value& opts = vend["compile-options"] = Json::arrayValue;
  272. for (auto const& s : la.CompileOptions) {
  273. opts.append(s);
  274. }
  275. }
  276. if (!la.CompileFeatures.empty()) {
  277. Json::Value& feats = vend["compile-features"] = Json::arrayValue;
  278. for (auto const& s : la.CompileFeatures) {
  279. feats.append(s);
  280. }
  281. }
  282. return vend;
  283. }
  284. Json::Value SerializeLocalArguments(
  285. cmCxxModuleMetadata::LocalArgumentsData const& la)
  286. {
  287. Json::Value lav(Json::objectValue);
  288. if (!la.IncludeDirectories.empty()) {
  289. Json::Value& inc = lav["include-directories"] = Json::arrayValue;
  290. for (auto const& s : la.IncludeDirectories) {
  291. inc.append(s);
  292. }
  293. }
  294. if (!la.SystemIncludeDirectories.empty()) {
  295. Json::Value& sinc = lav["system-include-directories"] = Json::arrayValue;
  296. for (auto const& s : la.SystemIncludeDirectories) {
  297. sinc.append(s);
  298. }
  299. }
  300. if (!la.Definitions.empty()) {
  301. Json::Value& defs = lav["definitions"] = Json::arrayValue;
  302. for (auto const& d : la.Definitions) {
  303. defs.append(SerializePreprocessorDefine(d));
  304. }
  305. }
  306. Json::Value vend = SerializeCMakeLocalArgumentsVendor(la);
  307. if (!vend.empty()) {
  308. Json::Value& cmvend = lav["vendor"] = Json::objectValue;
  309. cmvend["cmake"] = std::move(vend);
  310. }
  311. return lav;
  312. }
  313. Json::Value SerializeModule(std::string& manifestRoot,
  314. cmCxxModuleMetadata::ModuleData const& m)
  315. {
  316. Json::Value mv(Json::objectValue);
  317. mv["logical-name"] = m.LogicalName;
  318. if (cmSystemTools::FileIsFullPath(m.SourcePath)) {
  319. mv["source-path"] = m.SourcePath;
  320. } else {
  321. mv["source-path"] = cmSystemTools::ForceToRelativePath(
  322. manifestRoot, cmStrCat('/', m.SourcePath));
  323. }
  324. mv["is-interface"] = m.IsInterface;
  325. mv["is-std-library"] = m.IsStdLibrary;
  326. if (m.LocalArguments) {
  327. mv["local-arguments"] = SerializeLocalArguments(*m.LocalArguments);
  328. }
  329. return mv;
  330. }
  331. } // namespace
  332. Json::Value cmCxxModuleMetadata::ToJsonValue(cmCxxModuleMetadata const& meta)
  333. {
  334. Json::Value root(Json::objectValue);
  335. root["version"] = meta.Version;
  336. root["revision"] = meta.Revision;
  337. Json::Value& modules = root["modules"] = Json::arrayValue;
  338. std::string manifestRoot =
  339. cmSystemTools::GetFilenamePath(meta.MetadataFilePath);
  340. if (!cmSystemTools::FileIsFullPath(meta.MetadataFilePath)) {
  341. manifestRoot = cmStrCat('/', manifestRoot);
  342. }
  343. for (auto const& m : meta.Modules) {
  344. modules.append(SerializeModule(manifestRoot, m));
  345. }
  346. return root;
  347. }
  348. cmCxxModuleMetadata::SaveResult cmCxxModuleMetadata::SaveToFile(
  349. std::string const& path, cmCxxModuleMetadata const& meta)
  350. {
  351. SaveResult st;
  352. cmGeneratedFileStream ofs(path);
  353. if (!ofs.is_open()) {
  354. st.Error = "Unable to open temp file for writing";
  355. return st;
  356. }
  357. Json::StreamWriterBuilder wbuilder;
  358. wbuilder["indentation"] = " ";
  359. ofs << Json::writeString(wbuilder, ToJsonValue(meta));
  360. ofs.Close();
  361. if (!ofs.good()) {
  362. st.Error = cmStrCat("Write failed for file: "_s, path);
  363. return st;
  364. }
  365. st.Ok = true;
  366. return st;
  367. }
  368. void cmCxxModuleMetadata::PopulateTarget(
  369. cmTarget& target, cmCxxModuleMetadata const& meta,
  370. std::vector<std::string> const& configs)
  371. {
  372. std::set<cm::string_view> allIncludeDirectories;
  373. std::set<cm::string_view> allCompileOptions;
  374. std::set<cm::string_view> allCompileFeatures;
  375. std::set<std::string> allCompileDefinitions;
  376. std::set<std::string> baseDirs;
  377. std::string metadataDir =
  378. cmSystemTools::GetFilenamePath(meta.MetadataFilePath);
  379. auto fileSet = target.GetOrCreateFileSet("CXX_MODULES", "CXX_MODULES",
  380. cmFileSetVisibility::Interface);
  381. for (auto const& module : meta.Modules) {
  382. std::string sourcePath = module.SourcePath;
  383. if (!cmSystemTools::FileIsFullPath(sourcePath)) {
  384. sourcePath = cmStrCat(metadataDir, '/', sourcePath);
  385. }
  386. sourcePath = cmSystemTools::ToNormalizedPathOnDisk(std::move(sourcePath));
  387. // Module metadata files can reference files in different roots,
  388. // just use the immediate parent directory as a base directory
  389. baseDirs.insert(cmSystemTools::GetFilenamePath(sourcePath));
  390. fileSet.first->AddFileEntry(sourcePath);
  391. if (module.LocalArguments) {
  392. for (auto const& incDir : module.LocalArguments->IncludeDirectories) {
  393. allIncludeDirectories.emplace(incDir);
  394. }
  395. for (auto const& sysIncDir :
  396. module.LocalArguments->SystemIncludeDirectories) {
  397. allIncludeDirectories.emplace(sysIncDir);
  398. }
  399. for (auto const& opt : module.LocalArguments->CompileOptions) {
  400. allCompileOptions.emplace(opt);
  401. }
  402. for (auto const& opt : module.LocalArguments->CompileFeatures) {
  403. allCompileFeatures.emplace(opt);
  404. }
  405. for (auto const& def : module.LocalArguments->Definitions) {
  406. if (!def.Undef) {
  407. if (def.Value) {
  408. allCompileDefinitions.emplace(
  409. cmStrCat(def.Name, "="_s, *def.Value));
  410. } else {
  411. allCompileDefinitions.emplace(def.Name);
  412. }
  413. }
  414. }
  415. }
  416. }
  417. for (auto const& baseDir : baseDirs) {
  418. fileSet.first->AddDirectoryEntry(baseDir);
  419. }
  420. if (!allIncludeDirectories.empty()) {
  421. target.SetProperty("IMPORTED_CXX_MODULES_INCLUDE_DIRECTORIES",
  422. cmJoin(allIncludeDirectories, ";"));
  423. }
  424. if (!allCompileDefinitions.empty()) {
  425. target.SetProperty("IMPORTED_CXX_MODULES_COMPILE_DEFINITIONS",
  426. cmJoin(allCompileDefinitions, ";"));
  427. }
  428. if (!allCompileOptions.empty()) {
  429. target.SetProperty("IMPORTED_CXX_MODULES_COMPILE_OPTIONS",
  430. cmJoin(allCompileOptions, ";"));
  431. }
  432. if (!allCompileFeatures.empty()) {
  433. target.SetProperty("IMPORTED_CXX_MODULES_COMPILE_FEATURES",
  434. cmJoin(allCompileFeatures, ";"));
  435. }
  436. for (auto const& config : configs) {
  437. std::vector<std::string> moduleList;
  438. for (auto const& module : meta.Modules) {
  439. if (module.IsInterface) {
  440. std::string sourcePath = module.SourcePath;
  441. if (!cmSystemTools::FileIsFullPath(sourcePath)) {
  442. sourcePath = cmStrCat(metadataDir, '/', sourcePath);
  443. }
  444. sourcePath =
  445. cmSystemTools::ToNormalizedPathOnDisk(std::move(sourcePath));
  446. moduleList.push_back(cmStrCat(module.LogicalName, "="_s, sourcePath));
  447. }
  448. }
  449. if (!moduleList.empty()) {
  450. std::string upperConfig = cmSystemTools::UpperCase(config);
  451. std::string propertyName =
  452. cmStrCat("IMPORTED_CXX_MODULES_"_s, upperConfig);
  453. target.SetProperty(propertyName, cmJoin(moduleList, ";"));
  454. }
  455. }
  456. }