1 | //===-- ClangInstallAPI.cpp ----------------------------------------------===// |
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 | // This is the entry point to clang-installapi; it is a wrapper |
10 | // for functionality in the InstallAPI clang library. |
11 | // |
12 | //===----------------------------------------------------------------------===// |
13 | |
14 | #include "Options.h" |
15 | #include "clang/Basic/Diagnostic.h" |
16 | #include "clang/Basic/DiagnosticFrontend.h" |
17 | #include "clang/Driver/DriverDiagnostic.h" |
18 | #include "clang/Driver/Tool.h" |
19 | #include "clang/Frontend/TextDiagnosticPrinter.h" |
20 | #include "clang/InstallAPI/Frontend.h" |
21 | #include "clang/InstallAPI/FrontendRecords.h" |
22 | #include "clang/InstallAPI/InstallAPIDiagnostic.h" |
23 | #include "clang/InstallAPI/MachO.h" |
24 | #include "clang/Tooling/Tooling.h" |
25 | #include "llvm/ADT/ArrayRef.h" |
26 | #include "llvm/Option/Option.h" |
27 | #include "llvm/Support/CommandLine.h" |
28 | #include "llvm/Support/LLVMDriver.h" |
29 | #include "llvm/Support/ManagedStatic.h" |
30 | #include "llvm/Support/PrettyStackTrace.h" |
31 | #include "llvm/Support/Process.h" |
32 | #include "llvm/Support/Signals.h" |
33 | #include "llvm/TargetParser/Host.h" |
34 | #include <memory> |
35 | |
36 | using namespace clang; |
37 | using namespace clang::installapi; |
38 | using namespace clang::driver::options; |
39 | using namespace llvm::opt; |
40 | using namespace llvm::MachO; |
41 | |
42 | static bool runFrontend(StringRef ProgName, Twine Label, bool Verbose, |
43 | InstallAPIContext &Ctx, |
44 | llvm::vfs::InMemoryFileSystem *FS, |
45 | const ArrayRef<std::string> InitialArgs) { |
46 | |
47 | std::unique_ptr<llvm::MemoryBuffer> ProcessedInput = createInputBuffer(Ctx); |
48 | // Skip invoking cc1 when there are no header inputs. |
49 | if (!ProcessedInput) |
50 | return true; |
51 | |
52 | if (Verbose) |
53 | llvm::errs() << Label << " Headers:\n" |
54 | << ProcessedInput->getBuffer() << "\n\n" ; |
55 | |
56 | std::string InputFile = ProcessedInput->getBufferIdentifier().str(); |
57 | FS->addFile(Path: InputFile, /*ModTime=*/ModificationTime: 0, Buffer: std::move(ProcessedInput)); |
58 | // Reconstruct arguments with unique values like target triple or input |
59 | // headers. |
60 | std::vector<std::string> Args = {ProgName.data(), "-target" , |
61 | Ctx.Slice->getTriple().str().c_str()}; |
62 | llvm::copy(Range: InitialArgs, Out: std::back_inserter(x&: Args)); |
63 | Args.push_back(x: InputFile); |
64 | |
65 | // Create & run invocation. |
66 | clang::tooling::ToolInvocation Invocation( |
67 | std::move(Args), std::make_unique<InstallAPIAction>(args&: Ctx), Ctx.FM); |
68 | return Invocation.run(); |
69 | } |
70 | |
71 | static bool run(ArrayRef<const char *> Args, const char *ProgName) { |
72 | // Setup Diagnostics engine. |
73 | IntrusiveRefCntPtr<DiagnosticOptions> DiagOpts = new DiagnosticOptions(); |
74 | const llvm::opt::OptTable &ClangOpts = clang::driver::getDriverOptTable(); |
75 | unsigned MissingArgIndex, MissingArgCount; |
76 | llvm::opt::InputArgList ParsedArgs = ClangOpts.ParseArgs( |
77 | Args: ArrayRef(Args).slice(N: 1), MissingArgIndex, MissingArgCount); |
78 | ParseDiagnosticArgs(Opts&: *DiagOpts, Args&: ParsedArgs); |
79 | |
80 | IntrusiveRefCntPtr<DiagnosticsEngine> Diag = new clang::DiagnosticsEngine( |
81 | new clang::DiagnosticIDs(), DiagOpts.get(), |
82 | new clang::TextDiagnosticPrinter(llvm::errs(), DiagOpts.get())); |
83 | |
84 | // Create file manager for all file operations and holding in-memory generated |
85 | // inputs. |
86 | llvm::IntrusiveRefCntPtr<llvm::vfs::OverlayFileSystem> OverlayFileSystem( |
87 | new llvm::vfs::OverlayFileSystem(llvm::vfs::getRealFileSystem())); |
88 | llvm::IntrusiveRefCntPtr<llvm::vfs::InMemoryFileSystem> InMemoryFileSystem( |
89 | new llvm::vfs::InMemoryFileSystem); |
90 | OverlayFileSystem->pushOverlay(FS: InMemoryFileSystem); |
91 | IntrusiveRefCntPtr<clang::FileManager> FM( |
92 | new FileManager(clang::FileSystemOptions(), OverlayFileSystem)); |
93 | |
94 | // Capture all options and diagnose any errors. |
95 | Options Opts(*Diag, FM.get(), Args, ProgName); |
96 | if (Diag->hasErrorOccurred()) |
97 | return EXIT_FAILURE; |
98 | |
99 | InstallAPIContext Ctx = Opts.createContext(); |
100 | if (Diag->hasErrorOccurred()) |
101 | return EXIT_FAILURE; |
102 | |
103 | if (!Opts.DriverOpts.DylibToVerify.empty()) { |
104 | TargetList Targets; |
105 | llvm::for_each(Range&: Opts.DriverOpts.Targets, |
106 | F: [&](const auto &T) { Targets.push_back(Elt: T.first); }); |
107 | if (!Ctx.Verifier->verifyBinaryAttrs(ProvidedTargets: Targets, ProvidedBA: Ctx.BA, ProvidedReexports: Ctx.Reexports, |
108 | ProvidedClients: Opts.LinkerOpts.AllowableClients, |
109 | ProvidedRPaths: Opts.LinkerOpts.RPaths, FT: Ctx.FT)) |
110 | return EXIT_FAILURE; |
111 | }; |
112 | |
113 | // Set up compilation. |
114 | std::unique_ptr<CompilerInstance> CI(new CompilerInstance()); |
115 | CI->setFileManager(FM.get()); |
116 | CI->createDiagnostics(); |
117 | if (!CI->hasDiagnostics()) |
118 | return EXIT_FAILURE; |
119 | |
120 | // Execute, verify and gather AST results. |
121 | // An invocation is ran for each unique target triple and for each header |
122 | // access level. |
123 | Records FrontendRecords; |
124 | for (const auto &[Targ, Trip] : Opts.DriverOpts.Targets) { |
125 | Ctx.Verifier->setTarget(Targ); |
126 | Ctx.Slice = std::make_shared<FrontendRecordsSlice>(args: Trip); |
127 | for (const HeaderType Type : |
128 | {HeaderType::Public, HeaderType::Private, HeaderType::Project}) { |
129 | std::vector<std::string> ArgStrings = Opts.getClangFrontendArgs(); |
130 | Opts.addConditionalCC1Args(ArgStrings, Targ: Trip, Type); |
131 | Ctx.Type = Type; |
132 | StringRef = getName(T: Ctx.Type); |
133 | if (!runFrontend(ProgName, Label: HeaderLabel, Verbose: Opts.DriverOpts.Verbose, Ctx, |
134 | FS: InMemoryFileSystem.get(), InitialArgs: ArgStrings)) |
135 | return EXIT_FAILURE; |
136 | |
137 | // Run extra passes for unique compiler arguments. |
138 | for (const auto &[Label, ExtraArgs] : Opts.FEOpts.UniqueArgs) { |
139 | std::vector<std::string> FinalArguments = ArgStrings; |
140 | llvm::append_range(C&: FinalArguments, R: ExtraArgs); |
141 | if (!runFrontend(ProgName, Label: Label + " " + HeaderLabel, |
142 | Verbose: Opts.DriverOpts.Verbose, Ctx, FS: InMemoryFileSystem.get(), |
143 | InitialArgs: FinalArguments)) |
144 | return EXIT_FAILURE; |
145 | } |
146 | } |
147 | FrontendRecords.emplace_back(Args: std::move(Ctx.Slice)); |
148 | } |
149 | |
150 | if (Ctx.Verifier->verifyRemainingSymbols() == DylibVerifier::Result::Invalid) |
151 | return EXIT_FAILURE; |
152 | |
153 | // After symbols have been collected, prepare to write output. |
154 | auto Out = CI->createOutputFile(OutputPath: Ctx.OutputLoc, /*Binary=*/false, |
155 | /*RemoveFileOnSignal=*/false, |
156 | /*UseTemporary=*/false, |
157 | /*CreateMissingDirectories=*/false); |
158 | if (!Out) |
159 | return EXIT_FAILURE; |
160 | |
161 | // Assign attributes for serialization. |
162 | InterfaceFile IF(Ctx.Verifier->takeExports()); |
163 | // Assign attributes that are the same per slice first. |
164 | for (const auto &TargetInfo : Opts.DriverOpts.Targets) { |
165 | IF.addTarget(Target: TargetInfo.first); |
166 | IF.setFromBinaryAttrs(BA: Ctx.BA, Targ: TargetInfo.first); |
167 | } |
168 | // Then assign potentially different attributes per slice after. |
169 | auto assignLibAttrs = |
170 | [&IF]( |
171 | const auto &Attrs, |
172 | std::function<void(InterfaceFile *, StringRef, const Target &)> Add) { |
173 | for (const auto &Lib : Attrs) |
174 | for (const auto &T : IF.targets(Lib.getValue())) |
175 | Add(&IF, Lib.getKey(), T); |
176 | }; |
177 | |
178 | assignLibAttrs(Opts.LinkerOpts.AllowableClients, |
179 | &InterfaceFile::addAllowableClient); |
180 | assignLibAttrs(Opts.LinkerOpts.RPaths, &InterfaceFile::addRPath); |
181 | assignLibAttrs(Ctx.Reexports, &InterfaceFile::addReexportedLibrary); |
182 | |
183 | // Write output file and perform CI cleanup. |
184 | if (auto Err = TextAPIWriter::writeToStream(OS&: *Out, File: IF, FileKind: Ctx.FT)) { |
185 | Diag->Report(DiagID: diag::err_cannot_write_file) |
186 | << Ctx.OutputLoc << std::move(Err); |
187 | CI->clearOutputFiles(/*EraseFiles=*/true); |
188 | return EXIT_FAILURE; |
189 | } |
190 | |
191 | CI->clearOutputFiles(/*EraseFiles=*/false); |
192 | return EXIT_SUCCESS; |
193 | } |
194 | |
195 | int clang_installapi_main(int argc, char **argv, |
196 | const llvm::ToolContext &ToolContext) { |
197 | // Standard set up, so program fails gracefully. |
198 | llvm::sys::PrintStackTraceOnErrorSignal(Argv0: argv[0]); |
199 | llvm::PrettyStackTraceProgram StackPrinter(argc, argv); |
200 | llvm::llvm_shutdown_obj Shutdown; |
201 | |
202 | if (llvm::sys::Process::FixupStandardFileDescriptors()) |
203 | return EXIT_FAILURE; |
204 | |
205 | const char *ProgName = |
206 | ToolContext.NeedsPrependArg ? ToolContext.PrependArg : ToolContext.Path; |
207 | return run(Args: llvm::ArrayRef(argv, argc), ProgName); |
208 | } |
209 | |