Importing a local Swift package that uses an Apple module

Hi there,

I'm facing an issue in an Xcode project for an iOS app related to importing Swift packages.

I have a couple of local Swift packages that define extensions on Apple frameworks and/or define functions that make use of types exposed in such frameworks.

For example, I have an extension on Logger like this:

import Foundation
import os

public extension Logger {

    static let authentication = Self(subsystem: subsystem, category: "authentication")

    static let payments = Self(subsystem: subsystem, category: "payments")

    // …

    private static let subsystem = "com.myCompany.myApp"
}

and top-level functions like this:

import Foundation
import Testing

public func verifySuccess<T, E: Error>(
    _ result: Result<T, E>,
    _ comment: Comment? = nil,
    sourceLocation: SourceLocation = #_sourceLocation
) {
    // …
    Issue.record("Failed to verify \(comment ?? "result")", sourceLocation: sourceLocation)
}

The problem is that, back in the iOS app code, the compiler complains with "Cannot find 'Logger' in scope" or "Missing required module 'Testing'" when I import my local package and try to make use of these affordances:

import MyLocalPackage
// import Testing

struct SomeType {

    @Test
    func somethingHappens() {
        // …
        
        // This fails if I don't import "Testing" above
        verify(.success(true))
    }

}

I was somehow under the impression that by importing my package, its transitive dependencies (i.e. os and Testing) would be automatically imported as well.

With that in mind, I have two initial questions:

  1. Is my assumption wrong?
  2. Is there a way to specify in my local package that frameworks like os or Testing should be (automagically?) imported? Or do I have to import them explicitly every time?

To be honest, I could live with the fact that whenever I want to use any of these extensions/functions, I have to import the Apple frameworks as well.

But the annoying part of all this is that the compiler also complains in places where my local package is imported, even when I'm not using any of these extensions/functions:

import MyLocalPackage
import XCTest

final class SomeTests: XCTestCase {

    func testThatSomethingHappens() {
        // No use of 'Testing' related here, the compiler still
        // complains that it's missing
        XCTAssert(true);
    }

}

Note that I need to import my local package because it contains many other extensions (e.g. for XCTest), not just for Testing.

I would also appreciate a bit of guidance to understand what's causing that.

Thank you!

It sounds like you're expecting imports from a dependency to be "re-exported", which is not the case by default for Swift modules. Usually this is considered a good thing, since automatically having a bunch of names that you didn't import available in a source file can lead to conflicts and ambiguities. Automatic re-exporting would make it hard to exercise fine control over the dependencies of a given source file since you often don't control what the dependencies import (and those imports may even change over time).

There is no official way to re-export modules in Swift, but you can use the compiler internal @_exported import to re-export an dependency explicitly. Just be aware that, as with any underscored attribute, it's not officially part of the language and is subject to change in the future.

Thanks @tshortli!

If I understood correctly, your explanation makes sense and answers my first two questions.

In the case of the Logger extension from my example above, the compiler diagnostic goes in line with this. It seems logical that the compiler must know where Logger comes from before it can "resolve" the static properties in the extension.

What still puzzles me is the last part of my post. The compiler complains about a missing import of Testing, even when I'm not using anything from it. I assume it's simply because I'm importing my local package.

Sorry, I missed that part of your question. I'm not sure what causes the Missing required module 'Testing' error. Maybe @xymus has some insight?

The error on Missing required module 'Testing' should be because the compiler is trying to load the transitive dependencies and it can't find that one. So I believe the compiler is doing as you expected but it may have gone wrong earlier at the build system level. We usually see that error because search paths are missing at building the failing target or because that module was simply not built in that configuration. I would first ensure that the dependencies are correctly declared in the package manifest or Xcode config, that everything builds in the expected order, and that the search paths actually point to the folder where Testing.swiftmodule is generated.

We could use more context around these errors to say more, or ideally a full log of a clean build.

Thanks @xymus!

I think the search paths and Xcode's overall configuration should be correct, because this project has more than 6 years and this is the first time we see this problem. But who knows, maybe we missed something when adopting Xcode 16.

So I'll try first to reproduce the issue in a sample project. If I can't, then I will share some logs about the actual project where I'm facing this.

Hey there,

I was able to reproduce the issue with a sample project and narrow down the scenario a bit.

These are the steps I followed (irrelevant ones, like cleaning up template code, are omitted for brevity):

  1. Open Xcode
  2. Create a new workspace
  3. Create a new iOS app project and add it to the workspace, choosing "Swift Testing with XCTest UI Tests" for the "Testing System" option
  4. Delete all unit tests and the unit tests target
  5. Create a new Swift package, choosing "None" as the "Testing System"
  6. Add an import Testing statement to a source file in the package, optionally wrapped with a canImport(Testing) statement
  7. Back in the app project, add the resulting framework to the "Frameworks and Libraries" build setting in the "General" tab of the UI test target
  8. In the sample UI test code for the app target, import the Swift package
  9. Run the UI tests

As far as I can tell, the compiler only raises an error when importing the local Swift package into a UI test target. Unit test targets seem to work fine.

Also, there's no need to use any facilities offered by the local Swift package in client code. A simple import MyLibrary is enough to trigger the error.

I cannot tell which build setting can be toggled, if any, to influence this. I also wonder if this is some sort of side effect caused by the fact that we cannot use Swift Testing to write UI tests yet.

Does that help or should I gather more information?

Cheers!

2 Likes

Thanks for the reproducer project. @briancroom, it looks to me like this has something to do with the restriction that the Testing module cannot be used from UI Testing Bundle targets, do you have any insight into what's happening here?

Hey, yeah, that's right.

In Xcode 16, it's not possible to use Swift Testing from UI test bundle targets, and that's accomplished by preventing the compiler from finding the Testing module when building that product type. In your scenario, even though the Testing module isn't re-exported from the package target, the compiler still expects to be able to find it when building package clients, because the package is built without library evolution.

My suggestion would be that you split out the package code which uses the Testing module into a separate target, and take care to avoid importing that target in your UI test bundle source files.

2 Likes

I will give that a try, thanks @briancroom and @tshortli!