Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 21 additions & 15 deletions clang/include/clang/Frontend/CompilerInstance.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
#include "llvm/Support/BuryPointer.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/VirtualFileSystem.h"
#include "llvm/Support/VirtualOutputBackend.h"
#include <cassert>
#include <list>
#include <memory>
Expand Down Expand Up @@ -97,6 +98,9 @@ class CompilerInstance : public ModuleLoader {
/// The file manager.
IntrusiveRefCntPtr<FileManager> FileMgr;

/// The output manager.
IntrusiveRefCntPtr<llvm::vfs::OutputBackend> OutputMgr;

/// The source manager.
IntrusiveRefCntPtr<SourceManager> SourceMgr;

Expand Down Expand Up @@ -180,22 +184,8 @@ class CompilerInstance : public ModuleLoader {
/// 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;
std::optional<llvm::sys::fs::TempFile> File;

OutputFile(std::string filename,
std::optional<llvm::sys::fs::TempFile> file)
: Filename(std::move(filename)), File(std::move(file)) {}
};

/// The list of active output files.
std::list<OutputFile> OutputFiles;
std::list<llvm::vfs::OutputFile> OutputFiles;

/// Force an output buffer.
std::unique_ptr<llvm::raw_pwrite_stream> OutputStream;
Expand Down Expand Up @@ -448,6 +438,22 @@ class CompilerInstance : public ModuleLoader {
/// Replace the current file manager and virtual file system.
void setFileManager(IntrusiveRefCntPtr<FileManager> Value);

/// @}
/// @name Output Manager
/// @{

/// Set the output manager.
void
setOutputManager(IntrusiveRefCntPtr<llvm::vfs::OutputBackend> NewOutputs);

/// Create an output manager.
void createOutputManager();

bool hasOutputManager() const { return bool(OutputMgr); }

llvm::vfs::OutputBackend &getOutputManager();
llvm::vfs::OutputBackend &getOrCreateOutputManager();

/// @}
/// @name Source Manager
/// @{
Expand Down
179 changes: 61 additions & 118 deletions clang/lib/Frontend/CompilerInstance.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@
#include "llvm/Support/TimeProfiler.h"
#include "llvm/Support/Timer.h"
#include "llvm/Support/VirtualFileSystem.h"
#include "llvm/Support/VirtualOutputBackends.h"
#include "llvm/Support/VirtualOutputError.h"
#include "llvm/Support/raw_ostream.h"
#include "llvm/TargetParser/Host.h"
#include <optional>
Expand Down Expand Up @@ -522,6 +524,10 @@ void CompilerInstance::createPreprocessor(TranslationUnitKind TUKind) {
collectVFSEntries(*this, ModuleDepCollector);
}

// Modules need an output manager.
if (!hasOutputManager())
createOutputManager();

for (auto &Listener : DependencyCollectors)
Listener->attachToPreprocessor(*PP);

Expand Down Expand Up @@ -778,32 +784,23 @@ void CompilerInstance::createSema(TranslationUnitKind TUKind,
void CompilerInstance::clearOutputFiles(bool EraseFiles) {
// 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;
}

llvm::Error E = OF.File->keep(OF.Filename);
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();
},
[&](const llvm::ErrorInfoBase &EIB) { // Handle any remaining error
getDiagnostics().Report(diag::err_fe_unable_to_open_output)
<< O.getPath() << EIB.message();
});
}
OutputFiles.clear();
if (DeleteBuiltModules) {
Expand Down Expand Up @@ -837,6 +834,30 @@ std::unique_ptr<raw_pwrite_stream> CompilerInstance::createNullOutputFile() {
return std::make_unique<llvm::raw_null_ostream>();
}

// Output Manager

void CompilerInstance::setOutputManager(
IntrusiveRefCntPtr<llvm::vfs::OutputBackend> NewOutputs) {
assert(!OutputMgr && "Already has an output manager");
OutputMgr = std::move(NewOutputs);
}

void CompilerInstance::createOutputManager() {
assert(!OutputMgr && "Already has an output manager");
OutputMgr = llvm::makeIntrusiveRefCnt<llvm::vfs::OnDiskOutputBackend>();
}

llvm::vfs::OutputBackend &CompilerInstance::getOutputManager() {
assert(OutputMgr);
return *OutputMgr;
}

llvm::vfs::OutputBackend &CompilerInstance::getOrCreateOutputManager() {
if (!hasOutputManager())
createOutputManager();
return getOutputManager();
}

std::unique_ptr<raw_pwrite_stream>
CompilerInstance::createOutputFile(StringRef OutputPath, bool Binary,
bool RemoveFileOnSignal, bool UseTemporary,
Expand Down Expand Up @@ -871,98 +892,20 @@ CompilerInstance::createOutputFileImpl(StringRef OutputPath, bool Binary,
OutputPath = *AbsPath;
}

std::unique_ptr<llvm::raw_fd_ostream> OS;
std::optional<StringRef> 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;
}
}
}

