-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Clean up the code and some of the API of compiling plugins, and improve persistence of cached compiler outputs #4290
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
Clean up the code and some of the API of compiling plugins, and improve persistence of cached compiler outputs #4290
Conversation
…ve cached compiler outputs Clients now get the command line and compiler output even in the case of failure, and also when the plugin doesn't have to be recompiled because nothing changed (previously this information was lost). This is done by serializing a proper struct to JSON rather than just storing the hexadecimal representation of the configuration hash. Callers also have control over the name of the temporary directory to represent the plugin, rather than always having it be the last path component of the sources directory. The changes to the construction of the command line to compile plugins isn't nearly as extensive as it looks — it's mostly blankspace changes due to nesting.
@swift-ci please smoke test |
@@ -37,43 +37,46 @@ public struct DefaultPluginScriptRunner: PluginScriptRunner, Cancellable { | |||
self.cancellator = Cancellator(observabilityScope: .none) | |||
} | |||
|
|||
/// Public protocol function that starts compiling the plugin script to an executable. The tools version controls the availability of APIs in PackagePlugin, and should be set to the tools version of the package that defines the plugin (not of the target to which it is being applied). This function returns immediately and then calls the completion handler on the callbackq queue when compilation ends. | |||
/// Public protocol function that starts compiling the plugin script to an executable. The name is used as the basename for the executable and auxiliary files. The tools version controls the availability of APIs in PackagePlugin, and should be set to the tools version of the package that defines the plugin (not of the target to which it is being applied). This function returns immediately and then calls the completion handler on the callbackq queue after compilation ends. |
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.
changes in this file are a bit hard to follow. are there specific changes you want the reviewers to focus on?
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.
Ah, yes, sorry about that — it's actually a bit easier when ignoring spaces, because the nesting changed.
Maybe one useful thing would be to see what is stored in the PersistedCompilationState struct. It doesn't yet have things like a fingerprint of the compiled plugin, but that's where it would be added. I think that is the most interesting thing to look at that isn't just cleaning up names and structure.
This is pretty well tested by unit tests that check for whether the compilation actually happened etc so I am not too concerned about the logic itself.
commandLine: commandLine, | ||
environment: toolchain.swiftCompilerEnvironment, | ||
inputHash: compilerInputHash, | ||
duration: Double(startTime.distance(to: .now()).milliseconds() ?? 0) / 1000.0, |
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.
Millisecond precision seems enough here.
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.
perhaps use DispatchTimeInterval so units as obvious?
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 point, though because the idea is to encode this into JSON, that would lead to additional code to encode or decode it.
I looked at adding a fileprivate
extension to DispatchTimeInterval
to make it adopt Codable
, but that's not permissible — we then have to make it codable for the whole module.
Since we're currently not using the duration maybe I will just take it out for now.
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.
one way to do this is use a property wrapper which implements the serialization for the duration property. this is how we often deal with date formatting
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 see. As this is unused, I'll go ahead and remove it, but I'll keep that in mind for next time.
} | ||
|
||
// Check if we already have a compiled executable and a persisted state (we only recompile if things have changed). | ||
let stateFilePath = self.cacheDir.appending(component: execName + ".state") |
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.
nit: use "json" as extension, and names the file "state"
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.
Just the literal name "state" and not based on the name of the executable? In that case we should maybe call the executable just "plugin". But that makes it harder to see which plugin it is when looking for example using top
.
I was trying to keep all three files (executable, state, and diagnostics) with the same base name derived from the plugin name. So maybe just changing it to "json" here 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.
sry I meant execName + "-state.json"
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 makes sense.
// We failed to read the `.inputhash` file. We warn about it but proceed with the compilation (a cache miss). | ||
observabilityScope.emit(warning: "Couldn't read previous hash of plugin compilation inputs (\(error)") | ||
// We couldn't write out the `.state` file. We warn about it but proceed. | ||
observabilityScope.emit(debug: "... couldn't save plugin compilation state (\(error))") |
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.
curious about the "..." prefix in these debug messages
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 just left over from print-style debugging (my indication for the end of an activity, whereas the beginning has a "..." suffix instead). I'll change it.
} | ||
else { | ||
// There is no need to recompile the executable, so we just call the completion handler with the results from last time. | ||
// Return a PluginCompilationResult for both the successful and unsuccessful cases (to convey diagnostics, etc). | ||
let result = PluginCompilationResult( |
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.
nit: return here?
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'll move up the return.
var hostTriple: Triple { | ||
return UserToolchain.default.triple | ||
} | ||
|
||
func compilePluginScript(sources: Sources, toolsVersion: ToolsVersion, observabilityScope: ObservabilityScope) throws -> PluginCompilationResult { | ||
func compilePluginScript(sourceFiles: [AbsolutePath], pluginName: String, toolsVersion: ToolsVersion, observabilityScope: ObservabilityScope) throws -> PluginCompilationResult { |
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.
nit: these kind of functions that involve I/O seems like they should be async
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 the client calls that used this have been removed, so I can probably remove these.
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.
Removed 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.
I pushed the tsc_await
to all the call sites.
@swift-ci please smoke test |
e64084f
to
470eed8
Compare
@swift-ci please smoke test |
@tomerd Thanks for your feedback, I think I've addressed all of it. |
👍 |
This regressed the Windows build: https://ci-external.swift.org/job/oss-swift-windows-toolchain-x86_64-vs2019/509/console |
…ve persistence of cached compiler outputs (swiftlang#4290) Clients now get the command line and compiler output even in the case of failure, and also when the plugin doesn't have to be recompiled because nothing changed (previously this information was lost). This is done by serializing a proper struct to JSON rather than just storing the hexadecimal representation of the configuration hash. Callers also have control over the name of the temporary directory to represent the plugin, rather than always having it be the last path component of the sources directory. (cherry picked from commit 09d77a7)
… improve persistence of cached compiler outputs (#4290 & #4299) (#4309) * Clean up the code and some of the API of compiling plugins, and improve persistence of cached compiler outputs (#4290) Clients now get the command line and compiler output even in the case of failure, and also when the plugin doesn't have to be recompiled because nothing changed (previously this information was lost). This is done by serializing a proper struct to JSON rather than just storing the hexadecimal representation of the configuration hash. Callers also have control over the name of the temporary directory to represent the plugin, rather than always having it be the last path component of the sources directory. (cherry picked from commit 09d77a7) * Fix breakage on Windows, which doesn't have `ProcessResult.ExitStatus.signalled`. (#4299) (cherry picked from commit f9f74f1)
Motivation
Provide more information to clients of libSwiftPM even when a compiled plugin doesn't have to be recompiled because nothing has changed since the last time it was. Also make the API a bit cleaner.
Details
Clients now get the command line and compiler output even in the case of failure, and also when the plugin doesn't have to be recompiled because nothing changed (previously this information was lost). This is done by serializing a proper struct to JSON rather than just storing the hexadecimal representation of the configuration hash.
Callers also have control over the name of the temporary directory to represent the plugin, rather than always having it be the last path component of the sources directory.
The changes to the construction of the command line to compile plugins isn't nearly as extensive as it looks — it's mostly blankspace changes due to nesting.
Modifications:
Result:
[After your change, what will change.]