@@ -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
+ 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
+
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 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) {
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,136 @@ 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 = 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
+
148
391
let packagePIFBuilderDelegate = PackagePIFBuilderDelegate (
149
392
package : package
150
393
)
@@ -153,15 +396,15 @@ public final class PIFBuilder {
153
396
resolvedPackage: package ,
154
397
packageManifest: package . manifest,
155
398
delegate: packagePIFBuilderDelegate,
156
- buildToolPluginResultsByTargetName: [ : ] ,
399
+ buildToolPluginResultsByTargetName: buildtoolPluginResults ,
157
400
createDylibForDynamicProducts: self . parameters. shouldCreateDylibForDynamicProducts,
158
401
packageDisplayVersion: package . manifest. displayName,
159
402
fileSystem: self . fileSystem,
160
403
observabilityScope: self . observabilityScope
161
404
)
162
405
163
406
try packagePIFBuilder. build ( )
164
- return ( package , packagePIFBuilder. pifProject)
407
+ packagesAndProjects . append ( ( package , packagePIFBuilder. pifProject) )
165
408
}
166
409
167
410
var projects = packagesAndProjects. map ( \. 1 )
@@ -192,15 +435,23 @@ public final class PIFBuilder {
192
435
fileSystem: FileSystem ,
193
436
observabilityScope: ObservabilityScope ,
194
437
preservePIFModelStructure: Bool ,
195
- ) throws -> String {
438
+ pluginScriptRunner: PluginScriptRunner ,
439
+ pluginWorkingDirectory: AbsolutePath ,
440
+ pkgConfigDirectories: [ Basics . AbsolutePath ] ,
441
+ additionalFileRules: [ FileRuleDescription ]
442
+ ) async throws -> String {
196
443
let parameters = PIFBuilderParameters ( buildParameters, supportedSwiftVersions: [ ] )
197
444
let builder = Self (
198
445
graph: packageGraph,
199
446
parameters: parameters,
200
447
fileSystem: fileSystem,
201
- observabilityScope: observabilityScope
448
+ observabilityScope: observabilityScope,
449
+ pluginScriptRunner: pluginScriptRunner,
450
+ pluginWorkingDirectory: pluginWorkingDirectory,
451
+ pkgConfigDirectories: pkgConfigDirectories,
452
+ additionalFileRules: additionalFileRules
202
453
)
203
- return try builder. generatePIF ( preservePIFModelStructure: preservePIFModelStructure, buildParameters: buildParameters)
454
+ return try await builder. generatePIF ( preservePIFModelStructure: preservePIFModelStructure, buildParameters: buildParameters)
204
455
}
205
456
}
206
457
0 commit comments