Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Paralellize retrieving resolved packages #8220

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Sources/Commands/PackageCommands/ResetCommands.swift
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,8 @@ extension SwiftPackageCommand {
@OptionGroup(visibility: .hidden)
var globalOptions: GlobalOptions

func run(_ swiftCommandState: SwiftCommandState) throws {
try swiftCommandState.getActiveWorkspace().reset(observabilityScope: swiftCommandState.observabilityScope)
func run(_ swiftCommandState: SwiftCommandState) async throws {
try await swiftCommandState.getActiveWorkspace().reset(observabilityScope: swiftCommandState.observabilityScope)
}
}
}
2 changes: 1 addition & 1 deletion Sources/CoreCommands/BuildSystemSupport.swift
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ private struct NativeBuildSystemFactory: BuildSystemFactory {
return try BuildOperation(
productsBuildParameters: try productsBuildParameters ?? self.swiftCommandState.productsBuildParameters,
toolsBuildParameters: try toolsBuildParameters ?? self.swiftCommandState.toolsBuildParameters,
cacheBuildManifest: cacheBuildManifest && self.swiftCommandState.canUseCachedBuildManifest(),
cacheBuildManifest: await self.swiftCommandState.canUseCachedBuildManifest() && cacheBuildManifest,
packageGraphLoader: packageGraphLoader ?? {
try await self.swiftCommandState.loadPackageGraph(
explicitProduct: explicitProduct,
Expand Down
10 changes: 5 additions & 5 deletions Sources/CoreCommands/SwiftCommandState.swift
Original file line number Diff line number Diff line change
Expand Up @@ -99,13 +99,13 @@ extension _SwiftCommand {
}

public protocol SwiftCommand: ParsableCommand, _SwiftCommand {
func run(_ swiftCommandState: SwiftCommandState) throws
func run(_ swiftCommandState: SwiftCommandState) async throws
}

extension SwiftCommand {
public static var _errorLabel: String { "error" }

public func run() throws {
public func run() async throws {
let swiftCommandState = try SwiftCommandState(
options: globalOptions,
toolWorkspaceConfiguration: self.toolWorkspaceConfiguration,
Expand All @@ -119,7 +119,7 @@ extension SwiftCommand {
swiftCommandState.buildSystemProvider = try buildSystemProvider(swiftCommandState)
var toolError: Error? = .none
do {
try self.run(swiftCommandState)
try await self.run(swiftCommandState)
if swiftCommandState.observabilityScope.errorsReported || swiftCommandState.executionStatus == .failure {
throw ExitCode.failure
}
Expand Down Expand Up @@ -689,7 +689,7 @@ public final class SwiftCommandState {
try _manifestLoader.get()
}

public func canUseCachedBuildManifest() throws -> Bool {
public func canUseCachedBuildManifest() async throws -> Bool {
if !self.options.caching.cacheBuildManifest {
return false
}
Expand All @@ -706,7 +706,7 @@ public final class SwiftCommandState {
// Perform steps for build manifest caching if we can enabled it.
//
// FIXME: We don't add edited packages in the package structure command yet (SR-11254).
let hasEditedPackages = try self.getActiveWorkspace().state.dependencies.contains(where: \.isEdited)
let hasEditedPackages = try await self.getActiveWorkspace().state.dependencies.contains(where: \.isEdited)
if hasEditedPackages {
return false
}
Expand Down
20 changes: 15 additions & 5 deletions Sources/Workspace/ManagedDependency.swift
Original file line number Diff line number Diff line change
Expand Up @@ -172,12 +172,18 @@ extension Workspace.ManagedDependency: CustomStringConvertible {

extension Workspace {
/// A collection of managed dependencies.
final public class ManagedDependencies {
public struct ManagedDependencies {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Turning ManagedDependencies into a struct. The alternative would be to make it an actor but since it conforms to Collection, it wouldn't be that easy.

There are only two methods of ManagedDependencies that are mutable – add and remove. Instead of mutating self, we can return copy of Self and do the mutation in WorkspaceState.

private var dependencies: [PackageIdentity: ManagedDependency]

init() {
self.dependencies = [:]
}

private init(
_ dependencies: [PackageIdentity: ManagedDependency]
) {
self.dependencies = dependencies
}

init(_ dependencies: [ManagedDependency]) throws {
// rdar://86857825 do not use Dictionary(uniqueKeysWithValues:) as it can crash the process when input is incorrect such as in older versions of SwiftPM
Expand All @@ -204,12 +210,16 @@ extension Workspace {
return .none
}

public func add(_ dependency: ManagedDependency) {
self.dependencies[dependency.packageRef.identity] = dependency
public func add(_ dependency: ManagedDependency) -> Self {
var dependencies = dependencies
dependencies[dependency.packageRef.identity] = dependency
return ManagedDependencies(dependencies)
}

public func remove(_ identity: PackageIdentity) {
self.dependencies[identity] = nil
public func remove(_ identity: PackageIdentity) -> Self {
var dependencies = dependencies
dependencies[identity] = nil
return ManagedDependencies(dependencies)
}
}
}
Expand Down
16 changes: 8 additions & 8 deletions Sources/Workspace/Workspace+BinaryArtifacts.swift
Original file line number Diff line number Diff line change
Expand Up @@ -811,7 +811,7 @@ extension Workspace {
var artifactsToDownload: [BinaryArtifactsManager.RemoteArtifact] = []
var artifactsToExtract: [ManagedArtifact] = []

for artifact in state.artifacts {
for artifact in await state.artifacts {
if !manifestArtifacts.local
.contains(where: { $0.packageRef == artifact.packageRef && $0.targetName == artifact.targetName }) &&
!manifestArtifacts.remote
Expand All @@ -822,7 +822,7 @@ extension Workspace {
}

for artifact in manifestArtifacts.local {
let existingArtifact = self.state.artifacts[
let existingArtifact = await self.state.artifacts[
packageIdentity: artifact.packageRef.identity,
targetName: artifact.targetName
]
Expand Down Expand Up @@ -859,7 +859,7 @@ extension Workspace {
}

for artifact in manifestArtifacts.remote {
let existingArtifact = self.state.artifacts[
let existingArtifact = await self.state.artifacts[
packageIdentity: artifact.packageRef.identity,
targetName: artifact.targetName
]
Expand Down Expand Up @@ -891,9 +891,9 @@ extension Workspace {
}

// Remove the artifacts and directories which are not needed anymore.
observabilityScope.trap {
await observabilityScope.trap {
for artifact in artifactsToRemove {
state.artifacts.remove(packageIdentity: artifact.packageRef.identity, targetName: artifact.targetName)
await state.artifacts.remove(packageIdentity: artifact.packageRef.identity, targetName: artifact.targetName)

if isAtArtifactsDirectory(artifact) {
try fileSystem.removeFileTree(artifact.path)
Expand Down Expand Up @@ -930,15 +930,15 @@ extension Workspace {

// Add the new artifacts
for artifact in artifactsToAdd {
self.state.artifacts.add(artifact)
await self.state.artifacts.add(artifact)
}

guard !observabilityScope.errorsReported else {
throw Diagnostics.fatalError
}

observabilityScope.trap {
try self.state.save()
await observabilityScope.trap {
try await self.state.save()
}

func isAtArtifactsDirectory(_ artifact: ManagedArtifact) -> Bool {
Expand Down
80 changes: 43 additions & 37 deletions Sources/Workspace/Workspace+Dependencies.swift
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ extension Workspace {
}

// Update the resolved file.
try self.saveResolvedFile(
try await self.saveResolvedFile(
resolvedPackagesStore: resolvedPackagesStore,
dependencyManifests: updatedDependencyManifests,
originHash: resolvedFileOriginHash,
Expand Down Expand Up @@ -219,7 +219,7 @@ extension Workspace {
case .update(let forceResolution):
return try await resolveAndUpdateResolvedFile(forceResolution: forceResolution)
case .bestEffort:
guard !self.state.dependencies.hasEditedDependencies() else {
guard await !self.state.dependencies.hasEditedDependencies() else {
return try await resolveAndUpdateResolvedFile(forceResolution: false)
}
guard self.fileSystem.exists(self.location.resolvedVersionsFile) else {
Expand Down Expand Up @@ -410,9 +410,10 @@ extension Workspace {
//
// We require cloning if there is no checkout or if the checkout doesn't
// match with the pin.
let dependencies = await state.dependencies
let requiredResolvedPackages = resolvedPackagesStore.resolvedPackages.values.filter { pin in
// also compare the location in case it has changed
guard let dependency = state.dependencies[comparingLocation: pin.packageRef] else {
guard let dependency = dependencies[comparingLocation: pin.packageRef] else {
return true
}
switch dependency.state {
Expand All @@ -426,26 +427,31 @@ extension Workspace {
}

// Retrieve the required resolved packages.
for resolvedPackage in requiredResolvedPackages {
await observabilityScope.makeChildScope(
description: "retrieving resolved package versions for dependencies",
metadata: resolvedPackage.packageRef.diagnosticsMetadata
).trap {
switch resolvedPackage.packageRef.kind {
case .localSourceControl, .remoteSourceControl:
_ = try await self.checkoutRepository(
package: resolvedPackage.packageRef,
at: resolvedPackage.state,
observabilityScope: observabilityScope
)
case .registry:
_ = try await self.downloadRegistryArchive(
package: resolvedPackage.packageRef,
at: resolvedPackage.state,
observabilityScope: observabilityScope
)
default:
throw InternalError("invalid resolved package type \(resolvedPackage.packageRef.kind)")
await withThrowingTaskGroup(of: Void.self) { taskGroup in
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Returning back the parallelization of retrieving resolved packages

for resolvedPackage in requiredResolvedPackages {
let observabilityScope = observabilityScope.makeChildScope(
description: "retrieving resolved package versions for dependencies",
metadata: resolvedPackage.packageRef.diagnosticsMetadata
)
taskGroup.addTask {
await observabilityScope.trap {
switch resolvedPackage.packageRef.kind {
case .localSourceControl, .remoteSourceControl:
_ = try await self.checkoutRepository(
package: resolvedPackage.packageRef,
at: resolvedPackage.state,
observabilityScope: observabilityScope
)
case .registry:
_ = try await self.downloadRegistryArchive(
package: resolvedPackage.packageRef,
at: resolvedPackage.state,
observabilityScope: observabilityScope
)
default:
throw InternalError("invalid resolved package type \(resolvedPackage.packageRef.kind)")
}
}
}
}
}
Expand Down Expand Up @@ -523,7 +529,7 @@ extension Workspace {
}

// load and update the `Package.resolved` store with any changes from loading the top level dependencies
guard let resolvedPackagesStore = self.loadAndUpdateResolvedPackagesStore(
guard let resolvedPackagesStore = await self.loadAndUpdateResolvedPackagesStore(
dependencyManifests: currentManifests,
rootManifestsMinimumToolsVersion: rootManifestsMinimumToolsVersion,
observabilityScope: observabilityScope
Expand Down Expand Up @@ -554,7 +560,7 @@ extension Workspace {
case .notRequired:
// since nothing changed we can exit early,
// but need update resolved file and download an missing binary artifact
try self.saveResolvedFile(
try await self.saveResolvedFile(
resolvedPackagesStore: resolvedPackagesStore,
dependencyManifests: currentManifests,
originHash: resolvedFileOriginHash,
Expand Down Expand Up @@ -625,7 +631,7 @@ extension Workspace {
}

// Update the resolved file.
try self.saveResolvedFile(
try await self.saveResolvedFile(
resolvedPackagesStore: resolvedPackagesStore,
dependencyManifests: updatedDependencyManifests,
originHash: resolvedFileOriginHash,
Expand Down Expand Up @@ -679,15 +685,15 @@ extension Workspace {

// First remove the checkouts that are no longer required.
for (packageRef, state) in packageStateChanges {
observabilityScope.makeChildScope(
await observabilityScope.makeChildScope(
description: "removing unneeded checkouts",
metadata: packageRef.diagnosticsMetadata
).trap {
switch state {
case .added, .updated, .unchanged:
break
case .removed:
try self.remove(package: packageRef)
try await self.remove(package: packageRef)
}
}
}
Expand Down Expand Up @@ -772,8 +778,8 @@ extension Workspace {
state: .custom(version: version, path: path),
subpath: RelativePath(validating: "")
)
self.state.dependencies.add(dependency)
try self.state.save()
await self.state.add(dependency: dependency)
try await self.state.save()
return path
} else {
throw InternalError("invalid container for \(package.identity) of type \(package.kind)")
Expand All @@ -800,8 +806,8 @@ extension Workspace {
throw InternalError("invalid package type: \(package.kind)")
}

self.state.dependencies.add(dependency)
try self.state.save()
await self.state.add(dependency: dependency)
try await self.state.save()
return path
}
}
Expand Down Expand Up @@ -889,7 +895,7 @@ extension Workspace {
dependencyManifests: DependencyManifests,
rootManifestsMinimumToolsVersion: ToolsVersion,
observabilityScope: ObservabilityScope
) -> ResolvedPackagesStore? {
) async -> ResolvedPackagesStore? {
guard let resolvedPackagesStore = observabilityScope.trap({ try self.resolvedPackagesStore.load() }) else {
return nil
}
Expand All @@ -899,7 +905,7 @@ extension Workspace {
else {
return nil
}
for dependency in self.state.dependencies.filter(\.packageRef.kind.isResolvable) {
for dependency in await self.state.dependencies.filter(\.packageRef.kind.isResolvable) {
// a required dependency that is already loaded (managed) should be represented in the `Package.resolved` store.
// also comparing location as it may have changed at this point
if requiredDependencies.contains(where: { $0.equalsIncludingLocation(dependency.packageRef) }) {
Expand Down Expand Up @@ -1011,13 +1017,13 @@ extension Workspace {
// Get the existing managed dependency for this package ref, if any.

// first find by identity only since edit location may be different by design
var currentDependency = self.state.dependencies[binding.package.identity]
var currentDependency = await self.state.dependencies[binding.package.identity]
// Check if this is an edited dependency.
if case .edited(let basedOn, _) = currentDependency?.state, let originalReference = basedOn?.packageRef {
packageStateChanges[originalReference.identity] = (originalReference, .unchanged)
} else {
// if not edited, also compare by location since it may have changed
currentDependency = self.state.dependencies[comparingLocation: binding.package]
currentDependency = await self.state.dependencies[comparingLocation: binding.package]
}

switch binding.boundVersion {
Expand Down Expand Up @@ -1119,7 +1125,7 @@ extension Workspace {
}
}
// Set the state of any old package that might have been removed.
for packageRef in self.state.dependencies.lazy.map(\.packageRef)
for packageRef in await self.state.dependencies.lazy.map(\.packageRef)
where packageStateChanges[packageRef.identity] == nil
{
packageStateChanges[packageRef.identity] = (packageRef, .removed)
Expand Down
12 changes: 6 additions & 6 deletions Sources/Workspace/Workspace+Editing.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ extension Workspace {
observabilityScope: ObservabilityScope
) async throws {
// Look up the dependency and check if we can edit it.
guard let dependency = self.state.dependencies[.plain(packageIdentity)] else {
guard let dependency = await self.state.dependencies[.plain(packageIdentity)] else {
observabilityScope.emit(.dependencyNotFound(packageName: packageIdentity))
return
}
Expand Down Expand Up @@ -157,10 +157,10 @@ extension Workspace {
}

// Save the new state.
try self.state.dependencies.add(
dependency.edited(subpath: RelativePath(validating: packageIdentity), unmanagedPath: path)
try await self.state.add(
dependency: dependency.edited(subpath: RelativePath(validating: packageIdentity), unmanagedPath: path)
)
try self.state.save()
try await self.state.save()
}

/// Unedit a managed dependency. See public API unedit(packageName:forceRemove:).
Expand Down Expand Up @@ -222,8 +222,8 @@ extension Workspace {
)
} else {
// The original dependency was removed, update the managed dependency state.
self.state.dependencies.remove(dependency.packageRef.identity)
try self.state.save()
await self.state.remove(identity: dependency.packageRef.identity)
try await self.state.save()
}

// Resolve the dependencies if workspace root is provided. We do this to
Expand Down
Loading