Skip to content

Commit 561322a

Browse files
committed
DNM: Wire up the script runner to the PIF Builder
Add a plugin test case to verify plugins with swift build build system.
1 parent 6d4ce1c commit 561322a

File tree

5 files changed

+409
-31
lines changed

5 files changed

+409
-31
lines changed

Sources/CoreCommands/BuildSystemSupport.swift

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ private struct SwiftBuildSystemFactory: BuildSystemFactory {
114114
packageGraphLoader: (() async throws -> ModulesGraph)?,
115115
outputStream: OutputByteStream?,
116116
logLevel: Diagnostic.Severity?,
117-
observabilityScope: ObservabilityScope?
117+
observabilityScope: ObservabilityScope?,
118118
) throws -> any BuildSystem {
119119
return try SwiftBuildSystem(
120120
buildParameters: productsBuildParameters ?? self.swiftCommandState.productsBuildParameters,
@@ -124,10 +124,17 @@ private struct SwiftBuildSystemFactory: BuildSystemFactory {
124124
)
125125
},
126126
packageManagerResourcesDirectory: swiftCommandState.packageManagerResourcesDirectory,
127+
additionalFileRules: FileRuleDescription.swiftpmFileTypes,
128+
pkgConfigDirectories: self.swiftCommandState.options.locations.pkgConfigDirectories,
127129
outputStream: outputStream ?? self.swiftCommandState.outputStream,
128130
logLevel: logLevel ?? self.swiftCommandState.logLevel,
129131
fileSystem: self.swiftCommandState.fileSystem,
130-
observabilityScope: observabilityScope ?? self.swiftCommandState.observabilityScope
132+
observabilityScope: observabilityScope ?? self.swiftCommandState.observabilityScope,
133+
pluginConfiguration: .init(
134+
scriptRunner: self.swiftCommandState.getPluginScriptRunner(),
135+
workDirectory: try self.swiftCommandState.getActiveWorkspace().location.pluginWorkingDirectory,
136+
disableSandbox: self.swiftCommandState.shouldDisableSandbox
137+
),
131138
)
132139
}
133140
}

Sources/SwiftBuildSupport/PIFBuilder.swift

Lines changed: 263 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,68 @@ import TSCUtility
2020
@_spi(SwiftPMInternal)
2121
import SPMBuildCore
2222

23-
import func TSCBasic.memoize
2423
import func TSCBasic.topologicalSort
2524
import var TSCBasic.stdoutStream
2625

2726
import enum SwiftBuild.ProjectModel
2827

