Xcode can't find my target's module after I changed the executable name in Package.swift - swift

I wanted to change the name of the executable produced from my Swift Package Manager project. Following this answer, my Package.swift now looks like this:
let package = Package(
name: "Foo", // my package is called "Foo"...
products: [
// and I want my executable to be called "bar"
.executable(name: "bar", targets: ["Foo"])
],
targets: [
.executableTarget(
name: "Foo",
dependencies: []),
.testTarget(
name: "FooTests",
dependencies: ["Foo"]),
]
)
In main.swift in the "Foo" target, I have written a function to test in the "FooTests" target:
// in main.swift
func foo() -> Int {
1
}
// in FooTests.swift
import XCTest
#testable import Foo
final class FooTests: XCTestCase {
func testFoo() throws {
XCTAssertEqual(foo(), 1)
}
}
If I build the project by pressing command + B at this point, Xcode would complain that the module "Foo" cannot be found, highlighting the "#testable import" line in FooTests.swift. It also says that the foo method cannot be found in scope.
I'm quite confused. I've only changed the executable name, not the module name (which I think is the same as the target name).
I tried running this on the command line with swift test, and it works perfectly.
I've also tried #testable import bar (because I might as well), and the error message changes to:
Undefined symbol: bar.foo() -> Swift.Int
How can I fix this? I still want to use Xcode to test my project. After all, a big feature of XCTest is that it's nicely integrated with Xcode.
Note that I'm using Xcode 13.1 and I'm opening the Swift Package Manager project by opening the folder containing Package.swift with Xcode.

Related

Swift package manager localization

I'm wondering, how localization works with the swift package. I saw the WWDC20/10169 video - Swift packages: Resources and localization. I'm trying to do the same things within my own project.
If I do same configuration (as on WWDC video) and add .environment(\.locale, Locale(identifier: "es")) to View preview, it works (but only when test inside sp).
The problems start when I try to use localized resources inside the actual app.
The process is very simple - I just use a view with a localized string from the package inside the app. For test purposes I launch my app with a specific locale (scheme settings) - the result, I always get en translation.
My manifest:
let package = Package(
name: "MyLibrary",
defaultLocalization: "en",
platforms: [
.iOS(.v14),
],
products: [
.library(
name: "MyLibrary",
targets: ["MyLibrary"]),
],
dependencies: [
],
targets: [
.target(
name: "MyLibrary",
dependencies: [],
path: "Sources"
),
.testTarget(
name: "MyLibraryTests",
dependencies: ["MyLibrary"]),
]
)
Structure of the package:
resource_bundle_accessor is generated fine - so I have an access to Bundle.module, and indeed I can list all localizations as expected - en and es.
I also added supported languages into the project itself:
Here is a quick demo:
For generating localized string I want to use SwiftGen:
extension L10n {
private static func tr(_ table: String, _ key: String, _ args: CVarArg...) -> String {
let format = BundleToken.bundle.localizedString(forKey: key, value: nil, table: table)
return String(format: format, locale: Locale.current, arguments: args)
}
}
// swiftlint:disable convenience_type
private final class BundleToken {
static let bundle: Bundle = {
#if SWIFT_PACKAGE
return Bundle.module
#else
return Bundle(for: BundleToken.self)
#endif
}()
}
// swiftlint:enable convenience_type
Alternatively, I tested
let text = NSLocalizedString("welcome", tableName: "Localizable", bundle: .module, value: "", comment: "")
or similar from Bundle - Bundle.module.localizedString method and from SwiftUI Text("welcome", bundle: .module).
The result the same - it won't change the language - always I get dev language - en.
I also can confirm, that build contains localization (inside testLocalization.app in lib bundle):
Here is a link for project.
So my question - what I'm doing incorrectly? Where is the error?
I found the reason for this issue - the app itself require localization and setting it in project settings it's not enough, instead, we also should add CFBundleLocalizations in info plist with required localizations as an Array of strings. Even if in official doc says that is's support only few localizations, we can add additional one there (using same locale code as lproj folders) and everything will works.
<key>CFBundleLocalizations</key>
<array>
<string>en</string>
<string>es</string>
<string>uk-UA</string>
</array>
from old outdated doc:
An application can notify the system that it supports additional
localizations through its information property list (Info.plist) file.
To specify localizations not included in your bundle’s .lproj
directories, add the CFBundleLocalizations key to this file. The value
for the key is an array of strings, each of which contains an ISO
language designator as described in “Language and Locale
Designations.”
As usually, Apple didn't mention this in any kind of doc related to SP...
----- update
The proper solutions is "setting CFBundleAllowMixedLocalizations to YES in the application’s Info.plist is the proper solution" - from SDGGiesbrecht

