Swift Package that has `@objc` in Swift code can’t be used due to Objective-C module failure

When the Xcode target (such as Application) is importing a dynamic framework that is importing Swift Package that has public @objc interface, such as a delegate protocol, we can’t build the target due to Objective-C module failure.

Reproducible project is at https://github.com/niw/swift_package_objc_module_failure

In this example, the dependency is like this.
Code Block
Application Target (Test)
→ Dynamic Framework (TestFramework)
→ Swift Package (TestPackage)

And error would be
Code Block
<module-includes>:2:9: note: in file included from <module-includes>:2:
#import "Headers/TestFramework-Swift.h"
^
/Users/niw/Library/Developer/Xcode/DerivedData/Test-gcmgljtjqifdtzbblkpxoahggxkj/Build/Products/Debug-iphonesimulator/TestFramework.framework/Headers/TestFramework-Swift.h:192:9: error: module 'TestPackage' not found
@import TestPackage;
^
<unknown>:0: error: could not build Objective-C module 'TestFramework'


Is there any good solution to fix or workaround this?
Technically I think for now, we can’t safely using @objc at all in Swift Package, and package user may see this error, which is unexpected by the package author for sure.

I also reported this issue on Swift JIRA is at https://bugs.swift.org/browse/SR-13075

Replies

Let me add extra observations here as well, Swift JIRA is also updated.
  1. This is caused by missing -Xcc -module-map-file for swift -frontend for the transitive Swift package dependency used for the Swift files in the application target.

  2. This is not happening if the module that Swift Package provided is written in Objective-C.

This is expected behaviour, additional build settings needed for package dependencies are automatically passed to other package targets, but this behaviour ends with a client that is a traditional Xcode target (the framework in your example). This is the case because Xcode targets manage build settings manually. So you are responsible for passing the -module-map-file here.

If all clients of your framework are Swift, you can also work around the issue in your specific example by changing the build setting SWIFT_INSTALL_OBJC_HEADER to NO, because you don't really need the generated ObjC compatibility header in that case.
Thank you for replying, Developer Tools Engineer . I work with @niw. The answer seems to imply that Xcode should never add -fmodule-map-file for SPM modules that are transitively linked, however for some dependencies it absolutely does work. For example, our app has a dependencies like:

Code Block
App -> XcodeFrameworkA -> XcodeFrameworkB -> (static SPMLibrary) SPMModule


In Objective-C code in XcodeFrameworkA, this compiles and runs perfectly fine:

Code Block
// Foo.m
// XcodeFrameworkA
@import SPMModule;


And the build logs show that the frontend flag is being added, e.g.
Code Block
CompileC ... XCodeFrameworkA/Foo.m ... -fmodule-map-file=.../GeneratedModuleMaps/.../SPMModule.modulemap ...


My question is, what causes this kind of transitive dependency to work sometimes but not other times? Is this something we can rely on in the cases where it already "works"?
Interesting, that's a bit surprising to me. Could you share an example like this so that we can investigate further?
Sure, here a more elaborate version of @niw's demo: https://github.com/patricknixon/spm-transitive

It demonstrates that:
  • SPM modules are visible transitively to all private code downstream.

  • SPM modules cannot be imported by a header that is included in any public umbrella header.

  • A framework will "re-export" SPM dependencies in its generated Objective-C compatibility header if needed, which leads to the issue in the original post. In addition, the demo shows that such a framework can be used from Objective-C code but not from Swift.

Thanks for the example.
Hi, Developer Tools Engineer,

So as we discussed on Xcode Open Hours lab today, I filed FB7799213 with these examples (and there is also original FB7781285.) Please track the issue by using these feedback!

Thank you!
Hi

I have the same issue. Were you able to find any solutions except setting SWIFT_INSTALL_OBJC_HEADER to NO?

Regards
Mohsen