Compiling a dynamic framework with a statically linked library creates dependencies in ".swiftmodule" file

Steps to reproduce:

  1. Create a new project in Xcode with template Cocoa Touch Framework, and name it TestFramework.
  2. Add another target in this project with template Cocoa Touch Static Library, and name it TestLibrary.
  3. In the auto-generated file TestLibrary.swift, add a public interface like:
    public struct TestLibrary {
        public init() { }
        public let name = "static library"
    }
  1. In target TestFramework, link libTestLibrary.a.
  2. In target TestFramework, create a new file with the name TestFramework.swift.
  3. In TestFramework.swift, import TestLibrary and use the API it provides without exposing any symbol in public, like
    import TestLibrary
    public struct TestFramework {
        public init() { }
        public let library = TestLibrary().name
    }
  1. Build target TestFramework, and locate the product TestFramework.framework.
  2. Create a new project in Xcode with template Single View Application, and name it TestApplication.
  3. Drag the TestFramework.framework from step 7 into the Embedded Binaries build phase of TestApplication.
  4. In the file AppDelegate.swift of TestApplication, import TestFramework like
    import UIKit
    import TestFramework
  1. Run the app.
  2. At the line where TestFramework is imported, an error is prompted:

:exclamation: Missing required module 'TestLibrary'

  1. If you command-click on that import TestFramework, Xcode will generate the public interfaces of TestFramework, and it shows as:
    import Foundation
    import SwiftOnoneSupport
    import TestFramework
    import TestFramework.Swift
    import TestLibrary
    import UIKit
    ...

So this is where I got confused.

TestLibrary is statically linked with TestFramework, which means the binary of TestFramework has all the symbols from TestLibrary. Plus, TestFramework is not exposing any APIs from TestLibrary, but only using them. This means TestFramework is fully functional on its own regardless of TestLibrary being presented or not.

So why the .swiftmodule file of TestFramework creates an additional (probably unnecessary) dependency on TestLibrary?

As a result, when a user uses TestFramework to build an app, the app won't be able to compile because it can not find the TestLibrary, but it can run perfectly because it actually does not use TestLibrary directly if we can somehow bypass that dependency check.

So I am wondering if there is a way to remove a dependency from .swiftmodule file?
Or is there any compile/link flag I can pass in to prevent it generates any unnecessary dependency?

Just found another similar description of this problem while I was searching through the internet for a solution:
https://gist.github.com/briancroom/5d0f1b966fa9ef0ae4950e97f9d76f77

3 Likes

Ran into this as well, this was the closest thread to describing the problem.

We've got a static Objective-C library (InternalLib) with an umbrella header that we want to force-load into a dynamic Swift framework (SharedFramework). InternalLib we explicitly don't want to be importable or accessible.

It seems like the symbols are fine and in the binary, but also run into the same compile error where either in the Swift generated header it has an @import InternalLib that's exposed, which won't succeed, I'm assuming it's the same root cause here.

Guess this brings me to the broader question of how do I properly force-load that InternalLib into the dynamic SharedFramework such that an import SharedFramework is all that is needed.

There's not really a supported way to do this at this time. The work that would be necessary is described in @_exported and fixing import visibility and Update on implementation-only imports.

2 Likes

Yeah the implementation imports work pretty well but it seems like they still show up in the Swift generated header; not sure if that's a bug or not, haven't looked into how they work enough yet

AFAIK you need to copy module map of static library to the root of the dynamic framework and then path to that folder needs to be exposed in Import Path under swift build settings for Host App.

Linking refers to the creation of a single executable file from multiple object files. In other words: After the compiler has created all the object files , another program is So let's make this simple: frameworks are static or dynamic libraries esolved dependency structure into your code, and the compiler did.

Terms of Service

Privacy Policy

Cookie Policy