Header import conditionally fails in mixed Swift package

I'm in the process of migrating an iOS app to use the latest version of a storage SDK. The previous SDK provided a model abstraction similar to CoreData, which leverages the dynamic nature of Objective-C. Since I have access to the Obj-C model source code, I'm using composition to implement the model storage functionality, along with many of the support classes, in Swift. This results in a mixed source package that is similar to the following...

let package = Package(
    name: "MyPackage",
    products: [
        // Products define the executables and libraries a package produces, and make them visible to other packages.
        .library(
            name: "MyPackage",
            targets: ["MyPackage"]),
        .library(
            name: "MyPackageObjC",
            targets: ["MyPackageObjC"]),
        .library(
            name: "MyPackageSwift",
            targets: ["MyPackageSwift"]),
    ],
    dependencies: [
        // Dependencies declare other packages that this package depends on.
        // .package(url: /* package url */, from: "1.0.0"),
    ],
    targets: [
        // Targets are the basic building blocks of a package. A target can define a module or a test suite.
        // Targets can depend on other targets in this package, and on products in packages this package depends on.
        .target(
            name: "MyPackage",
            dependencies: ["MyPackageSwift","MyPackageObjC"]),
        .target(
            name: "MyPackageObjC",
            dependencies: ["MyPackageSwift"]),
        .target(
            name: "MyPackageSwift",
            dependencies: []),
        .testTarget(
            name: "MyPackageTests",
            dependencies: ["MyPackage"]),
    ]
)

And a file system layout that looks like....

.
├── Package.swift
    ├── README.md
    ├── Sources
    │   ├── MyPackage
    │   │   └── Export.swift
    │   ├── MyPackageObjC
    │   │   ├── MyObject.m
    │   │   ├── NameGenerator+LastName.m
    │   │   └── include
    │   │       ├── MyObject.h
    │   │       └── NameGenerator+LastName.h
    │   └── MyPackageSwift
    │       └── MyPackage.swift
    └── Tests
        ├── LinuxMain.swift
        └── MyPackageTests
            ├── MyPackageTests.swift
            └── XCTestManifests.swift

Since the Swift target cannot depend on the Obj-C target, I've added categories in Obj-C to implement interactions with the model class. For example, since my Swift model factory class cannot import the Obj-C model class, I've implemented the method that creates the model from a underlying document as an Obj-C category on the Swift class. This works well when I build the MyPackageObjC target, and I can use the resulting package in Obj-C code in my app. However, this fails when I try to import the MyPackageObjC target in Swift. I created the MyPackage target that depends on both the Obj-C and Swift targets a means to factor the iOS app out of the equation. Export.swift uses @_exported import MyPackage<variant> to replicate what happens in the iOS app.

From the file NameGenerator+LastName.h in my minimal test project....

@import MyPackageSwift; <- error when building 'MyPackage' 

@interface NameGenerator (LastName)

- (NSString*)generateLastName;

@end

Here's the details of the error.

<module-includes>:1:9: note: in file included from <module-includes>:1:
#import "/Users/scott/Desktop/MyPackage/Sources/MyPackageObjC/include/NameGenerator+LastName.h"
        ^
/Users/scott/Desktop/MyPackage/Sources/MyPackageObjC/include/NameGenerator+LastName.h:2:9: error: module 'MyPackageSwift' not found
@import MyPackageSwift;
        ^
/Users/scott/Desktop/MyPackage/Sources/MyPackage/Export.swift:2:19: error: could not build Objective-C module 'MyPackageObjC'
@_exported import MyPackageObjC
                  ^

After digging a bit deeper, I discovered that adding @import MyPackageSwift to any header in the Obj-C target causes this error when building the MyPackage target. But it works fine when building the Obj-C MyPackageObjC target.

Since this seems to work conditionally, is there any additional package compiler configuration entries I can use to resolve this?

What seems odd here is that the Obj-C target builds successfully earlier in the process. It's almost as if the Swift compiler is trying to compile the Obj-C header file through a traversal of @_exported import MyPackageObjC in the MyPackage Export.swift file. Does this only "work" for Swift targets?

Build target MyPackageSwift with configuration Debug

CreateBuildDirectory /Users/scott/Library/Developer/Xcode/DerivedData/MyPackage-auxgaiuowjopftgqahenyqhycdxr/Build/Products (in target 'MyPackageSwift' from project 'MyPackage')
    [...]

