How to exclude a local package from an Xcode target?

Hello, I'm facing a problem in an Xcode project and a local package, where #if canImport() does not behave as I would expect.

I have a local package named GraphQL in which I put all kind of types related to a specific feature (here, the GraphQL models generated by the Apollo library).

This package is linked into the application target, with great success.

I do not want this package to be used from my unit tests target. These are unit tests, not integration or end-to-end tests: those tests do not connect to any GraphQL server. I do not want to drag in this test target dependencies it does not need. The GraphQL local package is thus NOT linked into the test target.

Enter #if canImport(GraphQL) :boom:

There are places where I need #if canImport(GraphQL), so that files can compile both with and without the GraphQL package. The code wrapped inside this conditional can not be moved into a distinct file (it sometimes requires access to fileprivate apis).

Unfortunately, Xcode compiles code guarded by #if canImport(GraphQL), even in the unit test target which does not link against the GraphQL package. As expected, the test target won't build.

With Xcode 13.1, this problem would be transient, and an inconvenient Product > Clean Build Folder would fix the problem.

With Xcode 13.2 RC, the problem will just not go away, and the project is unusable.

Do you happen to know this situation, and maybe a workaround?

I think you can add your own Swift directives to your targets build settings, then it would be a matter of replacing #if canImport(GraphQL) with a test on your directive, something like #if !THIS_IS_THE_TEST_TARGET.

As for canImport() it is my experience as well that this is not a reliable way to test whether the target has a dependency on the module. If the module is present in the built products (because another target does have a dependency on it that caused it to build), then canImport() will always return true.

This is indeed my current workaround. But thanks for sharing it with other people :+1:

As for canImport() it is my experience as well that this is not a reliable way to test whether the target has a dependency on the module. If the module is present in the built products (because another target does have a dependency on it that caused it to build), then canImport() will always return true.

This really makes me wonder if the use case was expected by compiler and SPM designers, and the workaround (-D MAGIC_FLAG) is the correct solution they envisioned, or if I should report an issue somewhere. :confused:

The canImport directive is documented as:

The argument for the canImport() platform condition is the name of a module that may not be present on all platforms. This condition tests whether it’s possible to import the module, but doesn’t actually import it. If the module is present, the platform condition returns true ; otherwise, it returns false .

The word "present" is very ambiguous, ambiguous enough for language lawyers to say it is not incorrect for it to be true in my use case.

So... Don't we miss the #if canLink(module) directive?

The way canImport works is if the module exists in your search paths, it'll be true. Typically when using Xcode or SwiftPM, there's just one shared directory for any modules being built, so whether a dependency has been declared won't affect whether canImport will return true. Generally speaking, the compiler doesn't know at all about your declared dependencies from the package manifest today.

Thanks for this description of the current implementation, it explains a lot. Now, is it a satisfying situation?