@@ -20,12 +20,68 @@ import TSCUtility
20
20
@_spi ( SwiftPMInternal)
21
21
import SPMBuildCore
22
22
23
- import func TSCBasic. memoize
24
23
import func TSCBasic. topologicalSort
25
24
import var TSCBasic. stdoutStream
26
25
27
26
import enum SwiftBuild. ProjectModel
28
27
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
+
29
85
/// The parameters required by `PIFBuilder`.
30
86
struct PIFBuilderParameters {
31
87
let triple : Basics . Triple
@@ -72,6 +128,14 @@ public final class PIFBuilder {
72
128
/// The file system to read from.
73
129
let fileSystem : FileSystem
74
130
131
+ let pluginScriptRunner : PluginScriptRunner
132
+
133
+ let pluginWorkingDirectory : AbsolutePath
134
+
135
+ let pkgConfigDirectories : [ Basics . AbsolutePath ]
136
+
137
+ let additionalFileRules : [ FileRuleDescription ]
138
+
75
139
/// Creates a `PIFBuilder` instance.
76
140
/// - Parameters:
77
141
/// - graph: The package graph to build from.
@@ -82,12 +146,20 @@ public final class PIFBuilder {
82
146
graph: ModulesGraph ,
83
147
parameters: PIFBuilderParameters ,
84
148
fileSystem: FileSystem ,
85
- observabilityScope: ObservabilityScope
149
+ observabilityScope: ObservabilityScope ,
150
+ pluginScriptRunner: PluginScriptRunner ,
151
+ pluginWorkingDirectory: AbsolutePath ,
152
+ pkgConfigDirectories: [ Basics . AbsolutePath ] ,
153
+ additionalFileRules: [ FileRuleDescription ]
86
154
) {
87
155
self . graph = graph
88
156
self . parameters = parameters
89
157
self . fileSystem = fileSystem
90
158
self . observabilityScope = observabilityScope. makeChildScope ( description: " PIF Builder " )
159
+ self . pluginScriptRunner = pluginScriptRunner
160
+ self . pluginWorkingDirectory = pluginWorkingDirectory
161
+ self . pkgConfigDirectories = pkgConfigDirectories
162
+ self . additionalFileRules = additionalFileRules
91
163
}
92
164
93
165
/// Generates the PIF representation.
@@ -100,14 +172,14 @@ public final class PIFBuilder {
100
172
preservePIFModelStructure: Bool = false ,
101
173
printPIFManifestGraphviz: Bool = false ,
102
174
buildParameters: BuildParameters
103
- ) throws -> String {
175
+ ) async throws -> String {
104
176
let encoder = prettyPrint ? JSONEncoder . makeWithDefaults ( ) : JSONEncoder ( )
105
177
106
178
if !preservePIFModelStructure {
107
179
encoder. userInfo [ . encodeForSwiftBuild] = true
108
180
}
109
181
110
- let topLevelObject = try self . constructPIF ( buildParameters: buildParameters)
182
+ let topLevelObject = try await self . constructPIF ( buildParameters: buildParameters)
111
183
112
184
// Sign the PIF objects before encoding it for Swift Build.
113
185
try PIF . sign ( workspace: topLevelObject. workspace)
@@ -130,9 +202,51 @@ public final class PIFBuilder {
130
202
131
203
private var cachedPIF : PIF . TopLevelObject ?
132
204
205
+ /// Compute the available build tools, and their destination build path for host for each plugin.
206
+ private func availableBuildPluginTools(
207
+ graph: ModulesGraph ,
208
+ buildParameters: BuildParameters ,
209
+ pluginsPerModule: [ ResolvedModule . ID : [ ResolvedModule ] ] ,
210
+ hostTriple: Basics . Triple
211
+ ) async throws -> [ ResolvedModule . ID : [ String : PluginTool ] ] {
212
+ var accessibleToolsPerPlugin : [ ResolvedModule . ID : [ String : PluginTool ] ] = [ : ]
213
+
214
+ for (_, plugins) in pluginsPerModule {
215
+ for plugin in plugins where accessibleToolsPerPlugin [ plugin. id] == nil {
216
+ // Determine the tools to which this plugin has access, and create a name-to-path mapping from tool
217
+ // names to the corresponding paths. Built tools are assumed to be in the build tools directory.
218
+ let accessibleTools = try await plugin. preparePluginTools (
219
+ fileSystem: fileSystem,
220
+ environment: buildParameters. buildEnvironment,
221
+ for: hostTriple
222
+ ) { name, path in
223
+ return buildParameters. buildPath. appending ( path)
224
+ }
225
+
226
+ accessibleToolsPerPlugin [ plugin. id] = accessibleTools
227
+ }
228
+ }
229
+
230
+ return accessibleToolsPerPlugin
231
+ }
232
+
133
233
/// 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 availablePluginTools = try await availableBuildPluginTools (
243
+ graph: graph,
244
+ buildParameters: buildParameters,
245
+ pluginsPerModule: pluginsPerModule,
246
+ hostTriple: try pluginScriptRunner. hostTriple
247
+ )
248
+
249
+ return try await memoize ( to: & self . cachedPIF) {
136
250
guard let rootPackage = self . graph. rootPackages. only else {
137
251
if self . graph. rootPackages. isEmpty {
138
252
throw PIFGenerationError . rootPackageNotFound
@@ -144,7 +258,133 @@ public final class PIFBuilder {
144
258
let sortedPackages = self . graph. packages
145
259
. sorted { $0. manifest. displayName < $1. manifest. displayName } // TODO: use identity instead?
146
260
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 = availablePluginTools [ 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
+ let diagnosticsEmitter = observabilityScope. makeDiagnosticsEmitter {
347
+ var metadata = ObservabilityMetadata ( )
348
+ metadata. moduleName = module. name
349
+ metadata. pluginName = result. plugin. name
350
+ return metadata
351
+ }
352
+
353
+ for line in result. textOutput. split ( whereSeparator: { $0. isNewline } ) {
354
+ diagnosticsEmitter. emit ( info: line)
355
+ }
356
+
357
+ for diag in result. diagnostics {
358
+ diagnosticsEmitter. emit ( diag)
359
+ }
360
+
361
+ let result2 = PackagePIFBuilder . BuildToolPluginInvocationResult (
362
+ prebuildCommandOutputPaths: result. prebuildCommands. map ( { $0. outputFilesDirectory } ) ,
363
+ buildCommands: result. buildCommands. map ( { buildCommand in
364
+ var env : [ String : String ] = [ : ]
365
+ for (key, value) in buildCommand. configuration. environment {
366
+ env [ key. rawValue] = value
367
+ }
368
+
369
+ return PackagePIFBuilder . CustomBuildCommand (
370
+ displayName: buildCommand. configuration. displayName,
371
+ executable: buildCommand. configuration. executable. pathString,
372
+ arguments: buildCommand. configuration. arguments,
373
+ environment: env,
374
+ workingDir: buildCommand. configuration. workingDirectory,
375
+ inputPaths: buildCommand. inputFiles,
376
+ outputPaths: buildCommand. outputFiles. map ( \. pathString) ,
377
+ sandboxProfile: nil
378
+ )
379
+ } )
380
+ )
381
+
382
+ // Add a BuildToolPluginInvocationResult to the mapping.
383
+ buildToolPluginResults. append ( result2)
384
+ buildtoolPluginResults [ module. name] = result2
385
+ }
386
+ }
387
+
148
388
let packagePIFBuilderDelegate = PackagePIFBuilderDelegate (
149
389
package : package
150
390
)
@@ -153,15 +393,15 @@ public final class PIFBuilder {
153
393
resolvedPackage: package ,
154
394
packageManifest: package . manifest,
155
395
delegate: packagePIFBuilderDelegate,
156
- buildToolPluginResultsByTargetName: [ : ] ,
396
+ buildToolPluginResultsByTargetName: buildtoolPluginResults ,
157
397
createDylibForDynamicProducts: self . parameters. shouldCreateDylibForDynamicProducts,
158
398
packageDisplayVersion: package . manifest. displayName,
159
399
fileSystem: self . fileSystem,
160
400
observabilityScope: self . observabilityScope
161
401
)
162
402
163
403
try packagePIFBuilder. build ( )
164
- return ( package , packagePIFBuilder. pifProject)
404
+ packagesAndProjects . append ( ( package , packagePIFBuilder. pifProject) )
165
405
}
166
406
167
407
var projects = packagesAndProjects. map ( \. 1 )
@@ -192,15 +432,23 @@ public final class PIFBuilder {
192
432
fileSystem: FileSystem ,
193
433
observabilityScope: ObservabilityScope ,
194
434
preservePIFModelStructure: Bool ,
195
- ) throws -> String {
435
+ pluginScriptRunner: PluginScriptRunner ,
436
+ pluginWorkingDirectory: AbsolutePath ,
437
+ pkgConfigDirectories: [ Basics . AbsolutePath ] ,
438
+ additionalFileRules: [ FileRuleDescription ]
439
+ ) async throws -> String {
196
440
let parameters = PIFBuilderParameters ( buildParameters, supportedSwiftVersions: [ ] )
197
441
let builder = Self (
198
442
graph: packageGraph,
199
443
parameters: parameters,
200
444
fileSystem: fileSystem,
201
- observabilityScope: observabilityScope
445
+ observabilityScope: observabilityScope,
446
+ pluginScriptRunner: pluginScriptRunner,
447
+ pluginWorkingDirectory: pluginWorkingDirectory,
448
+ pkgConfigDirectories: pkgConfigDirectories,
449
+ additionalFileRules: additionalFileRules
202
450
)
203
- return try builder. generatePIF ( preservePIFModelStructure: preservePIFModelStructure, buildParameters: buildParameters)
451
+ return try await builder. generatePIF ( preservePIFModelStructure: preservePIFModelStructure, buildParameters: buildParameters)
204
452
}
205
453
}
206
454
0 commit comments