Skip to content

Commit ce2879f

Browse files
committed
[BuildPlan] Invoke built-tool plugins incrementally while building a build plan
`invokeBuildToolPlugins` and `runPrebuildCommands` are moved to `BuildPlan` type and are used as part of the plan generation. Build tools building is still part of the `BuildOperation`.
1 parent 7640598 commit ce2879f

File tree

5 files changed

+293
-263
lines changed

5 files changed

+293
-263
lines changed

Sources/Build/BuildOperation.swift

Lines changed: 24 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -734,69 +734,41 @@ public final class BuildOperation: PackageStructureDelegate, SPMBuildCore.BuildS
734734
private func plan(subset: BuildSubset? = nil) throws -> (description: BuildDescription, manifest: LLBuildManifest) {
735735
// Load the package graph.
736736
let graph = try getPackageGraph()
737-
let buildToolPluginInvocationResults: [ResolvedModule.ID: (target: ResolvedModule, results: [BuildToolPluginInvocationResult])]
738-
let prebuildCommandResults: [ResolvedModule.ID: [PrebuildCommandResult]]
739-
// Invoke any build tool plugins in the graph to generate prebuild commands and build commands.
737+
738+
let pluginTools: [ResolvedModule.ID: [String: PluginTool]]
739+
// FIXME: This is unfortunate but we need to build plugin tools upfront at the moment because
740+
// llbuild doesn't support dynamic dependency detection. In order to construct a manifest
741+
// we need to build and invoke all of the build-tool plugins and capture their outputs in
742+
// `BuildPlan`.
740743
if let pluginConfiguration: PluginConfiguration, !self.config.shouldSkipBuilding(for: .target) {
741744
let pluginsPerModule = graph.pluginsPerModule(
742745
satisfying: self.config.buildEnvironment(for: .host)
743746
)
744747

745-
let pluginTools = try buildPluginTools(
748+
pluginTools = try buildPluginTools(
746749
graph: graph,
747750
pluginsPerModule: pluginsPerModule,
748751
hostTriple: try pluginConfiguration.scriptRunner.hostTriple
749752
)
750-
751-
buildToolPluginInvocationResults = try graph.invokeBuildToolPlugins(
752-
pluginsPerTarget: pluginsPerModule,
753-
pluginTools: pluginTools,
754-
outputDir: pluginConfiguration.workDirectory.appending("outputs"),
755-
buildParameters: self.config.toolsBuildParameters,
756-
additionalFileRules: self.additionalFileRules,
757-
toolSearchDirectories: [self.config.toolchain(for: .host).swiftCompilerPath.parentDirectory],
758-
pkgConfigDirectories: self.pkgConfigDirectories,
759-
pluginScriptRunner: pluginConfiguration.scriptRunner,
760-
observabilityScope: self.observabilityScope,
761-
fileSystem: self.fileSystem
762-
)
763-
764-
// Surface any diagnostics from build tool plugins.
765-
var succeeded = true
766-
for (_, (target, results)) in buildToolPluginInvocationResults {
767-
// There is one result for each plugin that gets applied to a target.
768-
for result in results {
769-
let diagnosticsEmitter = self.observabilityScope.makeDiagnosticsEmitter {
770-
var metadata = ObservabilityMetadata()
771-
metadata.moduleName = target.name
772-
metadata.pluginName = result.plugin.name
773-
return metadata
774-
}
775-
for line in result.textOutput.split(whereSeparator: { $0.isNewline }) {
776-
diagnosticsEmitter.emit(info: line)
777-
}
778-
for diag in result.diagnostics {
779-
diagnosticsEmitter.emit(diag)
780-
}
781-
succeeded = succeeded && result.succeeded
782-
}
783-
784-
if !succeeded {
785-
throw StringError("build stopped due to build-tool plugin failures")
786-
}
787-
}
788-
789-
// Run any prebuild commands provided by build tool plugins. Any failure stops the build.
790-
prebuildCommandResults = try graph.reachableModules.reduce(into: [:], { partial, target in
791-
partial[target.id] = try buildToolPluginInvocationResults[target.id].map {
792-
try self.runPrebuildCommands(for: $0.results)
793-
}
794-
})
795753
} else {
796-
buildToolPluginInvocationResults = [:]
797-
prebuildCommandResults = [:]
754+
pluginTools = [:]
798755
}
799756

757+
// Create the build plan based on the modules graph and any information from plugins.
758+
let plan = try BuildPlan(
759+
destinationBuildParameters: self.config.destinationBuildParameters,
760+
toolsBuildParameters: self.config.buildParameters(for: .host),
761+
graph: graph,
762+
pluginConfiguration: self.pluginConfiguration,
763+
pluginTools: pluginTools,
764+
additionalFileRules: additionalFileRules,
765+
pkgConfigDirectories: pkgConfigDirectories,
766+
disableSandbox: self.pluginConfiguration?.disableSandbox ?? false,
767+
fileSystem: self.fileSystem,
768+
observabilityScope: self.observabilityScope
769+
)
770+
self._buildPlan = plan
771+
800772
// Emit warnings about any unhandled files in authored packages. We do this after applying build tool plugins, once we know what files they handled.
801773
// rdar://113256834 This fix works for the plugins that do not have PreBuildCommands.
802774
let targetsToConsider: [ResolvedModule]
@@ -809,7 +781,7 @@ public final class BuildOperation: PackageStructureDelegate, SPMBuildCore.BuildS
809781

810782
for module in targetsToConsider {
811783
// Subtract out any that were inputs to any commands generated by plugins.
812-
if let pluginResults = buildToolPluginInvocationResults[module.id]?.results {
784+
if let pluginResults = plan.buildToolPluginInvocationResults[module.id] {
813785
diagnoseUnhandledFiles(
814786
modulesGraph: graph,
815787
module: module,
@@ -818,21 +790,6 @@ public final class BuildOperation: PackageStructureDelegate, SPMBuildCore.BuildS
818790
}
819791
}
820792

821-
// Create the build plan based, on the graph and any information from plugins.
822-
let plan = try BuildPlan(
823-
destinationBuildParameters: self.config.destinationBuildParameters,
824-
toolsBuildParameters: self.config.buildParameters(for: .host),
825-
graph: graph,
826-
pluginConfiguration: self.pluginConfiguration,
827-
additionalFileRules: additionalFileRules,
828-
buildToolPluginInvocationResults: buildToolPluginInvocationResults.mapValues(\.results),
829-
prebuildCommandResults: prebuildCommandResults,
830-
disableSandbox: self.pluginConfiguration?.disableSandbox ?? false,
831-
fileSystem: self.fileSystem,
832-
observabilityScope: self.observabilityScope
833-
)
834-
self._buildPlan = plan
835-
836793
let (buildDescription, buildManifest) = try BuildDescription.create(
837794
from: plan,
838795
using: self.config,
@@ -943,49 +900,6 @@ public final class BuildOperation: PackageStructureDelegate, SPMBuildCore.BuildS
943900
return (buildSystem: llbuildSystem, tracker: progressTracker)
944901
}
945902

946-
/// Runs any prebuild commands associated with the given list of plugin invocation results, in order, and returns the
947-
/// results of running those prebuild commands.
948-
private func runPrebuildCommands(for pluginResults: [BuildToolPluginInvocationResult]) throws -> [PrebuildCommandResult] {
949-
guard let pluginConfiguration = self.pluginConfiguration else {
950-
throw InternalError("unknown plugin script runner")
951-
952-
}
953-
// Run through all the commands from all the plugin usages in the target.
954-
return try pluginResults.map { pluginResult in
955-
// As we go we will collect a list of prebuild output directories whose contents should be input to the build,
956-
// and a list of the files in those directories after running the commands.
957-
var derivedFiles: [AbsolutePath] = []
958-
var prebuildOutputDirs: [AbsolutePath] = []
959-
for command in pluginResult.prebuildCommands {
960-
self.observabilityScope.emit(info: "Running " + (command.configuration.displayName ?? command.configuration.executable.basename))
961-
962-
// Run the command configuration as a subshell. This doesn't return until it is done.
963-
// TODO: We need to also use any working directory, but that support isn't yet available on all platforms at a lower level.
964-
var commandLine = [command.configuration.executable.pathString] + command.configuration.arguments
965-
if !pluginConfiguration.disableSandbox {
966-
commandLine = try Sandbox.apply(command: commandLine, fileSystem: self.fileSystem, strictness: .writableTemporaryDirectory, writableDirectories: [pluginResult.pluginOutputDirectory])
967-
}
968-
let processResult = try AsyncProcess.popen(arguments: commandLine, environment: command.configuration.environment)
969-
let output = try processResult.utf8Output() + processResult.utf8stderrOutput()
970-
if processResult.exitStatus != .terminated(code: 0) {
971-
throw StringError("failed: \(command)\n\n\(output)")
972-
}
973-
974-
// Add any files found in the output directory declared for the prebuild command after the command ends.
975-
let outputFilesDir = command.outputFilesDirectory
976-
if let swiftFiles = try? self.fileSystem.getDirectoryContents(outputFilesDir).sorted() {
977-
derivedFiles.append(contentsOf: swiftFiles.map{ outputFilesDir.appending(component: $0) })
978-
}
979-
980-
// Add the output directory to the list of directories whose structure should affect the build plan.
981-
prebuildOutputDirs.append(outputFilesDir)
982-
}
983-
984-
// Add the results of running any prebuild commands for this invocation.
985-
return PrebuildCommandResult(derivedFiles: derivedFiles, outputDirectories: prebuildOutputDirs)
986-
}
987-
}
988-
989903
public func provideBuildErrorAdvice(for target: String, command: String, message: String) -> String? {
990904
// Find the target for which the error was emitted. If we don't find it, we can't give any advice.
991905
guard let _ = self._buildPlan?.targets.first(where: { $0.target.name == target }) else { return nil }
@@ -1075,8 +989,6 @@ extension BuildOperation {
1075989
graph: graph,
1076990
pluginConfiguration: nil,
1077991
additionalFileRules: [],
1078-
buildToolPluginInvocationResults: [:],
1079-
prebuildCommandResults: [:],
1080992
fileSystem: config.fileSystem,
1081993
observabilityScope: config.observabilityScope
1082994
)

0 commit comments

Comments
 (0)