I played around with this a bit yesterday, but have only found a workaround for the case where resources are needed in a test target:
- Create a new executable target (one with a
main.swift
file) e.g.ResourcesPathProvider
containing the resources. - Put the following code in the
main.swift
file:
import class Foundation.Bundle
print(Bundle.module.bundlePath, terminator: "")
- Add an extension on Bundle in the target where you need the resources:
import Foundation
@testable import ResourcesPathProvider
extension Bundle {
private static var productsDirectory: URL {
#if os(macOS)
if let bundle = Bundle.allBundles.first(where: { $0.bundlePath.hasSuffix(".xctest") }) {
return bundle.bundleURL.deletingLastPathComponent()
}
fatalError("Couldn't find the products directory")
#else
return Bundle.main.bundleURL
#endif
}
static var module: Bundle = {
do {
let process = Process()
process.executableURL = productsDirectory.appendingPathComponent("ResourcesPathProvider")
let pipe = Pipe()
process.standardOutput = pipe
try process.run()
process.waitUntilExit()
let data = pipe.fileHandleForReading.readDataToEndOfFile()
let output = String(decoding: data, as: UTF8.self)
guard let bundle = Bundle(path: output) else {
fatalError("Couldn't load module bundle from provided path: \(output)")
}
return bundle
} catch {
fatalError("Couldn't load module bundle: \(error)")
}
}()
}
- Use
Bundle.module
Note that I intentionally usedmodule
as the name for the accessor, so that once Swift fixes this, I can simply move the resources to the test target, remove the executable target and the extension.
The code in the extension is a slightly adapted version of the one SPM automatically generates for tests of new executable packages. It basically just searches the executable and executes it. Given that the executable simply prints the path to its resources bundle, we can use the output and load the bundle. The upside is, that this seems to be rather safe and does not rely on many build system intrinsics, except for the part where we're looking for the executable. However, given that SPM ships with that code, I think it's rather safe to rely on it for now. At least until some version of Swift finally fixes this issue.
However, this only works for cases where the resources are needed in tests. For testing targets that themselves have resources, I have yet to find a workaround.