Skip to content

Commit 1dd415a

Browse files
committed
Add a new helper, atomicallyWritingToFile, and use it
This replaces the use of a Clang utility function that was inexplicably a non-static member function of CompilerInstance. It would be nice to sink this all the way to LLVM and share the implementation across both projects, but the Clang implementation does a handful of things we don't need, and it's hard to justify including them in an LLVM-level interface. (I stared at llvm/Support/FileSystem.h for a long time before giving up.) Anyway, Serialization and FrontendTool both get their atomic writes now without depending on Clang, and without duplicating the scaffolding around the Clang API. We should probably adopt this for all our output files. No functionality change.
1 parent e8e0584 commit 1dd415a

File tree

4 files changed

+212
-101
lines changed

4 files changed

+212
-101
lines changed

include/swift/Basic/FileSystem.h

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
//
33
// This source file is part of the Swift.org open source project
44
//
5-
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
5+
// Copyright (c) 2014 - 2018 Apple Inc. and the Swift project authors
66
// Licensed under Apache License v2.0 with Runtime Library Exception
77
//
88
// See https://swift.org/LICENSE.txt for license information
@@ -13,17 +13,41 @@
1313
#ifndef SWIFT_BASIC_FILESYSTEM_H
1414
#define SWIFT_BASIC_FILESYSTEM_H
1515

16-
#include "llvm/ADT/Twine.h"
16+
#include "llvm/ADT/STLExtras.h"
17+
#include "llvm/ADT/StringRef.h"
1718
#include "llvm/Support/MemoryBuffer.h"
1819
#include <system_error>
1920

21+
namespace llvm {
22+
class raw_pwrite_stream;
23+
class Twine;
24+
}
25+
2026
namespace clang {
2127
namespace vfs {
2228
class FileSystem;
2329
}
2430
}
2531

2632
namespace swift {
33+
/// Invokes \p action with a raw_ostream that refers to a temporary file,
34+
/// which is then renamed into place as \p outputPath when the action
35+
/// completes.
36+
///
37+
/// If a temporary file cannot be created for whatever reason, \p action will
38+
/// be invoked with a stream directly opened at \p outputPath. Otherwise, if
39+
/// there is already a file at \p outputPath, it will not be overwritten if
40+
/// the new contents are identical.
41+
///
42+
/// If the process is interrupted with a signal, any temporary file will be
43+
/// removed.
44+
///
45+
/// As a special case, an output path of "-" is treated as referring to
46+
/// stdout.
47+
std::error_code atomicallyWritingToFile(
48+
llvm::StringRef outputPath, bool binaryMode,
49+
llvm::function_ref<void(llvm::raw_pwrite_stream &)> action);
50+
2751
/// Moves a file from \p source to \p destination, unless there is already
2852
/// a file at \p destination that contains the same data as \p source.
2953
///
@@ -38,6 +62,7 @@ namespace swift {
3862
const llvm::Twine &Name, int64_t FileSize = -1,
3963
bool RequiresNullTerminator = true, bool IsVolatile = false);
4064
} // end namespace vfs
65+
4166
} // end namespace swift
4267

4368
#endif // SWIFT_BASIC_FILESYSTEM_H

lib/Basic/FileSystem.cpp

Lines changed: 139 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
//
33
// This source file is part of the Swift.org open source project
44
//
5-
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
5+
// Copyright (c) 2014 - 2018 Apple Inc. and the Swift project authors
66
// Licensed under Apache License v2.0 with Runtime Library Exception
77
//
88
// See https://swift.org/LICENSE.txt for license information
@@ -11,9 +11,15 @@
1111
//===----------------------------------------------------------------------===//
1212

1313
#include "swift/Basic/FileSystem.h"
14+
15+
#include "swift/Basic/LLVM.h"
16+
#include "clang/Basic/FileManager.h"
17+
#include "llvm/ADT/Twine.h"
18+
#include "llvm/Support/Errc.h"
1419
#include "llvm/Support/FileSystem.h"
20+
#include "llvm/Support/Path.h"
1521
#include "llvm/Support/Process.h"
16-
#include "clang/Basic/FileManager.h"
22+
#include "llvm/Support/Signals.h"
1723