CreateBuildDirectory /Users/scott/Library/Developer/Xcode/DerivedData/MyPackage-auxgaiuowjopftgqahenyqhycdxr/Build/Intermediates.noindex (in target 'MyPackageSwift' from project 'MyPackage')
    [...]

WriteAuxiliaryFile /Users/scott/Library/Developer/Xcode/DerivedData/MyPackage-auxgaiuowjopftgqahenyqhycdxr/Build/Intermediates.noindex/MyPackage.build/Debug-iphonesimulator/MyPackageSwift.build/MyPackageSwift.modulemap (in target 'MyPackageSwift' from project 'MyPackage')
   [...]

WriteAuxiliaryFile /Users/scott/Library/Developer/Xcode/DerivedData/MyPackage-auxgaiuowjopftgqahenyqhycdxr/Build/Intermediates.noindex/MyPackage.build/Debug-iphonesimulator/MyPackageSwift.build/Objects-normal/x86_64/MyPackageSwift.o.LinkFileList (in target 'MyPackageSwift' from project 'MyPackage')
    [...]

WriteAuxiliaryFile /Users/scott/Library/Developer/Xcode/DerivedData/MyPackage-auxgaiuowjopftgqahenyqhycdxr/Build/Intermediates.noindex/MyPackage.build/Debug-iphonesimulator/MyPackageSwift.build/Objects-normal/x86_64/MyPackageSwift.o.SwiftFileList (in target 'MyPackageSwift' from project 'MyPackage')
    [...]

WriteAuxiliaryFile /Users/scott/Library/Developer/Xcode/DerivedData/MyPackage-auxgaiuowjopftgqahenyqhycdxr/Build/Intermediates.noindex/MyPackage.build/Debug-iphonesimulator/MyPackageSwift.build/Objects-normal/x86_64/MyPackageSwift-OutputFileMap.json (in target 'MyPackageSwift' from project 'MyPackage')
    [...]

Ditto /Users/scott/Library/Developer/Xcode/DerivedData/MyPackage-auxgaiuowjopftgqahenyqhycdxr/Build/Intermediates.noindex/MyPackage.build/Debug-iphonesimulator/MyPackageSwift.build/MyPackageSwift.modulemap /Users/scott/Library/Developer/Xcode/DerivedData/MyPackage-auxgaiuowjopftgqahenyqhycdxr/Build/Intermediates.noindex/GeneratedModuleMaps-iphonesimulator/MyPackageSwift.modulemap (in target 'MyPackageSwift' from project 'MyPackage')
    [...]

CompileSwiftSources normal x86_64 com.apple.xcode.tools.swift.compiler (in target 'MyPackageSwift' from project 'MyPackage')
    [...]

CompileSwift normal x86_64 /Users/scott/Desktop/MyPackage/Sources/MyPackageSwift/MyPackage.swift (in target 'MyPackageSwift' from project 'MyPackage')
    [...]

MergeSwiftModule normal x86_64 (in target 'MyPackageSwift' from project 'MyPackage')
   [...]

Ditto /Users/scott/Library/Developer/Xcode/DerivedData/MyPackage-auxgaiuowjopftgqahenyqhycdxr/Build/Intermediates.noindex/GeneratedModuleMaps-iphonesimulator/MyPackageSwift-Swift.h /Users/scott/Library/Developer/Xcode/DerivedData/MyPackage-auxgaiuowjopftgqahenyqhycdxr/Build/Intermediates.noindex/MyPackage.build/Debug-iphonesimulator/MyPackageSwift.build/Objects-normal/x86_64/MyPackageSwift-Swift.h (in target 'MyPackageSwift' from project 'MyPackage')
   [...]

Ditto /Users/scott/Library/Developer/Xcode/DerivedData/MyPackage-auxgaiuowjopftgqahenyqhycdxr/Build/Products/Debug-iphonesimulator/MyPackageSwift.swiftmodule/x86_64.swiftdoc /Users/scott/Library/Developer/Xcode/DerivedData/MyPackage-auxgaiuowjopftgqahenyqhycdxr/Build/Intermediates.noindex/MyPackage.build/Debug-iphonesimulator/MyPackageSwift.build/Objects-normal/x86_64/MyPackageSwift.swiftdoc (in target 'MyPackageSwift' from project 'MyPackage')
   [...]

