Skip to content

Commit 5b2165d

Browse files
authored
Fix generateResourceAccessor WASI regression (#3819)
* Fix `generateResourceAccessor` regression * Add testSwiftWASIBundleAccessor to BuildPlanTests * Add clarifying comment to `isWASI` branch of `generateResourceAccessor`
1 parent 9274d1b commit 5b2165d

File tree

2 files changed

+78
-1
lines changed

2 files changed

+78
-1
lines changed

Sources/Build/BuildPlan.swift

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -658,13 +658,26 @@ public final class SwiftTargetBuildDescription {
658658
// Do nothing if we're not generating a bundle.
659659
guard let bundlePath = self.bundlePath else { return }
660660

661+
let mainPathSubstitution: String
662+
if buildParameters.triple.isWASI() {
663+
// We prefer compile-time evaluation of the bundle path here for WASI. There's no benefit in evaluating this at runtime,
664+
// especially as Bundle support in WASI Foundation is partial. We expect all resource paths to evaluate to
665+
// `/\(resourceBundleName)/\(resourcePath)`, which allows us to pass this path to JS APIs like `fetch` directly, or to
666+
// `<img src=` HTML attributes. The resources are loaded from the server, and we can't hardcode the host part in the URL.
667+
// Making URLs relative by starting them with `/\(resourceBundleName)` makes it work in the browser.
668+
let mainPath = AbsolutePath(Bundle.main.bundlePath).appending(component: bundlePath.basename).pathString
669+
mainPathSubstitution = #""\#(mainPath.asSwiftStringLiteralConstant)""#
670+
} else {
671+
mainPathSubstitution = #"Bundle.main.bundleURL.appendingPathComponent("\#(bundlePath.basename.asSwiftStringLiteralConstant)").path"#
672+
}
673+
661674
let stream = BufferedOutputByteStream()
662675
stream <<< """
663676
import class Foundation.Bundle
664677
665678
extension Foundation.Bundle {
666679
static var module: Bundle = {
667-
let mainPath = Bundle.main.bundleURL.appendingPathComponent("\(bundlePath.basename.asSwiftStringLiteralConstant)").path
680+
let mainPath = \(mainPathSubstitution)
668681
let buildPath = "\(bundlePath.pathString.asSwiftStringLiteralConstant)"
669682
670683
let preferredBundle = Bundle(path: mainPath)

Tests/BuildTests/BuildPlanTests.swift

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2652,6 +2652,70 @@ final class BuildPlanTests: XCTestCase {
26522652
])
26532653
}
26542654

2655+
func testSwiftWASIBundleAccessor() throws {
2656+
// This has a Swift and ObjC target in the same package.
2657+
let fs = InMemoryFileSystem(emptyFiles:
2658+
"/PkgA/Sources/Foo/Foo.swift",
2659+
"/PkgA/Sources/Foo/foo.txt",
2660+
"/PkgA/Sources/Foo/bar.txt",
2661+
"/PkgA/Sources/Bar/Bar.swift"
2662+
)
2663+
2664+
let observability = ObservabilitySystem.makeForTesting()
2665+
2666+
let graph = try loadPackageGraph(
2667+
fs: fs,
2668+
manifests: [
2669+
Manifest.createRootManifest(
2670+
name: "PkgA",
2671+
path: .init("/PkgA"),
2672+
toolsVersion: .v5_2,
2673+
targets: [
2674+
TargetDescription(
2675+
name: "Foo",
2676+
resources: [
2677+
.init(rule: .copy, path: "foo.txt"),
2678+
.init(rule: .process, path: "bar.txt"),
2679+
]
2680+
),
2681+
TargetDescription(
2682+
name: "Bar"
2683+
),
2684+
]
2685+
)
2686+
],
2687+
observabilityScope: observability.topScope
2688+
)
2689+
2690+
XCTAssertNoDiagnostics(observability.diagnostics)
2691+
2692+
let plan = try BuildPlan(
2693+
buildParameters: mockBuildParameters(destinationTriple: .wasi),
2694+
graph: graph,
2695+
fileSystem: fs,
2696+
observabilityScope: observability.topScope
2697+
)
2698+
let result = BuildPlanResult(plan: plan)
2699+
2700+
let fooTarget = try result.target(for: "Foo").swiftTarget()
2701+
XCTAssertEqual(fooTarget.objects.map{ $0.pathString }, [
2702+
"/path/to/build/debug/Foo.build/Foo.swift.o",
2703+
"/path/to/build/debug/Foo.build/resource_bundle_accessor.swift.o"
2704+
])
2705+
2706+
let resourceAccessor = fooTarget.sources.first{ $0.basename == "resource_bundle_accessor.swift" }!
2707+
let contents = try fs.readFileContents(resourceAccessor).cString
2708+
XCTAssertMatch(contents, .contains("extension Foundation.Bundle"))
2709+
// Assert that `Bundle.main` is executed in the compiled binary (and not during compilation)
2710+
// See https://bugs.swift.org/browse/SR-14555 and https://github.com/apple/swift-package-manager/pull/2972/files#r623861646
2711+
XCTAssertMatch(contents, .contains("let mainPath = \""))
2712+
2713+
let barTarget = try result.target(for: "Bar").swiftTarget()
2714+
XCTAssertEqual(barTarget.objects.map{ $0.pathString }, [
2715+
"/path/to/build/debug/Bar.build/Bar.swift.o",
2716+
])
2717+
}
2718+
26552719
func testShouldLinkStaticSwiftStdlib() throws {
26562720
let fs = InMemoryFileSystem(emptyFiles:
26572721
"/Pkg/Sources/exe/main.swift",

0 commit comments

Comments
 (0)