Swift Package Manager - Type 'Bundle' has no member “module” error

Working on implementing SPM for a framework, and got stuck on the Type 'Bundle' has no member “module” error.
I have seen two other recent posts about this here and here, but following all the steps, it is still not working for me, no resource_bundle_accessor file is generated.
I asked about my Package.swift file here, and that has been answered and resolved. For completeness here's the file:
// swift-tools-version:5.3
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription
let package = Package(
name: "BioSwift",
products: [
// Products define the executables and libraries a package produces, and make them visible to other packages.
.library(
name: "BioSwift",
targets: ["BioSwift"]
)
],
targets: [
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
// Targets can depend on other targets in this package, and on products in packages this package depends on.
.target(
name: "BioSwift",
dependencies: [],
resources: [
.process("Resources")
]
),
.testTarget(
name: "BioSwiftTests",
dependencies: ["BioSwift"]
)
]
)
I get the Type 'Bundle' has no member “module” error when trying to access one of the resources in the bundle:
public let unimodURL = Bundle.module?.url(forResource: "unimod", withExtension: "xml")
The project is on GitHub here
Besides all errors in the test file, the only issue remains is that module is not an optional. To fix that, just change this:
public let unimodURL = Bundle.module?.url(forResource: "unimod", withExtension: "XML")
to:
public let unimodURL = Bundle.module.url(forResource: "unimod", withExtension: "xml")
Update:
If you use .xcodeproj file, you will continue seeing this error. You should consider opening it with the package.swift or import it as a package (instead of converting it to a project)!
by the way, here is the generated file, so you can add it as a development asset when you are working with the xcodeproject:
import class Foundation.Bundle
private class BundleFinder {}
extension Foundation.Bundle {
/// Returns the resource bundle associated with the current Swift module.
static var module: Bundle = {
let bundleName = "BioSwift_BioSwift"
let candidates = [
// Bundle should be present here when the package is linked into an App.
Bundle.main.resourceURL,
// Bundle should be present here when the package is linked into a framework.
Bundle(for: BundleFinder.self).resourceURL,
// For command-line tools.
Bundle.main.bundleURL,
]
for candidate in candidates {
let bundlePath = candidate?.appendingPathComponent(bundleName + ".bundle")
if let bundle = bundlePath.flatMap(Bundle.init(url:)) {
return bundle
}
}
fatalError("unable to find bundle named BioSwift_BioSwift")
}()
}
If you follow the instructions on this video you will see that you need to define how you would like to include non-clear purpose files in a package. In this case your package manifest should be something like:
// swift-tools-version:5.3
import PackageDescription
let package = Package(
name: "BioSwift",
products: [
// Products define the executables and libraries produced by a package, and make them visible to other packages.
.library(
name: "BioSwift",
targets: ["BioSwift"]),
],
dependencies: [],
targets: [
.target(
name: "BioSwift",
dependencies: [],
resources: [
.copy("Resources")
]),
.testTarget(
name: "BioSwiftTests",
dependencies: ["BioSwift"]),
]
)
Please pay attention on using Swift 5.3 tools and that you need to declare the resources for the respective target. The copy action on resources will leave them unprocessed when they are added in the compiled bundle.
I had same issue (when i created fresh new package) ,what i did was i added new View file inside my package . And the error was gone .
I had the same problem and I could solve it with the good ol' "did you try to turn it off and back on again?".
No seriously, I simply used Bundle.module in my code (could also just be a one liner like print(Bundle.module)), closed Xcode completly and reopened the package via the Package.swift file and it worked.
Please check your Resources folder path. It should be kept inside the Sources/PackageNameFolder/{Resources}.

Storekit configuration file for UITest

