Skip to content

Commit e7d756b

Browse files
authored
[Build] NFC: Compute per-module macro requirements on-demand (#7812)
Instead of having to go over all of the products and establish `module -> product` relationships during build plan construction let swift module build descriptions compute required macros based on their underlying modules as necessary. ### Motivation: Build plan is required to pre-compute macro requirements but that's expensive and shouldn't be necessary. ### Modifications: - Instead of passing macro products to `SwiftModuleBuildDescription` all we need to pass are build parameters to use for macros and the macros themselves could be found based on the underlying module. ### Result: Build plan construction is slightly simpler and less expensive to compute, product and module descriptions could now be computed independently.
1 parent 12c1422 commit e7d756b

File tree

6 files changed

+109
-59
lines changed

6 files changed

+109
-59
lines changed

Sources/Build/BuildDescription/SwiftModuleBuildDescription.swift

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,9 @@ public final class SwiftModuleBuildDescription {
4949
/// The build parameters for this target.
5050
let buildParameters: BuildParameters
5151

52+
/// The build parameters for the macro dependencies of this target.
53+
let macroBuildParameters: BuildParameters
54+
5255
/// Path to the temporary directory for this target.
5356
let tempsPath: AbsolutePath
5457

@@ -237,8 +240,13 @@ public final class SwiftModuleBuildDescription {
237240
/// The results of running any prebuild commands for this target.
238241
public let prebuildCommandResults: [CommandPluginResult]
239242

240-
/// Any macro products that this target requires to build.
241-
public let requiredMacroProducts: [ProductBuildDescription]
243+
public var requiredMacros: [ResolvedModule] {
244+
get throws {
245+
try self.target.recursiveModuleDependencies().filter {
246+
$0.type == .macro
247+
}
248+
}
249+
}
242250

243251
/// ObservabilityScope with which to emit diagnostics
244252
private let observabilityScope: ObservabilityScope
@@ -256,9 +264,9 @@ public final class SwiftModuleBuildDescription {
256264
toolsVersion: ToolsVersion,
257265
additionalFileRules: [FileRuleDescription] = [],
258266
buildParameters: BuildParameters,
267+
macroBuildParameters: BuildParameters,
259268
buildToolPluginInvocationResults: [BuildToolPluginInvocationResult] = [],
260269
prebuildCommandResults: [CommandPluginResult] = [],
261-
requiredMacroProducts: [ProductBuildDescription] = [],
262270
testTargetRole: TestTargetRole? = nil,
263271
shouldGenerateTestObservation: Bool = false,
264272
shouldDisableSandbox: Bool,
@@ -274,6 +282,7 @@ public final class SwiftModuleBuildDescription {
274282
self.target = target
275283
self.toolsVersion = toolsVersion
276284
self.buildParameters = buildParameters
285+
self.macroBuildParameters = macroBuildParameters
277286

278287
// Unless mentioned explicitly, use the target type to determine if this is a test target.
279288
if let testTargetRole {
@@ -288,7 +297,6 @@ public final class SwiftModuleBuildDescription {
288297
self.derivedSources = Sources(paths: [], root: self.tempsPath.appending("DerivedSources"))
289298
self.buildToolPluginInvocationResults = buildToolPluginInvocationResults
290299
self.prebuildCommandResults = prebuildCommandResults
291-
self.requiredMacroProducts = requiredMacroProducts
292300
self.shouldGenerateTestObservation = shouldGenerateTestObservation
293301
self.shouldDisableSandbox = shouldDisableSandbox
294302
self.fileSystem = fileSystem
@@ -415,17 +423,17 @@ public final class SwiftModuleBuildDescription {
415423
var args = [String]()
416424

417425
#if BUILD_MACROS_AS_DYLIBS
418-
self.requiredMacroProducts.forEach { macro in
419-
args += ["-Xfrontend", "-load-plugin-library", "-Xfrontend", macro.binaryPath.pathString]
426+
try self.requiredMacros.forEach { macro in
427+
args += [
428+
"-Xfrontend", "-load-plugin-library",
429+
"-Xfrontend", macroBuildParameters.macroBinaryPath(macro).pathString
430+
]
420431
}
421432
#else
422-
try self.requiredMacroProducts.forEach { macro in
423-
if let macroTarget = macro.product.modules.first {
424-
let executablePath = try macro.binaryPath.pathString
425-
args += ["-Xfrontend", "-load-plugin-executable", "-Xfrontend", "\(executablePath)#\(macroTarget.c99name)"]
426-
} else {
427-
throw InternalError("macro product \(macro.product.name) has no targets") // earlier validation should normally catch this
428-
}
433+
let macroModules = try self.requiredMacros
434+
try macroModules.forEach { macro in
435+
let executablePath = try macroBuildParameters.macroBinaryPath(macro).pathString
436+
args += ["-Xfrontend", "-load-plugin-executable", "-Xfrontend", "\(executablePath)#\(macro.c99name)"]
429437
}
430438
#endif
431439

@@ -439,7 +447,7 @@ public final class SwiftModuleBuildDescription {
439447
args += ["-disable-sandbox"]
440448
} else {
441449
// If there's at least one macro being used, we warn about our inability to disable sandboxing.
442-
if !self.requiredMacroProducts.isEmpty {
450+
if !macroModules.isEmpty {
443451
observabilityScope.emit(warning: "cannot disable sandboxing for Swift compilation because the selected toolchain does not support it")
444452
}
445453
}

Sources/Build/BuildManifest/LLBuildManifestBuilder+Product.swift

Lines changed: 56 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import struct Basics.AbsolutePath
1414
import struct Basics.InternalError
1515
import struct LLBuildManifest.Node
1616
import struct SPMBuildCore.BuildParameters
17+
import struct PackageGraph.ResolvedModule
1718
import struct PackageGraph.ResolvedProduct
1819

1920
extension LLBuildManifestBuilder {
@@ -138,31 +139,71 @@ extension ProductBuildDescription {
138139
}
139140
}
140141

142+
fileprivate func llbuildNameWithoutExtension(
143+
for product: String,
144+
buildParameters: BuildParameters
145+
) -> String {
146+
"\(product)-\(buildParameters.triple.tripleString)-\(buildParameters.buildConfig)\(buildParameters.suffix)"
147+
}
148+
149+
fileprivate func executableName(
150+
for product: String,
151+
buildParameters: BuildParameters
152+
) -> String {
153+
"\(llbuildNameWithoutExtension(for: product, buildParameters: buildParameters)).exe"
154+
}
155+
156+
fileprivate func dynamicLibraryName(
157+
for product: String,
158+
buildParameters: BuildParameters
159+
) -> String {
160+
"\(llbuildNameWithoutExtension(for: product, buildParameters: buildParameters)).dylib"
161+
}
162+
163+
fileprivate func staticLibraryName(
164+
for product: String,
165+
buildParameters: BuildParameters
166+
) -> String {
167+
"\(llbuildNameWithoutExtension(for: product, buildParameters: buildParameters)).a"
168+
}
169+
170+
fileprivate func testName(
171+
for testProduct: String,
172+
buildParameters: BuildParameters
173+
) -> String {
174+
"\(llbuildNameWithoutExtension(for: testProduct, buildParameters: buildParameters)).test"
175+
}
176+
177+
func getLLBuildTargetName(
178+
macro: ResolvedModule,
179+
buildParameters: BuildParameters
180+
) -> String {
181+
assert(macro.type == .macro)
182+
#if BUILD_MACROS_AS_DYLIBS
183+
return dynamicLibraryName(for: macro.name, buildParameters: buildParameters)
184+
#else
185+
return executableName(for: macro.name, buildParameters: buildParameters)
186+
#endif
187+
}
188+
141189
extension ResolvedProduct {
142190
public func getLLBuildTargetName(buildParameters: BuildParameters) throws -> String {
143-
let triple = buildParameters.triple.tripleString
144-
let config = buildParameters.buildConfig
145-
let suffix = buildParameters.suffix
146-
let potentialExecutableTargetName = "\(name)-\(triple)-\(config)\(suffix).exe"
147-
let potentialLibraryTargetName = "\(name)-\(triple)-\(config)\(suffix).dylib"
148-
149191
switch type {
150192
case .library(.dynamic):
151-
return potentialLibraryTargetName
193+
return dynamicLibraryName(for: self.name, buildParameters: buildParameters)
152194
case .test:
153-
return "\(name)-\(triple)-\(config)\(suffix).test"
195+
return testName(for: self.name, buildParameters: buildParameters)
154196
case .library(.static):
155-
return "\(name)-\(triple)-\(config)\(suffix).a"
197+
return staticLibraryName(for: self.name, buildParameters: buildParameters)
156198
case .library(.automatic):
157199
throw InternalError("automatic library not supported")
158200
case .executable, .snippet:
159-
return potentialExecutableTargetName
201+
return executableName(for: self.name, buildParameters: buildParameters)
160202
case .macro:
161-
#if BUILD_MACROS_AS_DYLIBS
162-
return potentialLibraryTargetName
163-
#else
164-
return potentialExecutableTargetName
165-
#endif
203+
guard let macroModule = self.modules.first else {
204+
throw InternalError("macro product \(self.name) has no targets")
205+
}
206+
return Build.getLLBuildTargetName(macro: macroModule, buildParameters: buildParameters)
166207
case .plugin:
167208
throw InternalError("unexpectedly asked for the llbuild target name of a plugin product")
168209
}

Sources/Build/BuildManifest/LLBuildManifestBuilder+Swift.swift

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -498,9 +498,12 @@ extension LLBuildManifestBuilder {
498498

499499
let additionalInputs = try self.addBuildToolPlugins(.swift(target))
500500

501-
// Depend on any required macro product's output.
502-
try target.requiredMacroProducts.forEach { macro in
503-
try inputs.append(.virtual(macro.llbuildTargetName))
501+
// Depend on any required macro's output.
502+
try target.requiredMacros.forEach { macro in
503+
inputs.append(.virtual(getLLBuildTargetName(
504+
macro: macro,
505+
buildParameters: target.macroBuildParameters
506+
)))
504507
}
505508

506509
return inputs + additionalInputs

Sources/Build/BuildPlan/BuildPlan+Test.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ extension BuildPlan {
106106
target: discoveryResolvedModule,
107107
toolsVersion: toolsVersion,
108108
buildParameters: testBuildDescription.buildParameters,
109+
macroBuildParameters: toolsBuildParameters,
109110
testTargetRole: .discovery,
110111
shouldDisableSandbox: shouldDisableSandbox,
111112
fileSystem: fileSystem,
@@ -148,6 +149,7 @@ extension BuildPlan {
148149
target: entryPointResolvedTarget,
149150
toolsVersion: toolsVersion,
150151
buildParameters: testBuildDescription.buildParameters,
152+
macroBuildParameters: toolsBuildParameters,
151153
testTargetRole: .entryPoint(isSynthesized: true),
152154
shouldDisableSandbox: shouldDisableSandbox,
153155
fileSystem: fileSystem,
@@ -192,6 +194,7 @@ extension BuildPlan {
192194
target: entryPointResolvedTarget,
193195
toolsVersion: toolsVersion,
194196
buildParameters: destinationBuildParameters,
197+
macroBuildParameters: toolsBuildParameters,
195198
testTargetRole: .entryPoint(isSynthesized: false),
196199
shouldDisableSandbox: shouldDisableSandbox,
197200
fileSystem: fileSystem,
@@ -214,6 +217,7 @@ extension BuildPlan {
214217
target: entryPointResolvedTarget,
215218
toolsVersion: toolsVersion,
216219
buildParameters: destinationBuildParameters,
220+
macroBuildParameters: toolsBuildParameters,
217221
testTargetRole: .entryPoint(isSynthesized: false),
218222
shouldDisableSandbox: shouldDisableSandbox,
219223
fileSystem: fileSystem,

Sources/Build/BuildPlan/BuildPlan.swift

Lines changed: 1 addition & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -312,12 +312,6 @@ public class BuildPlan: SPMBuildCore.BuildPlan {
312312
observabilityScope: observabilityScope
313313
))
314314
}
315-
let macroProductsByTarget = productMap.values.filter { $0.product.type == .macro }
316-
.reduce(into: [ResolvedModule.ID: ResolvedProduct]()) {
317-
if let target = $1.product.modules.first {
318-
$0[target.id] = $1.product
319-
}
320-
}
321315

322316
// Create build target description for each target which we need to plan.
323317
// Plugin targets are noted, since they need to be compiled, but they do
@@ -392,18 +386,6 @@ public class BuildPlan: SPMBuildCore.BuildPlan {
392386
throw InternalError("package not found for \(target)")
393387
}
394388

395-
let requiredMacroProducts = try target.recursiveModuleDependencies()
396-
.filter { $0.underlying.type == .macro }
397-
.compactMap {
398-
guard let product = macroProductsByTarget[$0.id],
399-
let description = productMap[product.id] else
400-
{
401-
throw InternalError("macro product not found for \($0)")
402-
}
403-
404-
return description.buildDescription
405-
}
406-
407389
var generateTestObservation = false
408390
if target.type == .test && shouldGenerateTestObservation {
409391
generateTestObservation = true
@@ -417,9 +399,9 @@ public class BuildPlan: SPMBuildCore.BuildPlan {
417399
toolsVersion: toolsVersion,
418400
additionalFileRules: additionalFileRules,
419401
buildParameters: buildParameters,
402+
macroBuildParameters: toolsBuildParameters,
420403
buildToolPluginInvocationResults: buildToolPluginInvocationResults[target.id] ?? [],
421404
prebuildCommandResults: prebuildCommandResults[target.id] ?? [],
422-
requiredMacroProducts: requiredMacroProducts,
423405
shouldGenerateTestObservation: generateTestObservation,
424406
shouldDisableSandbox: self.shouldDisableSandbox,
425407
fileSystem: fileSystem,

Sources/SPMBuildCore/BuildParameters/BuildParameters.swift

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -271,22 +271,34 @@ public struct BuildParameters: Encodable {
271271
return try buildPath.appending(binaryRelativePath(for: product))
272272
}
273273

274+
public func macroBinaryPath(_ module: ResolvedModule) throws -> AbsolutePath {
275+
assert(module.type == .macro)
276+
#if BUILD_MACROS_AS_DYLIBS
277+
return buildPath.appending(try dynamicLibraryPath(for: module.name))
278+
#else
279+
return buildPath.appending(try executablePath(for: module.name))
280+
#endif
281+
}
282+
274283
/// Returns the path to the dynamic library of a product for the current build parameters.
275-
func potentialDynamicLibraryPath(for product: ResolvedProduct) throws -> RelativePath {
276-
try RelativePath(validating: "\(self.triple.dynamicLibraryPrefix)\(product.name)\(self.suffix)\(self.triple.dynamicLibraryExtension)")
284+
private func dynamicLibraryPath(for name: String) throws -> RelativePath {
285+
try RelativePath(validating: "\(self.triple.dynamicLibraryPrefix)\(name)\(self.suffix)\(self.triple.dynamicLibraryExtension)")
286+
}
287+
288+
/// Returns the path to the executable of a product for the current build parameters.
289+
private func executablePath(for name: String) throws -> RelativePath {
290+
try RelativePath(validating: "\(name)\(self.suffix)\(self.triple.executableExtension)")
277291
}
278292

279293
/// Returns the path to the binary of a product for the current build parameters, relative to the build directory.
280294
public func binaryRelativePath(for product: ResolvedProduct) throws -> RelativePath {
281-
let potentialExecutablePath = try RelativePath(validating: "\(product.name)\(self.suffix)\(self.triple.executableExtension)")
282-
283295
switch product.type {
284296
case .executable, .snippet:
285-
return potentialExecutablePath
297+
return try executablePath(for: product.name)
286298
case .library(.static):
287299
return try RelativePath(validating: "lib\(product.name)\(self.suffix)\(self.triple.staticLibraryExtension)")
288300
case .library(.dynamic):
289-
return try potentialDynamicLibraryPath(for: product)
301+
return try dynamicLibraryPath(for: product.name)
290302
case .library(.automatic), .plugin:
291303
fatalError()
292304
case .test:
@@ -301,9 +313,9 @@ public struct BuildParameters: Encodable {
301313
}
302314
case .macro:
303315
#if BUILD_MACROS_AS_DYLIBS
304-
return try potentialDynamicLibraryPath(for: product)
316+
return try dynamicLibraryPath(for: product.name)
305317
#else
306-
return potentialExecutablePath
318+
return try executablePath(for: product.name)
307319
#endif
308320
}
309321
}

0 commit comments

Comments
 (0)