Ditto /Users/scott/Library/Developer/Xcode/DerivedData/MyPackage-auxgaiuowjopftgqahenyqhycdxr/Build/Products/Debug-iphonesimulator/MyPackageSwift.swiftmodule/x86_64.swiftmodule /Users/scott/Library/Developer/Xcode/DerivedData/MyPackage-auxgaiuowjopftgqahenyqhycdxr/Build/Intermediates.noindex/MyPackage.build/Debug-iphonesimulator/MyPackageSwift.build/Objects-normal/x86_64/MyPackageSwift.swiftmodule (in target 'MyPackageSwift' from project 'MyPackage')
    [...]

Ditto /Users/scott/Library/Developer/Xcode/DerivedData/MyPackage-auxgaiuowjopftgqahenyqhycdxr/Build/Products/Debug-iphonesimulator/MyPackageSwift.swiftmodule/x86_64-apple-ios-simulator.swiftmodule /Users/scott/Library/Developer/Xcode/DerivedData/MyPackage-auxgaiuowjopftgqahenyqhycdxr/Build/Intermediates.noindex/MyPackage.build/Debug-iphonesimulator/MyPackageSwift.build/Objects-normal/x86_64/MyPackageSwift.swiftmodule (in target 'MyPackageSwift' from project 'MyPackage')
    [...]

Ditto /Users/scott/Library/Developer/Xcode/DerivedData/MyPackage-auxgaiuowjopftgqahenyqhycdxr/Build/Products/Debug-iphonesimulator/MyPackageSwift.swiftmodule/x86_64-apple-ios-simulator.swiftdoc /Users/scott/Library/Developer/Xcode/DerivedData/MyPackage-auxgaiuowjopftgqahenyqhycdxr/Build/Intermediates.noindex/MyPackage.build/Debug-iphonesimulator/MyPackageSwift.build/Objects-normal/x86_64/MyPackageSwift.swiftdoc (in target 'MyPackageSwift' from project 'MyPackage')
    [...]

Ditto /Users/scott/Library/Developer/Xcode/DerivedData/MyPackage-auxgaiuowjopftgqahenyqhycdxr/Build/Products/Debug-iphonesimulator/MyPackageSwift.swiftmodule/Project/x86_64-apple-ios-simulator.swiftsourceinfo /Users/scott/Library/Developer/Xcode/DerivedData/MyPackage-auxgaiuowjopftgqahenyqhycdxr/Build/Intermediates.noindex/MyPackage.build/Debug-iphonesimulator/MyPackageSwift.build/Objects-normal/x86_64/MyPackageSwift.swiftsourceinfo (in target 'MyPackageSwift' from project 'MyPackage')
    [...]

Ditto /Users/scott/Library/Developer/Xcode/DerivedData/MyPackage-auxgaiuowjopftgqahenyqhycdxr/Build/Products/Debug-iphonesimulator/MyPackageSwift.swiftmodule/Project/x86_64.swiftsourceinfo /Users/scott/Library/Developer/Xcode/DerivedData/MyPackage-auxgaiuowjopftgqahenyqhycdxr/Build/Intermediates.noindex/MyPackage.build/Debug-iphonesimulator/MyPackageSwift.build/Objects-normal/x86_64/MyPackageSwift.swiftsourceinfo (in target 'MyPackageSwift' from project 'MyPackage')
   [...]

Ld /Users/scott/Library/Developer/Xcode/DerivedData/MyPackage-auxgaiuowjopftgqahenyqhycdxr/Build/Products/Debug-iphonesimulator/MyPackageSwift.o normal (in target 'MyPackageSwift' from project 'MyPackage')
    [...]

RegisterExecutionPolicyException /Users/scott/Library/Developer/Xcode/DerivedData/MyPackage-auxgaiuowjopftgqahenyqhycdxr/Build/Products/Debug-iphonesimulator/MyPackageSwift.o (in target 'MyPackageSwift' from project 'MyPackage')
   [...]

Build target MyPackageObjC with configuration Debug

