Add OutputBackend and OutputFile to the `llvm::vfs` namespace for
virtualizing compiler outputs. This is intended for use in Clang,
The headers are:
- llvm/Support/VirtualOutputConfig.h
- llvm/Support/VirtualOutputError.h
- llvm/Support/VirtualOutputFile.h
- llvm/Support/VirtualOutputBackend.h
OutputFile is moveable and owns an OutputFileImpl, which is provided by
the derived OutputBackend.
- OutputFileImpl::keep() and OutputFileImpl::discard() should keep or
discard the output. OutputFile guarantees that exactly one of these
will be called before destruction.
- OutputFile::keep() and OutputFile::discard() wrap OutputFileImpl
and catch usage errors such as double-close.
- OutputFile::discardOnDestroy() installs an error handler for the
destructor to use if the file is still open. The handler will be
called if discard() fails.
- OutputFile::~OutputFile() calls report_fatal_error() if none of
keep(), discard(), or discardOnDestroy() has been called. It still
calls OutputFileImpl::discard().
- getOS() returns the wrapped raw_pwrite_stream. For convenience,
OutputFile has an implicit conversion to `raw_ostream` and
`raw_ostream &operator<<(OutputFile&, T&&)`.
OutputBackend can be stored in IntrusiveRefCntPtr.
- Most operations are thread-safe.
- clone() returns a backend that targets the same destination.
All operations are thread-safe when done on different clones.
- createFile() takes a path and an OutputConfig (see below) and returns
an OutputFile. Backends implement createFileImpl().
OutputConfig has flags to configure the output. Backends may ignore or
override flags that aren't relevant or implementable.
- The initial flags are:
- AtomicWrite: whether the output should appear atomically (e.g., by
using a temporary file and renaming it).
- CrashCleanup: whether the output should be cleaned up if there's a
crash (e.g., with RemoveFileOnSignal).
- ImplyCreateDirectories: whether to implicitly create missing
directories in the path to the file.
- Text: matches sys::fs::OF_Text.
- CRLF: matches sys::fs::OF_CRLF.
- Append: matches sys::fs::OF_Append and can use with AtomicWrite
for atomic append.
- OnlyIfDifferent: skip writting the output file if the existing file
at the output path is identical to the content to be written.
- Each "Flag" has `setFlag(bool)` and `bool getFlag()` and shortcuts
`setFlag()` and `setNoFlag()`. The setters are `constexpr` and return
`OutputConfig&` to make it easy to declare a default value for a filed
in a class or struct.
- Setters and getters for Binary and TextWithCRLF are derived from Text
and CRLF. For convenience, sys::fs::OpenFlags can be passed
directly to setOpenFlags().
This patch intentionally lacks a number of important features that have
been left for follow-ups:
- Set a (virtual) current working directory.
- Create a directory.
- Create a file or directory with a unique name (avoiding collisions
with existing filenames).
Patch originally by dexonsmith
111 lines
3.4 KiB
C++
111 lines
3.4 KiB
C++
//===----------------------------------------------------------------------===//
|
|
//
|
|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
// See https://llvm.org/LICENSE.txt for license information.
|
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
///
|
|
/// \file
|
|
/// This file implements \c OutputFile class methods.
|
|
///
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "llvm/Support/VirtualOutputFile.h"
|
|
#include "llvm/Support/VirtualOutputError.h"
|
|
#include "llvm/Support/raw_ostream.h"
|
|
#include "llvm/Support/raw_ostream_proxy.h"
|
|
|
|
using namespace llvm;
|
|
using namespace llvm::vfs;
|
|
|
|
char OutputFileImpl::ID = 0;
|
|
char NullOutputFileImpl::ID = 0;
|
|
|
|
void OutputFileImpl::anchor() {}
|
|
void NullOutputFileImpl::anchor() {}
|
|
|
|
class OutputFile::TrackedProxy : public raw_pwrite_stream_proxy {
|
|
public:
|
|
void resetProxy() {
|
|
TrackingPointer = nullptr;
|
|
resetProxiedOS();
|
|
}
|
|
|
|
explicit TrackedProxy(TrackedProxy *&TrackingPointer, raw_pwrite_stream &OS)
|
|
: raw_pwrite_stream_proxy(OS), TrackingPointer(TrackingPointer) {
|
|
assert(!TrackingPointer && "Expected to add a proxy");
|
|
TrackingPointer = this;
|
|
}
|
|
|
|
~TrackedProxy() override { resetProxy(); }
|
|
|
|
TrackedProxy *&TrackingPointer;
|
|
};
|
|
|
|
Expected<std::unique_ptr<raw_pwrite_stream>> OutputFile::createProxy() {
|
|
if (OpenProxy)
|
|
return make_error<OutputError>(getPath(), OutputErrorCode::has_open_proxy);
|
|
|
|
return std::make_unique<TrackedProxy>(OpenProxy, getOS());
|
|
}
|
|
|
|
Error OutputFile::keep() {
|
|
// Catch double-closing logic bugs.
|
|
if (LLVM_UNLIKELY(!Impl))
|
|
report_fatal_error(
|
|
make_error<OutputError>(getPath(), OutputErrorCode::already_closed));
|
|
|
|
// Report a fatal error if there's an open proxy and the file is being kept.
|
|
// This is safer than relying on clients to remember to flush(). Also call
|
|
// OutputFile::discard() to give the backend a chance to clean up any
|
|
// side effects (such as temporaries).
|
|
if (LLVM_UNLIKELY(OpenProxy))
|
|
report_fatal_error(joinErrors(
|
|
make_error<OutputError>(getPath(), OutputErrorCode::has_open_proxy),
|
|
discard()));
|
|
|
|
Error E = Impl->keep();
|
|
Impl = nullptr;
|
|
DiscardOnDestroyHandler = nullptr;
|
|
return E;
|
|
}
|
|
|
|
Error OutputFile::discard() {
|
|
// Catch double-closing logic bugs.
|
|
if (LLVM_UNLIKELY(!Impl))
|
|
report_fatal_error(
|
|
make_error<OutputError>(getPath(), OutputErrorCode::already_closed));
|
|
|
|
// Be lenient about open proxies since client teardown paths won't
|
|
// necessarily clean up in the right order. Reset the proxy to flush any
|
|
// current content; if there is another write, there should be quick crash on
|
|
// null dereference.
|
|
if (OpenProxy)
|
|
OpenProxy->resetProxy();
|
|
|
|
Error E = Impl->discard();
|
|
Impl = nullptr;
|
|
DiscardOnDestroyHandler = nullptr;
|
|
return E;
|
|
}
|
|
|
|
void OutputFile::destroy() {
|
|
if (!Impl)
|
|
return;
|
|
|
|
// Clean up the file. Move the discard handler into a local since discard
|
|
// will reset it.
|
|
auto DiscardHandler = std::move(DiscardOnDestroyHandler);
|
|
Error E = discard();
|
|
assert(!Impl && "Expected discard to destroy Impl");
|
|
|
|
// If there's no handler, report a fatal error.
|
|
if (LLVM_UNLIKELY(!DiscardHandler))
|
|
llvm::report_fatal_error(joinErrors(
|
|
make_error<OutputError>(getPath(), OutputErrorCode::not_closed),
|
|
std::move(E)));
|
|
else if (E)
|
|
DiscardHandler(std::move(E));
|
|
}
|