mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
399 lines
14 KiB
C++
399 lines
14 KiB
C++
//===--- CompilerInvocation.cpp - Compiler invocation utilities -----------===//
|
|
//
|
|
// This source file is part of the Swift.org open source project
|
|
//
|
|
// Copyright (c) 2022 Apple Inc. and the Swift project authors
|
|
// Licensed under Apache License v2.0 with Runtime Library Exception
|
|
//
|
|
// See https://swift.org/LICENSE.txt for license information
|
|
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "swift/IDETool/CompilerInvocation.h"
|
|
|
|
#include "swift/Basic/Assertions.h"
|
|
#include "swift/Driver/FrontendUtil.h"
|
|
#include "swift/Frontend/Frontend.h"
|
|
#include "clang/AST/DeclObjC.h"
|
|
#include "clang/Basic/TargetInfo.h"
|
|
#include "clang/Frontend/CompilerInstance.h"
|
|
#include "clang/Frontend/PCHContainerOperations.h"
|
|
#include "clang/Frontend/TextDiagnosticBuffer.h"
|
|
#include "clang/Lex/PreprocessorOptions.h"
|
|
#include "clang/Serialization/ASTReader.h"
|
|
|
|
using namespace swift;
|
|
|
|
static void disableExpensiveSILOptions(SILOptions &Opts) {
|
|
// Disable the sanitizers.
|
|
Opts.Sanitizers = {};
|
|
|
|
// Disable PGO and code coverage.
|
|
Opts.GenerateProfile = false;
|
|
Opts.EmitProfileCoverageMapping = false;
|
|
Opts.UseProfile = "";
|
|
}
|
|
|
|
// Adjust the cc1 triple string we got from clang, to make sure it will be
|
|
// accepted when it goes through the swift clang importer.
|
|
static std::string adjustClangTriple(StringRef TripleStr) {
|
|
std::string Result;
|
|
llvm::raw_string_ostream OS(Result);
|
|
|
|
llvm::Triple Triple(TripleStr);
|
|
switch (Triple.getSubArch()) {
|
|
case llvm::Triple::SubArchType::ARMSubArch_v7:
|
|
OS << "armv7"; break;
|
|
case llvm::Triple::SubArchType::ARMSubArch_v7s:
|
|
OS << "armv7s"; break;
|
|
case llvm::Triple::SubArchType::ARMSubArch_v7k:
|
|
OS << "armv7k"; break;
|
|
case llvm::Triple::SubArchType::ARMSubArch_v6:
|
|
OS << "armv6"; break;
|
|
case llvm::Triple::SubArchType::ARMSubArch_v6m:
|
|
OS << "armv6m"; break;
|
|
case llvm::Triple::SubArchType::ARMSubArch_v6k:
|
|
OS << "armv6k"; break;
|
|
case llvm::Triple::SubArchType::ARMSubArch_v6t2:
|
|
OS << "armv6t2"; break;
|
|
case llvm::Triple::SubArchType::ARMSubArch_v5:
|
|
OS << "armv5"; break;
|
|
case llvm::Triple::SubArchType::ARMSubArch_v5te:
|
|
OS << "armv5te"; break;
|
|
case llvm::Triple::SubArchType::ARMSubArch_v4t:
|
|
OS << "armv4t"; break;
|
|
default:
|
|
// Adjust i386-macosx to x86_64 because there is no Swift stdlib for i386.
|
|
if ((Triple.getOS() == llvm::Triple::MacOSX ||
|
|
Triple.getOS() == llvm::Triple::Darwin) &&
|
|
Triple.getArch() == llvm::Triple::x86) {
|
|
OS << "x86_64";
|
|
} else {
|
|
OS << Triple.getArchName();
|
|
}
|
|
break;
|
|
}
|
|
OS << '-' << Triple.getVendorName() << '-'
|
|
<< Triple.getOSAndEnvironmentName();
|
|
OS.flush();
|
|
return Result;
|
|
}
|
|
|
|
static FrontendInputsAndOutputs resolveSymbolicLinksInInputs(
|
|
FrontendInputsAndOutputs &inputsAndOutputs, StringRef UnresolvedPrimaryFile,
|
|
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> FileSystem,
|
|
std::string &Error) {
|
|
assert(FileSystem);
|
|
|
|
llvm::SmallString<128> PrimaryFile;
|
|
if (auto err = FileSystem->getRealPath(UnresolvedPrimaryFile, PrimaryFile))
|
|
PrimaryFile = UnresolvedPrimaryFile;
|
|
|
|
unsigned primaryCount = 0;
|
|
// FIXME: The frontend should be dealing with symlinks, maybe similar to
|
|
// clang's FileManager ?
|
|
FrontendInputsAndOutputs replacementInputsAndOutputs;
|
|
for (const InputFile &input : inputsAndOutputs.getAllInputs()) {
|
|
llvm::SmallString<128> newFilename;
|
|
if (auto err = FileSystem->getRealPath(input.getFileName(), newFilename))
|
|
newFilename = input.getFileName();
|
|
llvm::sys::path::native(newFilename);
|
|
bool newIsPrimary = input.isPrimary() ||
|
|
(!PrimaryFile.empty() && PrimaryFile == newFilename);
|
|
if (newIsPrimary) {
|
|
++primaryCount;
|
|
}
|
|
assert(primaryCount < 2 && "cannot handle multiple primaries");
|
|
|
|
replacementInputsAndOutputs.addInput(
|
|
InputFile(newFilename.str(), newIsPrimary, input.getBuffer()));
|
|
}
|
|
|
|
if (PrimaryFile.empty() || primaryCount == 1) {
|
|
return replacementInputsAndOutputs;
|
|
}
|
|
|
|
llvm::SmallString<64> Err;
|
|
llvm::raw_svector_ostream OS(Err);
|
|
OS << "'" << PrimaryFile << "' is not part of the input files";
|
|
Error = std::string(OS.str());
|
|
return replacementInputsAndOutputs;
|
|
}
|
|
|
|
namespace {
|
|
class StreamDiagConsumer : public DiagnosticConsumer {
|
|
llvm::raw_ostream &OS;
|
|
|
|
public:
|
|
StreamDiagConsumer(llvm::raw_ostream &OS) : OS(OS) {}
|
|
|
|
void handleDiagnostic(SourceManager &SM,
|
|
const DiagnosticInfo &Info) override {
|
|
// FIXME: Print location info if available.
|
|
switch (Info.Kind) {
|
|
case DiagnosticKind::Error:
|
|
OS << "error: ";
|
|
break;
|
|
case DiagnosticKind::Warning:
|
|
OS << "warning: ";
|
|
break;
|
|
case DiagnosticKind::Note:
|
|
OS << "note: ";
|
|
break;
|
|
case DiagnosticKind::Remark:
|
|
OS << "remark: ";
|
|
break;
|
|
}
|
|
DiagnosticEngine::formatDiagnosticText(OS, Info.FormatString,
|
|
Info.FormatArgs);
|
|
}
|
|
};
|
|
} // end anonymous namespace
|
|
|
|
bool ide::initCompilerInvocation(
|
|
CompilerInvocation &Invocation, ArrayRef<const char *> OrigArgs,
|
|
FrontendOptions::ActionType Action, DiagnosticEngine &Diags,
|
|
StringRef UnresolvedPrimaryFile,
|
|
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> FileSystem,
|
|
const std::string &swiftExecutablePath,
|
|
const std::string &runtimeResourcePath, time_t sessionTimestamp,
|
|
std::string &Error) {
|
|
SmallVector<const char *, 16> Args;
|
|
// Make sure to put '-resource-dir' at the top to allow overriding them with
|
|
// the passed in arguments.
|
|
Args.push_back("-resource-dir");
|
|
Args.push_back(runtimeResourcePath.c_str());
|
|
Args.append(OrigArgs.begin(), OrigArgs.end());
|
|
|
|
SmallString<32> ErrStr;
|
|
llvm::raw_svector_ostream ErrOS(ErrStr);
|
|
StreamDiagConsumer DiagConsumer(ErrOS);
|
|
Diags.addConsumer(DiagConsumer);
|
|
|
|
// Derive 'swiftc' path from 'swift-frontend' path (swiftExecutablePath).
|
|
SmallString<256> driverPath(swiftExecutablePath);
|
|
llvm::sys::path::remove_filename(driverPath);
|
|
llvm::sys::path::append(driverPath, "swiftc");
|
|
|
|
bool InvocationCreationFailed =
|
|
driver::getSingleFrontendInvocationFromDriverArguments(
|
|
driverPath, Args, Diags,
|
|
[&](ArrayRef<const char *> FrontendArgs) {
|
|
return Invocation.parseArgs(
|
|
FrontendArgs, Diags, /*ConfigurationFileBuffers=*/nullptr,
|
|
/*workingDirectory=*/"", swiftExecutablePath);
|
|
},
|
|
/*ForceNoOutputs=*/true);
|
|
|
|
// Remove the StreamDiagConsumer as it's no longer needed.
|
|
Diags.removeConsumer(DiagConsumer);
|
|
|
|
Error = std::string(ErrOS.str());
|
|
if (InvocationCreationFailed) {
|
|
return true;
|
|
}
|
|
|
|
std::string SymlinkResolveError;
|
|
Invocation.getFrontendOptions().InputsAndOutputs =
|
|
resolveSymbolicLinksInInputs(
|
|
Invocation.getFrontendOptions().InputsAndOutputs,
|
|
UnresolvedPrimaryFile, FileSystem, SymlinkResolveError);
|
|
|
|
// SourceKit functionalities want to proceed even if there are missing inputs.
|
|
Invocation.getFrontendOptions()
|
|
.InputsAndOutputs.setShouldRecoverMissingInputs();
|
|
|
|
if (!SymlinkResolveError.empty()) {
|
|
// resolveSymbolicLinksInInputs fails if the unresolved primary file is not
|
|
// in the input files. We can't recover from that.
|
|
Error += SymlinkResolveError;
|
|
return true;
|
|
}
|
|
|
|
ClangImporterOptions &ImporterOpts = Invocation.getClangImporterOptions();
|
|
ImporterOpts.DetailedPreprocessingRecord = true;
|
|
|
|
assert(!Invocation.getModuleName().empty());
|
|
|
|
auto &LangOpts = Invocation.getLangOptions();
|
|
LangOpts.AttachCommentsToDecls = true;
|
|
LangOpts.CollectParsedToken = true;
|
|
#if defined(_WIN32)
|
|
// Source files that might be open in an editor should not be memory mapped on Windows,
|
|
// as they will become read-only.
|
|
LangOpts.OpenSourcesAsVolatile = true;
|
|
#endif
|
|
if (LangOpts.PlaygroundTransform) {
|
|
// The playground instrumenter changes the AST in ways that disrupt the
|
|
// SourceKit functionality. Since we don't need the instrumenter, and all we
|
|
// actually need is the playground semantics visible to the user, like
|
|
// silencing the "expression resolves to an unused l-value" error, disable
|
|
// it.
|
|
LangOpts.PlaygroundTransform = false;
|
|
}
|
|
|
|
// Disable the index-store functionality for the sourcekitd requests.
|
|
auto &FrontendOpts = Invocation.getFrontendOptions();
|
|
FrontendOpts.IndexStorePath.clear();
|
|
ImporterOpts.IndexStorePath.clear();
|
|
|
|
FrontendOpts.RequestedAction = Action;
|
|
|
|
// We don't care about LLVMArgs
|
|
FrontendOpts.LLVMArgs.clear();
|
|
|
|
// To save the time for module validation, consider the lifetime of ASTManager
|
|
// as a single build session.
|
|
// NOTE: Do this only if '-disable-modules-validate-system-headers' is *not*
|
|
// explicitly enabled.
|
|
auto &SearchPathOpts = Invocation.getSearchPathOptions();
|
|
if (!SearchPathOpts.DisableModulesValidateSystemDependencies) {
|
|
// NOTE: 'SessionTimestamp - 1' because clang compares it with '<=' that may
|
|
// cause unnecessary validations if they happens within one second
|
|
// from the SourceKit startup.
|
|
ImporterOpts.ExtraArgs.push_back("-fbuild-session-timestamp=" +
|
|
std::to_string(sessionTimestamp - 1));
|
|
ImporterOpts.ExtraArgs.push_back(
|
|
"-fmodules-validate-once-per-build-session");
|
|
}
|
|
|
|
// Disable expensive SIL options to reduce time spent in SILGen.
|
|
disableExpensiveSILOptions(Invocation.getSILOptions());
|
|
|
|
return false;
|
|
}
|
|
|
|
bool ide::initInvocationByClangArguments(ArrayRef<const char *> ArgList,
|
|
CompilerInvocation &Invok,
|
|
std::string &Error) {
|
|
const auto VFS = llvm::vfs::getRealFileSystem();
|
|
|
|
clang::TextDiagnosticBuffer DiagBuf;
|
|
clang::DiagnosticOptions DiagOpts;
|
|
llvm::IntrusiveRefCntPtr<clang::DiagnosticsEngine> ClangDiags =
|
|
clang::CompilerInstance::createDiagnostics(*VFS, DiagOpts, &DiagBuf,
|
|
/*ShouldOwnClient=*/false);
|
|
|
|
// Clang expects this to be like an actual command line. So we need to pass in
|
|
// "clang" for argv[0].
|
|
std::vector<const char *> ClangArgList;
|
|
ClangArgList.push_back("clang");
|
|
ClangArgList.insert(ClangArgList.end(), ArgList.begin(), ArgList.end());
|
|
|
|
// Create a new Clang compiler invocation.
|
|
clang::CreateInvocationOptions CIOpts;
|
|
CIOpts.Diags = ClangDiags;
|
|
CIOpts.ProbePrecompiled = true;
|
|
std::unique_ptr<clang::CompilerInvocation> ClangInvok =
|
|
clang::createInvocation(ClangArgList, std::move(CIOpts));
|
|
if (!ClangInvok || ClangDiags->hasErrorOccurred()) {
|
|
for (auto I = DiagBuf.err_begin(), E = DiagBuf.err_end(); I != E; ++I) {
|
|
Error += I->second;
|
|
Error += " ";
|
|
}
|
|
return true;
|
|
}
|
|
|
|
auto &PPOpts = ClangInvok->getPreprocessorOpts();
|
|
auto &HSOpts = ClangInvok->getHeaderSearchOpts();
|
|
|
|
Invok.setTargetTriple(adjustClangTriple(ClangInvok->getTargetOpts().Triple));
|
|
if (!HSOpts.Sysroot.empty())
|
|
Invok.setSDKPath(HSOpts.Sysroot);
|
|
if (!HSOpts.ModuleCachePath.empty())
|
|
Invok.setClangModuleCachePath(HSOpts.ModuleCachePath);
|
|
|
|
auto &CCArgs = Invok.getClangImporterOptions().ExtraArgs;
|
|
for (auto MacroEntry : PPOpts.Macros) {
|
|
std::string MacroFlag;
|
|
if (MacroEntry.second)
|
|
MacroFlag += "-U";
|
|
else
|
|
MacroFlag += "-D";
|
|
MacroFlag += MacroEntry.first;
|
|
CCArgs.push_back(MacroFlag);
|
|
}
|
|
|
|
for (auto &Entry : HSOpts.UserEntries) {
|
|
switch (Entry.Group) {
|
|
case clang::frontend::Quoted:
|
|
CCArgs.push_back("-iquote");
|
|
CCArgs.push_back(Entry.Path);
|
|
break;
|
|
case clang::frontend::Angled: {
|
|
std::string Flag;
|
|
if (Entry.IsFramework)
|
|
Flag += "-F";
|
|
else
|
|
Flag += "-I";
|
|
Flag += Entry.Path;
|
|
CCArgs.push_back(Flag);
|
|
break;
|
|
}
|
|
case clang::frontend::System:
|
|
if (Entry.IsFramework)
|
|
CCArgs.push_back("-iframework");
|
|
else
|
|
CCArgs.push_back("-isystem");
|
|
CCArgs.push_back(Entry.Path);
|
|
break;
|
|
case clang::frontend::ExternCSystem:
|
|
case clang::frontend::CSystem:
|
|
case clang::frontend::CXXSystem:
|
|
case clang::frontend::ObjCSystem:
|
|
case clang::frontend::ObjCXXSystem:
|
|
case clang::frontend::After:
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!PPOpts.ImplicitPCHInclude.empty()) {
|
|
clang::FileSystemOptions FileSysOpts;
|
|
clang::FileManager FileMgr(FileSysOpts, VFS);
|
|
auto PCHContainerOperations =
|
|
std::make_shared<clang::PCHContainerOperations>();
|
|
std::string HeaderFile = clang::ASTReader::getOriginalSourceFile(
|
|
PPOpts.ImplicitPCHInclude, FileMgr,
|
|
PCHContainerOperations->getRawReader(), *ClangDiags);
|
|
if (!HeaderFile.empty()) {
|
|
CCArgs.push_back("-include");
|
|
CCArgs.push_back(std::move(HeaderFile));
|
|
}
|
|
}
|
|
for (auto &Header : PPOpts.Includes) {
|
|
CCArgs.push_back("-include");
|
|
CCArgs.push_back(Header);
|
|
}
|
|
|
|
for (auto &Entry : HSOpts.ModulesIgnoreMacros) {
|
|
std::string Flag = "-fmodules-ignore-macro=";
|
|
Flag += Entry;
|
|
CCArgs.push_back(Flag);
|
|
}
|
|
|
|
for (auto &Entry : HSOpts.VFSOverlayFiles) {
|
|
CCArgs.push_back("-ivfsoverlay");
|
|
CCArgs.push_back(Entry);
|
|
}
|
|
|
|
if (!ClangInvok->getLangOpts().isCompilingModule()) {
|
|
CCArgs.push_back("-Xclang");
|
|
llvm::SmallString<64> Str;
|
|
Str += "-fmodule-name=";
|
|
Str += ClangInvok->getLangOpts().CurrentModule;
|
|
CCArgs.push_back(std::string(Str.str()));
|
|
}
|
|
|
|
if (PPOpts.DetailedRecord) {
|
|
Invok.getClangImporterOptions().DetailedPreprocessingRecord = true;
|
|
}
|
|
|
|
if (!ClangInvok->getFrontendOpts().Inputs.empty()) {
|
|
Invok.getFrontendOptions().ImplicitObjCHeaderPath =
|
|
ClangInvok->getFrontendOpts().Inputs[0].getFile().str();
|
|
}
|
|
|
|
return false;
|
|
}
|