WriteAuxiliaryFile /Users/scott/Library/Developer/Xcode/DerivedData/MyPackage-auxgaiuowjopftgqahenyqhycdxr/Build/Intermediates.noindex/MyPackage.build/Debug-iphonesimulator/MyPackageObjC.build/MyPackageObjC.modulemap (in target 'MyPackageObjC' from project 'MyPackage')
    [...]

Ditto /Users/scott/Library/Developer/Xcode/DerivedData/MyPackage-auxgaiuowjopftgqahenyqhycdxr/Build/Intermediates.noindex/MyPackage.build/Debug-iphonesimulator/MyPackageObjC.build/MyPackageObjC.modulemap /Users/scott/Library/Developer/Xcode/DerivedData/MyPackage-auxgaiuowjopftgqahenyqhycdxr/Build/Intermediates.noindex/GeneratedModuleMaps-iphonesimulator/MyPackageObjC.modulemap (in target 'MyPackageObjC' from project 'MyPackage')
   [...]

CompileC /Users/scott/Library/Developer/Xcode/DerivedData/MyPackage-auxgaiuowjopftgqahenyqhycdxr/Build/Intermediates.noindex/MyPackage.build/Debug-iphonesimulator/MyPackageObjC.build/Objects-normal/x86_64/NameGenerator+LastName.o /Users/scott/Desktop/MyPackage/Sources/MyPackageObjC/NameGenerator+LastName.m normal x86_64 objective-c com.apple.compilers.llvm.clang.1_0.compiler (in target 'MyPackageObjC' from project 'MyPackage')
    [...]

CompileC /Users/scott/Library/Developer/Xcode/DerivedData/MyPackage-auxgaiuowjopftgqahenyqhycdxr/Build/Intermediates.noindex/MyPackage.build/Debug-iphonesimulator/MyPackageObjC.build/Objects-normal/x86_64/MyObject.o /Users/scott/Desktop/MyPackage/Sources/MyPackageObjC/MyObject.m normal x86_64 objective-c com.apple.compilers.llvm.clang.1_0.compiler (in target 'MyPackageObjC' from project 'MyPackage')
    [...]

WriteAuxiliaryFile /Users/scott/Library/Developer/Xcode/DerivedData/MyPackage-auxgaiuowjopftgqahenyqhycdxr/Build/Intermediates.noindex/MyPackage.build/Debug-iphonesimulator/MyPackageObjC.build/Objects-normal/x86_64/MyPackageObjC.o.LinkFileList (in target 'MyPackageObjC' from project 'MyPackage')
   [...]

Ld /Users/scott/Library/Developer/Xcode/DerivedData/MyPackage-auxgaiuowjopftgqahenyqhycdxr/Build/Products/Debug-iphonesimulator/MyPackageObjC.o normal (in target 'MyPackageObjC' from project 'MyPackage')
    [...]

RegisterExecutionPolicyException /Users/scott/Library/Developer/Xcode/DerivedData/MyPackage-auxgaiuowjopftgqahenyqhycdxr/Build/Products/Debug-iphonesimulator/MyPackageObjC.o (in target 'MyPackageObjC' from project 'MyPackage')
    cd /Users/scott/Desktop/MyPackage
    builtin-RegisterExecutionPolicyException /Users/scott/Library/Developer/Xcode/DerivedData/MyPackage-auxgaiuowjopftgqahenyqhycdxr/Build/Products/Debug-iphonesimulator/MyPackageObjC.o


Build target MyPackage with configuration Debug

WriteAuxiliaryFile /Users/scott/Library/Developer/Xcode/DerivedData/MyPackage-auxgaiuowjopftgqahenyqhycdxr/Build/Intermediates.noindex/MyPackage.build/Debug/MyPackage.build/Objects-normal/x86_64/MyPackage.o.SwiftFileList (in target 'MyPackage' from project 'MyPackage')
    [...]

WriteAuxiliaryFile /Users/scott/Library/Developer/Xcode/DerivedData/MyPackage-auxgaiuowjopftgqahenyqhycdxr/Build/Intermediates.noindex/MyPackage.build/Debug/MyPackage.build/Objects-normal/x86_64/MyPackage-OutputFileMap.json (in target 'MyPackage' from project 'MyPackage')
   [...]