1824
using namespace swift;
1925

@@ -30,6 +36,137 @@ namespace {
3036
};
3137
} // end anonymous namespace
3238

39+
/// Does some simple checking to see if a temporary file can be written next to
40+
/// \p outputPath and then renamed into place.
41+
///
42+
/// Helper for swift::atomicallyWritingToFile.
43+
///
44+
/// If the result is an error, the write won't succeed at all, and the calling
45+
/// operation should bail out early.
46+
static llvm::ErrorOr<bool>
47+
canUseTemporaryForWrite(const StringRef outputPath) {
48+
namespace fs = llvm::sys::fs;
49+
50+
if (outputPath == "-") {
51+
// Special case: "-" represents stdout, and LLVM's output stream APIs are
52+
// aware of this. It doesn't make sense to use a temporary in this case.
53+
return false;
54+
}
55+
56+
fs::file_status status;
57+
(void)fs::status(outputPath, status);
58+
if (!fs::exists(status)) {
59+
// Assume we'll be able to write to both a temporary file and to the final
60+
// destination if the final destination doesn't exist yet.
61+
return true;
62+
}
63+
64+
// Fail early if we can't write to the final destination.
65+
if (!fs::can_write(outputPath))
66+
return llvm::make_error_code(llvm::errc::operation_not_permitted);
67+
68+
// Only use a temporary if the output is a regular file. This handles
69+
// things like '-o /dev/null'
70+
return fs::is_regular_file(status);
71+
}
72+
73+
/// Attempts to open a temporary file next to \p outputPath, with the intent
74+
/// that once the file has been written it will be renamed into place.
75+
///
76+
/// Helper for swift::atomicallyWritingToFile.
77+
///
78+
/// \param[out] openedStream On success, a stream opened for writing to the
79+
/// temporary file that was just created.
80+
/// \param outputPath The path to the final output file, which is used to decide
81+
/// where to put the temporary.
82+
/// \param openFlags Controls how the output stream will be opened.
83+
///
84+
/// \returns The path to the temporary file that was opened, or \c None if the
85+
/// file couldn't be created.
86+
static Optional<std::string>
87+
tryToOpenTemporaryFile(Optional<llvm::raw_fd_ostream> &openedStream,
88+
const StringRef outputPath,
89+
const llvm::sys::fs::OpenFlags openFlags) {
90+
namespace fs = llvm::sys::fs;
91+
92+
// Create a temporary file path.
93+
// Insert a placeholder for a random suffix before the extension (if any).
94+
// Then because some tools glob for build artifacts (such as clang's own
95+
// GlobalModuleIndex.cpp), also append .tmp.
96+
SmallString<128> tempPath;
97+
const StringRef outputExtension = llvm::sys::path::extension(outputPath);
98+
tempPath = outputPath.drop_back(outputExtension.size());
99+
tempPath += "-%%%%%%%%";
100+
tempPath += outputExtension;
101+
tempPath += ".tmp";
102+
103+
int fd;
104+
const unsigned perms = fs::all_read | fs::all_write;
105+
std::error_code EC = fs::createUniqueFile(tempPath, fd, tempPath, perms,
106+
openFlags);
107+
108+
if (EC) {
109+
// Ignore the specific error; the caller has to fall back to not using a
110+
// temporary anyway.
111+
return None;
112+
}
113+
114+
openedStream.emplace(fd, /*shouldClose=*/true);
115+
// Make sure the temporary file gets removed if we crash.
116+
llvm::sys::RemoveFileOnSignal(tempPath);
117+
return tempPath.str().str();
118+
}
119+
120+
std::error_code swift::atomicallyWritingToFile(
121+
const StringRef outputPath, const bool binaryMode,
122+
const llvm::function_ref<void(llvm::raw_pwrite_stream &)> action) {
123+
namespace fs = llvm::sys::fs;
124+
125+
// FIXME: This is mostly a simplified version of
126+
// clang::CompilerInstance::createOutputFile. It would be great to share the
127+
// implementation.
128+
assert(!outputPath.empty());
129+
130+
llvm::ErrorOr<bool> canUseTemporary = canUseTemporaryForWrite(outputPath);
131+
if (std::error_code error = canUseTemporary.getError())
132+
return error;
133+
134+
Optional<std::string> temporaryPath;
135+
{
136+
const fs::OpenFlags openFlags = (binaryMode ? fs::F_None : fs::F_Text);
137+
138+
Optional<llvm::raw_fd_ostream> OS;
139+
if (canUseTemporary.get()) {
140+
temporaryPath = tryToOpenTemporaryFile(OS, outputPath, openFlags);
141+
142+
if (!temporaryPath) {
143+
assert(!OS.hasValue());
144+
// If we failed to create the temporary, fall back to writing to the
145+
// file directly. This handles the corner case where we cannot write to
146+
// the directory, but can write to the file.
147+
}
148+
}
149+
150+
if (!OS.hasValue()) {
151+
std::error_code error;
152+
OS.emplace(outputPath, error, openFlags);
153+
if (error)
154+
return error;
155+
}
156+
157+
action(OS.getValue());
158+
// In addition to scoping the use of 'OS', ending the scope here also
159+
// ensures that it's been flushed (by destroying it).
160+
}
161+
162+
if (!temporaryPath.hasValue()) {
163+
// If we didn't use a temporary, we're done!
164+
return std::error_code();
165+
}
166+
167+
return swift::moveFileIfDifferent(temporaryPath.getValue(), outputPath);
168+
}
169+
33170
std::error_code swift::moveFileIfDifferent(const llvm::Twine &source,
34171
const llvm::Twine &destination) {
35172
namespace fs = llvm::sys::fs;

lib/FrontendTool/FrontendTool.cpp

Lines changed: 36 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
//
33
// This source file is part of the Swift.org open source project
44
//
5-
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
5+
// Copyright (c) 2014 - 2018 Apple Inc. and the Swift project authors
66
// Licensed under Apache License v2.0 with Runtime Library Exception
77
//
88
// See https://swift.org/LICENSE.txt for license information
@@ -62,11 +62,7 @@
6262
#include "swift/Syntax/SyntaxNodes.h"
6363
#include "swift/TBDGen/TBDGen.h"
6464

65-
// FIXME: We're just using CompilerInstance::createOutputFile.
66-
// This API should be sunk down to LLVM.
67-
#include "clang/Frontend/CompilerInstance.h"
6865
#include "clang/AST/ASTContext.h"
69-
#include "clang/APINotes/Types.h"
7066

7167
#include "llvm/ADT/Statistic.h"
7268
#include "llvm/IR/LLVMContext.h"
@@ -313,76 +309,60 @@ static bool writeSIL(SILModule &SM, const PrimarySpecificPaths &PSPs,
313309
PSPs.OutputFilename, opts.EmitSortedSIL);
314310
}
315311

