@@ -20,12 +20,68 @@ import TSCUtility
2020@_spi ( SwiftPMInternal)
2121import SPMBuildCore
2222
23- import func TSCBasic. memoize
2423import func TSCBasic. topologicalSort
2524import var TSCBasic. stdoutStream
2625
2726import 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`.
3086struct 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+ /// 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+
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 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) {
136250 guard let rootPackage = self . graph. rootPackages. only else {
137251 if self . graph. rootPackages. isEmpty {
138252 throw PIFGenerationError . rootPackageNotFound
@@ -144,7 +258,133 @@ 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 = 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+
148388 let packagePIFBuilderDelegate = PackagePIFBuilderDelegate (
149389 package : package
150390 )
@@ -153,15 +393,15 @@ public final class PIFBuilder {
153393 resolvedPackage: package ,
154394 packageManifest: package . manifest,
155395 delegate: packagePIFBuilderDelegate,
156- buildToolPluginResultsByTargetName: [ : ] ,
396+ buildToolPluginResultsByTargetName: buildtoolPluginResults ,
157397 createDylibForDynamicProducts: self . parameters. shouldCreateDylibForDynamicProducts,
158398 packageDisplayVersion: package . manifest. displayName,
159399 fileSystem: self . fileSystem,
160400 observabilityScope: self . observabilityScope
161401 )
162402
163403 try packagePIFBuilder. build ( )
164- return ( package , packagePIFBuilder. pifProject)
404+ packagesAndProjects . append ( ( package , packagePIFBuilder. pifProject) )
165405 }
166406
167407 var projects = packagesAndProjects. map ( \. 1 )
@@ -192,15 +432,23 @@ public final class PIFBuilder {
192432 fileSystem: FileSystem ,
193433 observabilityScope: ObservabilityScope ,
194434 preservePIFModelStructure: Bool ,
195- ) throws -> String {
435+ pluginScriptRunner: PluginScriptRunner ,
436+ pluginWorkingDirectory: AbsolutePath ,
437+ pkgConfigDirectories: [ Basics . AbsolutePath ] ,
438+ additionalFileRules: [ FileRuleDescription ]
439+ ) async throws -> String {
196440 let parameters = PIFBuilderParameters ( buildParameters, supportedSwiftVersions: [ ] )
197441 let builder = Self (
198442 graph: packageGraph,
199443 parameters: parameters,
200444 fileSystem: fileSystem,
201- observabilityScope: observabilityScope
445+ observabilityScope: observabilityScope,
446+ pluginScriptRunner: pluginScriptRunner,
447+ pluginWorkingDirectory: pluginWorkingDirectory,
448+ pkgConfigDirectories: pkgConfigDirectories,
449+ additionalFileRules: additionalFileRules
202450 )
203- return try builder. generatePIF ( preservePIFModelStructure: preservePIFModelStructure, buildParameters: buildParameters)
451+ return try await builder. generatePIF ( preservePIFModelStructure: preservePIFModelStructure, buildParameters: buildParameters)
204452 }
205453}
206454
0 commit comments