Skip to content

Revert "Clean up the code and some of the API of compiling plugins, and improve persistence of cached compiler outputs" #4298

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

Closed
Closed
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
7 changes: 0 additions & 7 deletions Sources/Basics/JSON+Extensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -123,10 +123,3 @@ extension JSONDecoder {
return try self.decode(kind, from: data)
}
}

extension JSONEncoder {
public func encode<T: Encodable>(path: AbsolutePath, fileSystem: FileSystem, _ value: T) throws {
let data = try self.encode(value)
try fileSystem.writeFileContents(path, data: data)
}
}
15 changes: 5 additions & 10 deletions Sources/Build/BuildOperation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -235,19 +235,14 @@ public final class BuildOperation: PackageStructureDelegate, SPMBuildCore.BuildS
// Compile the plugin, getting back a PluginCompilationResult.
let preparationStepName = "Compiling plugin \(plugin.targetName)..."
self.buildSystemDelegate?.preparationStepStarted(preparationStepName)
let result = try tsc_await {
self.pluginScriptRunner.compilePluginScript(
sourceFiles: plugin.sources.paths,
pluginName: plugin.targetName,
toolsVersion: plugin.toolsVersion,
observabilityScope: self.observabilityScope,
callbackQueue: DispatchQueue.sharedConcurrent,
completion: $0)
}
let result = try self.pluginScriptRunner.compilePluginScript(
sources: plugin.sources,
toolsVersion: plugin.toolsVersion,
observabilityScope: self.observabilityScope)
if !result.description.isEmpty {
self.buildSystemDelegate?.preparationStepHadOutput(preparationStepName, output: result.description)
}
self.buildSystemDelegate?.preparationStepFinished(preparationStepName, result: result.cached ? .skipped : (result.succeeded ? .succeeded : .failed))
self.buildSystemDelegate?.preparationStepFinished(preparationStepName, result: result.wasCached ? .skipped : (result.succeeded ? .succeeded : .failed))

// Throw an error on failure; we will already have emitted the compiler's output in this case.
if !result.succeeded {
Expand Down
3 changes: 1 addition & 2 deletions Sources/SPMBuildCore/PluginInvocation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -236,8 +236,7 @@ extension PluginTarget {

// Call the plugin script runner to actually invoke the plugin.
scriptRunner.runPluginScript(
sourceFiles: sources.paths,
pluginName: self.name,
sources: sources,
initialMessage: initialMessage,
toolsVersion: self.apiVersion,
workingDirectory: workingDirectory,
Expand Down
71 changes: 29 additions & 42 deletions Sources/SPMBuildCore/PluginScriptRunner.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,12 @@ import struct TSCUtility.Triple
/// Implements the mechanics of running and communicating with a plugin (implemented as a set of Swift source files). In most environments this is done by compiling the code to an executable, invoking it as a sandboxed subprocess, and communicating with it using pipes. Specific implementations are free to implement things differently, however.
public protocol PluginScriptRunner {

/// Public protocol function that starts compiling the plugin script to an exectutable. 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 when compilation ends.
/// Public protocol function that starts compiling the plugin script to an exectutable. 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.
func compilePluginScript(
sourceFiles: [AbsolutePath],
pluginName: String,
sources: Sources,
toolsVersion: ToolsVersion,
observabilityScope: ObservabilityScope,
callbackQueue: DispatchQueue,
completion: @escaping (Result<PluginCompilationResult, Error>) -> Void
)
observabilityScope: ObservabilityScope
) throws -> PluginCompilationResult

/// Implements the mechanics of running a plugin script implemented as a set of Swift source files, for use
/// by the package graph when it is evaluating package plugins.
Expand All @@ -42,8 +39,7 @@ public protocol PluginScriptRunner {
///
/// Every concrete implementation should cache any intermediates as necessary to avoid redundant work.
func runPluginScript(
sourceFiles: [AbsolutePath],
pluginName: String,
sources: Sources,
initialMessage: Data,
toolsVersion: ToolsVersion,
workingDirectory: AbsolutePath,
Expand Down Expand Up @@ -73,44 +69,36 @@ public protocol PluginScriptRunnerDelegate {

/// The result of compiling a plugin. The executable path will only be present if the compilation succeeds, while the other properties are present in all cases.
public struct PluginCompilationResult {
/// Whether compilation succeeded.
public var succeeded: Bool
/// Process result of invoking the Swift compiler to produce the executable (contains command line, environment, exit status, and any output).
public var compilerResult: ProcessResult?

/// Complete compiler command line.
public var commandLine: [String]
/// Path of the libClang diagnostics file emitted by the compiler (even if compilation succeded, it might contain warnings).
public var diagnosticsFile: AbsolutePath

/// Path of the compiled executable.
public var executableFile: AbsolutePath
public var compiledExecutable: AbsolutePath

/// Path of the libClang diagnostics file emitted by the compiler.
public var diagnosticsFile: AbsolutePath

/// Any output emitted by the compiler (stdout and stderr combined).
public var compilerOutput: String

/// Whether the compilation result came from the cache (false means that the compiler did run).
public var cached: Bool

public init(
succeeded: Bool,
commandLine: [String],
executableFile: AbsolutePath,
diagnosticsFile: AbsolutePath,
compilerOutput: String,
cached: Bool
) {
self.succeeded = succeeded
self.commandLine = commandLine
self.executableFile = executableFile
/// Whether the compilation result was cached.
public var wasCached: Bool

public init(compilerResult: ProcessResult?, diagnosticsFile: AbsolutePath, compiledExecutable: AbsolutePath, wasCached: Bool) {
self.compilerResult = compilerResult
self.diagnosticsFile = diagnosticsFile
self.compilerOutput = compilerOutput
self.cached = cached
self.compiledExecutable = compiledExecutable
self.wasCached = wasCached
}

/// Returns true if and only if the compilation succeeded or was cached
public var succeeded: Bool {
return self.wasCached || self.compilerResult?.exitStatus == .terminated(code: 0)
}
}

extension PluginCompilationResult: CustomStringConvertible {
public var description: String {
let output = compilerOutput.spm_chomp()
let stdout = (try? compilerResult?.utf8Output()) ?? ""
let stderr = (try? compilerResult?.utf8stderrOutput()) ?? ""
let output = (stdout + stderr).spm_chomp()
return output + (output.isEmpty || output.hasSuffix("\n") ? "" : "\n")
}
}
Expand All @@ -119,11 +107,10 @@ extension PluginCompilationResult: CustomDebugStringConvertible {
public var debugDescription: String {
return """
<PluginCompilationResult(
succeeded: \(succeeded),
commandLine: \(commandLine.map{ $0.spm_shellEscaped() }.joined(separator: " ")),
executable: \(executableFile.prettyPath())
diagnostics: \(diagnosticsFile.prettyPath())
compilerOutput: \(compilerOutput.spm_shellEscaped())
exitStatus: \(compilerResult.map{ "\($0.exitStatus)" } ?? "-"),
stdout: \((try? compilerResult?.utf8Output()) ?? ""),
stderr: \((try? compilerResult?.utf8stderrOutput()) ?? ""),
executable: \(compiledExecutable.prettyPath())
)>
"""
}
Expand Down
Loading