diff --git a/Sources/swift-bootstrap/CMakeLists.txt b/Sources/swift-bootstrap/CMakeLists.txt index 885163ba5e3..62fce540f2b 100644 --- a/Sources/swift-bootstrap/CMakeLists.txt +++ b/Sources/swift-bootstrap/CMakeLists.txt @@ -7,7 +7,7 @@ # See http://swift.org/CONTRIBUTORS.txt for Swift project authors add_executable(swift-bootstrap - main.swift) + SwiftBootstrapBuildTool.swift) target_link_libraries(swift-bootstrap PRIVATE ArgumentParser Basics diff --git a/Sources/swift-bootstrap/SwiftBootstrapBuildTool+Extensions.swift b/Sources/swift-bootstrap/SwiftBootstrapBuildTool+Extensions.swift new file mode 100644 index 00000000000..d7ed97c75f4 --- /dev/null +++ b/Sources/swift-bootstrap/SwiftBootstrapBuildTool+Extensions.swift @@ -0,0 +1,48 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2025 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 the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import ArgumentParser +import Basics +import SPMBuildCore +import PackageModel + +extension AbsolutePath { + public init?(argument: String) { + if let cwd: AbsolutePath = localFileSystem.currentWorkingDirectory { + guard let path = try? AbsolutePath(validating: argument, relativeTo: cwd) else { + return nil + } + self = path + } else { + guard let path = try? AbsolutePath(validating: argument) else { + return nil + } + self = path + } + } + + public static var defaultCompletionKind: CompletionKind { + // This type is most commonly used to select a directory, not a file. + // Specify '.file()' in an argument declaration when necessary. + .directory + } +} +extension AbsolutePath: ExpressibleByArgument {} + +extension BuildConfiguration { + public init?(argument: String) { + self.init(rawValue: argument) + } +} +extension BuildConfiguration: ExpressibleByArgument {} + +extension BuildSystemProvider.Kind: ExpressibleByArgument {} diff --git a/Sources/swift-bootstrap/main.swift b/Sources/swift-bootstrap/SwiftBootstrapBuildTool.swift similarity index 88% rename from Sources/swift-bootstrap/main.swift rename to Sources/swift-bootstrap/SwiftBootstrapBuildTool.swift index e93f246372a..04936b404b5 100644 --- a/Sources/swift-bootstrap/main.swift +++ b/Sources/swift-bootstrap/SwiftBootstrapBuildTool.swift @@ -36,10 +36,7 @@ import struct TSCBasic.OrderedSet import enum TSCUtility.Diagnostics import struct TSCUtility.Version -await { () async in - await SwiftBootstrapBuildTool.main() -}() - +@main struct SwiftBootstrapBuildTool: AsyncParsableCommand { static let configuration = CommandConfiguration( commandName: "swift-bootstrap", @@ -410,7 +407,7 @@ struct SwiftBootstrapBuildTool: AsyncParsableCommand { // Compute the transitive closure of available dependencies. let input = loadedManifests.map { identity, manifest in KeyedPair(manifest, key: identity) } - _ = try await topologicalSort(input) { pair in + _ = try await self.topologicalSort(input) { pair in // When bootstrapping no special trait build configuration is used let dependenciesRequired = try pair.item.dependenciesRequired(for: .everything) let dependenciesToLoad = dependenciesRequired.map{ $0.packageRef }.filter { !loadedManifests.keys.contains($0.identity) } @@ -481,82 +478,51 @@ struct SwiftBootstrapBuildTool: AsyncParsableCommand { delegateQueue: .sharedConcurrent ) } - } -} - -// TODO: move to shared area -extension AbsolutePath { - public init?(argument: String) { - if let cwd: AbsolutePath = localFileSystem.currentWorkingDirectory { - guard let path = try? AbsolutePath(validating: argument, relativeTo: cwd) else { - return nil - } - self = path - } else { - guard let path = try? AbsolutePath(validating: argument) else { - return nil - } - self = path - } - } - public static var defaultCompletionKind: CompletionKind { - // This type is most commonly used to select a directory, not a file. - // Specify '.file()' in an argument declaration when necessary. - .directory - } -} + private func topologicalSort( + _ 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, _ visited: inout Set, _ 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 + } -extension BuildConfiguration { - public init?(argument: String) { - self.init(rawValue: argument) - } -} + // Otherwise, visit each adjacent node. + for succ in try await successors(node) { + guard stack.append(succ) 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 TSCBasic.GraphError.unexpectedCycle + } + try await visit(succ, &stack, &visited, &result, successors) + let popped = stack.removeLast() + assert(popped == succ) + } -extension AbsolutePath: ExpressibleByArgument {} -extension BuildConfiguration: ExpressibleByArgument {} -extension BuildSystemProvider.Kind: ExpressibleByArgument {} - -public func topologicalSort( - _ 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, _ visited: inout Set, _ 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 - } + // Add to the result. + result.append(node) + } - // Otherwise, visit each adjacent node. - for succ in try await successors(node) { - guard stack.append(succ) 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 TSCBasic.GraphError.unexpectedCycle + // FIXME: This should use a stack not recursion. + var visited = Set() + var result = [T]() + var stack = OrderedSet() + 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) } - try await visit(succ, &stack, &visited, &result, successors) - let popped = stack.removeLast() - assert(popped == succ) - } - // Add to the result. - result.append(node) + return result.reversed() + } } +} - // FIXME: This should use a stack not recursion. - var visited = Set() - var result = [T]() - var stack = OrderedSet() - 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() -}