Carthage: Can't properly build a dynamic framework containing a static one

Created on 18 Oct 2018  路  8Comments  路  Source: Carthage/Carthage

I've reached the limit of my sanity with this one, despite reading everything there is around Carthage and static Swift frameworks. I sense that the problem I'm facing is essentially an Xcode configuration problem. Nevertheless, since the topic is an actual one in this project, I hope someone could save me here.

Here is the demo project (I'm using Xcode 10). There's a workspace containing a FrameworkA, dependent on FrameworkB. FrameworkA has the Mach-O type set to dynamic, FrameworkB to static. In the FrameworkA folder, there's already the resulting built framework (there's a script phase that copies it over).

The App in the test folder links and embeds this framework. Try at first to build the app. You'll get a build failure on the line of import FrameworkA saying missing required module FrameworkB. Why?!?!

Appreciate your help.

P.S. If you open the Static.workspace, build the framework, then the app will start building. But I assume that's because of Xcode looking into the DerivedData somehow and finding FrameworkB. If you delete the Static's project derived data, this error will pop up again when trying to build the app.

P.S.2. Of course, running carthage archive results in the same problem.

question stale

Most helpful comment

Here are 2 solutions for a dynamic framework A depending on a staticlibrary B.

  • First, even if the static library B is compiled with the framework A, a consuming client (an app or a framework) will still need to find the .swiftmodule or .modulemap file of the library B. The module information is used to expose the Swift API (or C,C++,objc) of module B when module A is imported!
  • If the static library B is only used for internal implementation of framework A, you can add the @_implementationOnly import B everywhere in framework A, and the requirement to know about the module information of B will disappeared for the consuming client side since the compiler does not need to expose back the API of module B (again this works if no public symbols in A refers to symbols in B)!
  • If A exports types in B, then you need to tell the compiler where to find the module / interfaces of B even when consuming A! This is why moving files around or trying to copy the framework A to an other machine without bringing the interface of B will cause a compiler issue telling you Missing required module 'B'. Simply tell the compiler where to find it the module information of B using HEADER_SEARCH_PATHS ! For example, in the jedisct1/swift-sodium#170 issue, you could add HEADER_SEARCH_PATHS = $(PROJECT_DIR)/Carthage/Checkouts/swift-sodium/Sodium (.xcconfig syntax ) or directly set it in the Xcode Project file in the Build Settings tab.
  • Be careful, the framework A still knows about the absolute path of the interfaces of B, so if you update HEADER_SEARCH_PATHS for the interface of B after moving the framework A around and do not delete the original module map, you may get a build error with Redefinition of module 'B'.
  • [Update - not tested] I will try add the headers of B in a Headers Phase (public tab) in Build Phases when Building A, this may create the needed header / module files?

All 8 comments

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

I am also having the same problem here. Oddly enough, this seems to succeed when built on the same machine, but when you move those artifacts to another machine, they don't work anymore.

I have seen archive behavior effect this as well. See: See: https://stackoverflow.com/a/44793192/731285

Also, see this open issue for a project that has the same issue: https://github.com/jedisct1/swift-sodium/issues/170

It's because Xcode picks it up from the DerivedData. Delete that, and the build should fail.

After a ton of digging, I believe this is a problem with how FrameworkA is built/linked.

Build the static workspace with --no-skip-current, then run

swift -F Carthage/Build/iOS/ -F Carthage/Build/iOS/Static/ -sdk `xcrun --sdk iphonesimulator12.2 --show-sdk-path` -target x86_64-apple-ios12.0-simulator -deprecated-integrated-repl

once in the repl type:

:print_module FrameworkA

as you can see there is no trace of FrameworkB apart from the import and this is what is throwing everything off

So yes, it works if you build Static.xcodeproj first because the App's project is finding stuff in derived data

From ViewController.swiftdeps

depends-external:
- "/Users/Tommaso/Code/lab/Carthage-2618/static-frameworks-problem/FrameworkA/FrameworkA.framework/Modules/FrameworkA.swiftmodule/x86_64.swiftmodule"
- "/Users/Tommaso/Library/Developer/Xcode/DerivedData/Static-fynxdwwbtsztawacmqqnsruwxyhr/Build/Products/Debug-iphonesimulator/FrameworkB.framework/Modules/FrameworkB.swiftmodule/x86_64.swiftmodule"

Honestly I don't know if this is possible at all. It's certainly not a problem with Carthage

Yeah I also think I realized it's an intrinsic Swift problem, and most probably it's the persistence of that _import_ in the binary, as @blender mentioned.

Here are 2 solutions for a dynamic framework A depending on a staticlibrary B.

  • First, even if the static library B is compiled with the framework A, a consuming client (an app or a framework) will still need to find the .swiftmodule or .modulemap file of the library B. The module information is used to expose the Swift API (or C,C++,objc) of module B when module A is imported!
  • If the static library B is only used for internal implementation of framework A, you can add the @_implementationOnly import B everywhere in framework A, and the requirement to know about the module information of B will disappeared for the consuming client side since the compiler does not need to expose back the API of module B (again this works if no public symbols in A refers to symbols in B)!
  • If A exports types in B, then you need to tell the compiler where to find the module / interfaces of B even when consuming A! This is why moving files around or trying to copy the framework A to an other machine without bringing the interface of B will cause a compiler issue telling you Missing required module 'B'. Simply tell the compiler where to find it the module information of B using HEADER_SEARCH_PATHS ! For example, in the jedisct1/swift-sodium#170 issue, you could add HEADER_SEARCH_PATHS = $(PROJECT_DIR)/Carthage/Checkouts/swift-sodium/Sodium (.xcconfig syntax ) or directly set it in the Xcode Project file in the Build Settings tab.
  • Be careful, the framework A still knows about the absolute path of the interfaces of B, so if you update HEADER_SEARCH_PATHS for the interface of B after moving the framework A around and do not delete the original module map, you may get a build error with Redefinition of module 'B'.
  • [Update - not tested] I will try add the headers of B in a Headers Phase (public tab) in Build Phases when Building A, this may create the needed header / module files?
Was this page helpful?
0 / 5 - 0 ratings