I created Configuration.storekit file, and it work for Run on ios simulator.
But when i'm trying to run UITest, it always fails on initing SKTestSession(configurationFileNamed: "Configuration") with error:
caught error: "Error Domain=SKTestErrorDomain Code=4 "File not found" UserInfo={NSLocalizedDescription=File not found}"
Here is my code
import XCTest
import StoreKitTest
class MyProjectUITests: XCTestCase {
override func setUpWithError() throws {
continueAfterFailure = false
}
override func tearDownWithError() throws {
}
func testExample() throws {
if #available(iOS 14.0, *) {
let session = try SKTestSession(configurationFileNamed: "Configuration")
session.disableDialogs = true
session.clearTransactions()
}
// UI tests
*****
}
}
If i'm trying to #testable import MyProject it fails with another errors:
Missing required modules: 'Amplitude', 'FBSDKCoreKit', 'Firebase'
I even tried to add Target Membership for Configuration.storekit file in MyProjectUITests target.
Can anyone say, how could I init SKTestSession(configurationFileNamed: "Configuration") without getting errors?
To solve the "File Not Found" I added the .storekit file to the Test-Bundle.
Make sure you use the right XCode Version, as #Dmitrii mentioned. I'm currently at 12.3
In addition, make sure to add the .storekit to the test-bundle:
Open the .xcodeproj file
Click on your Test-target
Go to tab "Build Phases"
At "Copy Bundle Resources" add the .storekit file
Not sure, if these are all the required steps to get it running.
At least the SKTestSession finds the configuration file now.

Importing another Package as a Dependency in SPM into another Package, with Swift 5.1

So im using SPM (Swift Package Manager) to create my own package library. I wanted to import another package into my package, to use, as shown below:
// swift-tools-version:5.1
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription
let package = Package(
name: "SwiftRecommendations",
platforms: [
.iOS(.v13),
.macOS(.v10_13),
.tvOS(.v13)
],
products: [
// Products define the executables and libraries produced by a package, and make them visible to other packages.
.library(
name: "SwiftRecommendations",
targets: ["SwiftRecommendations"]),
],
dependencies: [
.package(
url: "https://github.com/apple/swift-log.git",
.branch("master")
)
],
targets: [
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
// Targets can depend on other targets in this package, and on products in packages which this package depends on.
.target(
name: "SwiftRecommendations",
dependencies: ["Logging"]),
.testTarget(
name: "SwiftRecommendationsTests",
dependencies: ["SwiftRecommendations", "Logging"]),
]
)
Im trying to import and run Logger inside my test file, but it complains that No such module Logger after I add the import statement. I can see in my Xcode the Swift Package Dependencies is showing swift-log, so not sure what's going on.
My test class is as follows:
import XCTest
import Logger
#testable import SwiftRecommendations
final class SwiftRecommendationsTests: XCTestCase {
static var tokenService: TokenService?
var logger: Logger?
override func setUp(){
super.setUp()
self.logger = Logger(label: "com.samba.tv.recommendations")
}
Oh gosh I did actually have the wrong import statement. Really sorry... :( It should be import Logging not import Logger.

Qbs >= 1.8 new property override syntax fails for modules

I have an app with product myproduct, depending on custom module mymodule:
// myproduct.qbs
Product {
name: "myproduct"
Depends { name: "mymodule" }
}
// mymodule.qbs
Module {
name: "mymodule"
property string value: ""
validate: { if (value == "") throw "mymodule.value is not set!" }
}
With Qbs 1.7 and lower I could set this property externally with syntax:
qbs ... mymodule.value:"somevalue"
and it worked as expected. With qbs 1.8 and newer, this fails with error:
Property override key 'mymodule.value' not understood.
Please use one of the following:
projects.<project-name>.<property-name>:value
products.<product-name>.<property-name>:value
modules.<module-name>.<property-name>:value
products.<product-name>.<module-name>.<property-name>:value
But neither of those work (property value is left unchanged):
qbs ... modules.mymodule.value:"somevalue"
qbs ... products.myproduct.mymodule.value:"somevalue"
What am I missing? Or is this a qbs bug?
P.S. it works as expected with project.someproperty:"..."
Your example works fine for me. I suggest you file a bug report at bugreports.qt.io and attach a complete example project. We can then figure out what's the problem there.