cmCxxModuleMapper.cxx 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405
  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 "cmCxxModuleMapper.h"
  4. #include <cassert>
  5. #include <cstddef>
  6. #include <set>
  7. #include <sstream>
  8. #include <string>
  9. #include <utility>
  10. #include <vector>
  11. #include <cm/string_view>
  12. #include <cmext/string_view>
  13. #include "cmScanDepFormat.h"
  14. #include "cmStringAlgorithms.h"
  15. #include "cmSystemTools.h"
  16. CxxBmiLocation::CxxBmiLocation() = default;
  17. CxxBmiLocation::CxxBmiLocation(std::string path)
  18. : BmiLocation(std::move(path))
  19. {
  20. }
  21. CxxBmiLocation CxxBmiLocation::Unknown()
  22. {
  23. return {};
  24. }
  25. CxxBmiLocation CxxBmiLocation::Private()
  26. {
  27. return { std::string{} };
  28. }
  29. CxxBmiLocation CxxBmiLocation::Known(std::string path)
  30. {
  31. return { std::move(path) };
  32. }
  33. bool CxxBmiLocation::IsKnown() const
  34. {
  35. return this->BmiLocation.has_value();
  36. }
  37. bool CxxBmiLocation::IsPrivate() const
  38. {
  39. if (auto const& loc = this->BmiLocation) {
  40. return loc->empty();
  41. }
  42. return false;
  43. }
  44. std::string const& CxxBmiLocation::Location() const
  45. {
  46. if (auto const& loc = this->BmiLocation) {
  47. return *loc;
  48. }
  49. static std::string empty;
  50. return empty;
  51. }
  52. CxxBmiLocation CxxModuleLocations::BmiGeneratorPathForModule(
  53. std::string const& logical_name) const
  54. {
  55. auto bmi_loc = this->BmiLocationForModule(logical_name);
  56. if (bmi_loc.IsKnown() && !bmi_loc.IsPrivate()) {
  57. bmi_loc =
  58. CxxBmiLocation::Known(this->PathForGenerator(bmi_loc.Location()));
  59. }
  60. return bmi_loc;
  61. }
  62. namespace {
  63. std::string CxxModuleMapContentClang(CxxModuleLocations const& loc,
  64. cmScanDepInfo const& obj)
  65. {
  66. std::stringstream mm;
  67. // Clang's command line only supports a single output. If more than one is
  68. // expected, we cannot make a useful module map file.
  69. if (obj.Provides.size() > 1) {
  70. return {};
  71. }
  72. // A series of flags which tell the compiler where to look for modules.
  73. for (auto const& p : obj.Provides) {
  74. auto bmi_loc = loc.BmiGeneratorPathForModule(p.LogicalName);
  75. if (bmi_loc.IsKnown()) {
  76. // Force the TU to be considered a C++ module source file regardless of
  77. // extension.
  78. mm << "-x c++-module\n";
  79. mm << "-fmodule-output=" << bmi_loc.Location() << '\n';
  80. break;
  81. }
  82. }
  83. for (auto const& r : obj.Requires) {
  84. auto bmi_loc = loc.BmiGeneratorPathForModule(r.LogicalName);
  85. if (bmi_loc.IsKnown()) {
  86. mm << "-fmodule-file=" << r.LogicalName << "=" << bmi_loc.Location()
  87. << '\n';
  88. }
  89. }
  90. return mm.str();
  91. }
  92. std::string CxxModuleMapContentGcc(CxxModuleLocations const& loc,
  93. cmScanDepInfo const& obj)
  94. {
  95. std::stringstream mm;
  96. // Documented in GCC's documentation. The format is a series of
  97. // lines with a module name and the associated filename separated
  98. // by spaces. The first line may use `$root` as the module name
  99. // to specify a "repository root". That is used to anchor any
  100. // relative paths present in the file (CMake should never
  101. // generate any).
  102. // Write the root directory to use for module paths.
  103. mm << "$root " << loc.RootDirectory << "\n";
  104. for (auto const& p : obj.Provides) {
  105. auto bmi_loc = loc.BmiGeneratorPathForModule(p.LogicalName);
  106. if (bmi_loc.IsKnown()) {
  107. mm << p.LogicalName << ' ' << bmi_loc.Location() << '\n';
  108. }
  109. }
  110. for (auto const& r : obj.Requires) {
  111. auto bmi_loc = loc.BmiGeneratorPathForModule(r.LogicalName);
  112. if (bmi_loc.IsKnown()) {
  113. mm << r.LogicalName << ' ' << bmi_loc.Location() << '\n';
  114. }
  115. }
  116. return mm.str();
  117. }
  118. std::string CxxModuleMapContentMsvc(CxxModuleLocations const& loc,
  119. cmScanDepInfo const& obj,
  120. CxxModuleUsage const& usages)
  121. {
  122. std::stringstream mm;
  123. // A response file of `-reference NAME=PATH` arguments.
  124. // MSVC's command line only supports a single output. If more than one is
  125. // expected, we cannot make a useful module map file.
  126. if (obj.Provides.size() > 1) {
  127. return {};
  128. }
  129. auto flag_for_method = [](LookupMethod method) -> cm::static_string_view {
  130. switch (method) {
  131. case LookupMethod::ByName:
  132. return "-reference"_s;
  133. case LookupMethod::IncludeAngle:
  134. return "-headerUnit:angle"_s;
  135. case LookupMethod::IncludeQuote:
  136. return "-headerUnit:quote"_s;
  137. }
  138. assert(false && "unsupported lookup method");
  139. return ""_s;
  140. };
  141. for (auto const& p : obj.Provides) {
  142. if (p.IsInterface) {
  143. mm << "-interface\n";
  144. } else {
  145. mm << "-internalPartition\n";
  146. }
  147. auto bmi_loc = loc.BmiGeneratorPathForModule(p.LogicalName);
  148. if (bmi_loc.IsKnown()) {
  149. mm << "-ifcOutput " << bmi_loc.Location() << '\n';
  150. }
  151. }
  152. std::set<std::string> transitive_usage_directs;
  153. std::set<std::string> transitive_usage_names;
  154. for (auto const& r : obj.Requires) {
  155. auto bmi_loc = loc.BmiGeneratorPathForModule(r.LogicalName);
  156. if (bmi_loc.IsKnown()) {
  157. auto flag = flag_for_method(r.Method);
  158. mm << flag << ' ' << r.LogicalName << '=' << bmi_loc.Location() << "\n";
  159. transitive_usage_directs.insert(r.LogicalName);
  160. // Insert transitive usages.
  161. auto transitive_usages = usages.Usage.find(r.LogicalName);
  162. if (transitive_usages != usages.Usage.end()) {
  163. transitive_usage_names.insert(transitive_usages->second.begin(),
  164. transitive_usages->second.end());
  165. }
  166. }
  167. }
  168. for (auto const& transitive_name : transitive_usage_names) {
  169. if (transitive_usage_directs.count(transitive_name)) {
  170. continue;
  171. }
  172. auto module_ref = usages.Reference.find(transitive_name);
  173. if (module_ref != usages.Reference.end()) {
  174. auto flag = flag_for_method(module_ref->second.Method);
  175. mm << flag << ' ' << transitive_name << '=' << module_ref->second.Path
  176. << "\n";
  177. }
  178. }
  179. return mm.str();
  180. }
  181. }
  182. bool CxxModuleUsage::AddReference(std::string const& logical,
  183. std::string const& loc, LookupMethod method)
  184. {
  185. auto r = this->Reference.find(logical);
  186. if (r != this->Reference.end()) {
  187. auto& ref = r->second;
  188. if (ref.Path == loc && ref.Method == method) {
  189. return true;
  190. }
  191. auto method_name = [](LookupMethod m) -> cm::static_string_view {
  192. switch (m) {
  193. case LookupMethod::ByName:
  194. return "by-name"_s;
  195. case LookupMethod::IncludeAngle:
  196. return "include-angle"_s;
  197. case LookupMethod::IncludeQuote:
  198. return "include-quote"_s;
  199. }
  200. assert(false && "unsupported lookup method");
  201. return ""_s;
  202. };
  203. cmSystemTools::Error(cmStrCat("Disagreement of the location of the '",
  204. logical,
  205. "' module. "
  206. "Location A: '",
  207. ref.Path, "' via ", method_name(ref.Method),
  208. "; "
  209. "Location B: '",
  210. loc, "' via ", method_name(method), "."));
  211. return false;
  212. }
  213. auto& ref = this->Reference[logical];
  214. ref.Path = loc;
  215. ref.Method = method;
  216. return true;
  217. }
  218. cm::static_string_view CxxModuleMapExtension(
  219. cm::optional<CxxModuleMapFormat> format)
  220. {
  221. if (format) {
  222. switch (*format) {
  223. case CxxModuleMapFormat::Clang:
  224. return ".pcm"_s;
  225. case CxxModuleMapFormat::Gcc:
  226. return ".gcm"_s;
  227. case CxxModuleMapFormat::Msvc:
  228. return ".ifc"_s;
  229. }
  230. }
  231. return ".bmi"_s;
  232. }
  233. std::set<std::string> CxxModuleUsageSeed(
  234. CxxModuleLocations const& loc, std::vector<cmScanDepInfo> const& objects,
  235. CxxModuleUsage& usages)
  236. {
  237. // Track inner usages to populate usages from internal bits.
  238. //
  239. // This is a map of modules that required some other module that was not
  240. // found to those that were not found.
  241. std::map<std::string, std::set<std::string>> internal_usages;
  242. std::set<std::string> unresolved;
  243. for (cmScanDepInfo const& object : objects) {
  244. // Add references for each of the provided modules.
  245. for (auto const& p : object.Provides) {
  246. auto bmi_loc = loc.BmiGeneratorPathForModule(p.LogicalName);
  247. if (bmi_loc.IsKnown()) {
  248. // XXX(cxx-modules): How to support header units?
  249. usages.AddReference(p.LogicalName, bmi_loc.Location(),
  250. LookupMethod::ByName);
  251. }
  252. }
  253. // For each requires, pull in what is required.
  254. for (auto const& r : object.Requires) {
  255. // Find the required name in the current target.
  256. auto bmi_loc = loc.BmiGeneratorPathForModule(r.LogicalName);
  257. if (bmi_loc.IsPrivate()) {
  258. cmSystemTools::Error(
  259. cmStrCat("Unable to use module '", r.LogicalName,
  260. "' as it is 'PRIVATE' and therefore not accessible outside "
  261. "of its owning target."));
  262. continue;
  263. }
  264. // Find transitive usages.
  265. auto transitive_usages = usages.Usage.find(r.LogicalName);
  266. for (auto const& p : object.Provides) {
  267. auto& this_usages = usages.Usage[p.LogicalName];
  268. // Add the direct usage.
  269. this_usages.insert(r.LogicalName);
  270. // Add the transitive usage.
  271. if (transitive_usages != usages.Usage.end()) {
  272. this_usages.insert(transitive_usages->second.begin(),
  273. transitive_usages->second.end());
  274. } else if (bmi_loc.IsKnown()) {
  275. // Mark that we need to update transitive usages later.
  276. internal_usages[p.LogicalName].insert(r.LogicalName);
  277. }
  278. }
  279. if (bmi_loc.IsKnown()) {
  280. usages.AddReference(r.LogicalName, bmi_loc.Location(), r.Method);
  281. }
  282. }
  283. }
  284. // While we have internal usages to manage.
  285. while (!internal_usages.empty()) {
  286. size_t starting_size = internal_usages.size();
  287. // For each internal usage.
  288. for (auto usage = internal_usages.begin(); usage != internal_usages.end();
  289. /* see end of loop */) {
  290. auto& this_usages = usages.Usage[usage->first];
  291. for (auto use = usage->second.begin(); use != usage->second.end();
  292. /* see end of loop */) {
  293. // Check if this required module uses other internal modules; defer
  294. // if so.
  295. if (internal_usages.count(*use)) {
  296. // Advance the iterator.
  297. ++use;
  298. continue;
  299. }
  300. auto transitive_usages = usages.Usage.find(*use);
  301. if (transitive_usages != usages.Usage.end()) {
  302. this_usages.insert(transitive_usages->second.begin(),
  303. transitive_usages->second.end());
  304. }
  305. // Remove the entry and advance the iterator.
  306. use = usage->second.erase(use);
  307. }
  308. // Erase the entry if it doesn't have any remaining usages.
  309. if (usage->second.empty()) {
  310. usage = internal_usages.erase(usage);
  311. } else {
  312. ++usage;
  313. }
  314. }
  315. // Check that at least one usage was resolved.
  316. if (starting_size == internal_usages.size()) {
  317. // Nothing could be resolved this loop; we have a cycle, so record the
  318. // cycle and exit.
  319. for (auto const& usage : internal_usages) {
  320. unresolved.insert(usage.first);
  321. }
  322. break;
  323. }
  324. }
  325. return unresolved;
  326. }
  327. std::string CxxModuleMapContent(CxxModuleMapFormat format,
  328. CxxModuleLocations const& loc,
  329. cmScanDepInfo const& obj,
  330. CxxModuleUsage const& usages)
  331. {
  332. switch (format) {
  333. case CxxModuleMapFormat::Clang:
  334. return CxxModuleMapContentClang(loc, obj);
  335. case CxxModuleMapFormat::Gcc:
  336. return CxxModuleMapContentGcc(loc, obj);
  337. case CxxModuleMapFormat::Msvc:
  338. return CxxModuleMapContentMsvc(loc, obj, usages);
  339. }
  340. assert(false);
  341. return {};
  342. }