1//===--- ModulesDriver.cpp - Driver managed module builds -----------------===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8///
9/// \file
10/// This file defines functionality to support driver managed builds for
11/// compilations which use Clang modules or standard C++20 named modules.
12///
13//===----------------------------------------------------------------------===//
14
15#include "clang/Driver/ModulesDriver.h"
16#include "clang/Basic/Diagnostic.h"
17#include "clang/Basic/LLVM.h"
18#include "clang/Driver/Compilation.h"
19#include "clang/Driver/Driver.h"
20#include "clang/Driver/Job.h"
21#include "clang/Driver/Tool.h"
22#include "clang/Driver/ToolChain.h"
23#include "llvm/Support/JSON.h"
24#include "llvm/Support/Path.h"
25#include "llvm/Support/PrettyStackTrace.h"
26#include "llvm/Support/VirtualFileSystem.h"
27
28using namespace llvm::opt;
29using namespace clang;
30using namespace driver;
31using namespace modules;
32
33namespace clang::driver::modules {
34static bool fromJSON(const llvm::json::Value &Params,
35 StdModuleManifest::Module::LocalArguments &LocalArgs,
36 llvm::json::Path P) {
37 llvm::json::ObjectMapper O(Params, P);
38 return O.mapOptional(Prop: "system-include-directories",
39 Out&: LocalArgs.SystemIncludeDirs);
40}
41
42static bool fromJSON(const llvm::json::Value &Params,
43 StdModuleManifest::Module &ModuleEntry,
44 llvm::json::Path P) {
45 llvm::json::ObjectMapper O(Params, P);
46 return O.map(Prop: "is-std-library", Out&: ModuleEntry.IsStdlib) &&
47 O.map(Prop: "logical-name", Out&: ModuleEntry.LogicalName) &&
48 O.map(Prop: "source-path", Out&: ModuleEntry.SourcePath) &&
49 O.mapOptional(Prop: "local-arguments", Out&: ModuleEntry.LocalArgs);
50}
51
52static bool fromJSON(const llvm::json::Value &Params,
53 StdModuleManifest &Manifest, llvm::json::Path P) {
54 llvm::json::ObjectMapper O(Params, P);
55 return O.map(Prop: "modules", Out&: Manifest.Modules);
56}
57} // namespace clang::driver::modules
58
59/// Parses the Standard library module manifest from \p Buffer.
60static Expected<StdModuleManifest> parseManifest(StringRef Buffer) {
61 auto ParsedOrErr = llvm::json::parse(JSON: Buffer);
62 if (!ParsedOrErr)
63 return ParsedOrErr.takeError();
64
65 StdModuleManifest Manifest;
66 llvm::json::Path::Root Root;
67 if (!fromJSON(Params: *ParsedOrErr, Manifest, P: Root))
68 return Root.getError();
69
70 return Manifest;
71}
72
73/// Converts each file path in manifest from relative to absolute.
74///
75/// Each file path in the manifest is expected to be relative the manifest's
76/// location \p ManifestPath itself.
77static void makeManifestPathsAbsolute(
78 MutableArrayRef<StdModuleManifest::Module> ManifestEntries,
79 StringRef ManifestPath) {
80 StringRef ManifestDir = llvm::sys::path::parent_path(path: ManifestPath);
81 SmallString<256> TempPath;
82
83 auto PrependManifestDir = [&](StringRef Path) {
84 TempPath = ManifestDir;
85 llvm::sys::path::append(path&: TempPath, a: Path);
86 return std::string(TempPath);
87 };
88
89 for (auto &Entry : ManifestEntries) {
90 Entry.SourcePath = PrependManifestDir(Entry.SourcePath);
91 if (!Entry.LocalArgs)
92 continue;
93
94 for (auto &IncludeDir : Entry.LocalArgs->SystemIncludeDirs)
95 IncludeDir = PrependManifestDir(IncludeDir);
96 }
97}
98
99Expected<StdModuleManifest>
100driver::modules::readStdModuleManifest(StringRef ManifestPath,
101 llvm::vfs::FileSystem &VFS) {
102 auto MemBufOrErr = VFS.getBufferForFile(Name: ManifestPath);
103 if (!MemBufOrErr)
104 return llvm::createFileError(F: ManifestPath, EC: MemBufOrErr.getError());
105
106 auto ManifestOrErr = parseManifest(Buffer: (*MemBufOrErr)->getBuffer());
107 if (!ManifestOrErr)
108 return ManifestOrErr.takeError();
109 auto Manifest = std::move(*ManifestOrErr);
110
111 makeManifestPathsAbsolute(ManifestEntries: Manifest.Modules, ManifestPath);
112 return Manifest;
113}
114
115void driver::modules::buildStdModuleManifestInputs(
116 ArrayRef<StdModuleManifest::Module> ManifestEntries, Compilation &C,
117 InputList &Inputs) {
118 DerivedArgList &Args = C.getArgs();
119 const OptTable &Opts = C.getDriver().getOpts();
120 for (const auto &Entry : ManifestEntries) {
121 auto *InputArg =
122 makeInputArg(Args, Opts, Value: Args.MakeArgString(Str: Entry.SourcePath));
123 Inputs.emplace_back(Args: types::TY_CXXModule, Args&: InputArg);
124 }
125}
126
127using ManifestEntryLookup =
128 llvm::DenseMap<StringRef, const StdModuleManifest::Module *>;
129
130/// Builds a mapping from a module's source path to its entry in the manifest.
131static ManifestEntryLookup
132buildManifestLookupMap(ArrayRef<StdModuleManifest::Module> ManifestEntries) {
133 ManifestEntryLookup ManifestEntryBySource;
134 for (auto &Entry : ManifestEntries) {
135 [[maybe_unused]] const bool Inserted =
136 ManifestEntryBySource.try_emplace(Key: Entry.SourcePath, Args: &Entry).second;
137 assert(Inserted &&
138 "Manifest defines multiple modules with the same source path.");
139 }
140 return ManifestEntryBySource;
141}
142
143/// Returns the manifest entry corresponding to \p Job, or \c nullptr if none
144/// exists.
145static const StdModuleManifest::Module *
146getManifestEntryForCommand(const Command &Job,
147 const ManifestEntryLookup &ManifestEntryBySource) {
148 for (const auto &II : Job.getInputInfos()) {
149 if (const auto It = ManifestEntryBySource.find(Val: II.getFilename());
150 It != ManifestEntryBySource.end())
151 return It->second;
152 }
153 return nullptr;
154}
155
156/// Adds all \p SystemIncludeDirs to the \p CC1Args of \p Job.
157static void
158addSystemIncludeDirsFromManifest(Compilation &C, Command &Job,
159 ArgStringList &CC1Args,
160 ArrayRef<std::string> SystemIncludeDirs) {
161 const ToolChain &TC = Job.getCreator().getToolChain();
162 const DerivedArgList &TCArgs =
163 C.getArgsForToolChain(TC: &TC, BoundArch: Job.getSource().getOffloadingArch(),
164 DeviceOffloadKind: Job.getSource().getOffloadingDeviceKind());
165
166 for (const auto &IncludeDir : SystemIncludeDirs)
167 TC.addSystemInclude(DriverArgs: TCArgs, CC1Args, Path: IncludeDir);
168}
169
170static bool isCC1Job(const Command &Job) {
171 return StringRef(Job.getCreator().getName()) == "clang";
172}
173
174/// Apply command-line modifications specific for inputs originating from the
175/// Standard library module manifest.
176static void applyArgsForStdModuleManifestInputs(
177 Compilation &C, const ManifestEntryLookup &ManifestEntryBySource,
178 MutableArrayRef<std::unique_ptr<Command>> Jobs) {
179 for (auto &Job : Jobs) {
180 if (!isCC1Job(Job: *Job))
181 continue;
182
183 const auto *Entry = getManifestEntryForCommand(Job: *Job, ManifestEntryBySource);
184 if (!Entry)
185 continue;
186
187 auto CC1Args = Job->getArguments();
188 if (Entry->IsStdlib)
189 CC1Args.push_back(Elt: "-Wno-reserved-module-identifier");
190 if (Entry->LocalArgs)
191 addSystemIncludeDirsFromManifest(C, Job&: *Job, CC1Args,
192 SystemIncludeDirs: Entry->LocalArgs->SystemIncludeDirs);
193 Job->replaceArguments(List: CC1Args);
194 }
195}
196
197void driver::modules::runModulesDriver(
198 Compilation &C, ArrayRef<StdModuleManifest::Module> ManifestEntries) {
199 llvm::PrettyStackTraceString CrashInfo("Running modules driver.");
200
201 auto Jobs = C.getJobs().takeJobs();
202
203 const auto ManifestEntryBySource = buildManifestLookupMap(ManifestEntries);
204 // Apply manifest-entry specific command-line modifications before the scan as
205 // they might affect it.
206 applyArgsForStdModuleManifestInputs(C, ManifestEntryBySource, Jobs);
207
208 // TODO: Run the dependency scan.
209 // TODO: Prune jobs for modules specified in the manifest that are not
210 // required by any command-line input.
211 // TODO: Reorder and modify jobs based on the discovered dependencies.
212
213 // Add jobs back to the Compilation.
214 for (auto &Job : Jobs)
215 C.addCommand(C: std::move(Job));
216}
217