28+
public func memoize<T>(to cache: inout T?, build: () async throws -> T) async rethrows -> T {
29+
if let value = cache {
30+
return value
31+
} else {
32+
let value = try await build()
33+
cache = value
34+
return value
35+
}
36+
}
37+
38+
extension ModulesGraph {
39+
public static func computePluginGeneratedFiles(
40+
target: ResolvedModule,
41+
toolsVersion: ToolsVersion,
42+
additionalFileRules: [FileRuleDescription],
43+
buildParameters: BuildParameters,
44+
buildToolPluginInvocationResults: [PackagePIFBuilder.BuildToolPluginInvocationResult],
45+
prebuildCommandResults: [CommandPluginResult],
46+
observabilityScope: ObservabilityScope
47+
) throws -> (pluginDerivedSources: Sources, pluginDerivedResources: [Resource]) {
48+
var pluginDerivedSources = Sources(paths: [], root: buildParameters.dataPath)
49+
50+
// Add any derived files that were declared for any commands from plugin invocations.
51+
var pluginDerivedFiles = [AbsolutePath]()
52+
for command in buildToolPluginInvocationResults.reduce([], { $0 + $1.buildCommands }) {
53+
for absPath in command.outputPaths {
54+
pluginDerivedFiles.append(try AbsolutePath(validating: absPath))
55+
}
56+
}
57+
58+
// Add any derived files that were discovered from output directories of prebuild commands.
59+
for result in prebuildCommandResults {
60+
for path in result.derivedFiles {
61+
pluginDerivedFiles.append(path)
62+
}
63+
}
64+
65+
// Let `TargetSourcesBuilder` compute the treatment of plugin generated files.
66+
let (derivedSources, derivedResources) = TargetSourcesBuilder.computeContents(
67+
for: pluginDerivedFiles,
68+
toolsVersion: toolsVersion,
69+
additionalFileRules: additionalFileRules,
70+
defaultLocalization: target.defaultLocalization,
71+
targetName: target.name,
72+
targetPath: target.underlying.path,
73+
observabilityScope: observabilityScope
74+
)
75+
let pluginDerivedResources = derivedResources
76+
derivedSources.forEach { absPath in
77+
let relPath = absPath.relative(to: pluginDerivedSources.root)
78+
pluginDerivedSources.relativePaths.append(relPath)
79+
}
80+
81+
return (pluginDerivedSources, pluginDerivedResources)
82+
}
83+
}
84+
2985
/// The parameters required by `PIFBuilder`.
3086
struct PIFBuilderParameters {
3187
let triple: Basics.Triple
@@ -72,6 +128,14 @@ public final class PIFBuilder {
72128
/// The file system to read from.
73129
let fileSystem: FileSystem
74130

131+
let pluginScriptRunner: PluginScriptRunner
132+
133+
let pluginWorkingDirectory: AbsolutePath
134+
135+
let pkgConfigDirectories: [Basics.AbsolutePath]
136+
137+
let additionalFileRules: [FileRuleDescription]
138+
75139
/// Creates a `PIFBuilder` instance.
76140
/// - Parameters:
77141
/// - graph: The package graph to build from.
@@ -82,12 +146,20 @@ public final class PIFBuilder {
82146
graph: ModulesGraph,
83147
parameters: PIFBuilderParameters,
84148
fileSystem: FileSystem,
85-
observabilityScope: ObservabilityScope
149+
observabilityScope: ObservabilityScope,
150+
pluginScriptRunner: PluginScriptRunner,
151+
pluginWorkingDirectory: AbsolutePath,
152+
pkgConfigDirectories: [Basics.AbsolutePath],
153+
additionalFileRules: [FileRuleDescription]
86154
) {
87155
self.graph = graph
88156
self.parameters = parameters
89157
self.fileSystem = fileSystem
90158
self.observabilityScope = observabilityScope.makeChildScope(description: "PIF Builder")
159+
self.pluginScriptRunner = pluginScriptRunner
160+
self.pluginWorkingDirectory = pluginWorkingDirectory
161+
self.pkgConfigDirectories = pkgConfigDirectories
162+
self.additionalFileRules = additionalFileRules
91163
}
92164

93165
/// Generates the PIF representation.
@@ -100,14 +172,14 @@ public final class PIFBuilder {
100172
preservePIFModelStructure: Bool = false,
101173
printPIFManifestGraphviz: Bool = false,
102174
buildParameters: BuildParameters
103-
) throws -> String {
175+
) async throws -> String {
104176
let encoder = prettyPrint ? JSONEncoder.makeWithDefaults() : JSONEncoder()
105177

106178
if !preservePIFModelStructure {
107179
encoder.userInfo[.encodeForSwiftBuild] = true
108180
}
109181

110-
let topLevelObject = try self.constructPIF(buildParameters: buildParameters)
182+
let topLevelObject = try await self.constructPIF(buildParameters: buildParameters)
111183

112184
// Sign the PIF objects before encoding it for Swift Build.
113185
try PIF.sign(workspace: topLevelObject.workspace)
@@ -130,9 +202,51 @@ public final class PIFBuilder {
130202

131203
private var cachedPIF: PIF.TopLevelObject?
132204

205+
private func buildPluginTools(
206+
graph: ModulesGraph,
207+
buildParameters: BuildParameters,
208+
pluginsPerModule: [ResolvedModule.ID: [ResolvedModule]],
209+
hostTriple: Basics.Triple
210+
) async throws -> [ResolvedModule.ID: [String: PluginTool]] {
211+
var accessibleToolsPerPlugin: [ResolvedModule.ID: [String: PluginTool]] = [:]
212+
213+
for (_, plugins) in pluginsPerModule {
214+
for plugin in plugins where accessibleToolsPerPlugin[plugin.id] == nil {
215+
// Determine the tools to which this plugin has access, and create a name-to-path mapping from tool
216+
// names to the corresponding paths. Built tools are assumed to be in the build tools directory.
217+
let accessibleTools = try await plugin.preparePluginTools(
218+
fileSystem: fileSystem,
219+
environment: buildParameters.buildEnvironment,
220+
for: hostTriple
221+
) { name, path in
222+
//return buildParameters.binaryPath(for: graph.product(for: name))
223+
return buildParameters.buildPath.appending(path)
224+
}
225+
226+
accessibleToolsPerPlugin[plugin.id] = accessibleTools
227+
}
228+
}
229+
230+
return accessibleToolsPerPlugin
231+
}
232+
133233
/// Constructs a `PIF.TopLevelObject` representing the package graph.
134-
private func constructPIF(buildParameters: BuildParameters) throws -> PIF.TopLevelObject {
135-
try memoize(to: &self.cachedPIF) {
234+
private func constructPIF(buildParameters: BuildParameters) async throws -> PIF.TopLevelObject {
235+
let pluginScriptRunner = self.pluginScriptRunner
236+
let outputDir = self.pluginWorkingDirectory.appending("outputs")
237+
238+
let pluginsPerModule = graph.pluginsPerModule(
239+
satisfying: buildParameters.buildEnvironment // .buildEnvironment(for: .host)
240+
)
241+
242+
let pluginTools = try await buildPluginTools(
243+
graph: graph,
244+
buildParameters: buildParameters,
245+
pluginsPerModule: pluginsPerModule,
246+
hostTriple: try pluginScriptRunner.hostTriple
247+
)
248+
249+
return try await memoize(to: &self.cachedPIF) {
136250
guard let rootPackage = self.graph.rootPackages.only else {
137251
if self.graph.rootPackages.isEmpty {
138252
throw PIFGenerationError.rootPackageNotFound
@@ -144,7 +258,136 @@ public final class PIFBuilder {
144258
let sortedPackages = self.graph.packages
145259
.sorted { $0.manifest.displayName < $1.manifest.displayName } // TODO: use identity instead?
146260

147-
let packagesAndProjects: [(ResolvedPackage, ProjectModel.Project)] = try sortedPackages.map { package in
261+
var packagesAndProjects: [(ResolvedPackage, ProjectModel.Project)] = []
262+
263+
for package in sortedPackages {
264+
var buildtoolPluginResults: [String: PackagePIFBuilder.BuildToolPluginInvocationResult] = [:]
265+
266+
for module in package.modules {
267+
// Apply each build tool plugin used by the target in order,
268+
// creating a list of results (one for each plugin usage).
269+
var buildToolPluginResults: [PackagePIFBuilder.BuildToolPluginInvocationResult] = []
270+
271+
for plugin in module.pluginDependencies(satisfying: buildParameters.buildEnvironment) {
272+
let pluginModule = plugin.underlying as! PluginModule
273+
274+
// Determine the tools to which this plugin has access, and create a name-to-path mapping from tool
275+
// names to the corresponding paths. Built tools are assumed to be in the build tools directory.
276+
guard let accessibleTools = pluginTools[plugin.id] else {
277+
throw InternalError("No tools found for plugin \(plugin.name)")
278+
}
279+
280+
// Assign a plugin working directory based on the package, target, and plugin.
281+
let pluginOutputDir = outputDir.appending(
282+
components: [
283+
package.identity.description,
284+
module.name,
285+
buildParameters.destination == .host ? "tools" : "destination",
286+
plugin.name,
287+
]
288+
)
289+
290+
// Determine the set of directories under which plugins are allowed to write.
291+
// We always include just the output directory, and for now there is no possibility
292+
// of opting into others.
293+
let writableDirectories = [outputDir]
294+
295+
// Determine a set of further directories under which plugins are never allowed
296+
// to write, even if they are covered by other rules (such as being able to write
297+
// to the temporary directory).
298+
let readOnlyDirectories = [package.path]
299+
300+
// In tools version 6.0 and newer, we vend the list of files generated by previous plugins.
301+
let pluginDerivedSources: Sources
302+
let pluginDerivedResources: [Resource]
303+
if package.manifest.toolsVersion >= .v6_0 {
304+
// Set up dummy observability because we don't want to emit diagnostics for this before the actual
305+
// build.
306+
let observability = ObservabilitySystem { _, _ in }
307+
// Compute the generated files based on all results we have computed so far.
308+
(pluginDerivedSources, pluginDerivedResources) = try ModulesGraph.computePluginGeneratedFiles(
309+
target: module,
310+
toolsVersion: package.manifest.toolsVersion,
311+
additionalFileRules: self.additionalFileRules,
312+
buildParameters: buildParameters,
313+
buildToolPluginInvocationResults: buildToolPluginResults,
314+
prebuildCommandResults: [],
315+
observabilityScope: observability.topScope
316+
)
317+
} else {
318+
pluginDerivedSources = .init(paths: [], root: package.path)
319+
pluginDerivedResources = []
320+
}
321+
322+
let result = try await pluginModule.invoke(
323+
module: plugin,
324+
action: .createBuildToolCommands(
325+
package: package,
326+
target: module,
327+
pluginGeneratedSources: pluginDerivedSources.paths,
328+
pluginGeneratedResources: pluginDerivedResources.map(\.path)
329+
),
330+
buildEnvironment: buildParameters.buildEnvironment,
331+
scriptRunner: pluginScriptRunner,
332+
workingDirectory: package.path,
333+
outputDirectory: pluginOutputDir,
334+
toolSearchDirectories: [buildParameters.toolchain.swiftCompilerPath.parentDirectory],
335+
accessibleTools: accessibleTools,
336+
writableDirectories: writableDirectories,
337+
readOnlyDirectories: readOnlyDirectories,
338+
allowNetworkConnections: [],
339+
pkgConfigDirectories: self.pkgConfigDirectories,
340+
sdkRootPath: buildParameters.toolchain.sdkRootPath,
341+
fileSystem: fileSystem,
342+
modulesGraph: self.graph,
343+
observabilityScope: observabilityScope
344+
)
345+
346+
// FIXME
347+
/*if surfaceDiagnostics {
348+
let diagnosticsEmitter = observabilityScope.makeDiagnosticsEmitter {
349+
var metadata = ObservabilityMetadata()
350+
metadata.moduleName = module.name
351+
metadata.pluginName = result.plugin.name
352+
return metadata
353+
}
354+
355+
for line in result.textOutput.split(whereSeparator: { $0.isNewline }) {
356+
diagnosticsEmitter.emit(info: line)
357+
}
358+
359+
for diag in result.diagnostics {
360+
diagnosticsEmitter.emit(diag)
361+
}
362+
}*/
363+
364+
let result2 = PackagePIFBuilder.BuildToolPluginInvocationResult(
365+
prebuildCommandOutputPaths: result.prebuildCommands.map( { $0.outputFilesDirectory } ),
366+
buildCommands: result.buildCommands.map( { buildCommand in
367+
var env: [String: String] = [:]
368+
for (key, value) in buildCommand.configuration.environment {
369+
env[key.rawValue] = value
370+
}
371+
372+
return PackagePIFBuilder.CustomBuildCommand(
373+
displayName: buildCommand.configuration.displayName,
374+
executable: buildCommand.configuration.executable.pathString,
375+
arguments: buildCommand.configuration.arguments,
376+
environment: env,
377+
workingDir: buildCommand.configuration.workingDirectory,
378+
inputPaths: buildCommand.inputFiles,
379+
outputPaths: buildCommand.outputFiles.map(\.pathString),
380+
sandboxProfile: nil
381+
)
382+
} )
383+
)
384+
385+
// Add a BuildToolPluginInvocationResult to the mapping.
386+
buildToolPluginResults.append(result2)
387+
buildtoolPluginResults[module.name] = result2
388+
}
389+
}
390+
148391
let packagePIFBuilderDelegate = PackagePIFBuilderDelegate(
149392
package: package
150393
)
@@ -153,15 +396,15 @@ public final class PIFBuilder {
153396
resolvedPackage: package,
154397
packageManifest: package.manifest,
155398
delegate: packagePIFBuilderDelegate,
156-
buildToolPluginResultsByTargetName: [:],
399+
buildToolPluginResultsByTargetName: buildtoolPluginResults,
157400
createDylibForDynamicProducts: self.parameters.shouldCreateDylibForDynamicProducts,
158401
packageDisplayVersion: package.manifest.displayName,
159402
fileSystem: self.fileSystem,
160403
observabilityScope: self.observabilityScope
161404
)
162405

163406
try packagePIFBuilder.build()
164-
return (package, packagePIFBuilder.pifProject)
407+
packagesAndProjects.append((package, packagePIFBuilder.pifProject))
165408
}
166409

167410
var projects = packagesAndProjects.map(\.1)
@@ -192,15 +435,23 @@ public final class PIFBuilder {
192435
fileSystem: FileSystem,
193436
observabilityScope: ObservabilityScope,
194437
preservePIFModelStructure: Bool,
195-
) throws -> String {
438+
pluginScriptRunner: PluginScriptRunner,
439+
pluginWorkingDirectory: AbsolutePath,
440+
pkgConfigDirectories: [Basics.AbsolutePath],
441+
additionalFileRules: [FileRuleDescription]
442+
) async throws -> String {
196443
let parameters = PIFBuilderParameters(buildParameters, supportedSwiftVersions: [])
197444
let builder = Self(
198445
graph: packageGraph,
199446
parameters: parameters,
200447
fileSystem: fileSystem,
201-
observabilityScope: observabilityScope
448+
observabilityScope: observabilityScope,
449+
pluginScriptRunner: pluginScriptRunner,
450+
pluginWorkingDirectory: pluginWorkingDirectory,
451+
pkgConfigDirectories: pkgConfigDirectories,
452+
additionalFileRules: additionalFileRules
202453
)
203-
return try builder.generatePIF(preservePIFModelStructure: preservePIFModelStructure, buildParameters: buildParameters)
454+
return try await builder.generatePIF(preservePIFModelStructure: preservePIFModelStructure, buildParameters: buildParameters)
204455
}
205456
}
206457

0 commit comments

Comments
 (0)