| 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::append_range(C&: Args, R: InitialArgs); |
| 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 | DiagnosticOptions DiagOpts; |
| 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, |
| 82 | new clang::TextDiagnosticPrinter(llvm::errs(), DiagOpts)); |
| 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 | for (const auto &T : Opts.DriverOpts.Targets) |
| 106 | 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(VFS&: FM->getVirtualFileSystem()); |
| 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 &[Attr, ArchSet] : Attrs.get()) |
| 174 | for (const auto &T : IF.targets(ArchSet)) |
| 175 | Add(&IF, Attr, 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 | |