Skip to content

Remove uses of temp_await from PubGrubDependencyResolver #7452

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

Draft
wants to merge 12 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ let benchmarks = {
let workspace = try Workspace(fileSystem: localFileSystem, location: .init(forRootPackage: path, fileSystem: localFileSystem))

for _ in benchmark.scaledIterations {
try workspace.loadPackageGraph(rootPath: path, observabilityScope: ObservabilitySystem.NOOP)
try await workspace.loadPackageGraph(rootPath: path, observabilityScope: ObservabilitySystem.NOOP)
}
}

Expand Down
1 change: 1 addition & 0 deletions Sources/Basics/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ add_library(Basics
DispatchTimeInterval+Extensions.swift
EnvironmentVariables.swift
Errors.swift
GraphAlgorithms.swift
FileSystem/AbsolutePath.swift
FileSystem/FileSystem+Extensions.swift
FileSystem/InMemoryFileSystem.swift
Expand Down
14 changes: 14 additions & 0 deletions Sources/Basics/Concurrency/ConcurrencyHelpers.swift
Original file line number Diff line number Diff line change
Expand Up @@ -81,3 +81,17 @@ public func safe_async<T>(_ body: @escaping @Sendable (@escaping (Result<T, Neve
// As of Swift 5.7 and 5.8 swift-corelibs-foundation doesn't have `Sendable` annotations yet.
extension URL: @unchecked Sendable {}
#endif

extension Collection {
package func parallelMap<NewElement>(
transform: @escaping (Element) async throws -> NewElement
) async rethrows -> [NewElement] {
try await withThrowingTaskGroup(of: NewElement.self) { group in
for element in self {
group.addTask { try await transform(element) }
}

return try await group.reduce(into: []) { $0.append($1) }
}
}
}
121 changes: 121 additions & 0 deletions Sources/Basics/GraphAlgorithms.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
/*
This source file is part of the Swift.org open source project

Copyright (c) 2014 - 2024 Apple Inc. and the Swift project authors
Licensed under Apache License v2.0 with Runtime Library Exception

See http://swift.org/LICENSE.txt for license information
See http://swift.org/CONTRIBUTORS.txt for Swift project authors
*/

import OrderedCollections

package func findCycle<T: Hashable>(
_ nodes: [T],
successors: (T) async throws -> [T]
) async rethrows -> (path: [T], cycle: [T])? {
// Ordered set to hold the current traversed path.
var path = OrderedSet<T>()
var validNodes = Set<T>()

// Function to visit nodes recursively.
// FIXME: Convert to stack.
func visit(_ node: T, _ successors: (T) async throws -> [T]) async rethrows -> (path: [T], cycle: [T])? {
if validNodes.contains(node) { return nil }

// If this node is already in the current path then we have found a cycle.
if !path.append(node).inserted {
let index = path.firstIndex(of: node)!
return (Array(path[path.startIndex..<index]), Array(path[index..<path.endIndex]))
}

for succ in try await successors(node) {
if let cycle = try await visit(succ, successors) {
return cycle
}
}
// No cycle found for this node, remove it from the path.
let item = path.removeLast()
assert(item == node)
validNodes.insert(node)
return nil
}

for node in nodes {
if let cycle = try await visit(node, successors) {
return cycle
}
}
// Couldn't find any cycle in the graph.
return nil
}

enum GraphError: Error {
/// A cycle was detected in the input.
case unexpectedCycle
}

/// Perform a topological sort of an graph.
///
/// This function is optimized for use cases where cycles are unexpected, and
/// does not attempt to retain information on the exact nodes in the cycle.
///
/// - Parameters:
/// - nodes: The list of input nodes to sort.
/// - successors: A closure for fetching the successors of a particular node.
///
/// - Returns: A list of the transitive closure of nodes reachable from the
/// inputs, ordered such that every node in the list follows all of its
/// predecessors.
///
/// - Throws: GraphError.unexpectedCycle
///
/// - Complexity: O(v + e) where (v, e) are the number of vertices and edges
/// reachable from the input nodes via the relation.
package func topologicalSort<T: Hashable>(
_ nodes: [T], successors: (T) async throws -> [T]
) async throws -> [T] {
// Implements a topological sort via recursion and reverse postorder DFS.
func visit(
_ node: T,
_ stack: inout OrderedSet<T>,
_ visited: inout Set<T>,
_ result: inout [T],
_ successors: (T) async throws -> [T]
) async throws {
// Mark this node as visited -- we are done if it already was.
if !visited.insert(node).inserted {
return
}

// Otherwise, visit each adjacent node.
for succ in try await successors(node) {
guard stack.append(succ).inserted else {
// If the successor is already in this current stack, we have found a cycle.
//
// FIXME: We could easily include information on the cycle we found here.
throw GraphError.unexpectedCycle
}
// FIXME: This should use a stack not recursion.
try await visit(succ, &stack, &visited, &result, successors)
let popped = stack.removeLast()
assert(popped == succ)
}

// Add to the result.
result.append(node)
}

var visited = Set<T>()
var result = [T]()
var stack = OrderedSet<T>()
for node in nodes {
precondition(stack.isEmpty)
stack.append(node)
try await visit(node, &stack, &visited, &result, successors)
let popped = stack.removeLast()
assert(popped == node)
}

return result.reversed()
}
29 changes: 29 additions & 0 deletions Sources/Basics/Observability.swift
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,20 @@ extension DiagnosticsEmitterProtocol {
}
}

/// trap an async throwing closure, emitting diagnostics on error and returning the value returned by the closure
public func trap<T>(_ closure: () async throws -> T) async -> T? {
do {
return try await closure()
} catch Diagnostics.fatalError {
// FIXME: (diagnostics) deprecate this with Diagnostics.fatalError
return nil
} catch {
self.emit(error)
return nil
}
}


/// trap a throwing closure, emitting diagnostics on error and returning boolean representing success
@discardableResult
public func trap(_ closure: () throws -> Void) -> Bool {
Expand All @@ -265,6 +279,21 @@ extension DiagnosticsEmitterProtocol {
}
}

/// trap an async throwing closure, emitting diagnostics on error and returning boolean representing success
@discardableResult
public func trap(_ closure: () async throws -> Void) async -> Bool {
do {
try await closure()
return true
} catch Diagnostics.fatalError {
// FIXME: (diagnostics) deprecate this with Diagnostics.fatalError
return false
} catch {
self.emit(error)
return false
}
}

/// If `underlyingError` is not `nil`, its human-readable description is interpolated with `message`,
/// otherwise `message` itself is returned.
private func makeMessage(from message: String, underlyingError: Error?) -> String {
Expand Down
6 changes: 3 additions & 3 deletions Sources/Commands/PackageCommands/APIDiff.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ struct DeprecatedAPIDiff: ParsableCommand {
}
}

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

func run(_ swiftCommandState: SwiftCommandState) throws {
func run(_ swiftCommandState: SwiftCommandState) async throws {
let apiDigesterPath = try swiftCommandState.getTargetToolchain().getSwiftAPIDigester()
let apiDigesterTool = SwiftAPIDigester(fileSystem: swiftCommandState.fileSystem, tool: apiDigesterPath)

Expand Down Expand Up @@ -104,7 +104,7 @@ struct APIDiff: SwiftCommand {
observabilityScope: swiftCommandState.observabilityScope
)

let baselineDir = try baselineDumper.emitAPIBaseline(
let baselineDir = try await baselineDumper.emitAPIBaseline(
for: modulesToDiff,
at: overrideBaselineDir,
force: regenerateBaseline,
Expand Down
16 changes: 8 additions & 8 deletions Sources/Commands/PackageCommands/EditCommands.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import CoreCommands
import SourceControl

extension SwiftPackageCommand {
struct Edit: SwiftCommand {
struct Edit: AsyncSwiftCommand {
static let configuration = CommandConfiguration(
abstract: "Put a package in editable mode")

Expand All @@ -35,12 +35,12 @@ extension SwiftPackageCommand {
@Argument(help: "The name of the package to edit")
var packageName: String

func run(_ swiftCommandState: SwiftCommandState) throws {
try swiftCommandState.resolve()
func run(_ swiftCommandState: SwiftCommandState) async throws {
try await swiftCommandState.resolve()
let workspace = try swiftCommandState.getActiveWorkspace()

// Put the dependency in edit mode.
workspace.edit(
await workspace.edit(
packageName: packageName,
path: path,
revision: revision,
Expand All @@ -50,7 +50,7 @@ extension SwiftPackageCommand {
}
}

struct Unedit: SwiftCommand {
struct Unedit: AsyncSwiftCommand {
static let configuration = CommandConfiguration(
abstract: "Remove a package from editable mode")

Expand All @@ -64,11 +64,11 @@ extension SwiftPackageCommand {
@Argument(help: "The name of the package to unedit")
var packageName: String

func run(_ swiftCommandState: SwiftCommandState) throws {
try swiftCommandState.resolve()
func run(_ swiftCommandState: SwiftCommandState) async throws {
try await swiftCommandState.resolve()
let workspace = try swiftCommandState.getActiveWorkspace()

try workspace.unedit(
try await workspace.unedit(
packageName: packageName,
forceRemove: shouldForceRemove,
root: swiftCommandState.getWorkspaceRoot(),
Expand Down
6 changes: 3 additions & 3 deletions Sources/Commands/PackageCommands/InstalledPackages.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import PackageModel
import TSCBasic

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

func run(_ tool: SwiftCommandState) throws {
func run(_ tool: SwiftCommandState) async throws {
let swiftpmBinDir = try tool.fileSystem.getOrCreateSwiftPMInstalledBinariesDirectory()

let env = ProcessInfo.processInfo.environment
Expand All @@ -48,7 +48,7 @@ extension SwiftPackageCommand {
let workspace = try tool.getActiveWorkspace()
let packageRoot = try tool.getPackageRoot()

let packageGraph = try workspace.loadPackageGraph(
let packageGraph = try await workspace.loadPackageGraph(
rootPath: packageRoot,
observabilityScope: tool.observabilityScope
)
Expand Down
14 changes: 7 additions & 7 deletions Sources/Commands/PackageCommands/Resolve.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ extension SwiftPackageCommand {
var packageName: String?
}

struct Resolve: SwiftCommand {
struct Resolve: AsyncSwiftCommand {
static let configuration = CommandConfiguration(
abstract: "Resolve package dependencies")

Expand All @@ -39,11 +39,11 @@ extension SwiftPackageCommand {
@OptionGroup()
var resolveOptions: ResolveOptions

func run(_ swiftCommandState: SwiftCommandState) throws {
func run(_ swiftCommandState: SwiftCommandState) async throws {
// If a package is provided, use that to resolve the dependencies.
if let packageName = resolveOptions.packageName {
let workspace = try swiftCommandState.getActiveWorkspace()
try workspace.resolve(
try await workspace.resolve(
packageName: packageName,
root: swiftCommandState.getWorkspaceRoot(),
version: resolveOptions.version,
Expand All @@ -56,12 +56,12 @@ extension SwiftPackageCommand {
}
} else {
// Otherwise, run a normal resolve.
try swiftCommandState.resolve()
try await swiftCommandState.resolve()
}
}
}

struct Fetch: SwiftCommand {
struct Fetch: AsyncSwiftCommand {
static let configuration = CommandConfiguration(shouldDisplay: false)

@OptionGroup(visibility: .hidden)
Expand All @@ -70,11 +70,11 @@ extension SwiftPackageCommand {
@OptionGroup()
var resolveOptions: ResolveOptions

func run(_ swiftCommandState: SwiftCommandState) throws {
func run(_ swiftCommandState: SwiftCommandState) async throws {
swiftCommandState.observabilityScope.emit(warning: "'fetch' command is deprecated; use 'resolve' instead")

let resolveCommand = Resolve(globalOptions: _globalOptions, resolveOptions: _resolveOptions)
try resolveCommand.run(swiftCommandState)
try await resolveCommand.run(swiftCommandState)
}
}
}
6 changes: 3 additions & 3 deletions Sources/Commands/PackageCommands/Update.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import PackageGraph
import Workspace

extension SwiftPackageCommand {
struct Update: SwiftCommand {
struct Update: AsyncSwiftCommand {
static let configuration = CommandConfiguration(
abstract: "Update package dependencies")

Expand All @@ -32,10 +32,10 @@ extension SwiftPackageCommand {
@Argument(help: "The packages to update")
var packages: [String] = []

func run(_ swiftCommandState: SwiftCommandState) throws {
func run(_ swiftCommandState: SwiftCommandState) async throws {
let workspace = try swiftCommandState.getActiveWorkspace()

let changes = try workspace.updateDependencies(
let changes = try await workspace.updateDependencies(
root: swiftCommandState.getWorkspaceRoot(),
packages: packages,
dryRun: dryRun,
Expand Down
2 changes: 1 addition & 1 deletion Sources/Commands/SwiftBuildCommand.swift
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ public struct SwiftBuildCommand: AsyncSwiftCommand {
library: library
)
}
for library in try options.testLibraryOptions.enabledTestingLibraries(swiftCommandState: swiftCommandState) {
for library in try await options.testLibraryOptions.enabledTestingLibraries(swiftCommandState: swiftCommandState) {
updateTestingParameters(of: &productsBuildParameters, library: library)
updateTestingParameters(of: &toolsBuildParameters, library: library)
try build(swiftCommandState, subset: subset, productsBuildParameters: productsBuildParameters, toolsBuildParameters: toolsBuildParameters)
Expand Down
Loading