std::optional<llvm::sys::fs::TempFile> 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";
llvm::sys::fs::OpenFlags BinaryFlags =
Binary ? llvm::sys::fs::OF_None : llvm::sys::fs::OF_Text;
Expected<llvm::sys::fs::TempFile> ExpectedFile =
llvm::sys::fs::TempFile::create(
TempPath, llvm::sys::fs::all_read | llvm::sys::fs::all_write,
BinaryFlags);

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, llvm::sys::fs::all_read | llvm::sys::fs::all_write,
BinaryFlags);
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 (!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));

if (!Binary || OS->supportsSeeking())
return std::move(OS);

return std::make_unique<llvm::buffer_unique_ostream>(std::move(OS));
using namespace llvm::vfs;
Expected<OutputFile> O = getOrCreateOutputManager().createFile(
OutputPath,
OutputConfig()
.setTextWithCRLF(!Binary)
.setDiscardOnSignal(RemoveFileOnSignal)
.setAtomicWrite(UseTemporary)
.setImplyCreateDirectories(UseTemporary && CreateMissingDirectories));
if (!O)
return O.takeError();

O->discardOnDestroy([](llvm::Error E) { consumeError(std::move(E)); });
OutputFiles.push_back(std::move(*O));
return OutputFiles.back().createProxy();
}

// Initialization Utilities
Expand Down
23 changes: 15 additions & 8 deletions clang/tools/clang-installapi/ClangInstallAPI.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -153,12 +153,15 @@ static bool run(ArrayRef<const char *> Args, const char *ProgName) {
return EXIT_FAILURE;

// After symbols have been collected, prepare to write output.
auto Out = CI->createOutputFile(Ctx.OutputLoc, /*Binary=*/false,
/*RemoveFileOnSignal=*/false,
/*UseTemporary=*/false,
/*CreateMissingDirectories=*/false);
if (!Out)
auto Out = CI->getOrCreateOutputManager().createFile(
Ctx.OutputLoc, llvm::vfs::OutputConfig()
.setTextWithCRLF()
.setNoImplyCreateDirectories()
.setNoAtomicWrite());
if (!Out) {
Diag->Report(diag::err_cannot_open_file) << Ctx.OutputLoc;
return EXIT_FAILURE;
}

// Assign attributes for serialization.
InterfaceFile IF(Ctx.Verifier->takeExports());
Expand Down Expand Up @@ -186,11 +189,15 @@ static bool run(ArrayRef<const char *> Args, const char *ProgName) {
if (auto Err = TextAPIWriter::writeToStream(*Out, IF, Ctx.FT)) {
Diag->Report(diag::err_cannot_write_file)
<< Ctx.OutputLoc << std::move(Err);
CI->clearOutputFiles(/*EraseFiles=*/true);
if (auto Err = Out->discard())
llvm::consumeError(std::move(Err));
return EXIT_FAILURE;
}
if (auto Err = Out->keep()) {
Diag->Report(diag::err_cannot_write_file)
<< Ctx.OutputLoc << std::move(Err);
return EXIT_FAILURE;
}

CI->clearOutputFiles(/*EraseFiles=*/false);
return EXIT_SUCCESS;
}

Expand Down