diff --git a/clang/include/clang/Frontend/CompilerInstance.h b/clang/include/clang/Frontend/CompilerInstance.h --- a/clang/include/clang/Frontend/CompilerInstance.h +++ b/clang/include/clang/Frontend/CompilerInstance.h @@ -23,6 +23,7 @@ #include "llvm/ADT/StringRef.h" #include "llvm/Support/BuryPointer.h" #include "llvm/Support/FileSystem.h" +#include "llvm/Support/VirtualOutputBackend.h" #include #include #include @@ -85,6 +86,9 @@ /// The file manager. IntrusiveRefCntPtr FileMgr; + /// The output backend. + IntrusiveRefCntPtr TheOutputBackend; + /// The source manager. IntrusiveRefCntPtr SourceMgr; @@ -157,21 +161,8 @@ /// The stream for verbose output. raw_ostream *VerboseOutputStream = &llvm::errs(); - /// Holds information about the output file. - /// - /// If TempFilename is not empty we must rename it to Filename at the end. - /// TempFilename may be empty and Filename non-empty if creating the temporary - /// failed. - struct OutputFile { - std::string Filename; - Optional File; - - OutputFile(std::string filename, Optional file) - : Filename(std::move(filename)), File(std::move(file)) {} - }; - /// The list of active output files. - std::list OutputFiles; + std::list OutputFiles; /// Force an output buffer. std::unique_ptr OutputStream; @@ -412,6 +403,22 @@ /// Replace the current file manager and virtual file system. void setFileManager(FileManager *Value); + /// } + /// @name Output Backend. + /// { + + /// Set the output backend. + void + setOutputBackend(IntrusiveRefCntPtr NewOutputs); + + /// Create an output manager. + void createOutputBackend(); + + bool hasOutputBackend() const { return bool(TheOutputBackend); } + + llvm::vfs::OutputBackend &getOutputBackend(); + llvm::vfs::OutputBackend &getOrCreateOutputBackend(); + /// } /// @name Source Manager /// { diff --git a/clang/lib/Frontend/CompilerInstance.cpp b/clang/lib/Frontend/CompilerInstance.cpp --- a/clang/lib/Frontend/CompilerInstance.cpp +++ b/clang/lib/Frontend/CompilerInstance.cpp @@ -53,6 +53,7 @@ #include "llvm/Support/Signals.h" #include "llvm/Support/TimeProfiler.h" #include "llvm/Support/Timer.h" +#include "llvm/Support/VirtualOutputBackends.h" #include "llvm/Support/raw_ostream.h" #include #include @@ -521,6 +522,10 @@ collectVFSEntries(*this, ModuleDepCollector); } + // Modules need an output manager. + if (!hasOutputBackend()) + createOutputBackend(); + for (auto &Listener : DependencyCollectors) Listener->attachToPreprocessor(*PP); @@ -764,36 +769,19 @@ // The ASTConsumer can own streams that write to the output files. assert(!hasASTConsumer() && "ASTConsumer should be reset"); // Ignore errors that occur when trying to discard the temp file. - for (OutputFile &OF : OutputFiles) { - if (EraseFiles) { - if (OF.File) - consumeError(OF.File->discard()); - if (!OF.Filename.empty()) - llvm::sys::fs::remove(OF.Filename); - continue; - } - - if (!OF.File) - continue; - - if (OF.File->TmpName.empty()) { - consumeError(OF.File->discard()); - continue; - } - - // If '-working-directory' was passed, the output filename should be - // relative to that. - SmallString<128> NewOutFile(OF.Filename); - FileMgr->FixupRelativePath(NewOutFile); - - llvm::Error E = OF.File->keep(NewOutFile); - if (!E) - continue; - - getDiagnostics().Report(diag::err_unable_to_rename_temp) - << OF.File->TmpName << OF.Filename << std::move(E); - - llvm::sys::fs::remove(OF.File->TmpName); + if (!EraseFiles) { + for (auto &O : OutputFiles) + llvm::handleAllErrors( + O.keep(), + [&](const llvm::vfs::TempFileOutputError &E) { + getDiagnostics().Report(diag::err_unable_to_rename_temp) + << E.getTempPath() << E.getOutputPath() + << E.convertToErrorCode().message(); + }, + [&](const llvm::vfs::OutputError &E) { + getDiagnostics().Report(diag::err_fe_unable_to_open_output) + << E.getOutputPath() << E.convertToErrorCode().message(); + }); } OutputFiles.clear(); if (DeleteBuiltModules) { @@ -827,6 +815,30 @@ return std::make_unique(); } +void CompilerInstance::setOutputBackend( + IntrusiveRefCntPtr NewOutputs) { + assert(!TheOutputBackend && "Already has an output manager"); + TheOutputBackend = std::move(NewOutputs); +} + +void CompilerInstance::createOutputBackend() { + assert(!TheOutputBackend && "Already has an output manager"); + TheOutputBackend = + llvm::makeIntrusiveRefCnt(); +} + +llvm::vfs::OutputBackend &CompilerInstance::getOutputBackend() { + assert(TheOutputBackend); + return *TheOutputBackend; +} + +llvm::vfs::OutputBackend &CompilerInstance::getOrCreateOutputBackend() { + if (!hasOutputBackend()) + createOutputBackend(); + return getOutputBackend(); +} + + std::unique_ptr CompilerInstance::createOutputFile(StringRef OutputPath, bool Binary, bool RemoveFileOnSignal, bool UseTemporary, @@ -849,94 +861,29 @@ assert((!CreateMissingDirectories || UseTemporary) && "CreateMissingDirectories is only allowed when using temporary files"); - std::unique_ptr OS; - Optional OSFile; - - if (UseTemporary) { - if (OutputPath == "-") - UseTemporary = false; - else { - llvm::sys::fs::file_status Status; - llvm::sys::fs::status(OutputPath, Status); - if (llvm::sys::fs::exists(Status)) { - // Fail early if we can't write to the final destination. - if (!llvm::sys::fs::can_write(OutputPath)) - return llvm::errorCodeToError( - make_error_code(llvm::errc::operation_not_permitted)); - - // Don't use a temporary if the output is a special file. This handles - // things like '-o /dev/null' - if (!llvm::sys::fs::is_regular_file(Status)) - UseTemporary = false; - } - } - } - - Optional Temp; - if (UseTemporary) { - // Create a temporary file. - // Insert -%%%%%%%% before the extension (if any), and because some tools - // (noticeable, clang's own GlobalModuleIndex.cpp) glob for build - // artifacts, also append .tmp. - StringRef OutputExtension = llvm::sys::path::extension(OutputPath); - SmallString<128> TempPath = - StringRef(OutputPath).drop_back(OutputExtension.size()); - TempPath += "-%%%%%%%%"; - TempPath += OutputExtension; - TempPath += ".tmp"; - Expected ExpectedFile = - llvm::sys::fs::TempFile::create( - TempPath, llvm::sys::fs::all_read | llvm::sys::fs::all_write, - Binary ? llvm::sys::fs::OF_None : llvm::sys::fs::OF_Text); - - llvm::Error E = handleErrors( - ExpectedFile.takeError(), [&](const llvm::ECError &E) -> llvm::Error { - std::error_code EC = E.convertToErrorCode(); - if (CreateMissingDirectories && - EC == llvm::errc::no_such_file_or_directory) { - StringRef Parent = llvm::sys::path::parent_path(OutputPath); - EC = llvm::sys::fs::create_directories(Parent); - if (!EC) { - ExpectedFile = llvm::sys::fs::TempFile::create(TempPath); - if (!ExpectedFile) - return llvm::errorCodeToError( - llvm::errc::no_such_file_or_directory); - } - } - return llvm::errorCodeToError(EC); - }); - - if (E) { - consumeError(std::move(E)); - } else { - Temp = std::move(ExpectedFile.get()); - OS.reset(new llvm::raw_fd_ostream(Temp->FD, /*shouldClose=*/false)); - OSFile = Temp->TmpName; - } - // If we failed to create the temporary, fallback to writing to the file - // directly. This handles the corner case where we cannot write to the - // directory, but can write to the file. + // If '-working-directory' was passed, the output filename should be + // relative to that. + Optional> AbsPath; + if (OutputPath != "-" && !llvm::sys::path::is_absolute(OutputPath)) { + AbsPath.emplace(OutputPath); + FileMgr->FixupRelativePath(*AbsPath); + OutputPath = *AbsPath; } - if (!OS) { - OSFile = OutputPath; - std::error_code EC; - OS.reset(new llvm::raw_fd_ostream( - *OSFile, EC, - (Binary ? llvm::sys::fs::OF_None : llvm::sys::fs::OF_TextWithCRLF))); - if (EC) - return llvm::errorCodeToError(EC); - } - - // Add the output file -- but don't try to remove "-", since this means we are - // using stdin. - OutputFiles.emplace_back(((OutputPath != "-") ? OutputPath : "").str(), - std::move(Temp)); + using namespace llvm::vfs; + Expected O = getOrCreateOutputBackend().createFile( + OutputPath, + OutputConfig() + .setTextWithCRLF(!Binary) + .setDiscardOnSignal(RemoveFileOnSignal) + .setAtomicWrite(UseTemporary) + .setImplyCreateDirectories(UseTemporary && CreateMissingDirectories)); + if (!O) + return O.takeError(); - if (!Binary || OS->supportsSeeking()) - return std::move(OS); - - return std::make_unique(std::move(OS)); + O->discardOnDestroy([](llvm::Error E) { consumeError(std::move(E)); }); + OutputFiles.push_back(std::move(*O)); + return OutputFiles.back().createProxy(); } // Initialization Utilities @@ -1231,6 +1178,11 @@ SourceMgr.pushModuleBuildStack(ModuleName, FullSourceLoc(ImportLoc, ImportingInstance.getSourceManager())); + // Share an output manager. + assert(ImportingInstance.hasOutputBackend() && + "Expected an output manager to already be set up"); + Instance.setOutputBackend(&ImportingInstance.getOutputBackend()); + // If we're collecting module dependencies, we need to share a collector // between all of the module CompilerInstances. Other than that, we don't // want to produce any dependency output from the module build.