Skip to content

Commit 443d884

Browse files
committed
Allow swift-build to have async entrypoint
Due to a bug in Swift 5.9, we can't have multiple executables with `async` entrypoints linked into test products. This was the reason why we weren't able to make `swift-build` entrypoint `async`. Since we have a single `swift-package-manager` executable anyway, we can link that one into tests instead, and pass "simulated" executable name via an environment variable. This allows us to work around the bug and start using structured concurrency from `swift-build` entrypoint.
1 parent 31bc808 commit 443d884

File tree

6 files changed

+36
-22
lines changed

6 files changed

+36
-22
lines changed

Package.swift

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -681,9 +681,7 @@ package.targets.append(contentsOf: [
681681
.testTarget(
682682
name: "FunctionalPerformanceTests",
683683
dependencies: [
684-
"swift-build",
685-
"swift-package",
686-
"swift-test",
684+
"swift-package-manager",
687685
"SPMTestSupport"
688686
]
689687
),
@@ -695,9 +693,7 @@ if ProcessInfo.processInfo.environment["SWIFTCI_DISABLE_SDK_DEPENDENT_TESTS"] ==
695693
.testTarget(
696694
name: "FunctionalTests",
697695
dependencies: [
698-
"swift-build",
699-
"swift-package",
700-
"swift-test",
696+
"swift-package-manager",
701697
"PackageModel",
702698
"SPMTestSupport"
703699
]
@@ -713,10 +709,7 @@ if ProcessInfo.processInfo.environment["SWIFTCI_DISABLE_SDK_DEPENDENT_TESTS"] ==
713709
.testTarget(
714710
name: "CommandsTests",
715711
dependencies: [
716-
"swift-build",
717-
"swift-package",
718-
"swift-test",
719-
"swift-run",
712+
"swift-package-manager",
720713
"Basics",
721714
"Build",
722715
"Commands",

Sources/Commands/SwiftBuildTool.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ struct BuildToolOptions: ParsableArguments {
9595
}
9696

9797
/// swift-build tool namespace
98-
public struct SwiftBuildTool: SwiftCommand {
98+
public struct SwiftBuildTool: AsyncSwiftCommand {
9999
public static var configuration = CommandConfiguration(
100100
commandName: "build",
101101
_superCommandName: "swift",
@@ -110,7 +110,7 @@ public struct SwiftBuildTool: SwiftCommand {
110110
@OptionGroup()
111111
var options: BuildToolOptions
112112

113-
public func run(_ swiftTool: SwiftTool) throws {
113+
public func run(_ swiftTool: SwiftTool) async throws {
114114
if options.shouldPrintBinPath {
115115
return try print(swiftTool.productsBuildParameters.buildPath.description)
116116
}

Sources/SPMTestSupport/SwiftPMProduct.swift

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ public enum SwiftPM {
2929

3030
extension SwiftPM {
3131
/// Executable name.
32-
private var executableName: RelativePath {
32+
private var executableName: String {
3333
switch self {
3434
case .Build:
3535
return "swift-build"
@@ -45,7 +45,7 @@ extension SwiftPM {
4545
}
4646

4747
public var xctestBinaryPath: AbsolutePath {
48-
Self.xctestBinaryPath(for: executableName)
48+
Self.xctestBinaryPath(for: RelativePath("swift-package-manager"))
4949
}
5050

5151
public static func xctestBinaryPath(for executableName: RelativePath) -> AbsolutePath {
@@ -114,11 +114,12 @@ extension SwiftPM {
114114
#endif
115115
// FIXME: We use this private environment variable hack to be able to
116116
// create special conditions in swift-build for swiftpm tests.
117-
environment["SWIFTPM_TESTS_MODULECACHE"] = xctestBinaryPath.parentDirectory.pathString
118-
117+
environment["SWIFTPM_TESTS_MODULECACHE"] = self.xctestBinaryPath.parentDirectory.pathString
118+
119119
// Unset the internal env variable that allows skipping certain tests.
120120
environment["_SWIFTPM_SKIP_TESTS_LIST"] = nil
121-
121+
environment["SWIFTPM_EXEC_NAME"] = self.executableName
122+
122123
for (key, value) in env ?? [:] {
123124
environment[key] = value
124125
}

Sources/swift-build/CMakeLists.txt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,12 @@
77
# See http://swift.org/CONTRIBUTORS.txt for Swift project authors
88

99
add_executable(swift-build
10-
main.swift)
10+
Entrypoint.swift)
1111
target_link_libraries(swift-build PRIVATE
1212
Commands)
1313

14+
target_compile_options(swift-build PRIVATE
15+
-parse-as-library)
16+
1417
install(TARGETS swift-build
1518
DESTINATION bin)

Sources/swift-build/main.swift renamed to Sources/swift-build/Entrypoint.swift

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,9 @@
1212

1313
import Commands
1414

15-
SwiftBuildTool.main()
15+
@main
16+
struct Entrypoint {
17+
static func main() async {
18+
await SwiftBuildTool.main()
19+
}
20+
}

Sources/swift-package-manager/SwiftPM.swift

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,17 +17,22 @@ import PackageCollectionsTool
1717
import PackageRegistryTool
1818

1919
let firstArg = CommandLine.arguments[0]
20-
let execName = (try? AbsolutePath(validating: firstArg).basenameWithoutExt) ??
20+
let baseNameWithoutExtension = (try? AbsolutePath(validating: firstArg).basenameWithoutExt) ??
2121
(try? RelativePath(validating: firstArg).basenameWithoutExt)
2222

2323
@main
2424
struct SwiftPM {
2525
static func main() async {
26+
await main(execName: baseNameWithoutExtension)
27+
}
28+
29+
@discardableResult
30+
private static func main(execName: String?) async -> Bool {
2631
switch execName {
2732
case "swift-package":
2833
await SwiftPackageTool.main()
2934
case "swift-build":
30-
SwiftBuildTool.main()
35+
await SwiftBuildTool.main()
3136
case "swift-experimental-sdk":
3237
await SwiftSDKTool.main()
3338
case "swift-test":
@@ -39,7 +44,14 @@ struct SwiftPM {
3944
case "swift-package-registry":
4045
await SwiftPackageRegistryTool.main()
4146
default:
42-
fatalError("swift-package-manager launched with unexpected name: \(execName ?? "(unknown)")")
47+
// Workaround a bug in Swift 5.9, where multiple executables with an `async` main entrypoint can't be linked
48+
// into the same test bundle. We're then linking single `swift-package-manager` binary instead and passing
49+
// executable name via `SWIFTPM_EXEC_NAME`.
50+
if await !main(execName: EnvironmentVariables.process()["SWIFTPM_EXEC_NAME"]) {
51+
fatalError("swift-package-manager launched with unexpected name: \(execName ?? "(unknown)")")
52+
}
4353
}
54+
55+
return true
4456
}
4557
}

0 commit comments

Comments
 (0)