316-
/// Invokes \p action with a raw_ostream that refers to a temporary file, which
317-
/// is then renamed into place as \p outputPath when the action completes.
312+
/// A wrapper around swift::atomicallyWritingToFile that handles diagnosing any
313+
/// filesystem errors and ignores empty output paths.
318314
///
319-
/// If a temporary file cannot be created for whatever reason, \p action will
320-
/// be invoked with a stream directly opened at \p outputPath. Otherwise, if
321-
/// there is already a file at \p outputPath, it will not be overwritten if
322-
/// the new contents are identical.
323-
///
324-
/// If the process is interrupted with a signal, any temporary file will be
325-
/// removed.
326-
///
327-
/// As a special case, an output path of "-" is treated as referring to stdout.
328-
///
329-
/// If an error occurs, it is reported via \p diags.
315+
/// \returns true if there were any errors, either from the filesystem
316+
/// operations or from \p action returning true.
330317
static bool atomicallyWritingToTextFile(
331318
StringRef outputPath, DiagnosticEngine &diags,
332319
llvm::function_ref<bool(llvm::raw_pwrite_stream &)> action) {
333-
if (outputPath.empty())
334-
return false;
335-
336-
std::string tmpFilePath;
337-
bool hadError;
338-
{
339-
clang::CompilerInstance Clang;
320+
assert(!outputPath.empty());
340321

341-
std::error_code EC;
342-
std::unique_ptr<llvm::raw_pwrite_stream> out =
343-
Clang.createOutputFile(outputPath, EC,
344-
/*Binary=*/false,
345-
/*RemoveFileOnSignal=*/true,
346-
/*BaseInput=*/"",
347-
llvm::sys::path::extension(outputPath),
348-
/*UseTemporary=*/true,
349-
/*CreateMissingDirectories=*/false,
350-
/*ResultPathName=*/nullptr,
351-
&tmpFilePath);
352-
353-
if (!out) {
354-
diags.diagnose(SourceLoc(), diag::error_opening_output,
355-
tmpFilePath, EC.message());
356-
return true;
357-
}
358-
359-
hadError = action(*out);
360-
}
361-
362-
if (!tmpFilePath.empty()) {
363-
if (auto EC = swift::moveFileIfDifferent(tmpFilePath, outputPath)) {
364-
diags.diagnose(SourceLoc(), diag::error_opening_output,
365-
outputPath, EC.message());
366-
return true;
367-
}
322+
bool actionFailed = false;
323+
std::error_code EC =
324+
swift::atomicallyWritingToFile(outputPath, /*binary*/false,
325+
[&](llvm::raw_pwrite_stream &out) {
326+
actionFailed = action(out);
327+
});
328+
if (EC) {
329+
diags.diagnose(SourceLoc(), diag::error_opening_output,
330+
outputPath, EC.message());
331+
return true;
368332
}
369-
370-
return hadError;
333+
return actionFailed;
371334
}
372335

336+
/// Prints the Objective-C "generated header" interface for \p M to \p
337+
/// outputPath.
338+
///
339+
/// ...unless \p outputPath is empty, in which case it does nothing.
340+
///
341+
/// \returns true if there were any errors
342+
///
343+
/// \see swift::printAsObjC
373344
static bool printAsObjCIfNeeded(StringRef outputPath, ModuleDecl *M,
374345
StringRef bridgingHeader, bool moduleIsPublic) {
346+
if (outputPath.empty())
347+
return false;
375348
return atomicallyWritingToTextFile(outputPath, M->getDiags(),
376-
[&](raw_ostream &out) -> bool {
349+
[&](raw_ostream &out) -> bool {
377350
auto requiredAccess = moduleIsPublic ? AccessLevel::Public
378351
: AccessLevel::Internal;
379352
return printAsObjC(out, M, bridgingHeader, requiredAccess);
380353
});
381354
}
382355

356+
/// Prints the stable textual interface for \p M to \p outputPath.
357+
///
358+
/// ...unless \p outputPath is empty, in which case it does nothing.
359+
///
360+
/// \returns true if there were any errors
383361
static bool printModuleInterfaceIfNeeded(StringRef outputPath, ModuleDecl *M) {
362+
if (outputPath.empty())
363+
return false;
384364
return atomicallyWritingToTextFile(outputPath, M->getDiags(),
385-
[&](raw_ostream &out) -> bool {
365+
[&](raw_ostream &out) -> bool {
386366
auto printOptions = PrintOptions::printTextualInterfaceFile();
387367
SmallVector<Decl *, 16> topLevelDecls;
388368
M->getTopLevelDecls(topLevelDecls);

0 commit comments

Comments
 (0)