-
Notifications
You must be signed in to change notification settings - Fork 1.4k
[SR-12912] Fix crash in test targets when accessing Bundle.module #2817
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
Conversation
Sources/Build/BuildPlan.swift
Outdated
let stream = BufferedOutputByteStream() | ||
stream <<< """ | ||
import class Foundation.Bundle | ||
|
||
extension Foundation.Bundle { | ||
static var module: Bundle = { | ||
let bundlePath = Bundle.main.bundlePath + "/" + "\(bundleBasename)" | ||
let bundlePath = "\(bundlePath)" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wouldn't this hardcode the absolute path into each artifact (also non-tests)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not familiar with how bundlePath
is derived so that is possible. I wasn't able to confirm this on an iOS device itself as Xcode won't let me install it on my test device with a custom toolchain. Is it possible to check if a given I saw that there is an BuildPlan
is compiling a test target?isTestTarget
member variable on BuildPlan and changed the logic to only use bundlePath
when that is true.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Be aware that the Xcode integration of packages does not use SwiftPM from the toolchain, you will only see an effect of your changes in commandline SwiftPM.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right, but in this case, even when just building with swift build
, wouldn't this also change the path that's built into a package target that's linked into an executable, meaning that if the executable and its associated resource bundles are later moved, the bundles can't be found?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right, but in this case, even when just building with
swift build
, wouldn't this also change the path that's built into a package target that's linked into an executable, meaning that if the executable and its associated resource bundles are later moved, the bundles can't be found?
That seems likely, when I tested I did move the executable but not the resource bundle. Would it make sense to conditionally check if the current Bundle.main path is located in an Xcode path and fallback to the hard coded path? (see latest commit)
@neonichu I'm guessing that Xcode has its own method for computing Bundle.module? I had gone down a rabbit hole of trying to run my tests with xcodebuild on my package, but had other issues altogether there.
Sources/Build/BuildPlan.swift
Outdated
@@ -577,14 +577,16 @@ public final class SwiftTargetBuildDescription { | |||
private func generateResourceAccessor() throws { | |||
// Do nothing if we're not generating a bundle. | |||
guard let bundlePath = self.bundlePath else { return } | |||
|
|||
|
|||
let pathToModule = isTestTarget ? bundlePath : "Bundle.main.bundlePath/\(bundlePath.basename)" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just realized this is not actually going to work, I've made some changes locally and am building a new toolchain before pushing the changes, but tentatively this has been re-written to
let pathToModule = isTestTarget ? bundlePath : "Bundle.main.bundlePath + \"/\" + \(bundlePath.basename)"
…xpected, however, when the target under test accesses Bundle.module, it fails for the same reason as before
I've converted this to a Draft PR because this is going to take a bit of consideration. To address the concern raised by @abertelrud, I updated the generation of let pathToModule = isTestTarget ? #""\#(bundlePath.pathString)""# : #"Bundle.main.bundlePath + "/" + "\#(bundlePath.basename)""#
let stream = BufferedOutputByteStream()
stream <<< """
import class Foundation.Bundle
extension Foundation.Bundle {
static var module: Bundle = {
let bundlePath = \(pathToModule)
guard let bundle = Bundle(path: bundlePath) else {
fatalError("could not load resource bundle: \\(bundlePath)")
}
return bundle
}()
}
""" However, when a target that is being tested calls |
…the build output directory path.
I think I've come up with a reasonable solution: we first check if the resource bundle is accessible by appending to the bundle name to Bundle.main, if that fails we fall back to the absolute path to the resource bundle in the build directory, and if that fails we bail out with a fatal error. |
@Steven0351 Did you already find time to implement your "reasonable solution" so we can get this merged before final release of Xcode 12? Feel free to ping me if I can help here as the bug is really blocking a proper CI configuration for any open source repo using resources right now ... |
@Jeehut I had committed what I thought was a "reasonable" solution, but there has been no feedback on those commits after I marked them ready for review. Bundle.module is generated at build time, so my workaround has the original bundle, but falls back to the build path in the event that the preferred bundle is not available (which should only be the case when a target is under test). I've been using my fork to run tests for macOS Swift packages without issue, but I'm only really able to run those locally because I haven't published that for use in any of my CI anywhere. |
I can trigger CI and/or request reviews from relevant people, is this ready for CI or review at all? |
@MaxDesiatov It's ready for review. I've run the tests locally when I originally patched and validated that my macOS Swift PM projects with bundle resources were able to both be tested and run (along with being moved around the file system without breaking non-test behavior). |
Would you be able to add corresponding automated tests to the SwiftPM test suite, so that it could be tested on CI? |
@MaxDesiatov I could give it a go but I'll have to dig into the test suite to see how this part of the codebase is tested. I could probably write something that validates the values that are generated, but I didn't notice anything in the current test suite that would enable validating the properties at runtime. Do you have a suggested approach there? |
Did you check the integration test suite with test cases such as this one? |
Added passing integration test into TestToolTests.swift Added failing integration test into BasicTests.swift
@MaxDesiatov I had never noticed the IntegrationsTest project since it's not visible when opening the root package in Xcode (thanks for the tip!). I did run into an issue there: it uses the existing swift toolchain by default. This will fail if CI runs without setting the SWIFTPM_BIN_DIR environment variable, which I have no way of knowing because those configs aren't publicly available. This can be made to pass by invoking @neonichu Would you be able to review/throw in your opinion here as well? |
@swift-ci please smoke test |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for revising the PR (and apologies about the delay in taking another look at it). Looks good, though I'd suggest moving the lookup of the fallback bundle to a place where it will only be run if the first one isn't found. There can be a performance cost to instantiating a bundle.
Sources/Build/BuildPlan.swift
Outdated
let buildPath = "\(bundlePath.pathString)" | ||
|
||
let preferredBundle = Bundle(path: mainPath) | ||
let fallBackBundle = Bundle(path: buildPath) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since this could be a little expensive, it might be worth instantiating this second Bundle only if the first one can't be found, especially since at deployment time for the non-test products, the first one should always be found.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's fair; I've inlined the instantiation of this in the ternery expression per this feedback
Thanks for making the change! |
@swift-ci please test |
Looks like that Linux build failure was related to an issue compiling core-libs-foundation? |
@swift-ci please smoke test |
The Linux failure on the full test does indeed look unrelated. The smoke tests did pass. I would like to get one more review on this but as far as I am concerned this looks good. |
Looks like the only failure is from an accidental full test run, so this can be merged. |
…iftlang#2817) * SR-12912 Setting the path for Bundle.module fails in the case where the bundle is accessed during tests. This uses the already derived bundlePath instead. * Only use bundlePath when building a testTarget * Updating this for discussion in PR. This hardcodes the test path as expected, however, when the target under test accesses Bundle.module, it fails for the same reason as before * This prefers Bundle.main, but if it is inaccessible it falls back to the build output directory path. * Cleaned up changes in BuildPlan.swift Added passing integration test into TestToolTests.swift Added failing integration test into BasicTests.swift * Remove integration test from TestToolTests.swift * Only instantiate Bundle for buildPath if main bundle is not found
Any chance for this to make it into Swift 5.3? |
…iftlang#2817) * SR-12912 Setting the path for Bundle.module fails in the case where the bundle is accessed during tests. This uses the already derived bundlePath instead. * Only use bundlePath when building a testTarget * Updating this for discussion in PR. This hardcodes the test path as expected, however, when the target under test accesses Bundle.module, it fails for the same reason as before * This prefers Bundle.main, but if it is inaccessible it falls back to the build output directory path. * Cleaned up changes in BuildPlan.swift Added passing integration test into TestToolTests.swift Added failing integration test into BasicTests.swift * Remove integration test from TestToolTests.swift * Only instantiate Bundle for buildPath if main bundle is not found
I've created #2905 for that. |
@MaxDesiatov Thank you! |
) (#2905) * SR-12912 Setting the path for Bundle.module fails in the case where the bundle is accessed during tests. This uses the already derived bundlePath instead. * Only use bundlePath when building a testTarget * Updating this for discussion in PR. This hardcodes the test path as expected, however, when the target under test accesses Bundle.module, it fails for the same reason as before * This prefers Bundle.main, but if it is inaccessible it falls back to the build output directory path. * Cleaned up changes in BuildPlan.swift Added passing integration test into TestToolTests.swift Added failing integration test into BasicTests.swift * Remove integration test from TestToolTests.swift * Only instantiate Bundle for buildPath if main bundle is not found Co-authored-by: Steven Sherry <[email protected]>
Forgive my ignorance, how can one tell in which Swift version this fix will be included? Swift 5.3, as of Xcode 12 still has the original implementation as evidenced with the shorter error message. |
I saw some folks mentioned it's in Xcode 12.2 Beta 2, but I haven't verified it myself |
I can verify it is available in Xcode 12.2 Beta 2. |
Unfortunately it's not in Xcode 12.1 though 😢 So we'll have to wait until 12.2 is released. |
Currently, running
swift test
fails when a test target accessesBundle.module
during a test (https://forums.swift.org/t/swift-5-3-spm-resources-in-tests-uses-wrong-bundle-path/37051).This patch uses the bundlePath that already exists instead of crafting the path from
Bundle.main
. I've tested this in both library, executable, and test targets on macOS and it seems to do the trick.