-
Notifications
You must be signed in to change notification settings - Fork 10.5k
Hook up -emit-interface-path to a simple AST printer #18224
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Hook up -emit-interface-path to a simple AST printer #18224
Conversation
@swift-ci Please test |
@CodaFi, the second commit is the one relevant to you, since you were looking at filesystem stuff. |
Build failed |
@swift-ci Please test macOS |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since I'm looking at this without experience, I found somethings that will only confuse newbies, I think. Not sure which thoughts you'll want to take.
include/swift/AST/PrintOptions.h
Outdated
@@ -424,6 +424,8 @@ struct PrintOptions { | |||
return result; | |||
} | |||
|
|||
static PrintOptions printTextualInterfaceFile(); | |||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why the blank line here but not after 429?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My understanding is that those two are used by SourceKit in related ways. I could at least add a doc comment to mine, though, to encourage it in the future for the others.
include/swift/Basic/FileSystem.h
Outdated
std::error_code moveFileIfDifferent(const llvm::Twine &source, | ||
const llvm::Twine &destination); | ||
|
||
/// Invokes \p action with a raw_ostream that refers to a temporary file, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice comment!
@@ -64,6 +64,21 @@ void PrintOptions::clearSynthesizedExtension() { | |||
TransformContext.reset(); | |||
} | |||
|
|||
PrintOptions PrintOptions::printTextualInterfaceFile() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The name of this function sounds like it does the printing, but it only computes options. I see that it follows the practice of the other two, but still...
lib/Basic/FileSystem.cpp
Outdated
@@ -29,6 +35,82 @@ namespace { | |||
}; | |||
} // end anonymous namespace | |||
|
|||
std::error_code swift::atomicallyWritingToFile( | |||
StringRef outputPath, bool binaryMode, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could binaryMode
be a const
, or is it changed inside of the function?
lib/Basic/FileSystem.cpp
Outdated
@@ -29,6 +35,82 @@ namespace { | |||
}; | |||
} // end anonymous namespace | |||
|
|||
std::error_code swift::atomicallyWritingToFile( | |||
StringRef outputPath, bool binaryMode, | |||
llvm::function_ref<void(llvm::raw_pwrite_stream &)> action) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wanted to check the correctness of this function for my review, but it is so long that I doubt my ability to do so.
If you were to break it up into smaller pieces, I could review it with confidence.
lib/FrontendTool/FrontendTool.cpp
Outdated
/// filesystem errors and ignores empty output paths. | ||
static bool atomicallyWritingToTextFile( | ||
StringRef outputPath, DiagnosticEngine &diags, | ||
llvm::function_ref<bool(llvm::raw_pwrite_stream &)> action) { | ||
if (outputPath.empty()) | ||
return false; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The comment does say that this function ignores empty output paths, but that calling convention does not seem as robust as one that would create an error for an empty output path. But, I don't know the context.
swift::atomicallyWritingToFile(outputPath, /*binary*/false, | ||
[&](llvm::raw_pwrite_stream &out) { | ||
actionFailed = action(out); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it might read a little more clearly with an early return before the if (EC)
:
if (!actionFailed)
return false;
Then, you could move the return true;
down to the last line of the function.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm, and have actionFailed
default to true
? Yeah, that makes sense.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oops, no, that's not quite right. actionFailed
and EC
are independent.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sigh... You are right. If the action fails (action
returns true--BTW action
return convention (true == error) ought to also be in the Doxygen, sigh) the file is still written. Is this right? Should the file be removed if action
returns true?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think yes, it's a temporary file and it would clutter up the user's directory.
(None of our actions currently fail, so this is kind of a moot point. Another option would be to take that part out.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we don't remove the file, at least we could include an assertion that the action succeeds with a FIXME about removing the file. Or something...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, wait, hang on. Are you talking about removing the temporary file or the final output file? As written the code always cleans up the temporary file, but does not delete the final output file if there's an error in writing. I figured that was a safer option given Clang's mentioning "you can write to this file but not to the directory that contains it", although it does sometimes mean you've produced erroneous data.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If line 326 above evaluates to true, whatever file is written by atomicallyWritingToFile is left on the file system, whichever one that is. That’s what I saw.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right, okay. That's intended but I could see how we might prefer otherwise.
lib/FrontendTool/FrontendTool.cpp
Outdated
return actionFailed; | ||
} | ||
|
||
static bool printAsObjCIfNeeded(StringRef outputPath, ModuleDecl *M, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The name says "IfNeeded" but the function has no "if" test in it. I presume that it has to do with an empty output path? Some sort of reformulation could be clearer.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same for printModuleInterfaceIfNeeded
below.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't change the name of the function, only the implementation. I guess "atomicallyWritingToTextFile" should also have an "IfNeeded" or something?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My preference would be for an empty output path to be an error, and to only call the function with a non-empty output path. But one could instead append "IgnoringEmptyOutputPath" to the function name.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's definitely too verbose—it makes the call site too hard to read.
I can push the empty string checks back out of these functions, but it was your refactoring of performCompile that put them inside to begin with. :-/
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for asking. It's a question of centralizing the invariants. If there is only one call to printAsObjC, then I would put the test at the call site. If there are two, I would put the test in printAsObjCIfNeeded and have just test and call printAsObjC (or whatever). Same for emitModuleInterface[IfNeeded]. Does that make sense?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's only one call to all of these (they were all inline in performCompile
at one point), so I'll put it outside, then.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oops, I take it back. There are two calls. Hm. So you're saying it's better to have the test in printAsObjCIfNeeded
rather than sink it down to the helper function, since we haven't thought of a way to encode it in the helper function's name without me thinking it's too verbose?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, that’s right. One place, so someday we can’t change one and miss the other. Tx
using namespace swift; | ||
|
||
bool swift::emitModuleInterface(raw_ostream &out, ModuleDecl *M) { | ||
auto printOptions = PrintOptions::printTextualInterfaceFile(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
const?
lib/Serialization/Serialization.cpp
Outdated
std::error_code EC = swift::atomicallyWritingToFile(outputPath, | ||
/*binary*/true, | ||
action); | ||
if (EC) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not a big deal, but slightly better IMO to flip this with an early return if !EC.
PS: Always a pleasure reviewing your code. Your care inspires me to look for perfection. |
include/swift/Basic/FileSystem.h
Outdated
/// | ||
/// In the latter case, the file at \p source is deleted. If an error occurs, | ||
/// the file at \p source will still be present at \p source. | ||
std::error_code moveFileIfDifferent(const llvm::Twine &source, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yay, more things to upstream!
We'll want more complexity soon, but this is a start.
caafaee
to
aacc7ae
Compare
Rebased, feedback incorporated. @swift-ci Please smoke test |
/// Retrieve the set of options suitable for stable textual interfaces. | ||
/// | ||
/// This is a format that will be parsed again later, so the output must be | ||
/// consistent and well-formed. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice!
lib/Basic/FileSystem.cpp
Outdated
|
||
fs::file_status status; | ||
(void)fs::status(outputPath, status); | ||
if (fs::exists(status)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably reads slightly better if you flip the "if" and use an early return.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, of course. This is left over from before I pulled it out into its own function. Easy enough.
lib/Basic/FileSystem.cpp
Outdated
|
||
// Don't use a temporary if the output is a special file. This handles | ||
// things like '-o /dev/null' | ||
if (!fs::is_regular_file(status)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you flip the if above, this turns into return fs::is_regular_file(status)
lib/Basic/FileSystem.cpp
Outdated
|
||
// Create a temporary file path. | ||
// Insert -%%%%%%%% before the extension (if any), and because some tools | ||
// (noticeable, clang's own GlobalModuleIndex.cpp) glob for build |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Typo? "noticeably" instead of "noticeable"?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess so. It's not my typo; that bit is copied verbatim. They probably meant "notably".
/// If the result is an error, the write won't succeed at all, and the calling | ||
/// operation should bail out early. | ||
static llvm::ErrorOr<bool> | ||
canUseTemporaryForWrite(const StringRef outputPath) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like that const
lib/Basic/FileSystem.cpp
Outdated
// Create a temporary file path. | ||
// Insert -%%%%%%%% before the extension (if any), and because some tools | ||
// (noticeable, clang's own GlobalModuleIndex.cpp) glob for build | ||
// artifacts, also append .tmp. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good comment, but I'm left wondering what would happen as a result of the tool appending .tmp if you didn't add the percents. Is this string just arbitrary? Does anything else count on it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's a mktemp-like pattern, described in the documentation for llvm::sys::fs::createUniqueFile
.
tempPath = outputPath.drop_back(outputExtension.size()); | ||
tempPath += "-%%%%%%%%"; | ||
tempPath += outputExtension; | ||
tempPath += ".tmp"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wish we had Swift's string interpolation, sigh.
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.
No functionality change.
aacc7ae
to
0204561
Compare
@swift-ci Please smoke test |
if (std::error_code error = canUseTemporary.getError()) | ||
return error; | ||
|
||
Optional<std::string> temporaryPath; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like how the refactoring is going! When I see a declaration, followed by a block that computes it, I wonder if it works better as a separate function. But I'm not sure I see that factoring here would be comprehensible, even though the code wants it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah. It's almost llvm::ErrorOr<Optional<std::string>>
but trying to work with that type (and trying to name the function that would be extracted) seems worse than any of the alternatives. tryToOpenTemporaryFile
represents the best compromise I came up with.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is looking pretty good! There's still a lot going on in swift::atomicallyWritingToFile
but I don't see a sensible decomposition.
Next step after #18090 for module stability / textual interfaces. Still mostly just a dummy at this point, but we can start writing tests.