WriteAuxiliaryFile /Users/scott/Library/Developer/Xcode/DerivedData/MyPackage-auxgaiuowjopftgqahenyqhycdxr/Build/Intermediates.noindex/MyPackage.build/Debug/MyPackage.build/MyPackage.modulemap (in target 'MyPackage' from project 'MyPackage')
    [...]

Ditto /Users/scott/Library/Developer/Xcode/DerivedData/MyPackage-auxgaiuowjopftgqahenyqhycdxr/Build/Intermediates.noindex/MyPackage.build/Debug/MyPackage.build/MyPackage.modulemap /Users/scott/Library/Developer/Xcode/DerivedData/MyPackage-auxgaiuowjopftgqahenyqhycdxr/Build/Intermediates.noindex/GeneratedModuleMaps/MyPackage.modulemap (in target 'MyPackage' from project 'MyPackage')
   [...]

CompileSwiftSources normal x86_64 com.apple.xcode.tools.swift.compiler (in target 'MyPackage' from project 'MyPackage')
    [...]

CompileSwift normal x86_64 /Users/scott/Desktop/MyPackage/Sources/MyPackage/Export.swift (in target 'MyPackage' from project 'MyPackage')
    [...]

<module-includes>:1:9: note: in file included from <module-includes>:1:
#import "/Users/scott/Desktop/MyPackage/Sources/MyPackageObjC/include/NameGenerator+LastName.h"
        ^
/Users/scott/Desktop/MyPackage/Sources/MyPackageObjC/include/NameGenerator+LastName.h:2:9: error: module 'MyPackageSwift' not found
@import MyPackageSwift;
        ^
/Users/scott/Desktop/MyPackage/Sources/MyPackage/Export.swift:2:19: error: could not build Objective-C module 'MyPackageObjC'
@_exported import MyPackageObjC
                  ^

WriteAuxiliaryFile /Users/scott/Library/Developer/Xcode/DerivedData/MyPackage-auxgaiuowjopftgqahenyqhycdxr/Build/Intermediates.noindex/MyPackage.build/Debug/MyPackage.build/Objects-normal/x86_64/MyPackage.o.LinkFileList (in target 'MyPackage' from project 'MyPackage')
    [...]

Build failed    3/2/21, 8:45 AM    0.5 seconds

To add some clarity here, the new storage SDK comes in two flavors: Objective-C and Swift.

What I'm trying to avoid is using the Objective-C variant as both the Swift and Obj-C versions cannot be used simultaneously. This would prevent future development, including new features developed in Swift, from using Swift language features as part of the Swift SDK. Objective-C classes cannot subclass Swift classes and the model class makes extensive use of the dynamic nature of Objective-C to provide access to data via properties, similar to CoreData. Nor can my Objective-C code "see" the Swift SDK, as it uses the advanced features of the Swift language, so I can't use composition with the Swift SDK directly.

IOW, PersistenceSwift reflects a bridge between the Swift version of the SDK, the Obj-C model class and existing Obj-C / Swift code that subclasses it. This would result in a dependency graph of...

SwiftStorageSDK [Third party] -> PersistenceSwift -> PersistenceObjC [w/Obj-C model class] -> Obj-C / Swift client code

Swift and Obj-C client code accesses Swift utility classes from PersistenceSwift and subclass the Obj-C model class from PersistenceObjC, similar to how Swift and Obj-C can subclass NSManagedObject and create strongly typed access to the underlying data via @dynamic properties. PersistenceObjC uses composition to interact indirectly with the Swift SDK under the hood via an API in PersistenceSwift, which bridges access with Swift classes that extend NSObject, etc.

This works great from Objective-C, but fails with Swift. Which, ironically, was the least of my worries.

While the app I'm migrating is mostly Objective-C, it has a significant amount of Swift code that also interacts with, and subclasses the Obj-C model class. Unfortunately, the vendor has not provided a corresponding model class in the new version of the SDK.

I'm staring to wonder if this is a bug, rather than a problem with my strategy, in general, as the PersistenceObjC target builds just fine and both the classes from both PersistenceSwift and PersistenceObjC can be seen by Obj-C client code in the app. However, any attempt to import the PersistenceObjC module in Swift from results in an error building that target. Also, the Persistence target fails to build as well (represented by MyPackage in the test Swift package.)

Perhaps I need to create a custom module map to make this visible from the Swift side of things?

Terms of Service

Privacy Policy

Cookie Policy