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

[SR-15550] Loading a resource from a bundle fails due to incorrect bundle path #4372

Closed
swift-ci opened this issue Dec 3, 2021 · 7 comments
Assignees
Labels

Comments

@swift-ci
Copy link
Contributor

swift-ci commented Dec 3, 2021

Previous ID SR-15550
Radar None
Original Reporter dlbuckley (JIRA User)
Type Bug
Status Closed
Resolution Done

Attachment: Download

Environment

macOS 12.0.1

Xcode 13.1

Additional Detail from JIRA
Votes 0
Component/s Foundation, Package Manager
Labels Bug
Assignee @Kyle-Ye
Priority Medium

md5: e29ed1c01a487ed1bfed5abc764a545c

Issue Description:

I created a new executable SPM project today and tried to load a simple file resource using `Bundle` but ran into an issue where the Bundle seems to be initialised with the wrong path. Due to this calling methods like `url(forResource: ...` are unable to find the correct resource and return `nil`.

I have attached a demo project to demonstrate this, but here are the reproduction steps just in case:

1) Open terminal and create a new SPM project using `swift package init --type=executable`

2) Create a new file and add it somewhere in the `Sources` directory.

3) Add a reference to the new file to the `executableTarget` in the `Package.swift` resulting in something like:

.executableTarget(
            name: "AOC-D1C1",
            dependencies: [],
            resources: [.copy("Input.txt")])

4) Open `main.swift` and try to locate the file using `Bundle.main.url(forResource: "Input", withExtension: "txt")`

5) The returning `URL` is `nil` and the bundle `.bundlePath` property points to the executable directory, but not the `.bundle` within the directory.

@Kyle-Ye
Copy link
Collaborator

Kyle-Ye commented Dec 3, 2021

@Kyle-Ye
Copy link
Collaborator

Kyle-Ye commented Dec 3, 2021

You could use Bundle.module to get the bundle you want.

But there seems to be another problem here: Xcode will give an error if you try to use Bundle.module. (Maybe because it is a executable target or it is in the main.swift file? I do not know, and I think we can post another SR for it)

Saying that, using Bundle.module and building it in terminal with `swift build` or `swift run` is OK.

I think it's a bug of Xcode's private SwiftPM.

[Update] since it was not a bug on swift-package-manager rather the Xcode's private SwiftPM. I think we should file it to Feedback instead of opening a new SR bug in the forum.

@Kyle-Ye
Copy link
Collaborator

Kyle-Ye commented Dec 3, 2021

FYI: dlbuckley (JIRA User)

```

> cat ./Sources/AOC-D1C1/main.swift

import Foundation

let urlFromMain = Bundle.module.url(forResource: "Input", withExtension: "txt")

print("(urlFromMain?.absoluteString ?? "Not found in main")")

> swift run

[3/3] Build complete!

file:///Users/kyle/Downloads/AOC-D1C1/.build/arm64-apple-macosx/debug/AOC-D1C1_AOC-D1C1.bundle/Input.txt

```

My environment

```

Swift version: Apple Swift version 5.5.1 (swiftlang-1300.0.31.4 clang-1300.0.29.6)

Target: arm64-apple-macosx12.0

Unix version: Darwin 21.2.0 Darwin Kernel Version 21.2.0: Tue Nov 16 23:24:18 PST 2021; root:xnu-8019.61.4~4/RELEASE_ARM64_T8101 arm64 arm;

macOS version: 12.1

Xcode-select path: '/Applications/Xcode.app/Contents/Developer'

Xcode: Xcode 13.1 (Build version 13A1030d)

```

@swift-ci
Copy link
Contributor Author

swift-ci commented Dec 3, 2021

Comment by Dale Buckley (JIRA)

@Kyle-Ye I did notice after I filed this that I couldn't define my own `Bundle.module` as one was already defined which seemed odd. I can file a feedback instead if you think that would help it get visibility in the right place?

Thanks for investigating.

@Kyle-Ye
Copy link
Collaborator

Kyle-Ye commented Dec 4, 2021

Yes, the swift-package-manager will create a Bundle.module for automatically. So you can not create one on your own. (Using Xcode will do the same thing, but it use a private SwiftPM framework not the open source one, so there will be some different when you call `swift build` and in Xcode hit run)

The BuildPlan file is how Bundle.module work:

private func generateResourceAccessor() throws {
// Do nothing if we're not generating a bundle.
guard let bundlePath = self.bundlePath else { return }
let mainPathSubstitution: String
if buildParameters.triple.isWASI() {
// We prefer compile-time evaluation of the bundle path here for WASI. There's no benefit in evaluating this at runtime,
// especially as Bundle support in WASI Foundation is partial. We expect all resource paths to evaluate to
// `/\(resourceBundleName)/\(resourcePath)`, which allows us to pass this path to JS APIs like `fetch` directly, or to
// `<img src=` HTML attributes. The resources are loaded from the server, and we can't hardcode the host part in the URL.
// Making URLs relative by starting them with `/\(resourceBundleName)` makes it work in the browser.
let mainPath = AbsolutePath(Bundle.main.bundlePath).appending(component: bundlePath.basename).pathString
mainPathSubstitution = #""\#(mainPath.asSwiftStringLiteralConstant)""#
} else {
mainPathSubstitution = #"Bundle.main.bundleURL.appendingPathComponent("\#(bundlePath.basename.asSwiftStringLiteralConstant)").path"#
}
let stream = BufferedOutputByteStream()
stream <<< """
import class Foundation.Bundle
extension Foundation.Bundle {
static var module: Bundle = {
let mainPath = \(mainPathSubstitution)
let buildPath = "\(bundlePath.pathString.asSwiftStringLiteralConstant)"
let preferredBundle = Bundle(path: mainPath)
guard let bundle = preferredBundle != nil ? preferredBundle : Bundle(path: buildPath) else {
fatalError("could not load resource bundle: from \\(mainPath) or \\(buildPath)")
}
return bundle
}()
}
"""
let subpath = RelativePath("resource_bundle_accessor.swift")
// Add the file to the dervied sources.
derivedSources.relativePaths.append(subpath)
// Write this file out.
// FIXME: We should generate this file during the actual build.
let path = derivedSources.root.appending(subpath)
try self.fileSystem.writeIfChanged(path: path, bytes: stream.bytes)
}

There is some discussion on Xcode's SwiftPM and open source swift-package-manager
#3824

dlbuckley (JIRA User)

@swift-ci
Copy link
Contributor Author

swift-ci commented Dec 4, 2021

Comment by Dale Buckley (JIRA)

Filed feedback with FB number: FB9794565

It does seem a bit silly that there are differences based on how it's built depending on if it's build using Xcode or SPM directly.

The private Xcode SPM stuff has tripped us over (as a team) previously when we were trying to build out a custom framework caching system, fingers crossed this can be unified in future so we only have one version of SPM used by `swift build` and Xcode build systems.

@Kyle-Ye
Copy link
Collaborator

Kyle-Ye commented Dec 4, 2021

Yes, that's what I am hoping too. Certainly Xcode or Apple has some private thing to do, but I think it can be done upon the open source swift-package-manager with some customisation rather than maintaining a completely new one.

@swift-ci swift-ci transferred this issue from apple/swift-issues Apr 25, 2022
@shahmishal shahmishal transferred this issue from apple/swift May 4, 2022
This issue was closed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants