Skip to content

Commit 840979b

Browse files
committed
Make packageGraphLoader async
1 parent 4b9f96f commit 840979b

25 files changed

+420
-413
lines changed

Sources/Basics/Concurrency/ThreadSafeBox.swift

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,18 @@ public final class ThreadSafeBox<Value> {
4343
return value
4444
}
4545

46+
@discardableResult
47+
public func memoize(body: () async throws -> Value) async rethrows -> Value {
48+
if let value = self.get() {
49+
return value
50+
}
51+
let value = try await body()
52+
self.lock.withLock {
53+
self.underlying = value
54+
}
55+
return value
56+
}
57+
4658
public func clear() {
4759
self.lock.withLock {
4860
self.underlying = nil

Sources/Basics/FileSystem/AbsolutePath.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ public struct AbsolutePath: Hashable, Sendable {
4040
/// Root directory (whose string representation is just a path separator).
4141
public static let root = Self(TSCAbsolutePath.root)
4242

43-
internal let underlying: TSCAbsolutePath
43+
package let underlying: TSCAbsolutePath
4444

4545
// public for transition
4646
public init(_ underlying: TSCAbsolutePath) {

Sources/Build/BuildOperation.swift

Lines changed: 44 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,11 @@ import DriverSupport
3636
import SwiftDriver
3737
#endif
3838

39+
struct BuildManifestDescription {
40+
let description: BuildDescription
41+
let manifest: LLBuildManifest
42+
}
43+
3944
public final class BuildOperation: PackageStructureDelegate, SPMBuildCore.BuildSystem, BuildErrorAdviceProvider {
4045
/// The delegate used by the build system.
4146
public weak var delegate: SPMBuildCore.BuildSystemDelegate?
@@ -47,7 +52,7 @@ public final class BuildOperation: PackageStructureDelegate, SPMBuildCore.BuildS
4752
let toolsBuildParameters: BuildParameters
4853

4954
/// The closure for loading the package graph.
50-
let packageGraphLoader: () throws -> ModulesGraph
55+
let packageGraphLoader: () async throws -> ModulesGraph
5156

5257
/// the plugin configuration for build plugins
5358
let pluginConfiguration: PluginConfiguration?
@@ -94,7 +99,9 @@ public final class BuildOperation: PackageStructureDelegate, SPMBuildCore.BuildS
9499
private let observabilityScope: ObservabilityScope
95100

96101
public var builtTestProducts: [BuiltTestProduct] {
97-
(try? getBuildDescription())?.builtTestProducts ?? []
102+
get async {
103+
(try? await getBuildDescription())?.builtTestProducts ?? []
104+
}
98105
}
99106

100107
/// File rules to determine resource handling behavior.
@@ -113,7 +120,7 @@ public final class BuildOperation: PackageStructureDelegate, SPMBuildCore.BuildS
113120
productsBuildParameters: BuildParameters,
114121
toolsBuildParameters: BuildParameters,
115122
cacheBuildManifest: Bool,
116-
packageGraphLoader: @escaping () throws -> ModulesGraph,
123+
packageGraphLoader: @escaping () async throws -> ModulesGraph,
117124
pluginConfiguration: PluginConfiguration? = .none,
118125
scratchDirectory: AbsolutePath,
119126
additionalFileRules: [FileRuleDescription],
@@ -148,18 +155,20 @@ public final class BuildOperation: PackageStructureDelegate, SPMBuildCore.BuildS
148155
self.observabilityScope = observabilityScope.makeChildScope(description: "Build Operation")
149156
}
150157

151-
public func getPackageGraph() throws -> ModulesGraph {
152-
try self.packageGraph.memoize {
153-
try self.packageGraphLoader()
158+
public var modulesGraph: ModulesGraph {
159+
get async throws {
160+
try await self.packageGraph.memoize {
161+
try await self.packageGraphLoader()
162+
}
154163
}
155164
}
156165

157166
/// Compute and return the latest build description.
158167
///
159168
/// This will try skip build planning if build manifest caching is enabled
160169
/// and the package structure hasn't changed.
161-
public func getBuildDescription(subset: BuildSubset? = nil) throws -> BuildDescription {
162-
return try self.buildDescription.memoize {
170+
public func getBuildDescription(subset: BuildSubset? = nil) async throws -> BuildDescription {
171+
return try await self.buildDescription.memoize {
163172
if self.cacheBuildManifest {
164173
do {
165174
// if buildPackageStructure returns a valid description we use that, otherwise we perform full planning
@@ -182,12 +191,12 @@ public final class BuildOperation: PackageStructureDelegate, SPMBuildCore.BuildS
182191
}
183192
}
184193
// We need to perform actual planning if we reach here.
185-
return try self.plan(subset: subset).description
194+
return try await self.plan(subset: subset).description
186195
}
187196
}
188197

189-
public func getBuildManifest() throws -> LLBuildManifest {
190-
return try self.plan().manifest
198+
public func getBuildManifest() async throws -> LLBuildManifest {
199+
return try await self.plan().manifest
191200
}
192201

193202
/// Cancel the active build operation.
@@ -337,7 +346,7 @@ public final class BuildOperation: PackageStructureDelegate, SPMBuildCore.BuildS
337346
}
338347

339348
/// Perform a build using the given build description and subset.
340-
public func build(subset: BuildSubset) throws {
349+
public func build(subset: BuildSubset) async throws {
341350
guard !self.productsBuildParameters.shouldSkipBuilding else {
342351
return
343352
}
@@ -347,7 +356,7 @@ public final class BuildOperation: PackageStructureDelegate, SPMBuildCore.BuildS
347356
// Get the build description (either a cached one or newly created).
348357

349358
// Get the build description
350-
let buildDescription = try getBuildDescription(subset: subset)
359+
let buildDescription = try await getBuildDescription(subset: subset)
351360

352361
// Verify dependency imports on the described targets
353362
try verifyTargetImports(in: buildDescription)
@@ -361,15 +370,15 @@ public final class BuildOperation: PackageStructureDelegate, SPMBuildCore.BuildS
361370
// If any plugins are part of the build set, compile them now to surface
362371
// any errors up-front. Returns true if we should proceed with the build
363372
// or false if not. It will already have thrown any appropriate error.
364-
guard try self.compilePlugins(in: subset) else {
373+
guard try await self.compilePlugins(in: subset) else {
365374
return
366375
}
367376

368377
// delegate is only available after createBuildSystem is called
369378
progressTracker.buildStart(configuration: self.productsBuildParameters.configuration)
370379

371380
// Perform the build.
372-
let llbuildTarget = try computeLLBuildTargetName(for: subset)
381+
let llbuildTarget = try await computeLLBuildTargetName(for: subset)
373382
let success = buildSystem.build(target: llbuildTarget)
374383

375384
let duration = buildStartTime.distance(to: .now())
@@ -422,10 +431,10 @@ public final class BuildOperation: PackageStructureDelegate, SPMBuildCore.BuildS
422431
/// true if the build should proceed. Throws an error in case of failure. A
423432
/// reason why the build might not proceed even on success is if only plugins
424433
/// should be compiled.
425-
func compilePlugins(in subset: BuildSubset) throws -> Bool {
434+
func compilePlugins(in subset: BuildSubset) async throws -> Bool {
426435
// Figure out what, if any, plugin descriptions to compile, and whether
427436
// to continue building after that based on the subset.
428-
let allPlugins = try getBuildDescription().pluginDescriptions
437+
let allPlugins = try await getBuildDescription().pluginDescriptions
429438
let pluginsToCompile: [PluginDescription]
430439
let continueBuilding: Bool
431440
switch subset {
@@ -518,15 +527,15 @@ public final class BuildOperation: PackageStructureDelegate, SPMBuildCore.BuildS
518527
}
519528

520529
/// Compute the llbuild target name using the given subset.
521-
func computeLLBuildTargetName(for subset: BuildSubset) throws -> String {
530+
func computeLLBuildTargetName(for subset: BuildSubset) async throws -> String {
522531
switch subset {
523532
case .allExcludingTests:
524533
return LLBuildManifestBuilder.TargetKind.main.targetName
525534
case .allIncludingTests:
526535
return LLBuildManifestBuilder.TargetKind.test.targetName
527536
case .product(let productName, let destination):
528537
// FIXME: This is super unfortunate that we might need to load the package graph.
529-
let graph = try getPackageGraph()
538+
let graph = try await self.modulesGraph
530539

531540
let buildTriple: BuildTriple? = if let destination {
532541
destination == .host ? .tools : .destination
@@ -562,7 +571,7 @@ public final class BuildOperation: PackageStructureDelegate, SPMBuildCore.BuildS
562571
return try product.getLLBuildTargetName(buildParameters: buildParameters)
563572
case .target(let targetName, let destination):
564573
// FIXME: This is super unfortunate that we might need to load the package graph.
565-
let graph = try getPackageGraph()
574+
let graph = try await self.modulesGraph
566575

567576
let buildTriple: BuildTriple? = if let destination {
568577
destination == .host ? .tools : .destination
@@ -591,9 +600,9 @@ public final class BuildOperation: PackageStructureDelegate, SPMBuildCore.BuildS
591600
}
592601

593602
/// Create the build plan and return the build description.
594-
private func plan(subset: BuildSubset? = nil) throws -> (description: BuildDescription, manifest: LLBuildManifest) {
603+
private func plan(subset: BuildSubset? = nil) async throws -> BuildManifestDescription {
595604
// Load the package graph.
596-
let graph = try getPackageGraph()
605+
let graph = try await self.modulesGraph
597606
let buildToolPluginInvocationResults: [ResolvedModule.ID: (target: ResolvedModule, results: [BuildToolPluginInvocationResult])]
598607
let prebuildCommandResults: [ResolvedModule.ID: [PrebuildCommandResult]]
599608
// Invoke any build tool plugins in the graph to generate prebuild commands and build commands.
@@ -624,7 +633,7 @@ public final class BuildOperation: PackageStructureDelegate, SPMBuildCore.BuildS
624633
observabilityScope: self.observabilityScope
625634
)
626635

627-
buildToolPluginInvocationResults = try graph.invokeBuildToolPlugins(
636+
buildToolPluginInvocationResults = try await graph.invokeBuildToolPlugins(
628637
outputDir: pluginConfiguration.workDirectory.appending("outputs"),
629638
buildParameters: pluginsBuildParameters,
630639
additionalFileRules: self.additionalFileRules,
@@ -634,7 +643,7 @@ public final class BuildOperation: PackageStructureDelegate, SPMBuildCore.BuildS
634643
observabilityScope: self.observabilityScope,
635644
fileSystem: self.fileSystem
636645
) { name, path in
637-
try buildOperationForPluginDependencies.build(subset: .product(name, for: .host))
646+
try await buildOperationForPluginDependencies.build(subset: .product(name, for: .host))
638647
if let builtTool = try buildOperationForPluginDependencies.buildPlan.buildProducts.first(where: {
639648
$0.product.name == name && $0.buildParameters.destination == .host
640649
}) {
@@ -743,7 +752,7 @@ public final class BuildOperation: PackageStructureDelegate, SPMBuildCore.BuildS
743752
)
744753

745754
// Finally create the llbuild manifest from the plan.
746-
return (buildDescription, buildManifest)
755+
return .init(description: buildDescription, manifest: buildManifest)
747756
}
748757

749758
/// Build the package structure target.
@@ -871,7 +880,16 @@ public final class BuildOperation: PackageStructureDelegate, SPMBuildCore.BuildS
871880

872881
public func packageStructureChanged() -> Bool {
873882
do {
874-
_ = try self.plan()
883+
_ = try temp_await { (callback: @escaping (Result<BuildManifestDescription, any Error>) -> Void) in
884+
_Concurrency.Task {
885+
do {
886+
let value = try await self.plan()
887+
callback(.success(value))
888+
} catch {
889+
callback(.failure(error))
890+
}
891+
}
892+
}
875893
}
876894
catch Diagnostics.fatalError {
877895
return false

Sources/Commands/PackageCommands/APIDiff.swift

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ struct DeprecatedAPIDiff: ParsableCommand {
3232
}
3333
}
3434

35-
struct APIDiff: SwiftCommand {
35+
struct APIDiff: AsyncSwiftCommand {
3636
static let configuration = CommandConfiguration(
3737
commandName: "diagnose-api-breaking-changes",
3838
abstract: "Diagnose API-breaking changes to Swift modules in a package",
@@ -74,7 +74,7 @@ struct APIDiff: SwiftCommand {
7474
@Flag(help: "Regenerate the API baseline, even if an existing one is available.")
7575
var regenerateBaseline: Bool = false
7676

77-
func run(_ swiftCommandState: SwiftCommandState) throws {
77+
func run(_ swiftCommandState: SwiftCommandState) async throws {
7878
let apiDigesterPath = try swiftCommandState.getTargetToolchain().getSwiftAPIDigester()
7979
let apiDigesterTool = SwiftAPIDigester(fileSystem: swiftCommandState.fileSystem, tool: apiDigesterPath)
8080

@@ -85,14 +85,14 @@ struct APIDiff: SwiftCommand {
8585
// We turn build manifest caching off because we need the build plan.
8686
let buildSystem = try swiftCommandState.createBuildSystem(explicitBuildSystem: .native, cacheBuildManifest: false)
8787

88-
let packageGraph = try buildSystem.getPackageGraph()
88+
let packageGraph = try await buildSystem.modulesGraph
8989
let modulesToDiff = try determineModulesToDiff(
9090
packageGraph: packageGraph,
9191
observabilityScope: swiftCommandState.observabilityScope
9292
)
9393

9494
// Build the current package.
95-
try buildSystem.build()
95+
try await buildSystem.build()
9696

9797
// Dump JSON for the baseline package.
9898
let baselineDumper = try APIDigesterBaselineDumper(
@@ -104,7 +104,7 @@ struct APIDiff: SwiftCommand {
104104
observabilityScope: swiftCommandState.observabilityScope
105105
)
106106

107-
let baselineDir = try baselineDumper.emitAPIBaseline(
107+
let baselineDir = try await baselineDumper.emitAPIBaseline(
108108
for: modulesToDiff,
109109
at: overrideBaselineDir,
110110
force: regenerateBaseline,

Sources/Commands/PackageCommands/DumpCommands.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import Foundation
1717
import PackageModel
1818
import XCBuildSupport
1919

20-
struct DumpSymbolGraph: SwiftCommand {
20+
struct DumpSymbolGraph: AsyncSwiftCommand {
2121
static let configuration = CommandConfiguration(
2222
abstract: "Dump Symbol Graph")
2323
static let defaultMinimumAccessLevel = SymbolGraphExtract.AccessLevel.public
@@ -43,12 +43,12 @@ struct DumpSymbolGraph: SwiftCommand {
4343
@Flag(help: "Emit extension block symbols for extensions to external types or directly associate members and conformances with the extended nominal.")
4444
var extensionBlockSymbolBehavior: ExtensionBlockSymbolBehavior = .omitExtensionBlockSymbols
4545

46-
func run(_ swiftCommandState: SwiftCommandState) throws {
46+
func run(_ swiftCommandState: SwiftCommandState) async throws {
4747
// Build the current package.
4848
//
4949
// We turn build manifest caching off because we need the build plan.
5050
let buildSystem = try swiftCommandState.createBuildSystem(explicitBuildSystem: .native, cacheBuildManifest: false)
51-
try buildSystem.build()
51+
try await buildSystem.build()
5252

5353
// Configure the symbol graph extractor.
5454
let symbolGraphExtractor = try SymbolGraphExtract(
@@ -66,7 +66,7 @@ struct DumpSymbolGraph: SwiftCommand {
6666
// Run the tool once for every library and executable target in the root package.
6767
let buildPlan = try buildSystem.buildPlan
6868
let symbolGraphDirectory = buildPlan.destinationBuildParameters.dataPath.appending("symbolgraph")
69-
let targets = try buildSystem.getPackageGraph().rootPackages.flatMap{ $0.targets }.filter{ $0.type == .library }
69+
let targets = try await buildSystem.modulesGraph.rootPackages.flatMap{ $0.targets }.filter{ $0.type == .library }
7070
for target in targets {
7171
print("-- Emitting symbol graph for", target.name)
7272
let result = try symbolGraphExtractor.extractSymbolGraph(

Sources/Commands/PackageCommands/InstalledPackages.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import PackageModel
1717
import TSCBasic
1818

1919
extension SwiftPackageCommand {
20-
struct Install: SwiftCommand {
20+
struct Install: AsyncSwiftCommand {
2121
static let configuration = CommandConfiguration(
2222
commandName: "experimental-install",
2323
abstract: "Offers the ability to install executable products of the current package."
@@ -29,7 +29,7 @@ extension SwiftPackageCommand {
2929
@Option(help: "The name of the executable product to install")
3030
var product: String?
3131

32-
func run(_ tool: SwiftCommandState) throws {
32+
func run(_ tool: SwiftCommandState) async throws {
3333
let swiftpmBinDir = try tool.fileSystem.getOrCreateSwiftPMInstalledBinariesDirectory()
3434

3535
let env = ProcessInfo.processInfo.environment
@@ -80,7 +80,7 @@ extension SwiftPackageCommand {
8080
throw StringError("\(productToInstall.name) is already installed at \(existingPkg.path)")
8181
}
8282

83-
try tool.createBuildSystem(explicitProduct: productToInstall.name)
83+
try await tool.createBuildSystem(explicitProduct: productToInstall.name)
8484
.build(subset: .product(productToInstall.name))
8585

8686
let binPath = try tool.productsBuildParameters.buildPath.appending(component: productToInstall.name)

Sources/Commands/PackageCommands/Learn.swift

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,7 @@ import PackageGraph
1717
import PackageModel
1818

1919
extension SwiftPackageCommand {
20-
struct Learn: SwiftCommand {
21-
20+
struct Learn: AsyncSwiftCommand {
2221
@OptionGroup()
2322
var globalOptions: GlobalOptions
2423

@@ -90,7 +89,7 @@ extension SwiftPackageCommand {
9089
return snippetGroups.filter { !$0.snippets.isEmpty }
9190
}
9291

93-
func run(_ swiftCommandState: SwiftCommandState) throws {
92+
func run(_ swiftCommandState: SwiftCommandState) async throws {
9493
let graph = try swiftCommandState.loadPackageGraph()
9594
let package = graph.rootPackages[graph.rootPackages.startIndex]
9695
print(package.products.map { $0.description })
@@ -99,7 +98,7 @@ extension SwiftPackageCommand {
9998

10099
var cardStack = CardStack(package: package, snippetGroups: snippetGroups, swiftCommandState: swiftCommandState)
101100

102-
cardStack.run()
101+
await cardStack.run()
103102
}
104103
}
105104
}

0 commit comments

Comments
 (0)