Cannot build framework with objective-c static library AND Swift file

I have a simple framework example I wrote to show the problem. It consists of an Objective-C based framework "Hello" with the class "Hello", with a swift file "Something.swift", and a modulemap "hello.modulemap".

In the project, I have 2 targets:

  • Hello, a dynamic framework
  • HelloStatic, a static library

In the build settings, I've set:

  • Build Libraries for Distribution = Yes
  • Module Identifier = Hello
  • Defines Module = Yes
  • Module Map File = $(SRCROOT)/Hello/hello.modulemap
  • Product Module Name = Hello
  • Enable Clang Module Debugging = Yes
  • Enable Modules (C and Objective C) = Yes
  • Objective-C Generated Interface Header Name = Hello-Swift.h
  • Swift Language Version = Swift 5

For the HelloStatic target, I've tried both Hello and HelloStatic as the Product Name, leaving the Product Module Name = Hello (because I don't want to have to change the import statement depending on whether I consume the dynamic framework or the static library.)

The Mach-O type for the targets is Dynamic Library and Static Library respectively.

The Issue:

  • If I build the target Hello, everything works fine as expected. The swift file recognizes the modulemap, and the extension for Hello works as expected.
  • If I build the target HelloStatic, the swift file tells me: Cannot find type 'Hello' in scope on the line with the 'extension Hello'

If I disable Build Libraries for Distribution, then I can add a Bridging-Header file which does an #import "Hello.h" and the build succeeds, but it doesn't generate the Hello-Swift.h header, nor the *.swiftinterface files needed to consume a Swift library any another App.

I figure that the compiler is either ignoring the modulemap file, or doesn't find it in it's search paths.

Help!

Since the dynamic library builds just fine, there has to be a solution to create a static library.

I almost forgot: If I swap out the 'extension Hello' with 'public class Goodbye', then I am able to build both the dynamic framework and the static library just fine, because my Swift module doesn't explicitly use "Hello" directly. (That is the reason for using the Selector mechanism, for those who were wondering).

If I didn't use the Selector mechanism, then everywhere I try to use the Hello class in my Swift file I would get an error when building the static library. However, if I want to call the Objective-C text: function I would use Hello.text in my App, but would have to use Goodbye.Text to call the function in the Swift module, and I don't want to do that. I want to be able to use Hello.Text like I can when I have a dynamic framework, when I use the Static Library as a framework.

The full project can be found at: https://github.com/reststop/Hello

Any help would be appreciated. I've scoured the forums and tried almost everything that looked like it might help. I may even be willing to pay a small bounty, as I've spent over a week on this and have come up empty. (working on a full-fledged Library -- I distilled this to show the issue).

Hello.h

//
// Hello.h
//
#import <Foundation/Foundation.h>
FOUNDATION_EXPORT double HelloVersionNumber;
FOUNDATION_EXPORT const unsigned char HelloVersionString[];

@interface Hello : NSObject
+ (void)initWithSub:(NSString*)sub uid:(NSString*)uid;
+ (void)text:(NSString *)string;
@end

Hello.m

//
// Hello.m
//
#import "Hello.h"

@interface Hello ()
@end

@implementation Hello
static NSString *mySub;
static NSString *myUid;

+ (void)initWithSub:(NSString*)sub uid:(NSString*)uid {
  mySub = sub;
  myUid = uid;
  NSLog(@"Initialize: sub( %@ ), uid( %@ )", sub, uid);
}

+ (void)text:(NSString *)string {
  NSLog(@"Text in Objc: %@", string);
}

@end

Smething.swift

//
// Something.swift
//
import Foundation

// Use one of the following:

//class
//public class Goodbye {

//extension
//public extension Hello {

public extension Hello {

  public class func Text(_ text: String) {
    if let hello = NSClassFromString("Hello") {
      let selector: Selector = NSSelectorFromString("text:")
      if hello.responds(to: selector) {
        debugPrint("Swift calling \(selector) \(text)")
        hello.perform(selector, with: text as NSString, afterDelay: 0)
      }
    }
  }

} // end of class or extension

Hello.modulemap

framework module Hello {
  umbrella header "Hello.h"
  export *
  module * { export * }
}

Some additional details... I examined the build output and the two places which fail are:

  • SwiftEmitModule normal arm64 Emitting module for Hello (in target 'HelloStatic' from project 'Hello')
  • SwiftCompile normal arm64 Compiling Somethiing.swift (in target 'HelloStatic' from project 'Hello')

A painstaking diff of the output reveals the Dynamic Lib has additional options specified in the Emit:

  • -import-underlying-module
  • -enable-anonymous-context-mangled-names
  • -Xcc -ivfsoverlay
  • -Xcc ${TEMP_DIR}/unextended-module-overlay.yaml

and (in addition to the above) a couple more in the compile:

  • -emit-localized-strings
  • -emit-localized-strings-path ${TEMP_DIR}/Objects-normal/arm64
  • -index-store-path ${SRCROOT}/DerivedData/${PRODUCT_NAME}/Index.noindex/DataStore
  • -index-system-modules

I tried adding some of these to Other Swift Flags, but they generated errors for the static build.

For the "looked promising" -import-underlying-module (this seems to be the crux of the matter):

Underlying Objective-C module 'Hello' not found

For the overlay flags:

Virtual filesystem overlay file '(TMP_DIR)/unextended-module-overlay.yaml' not found

So what exactly is different with the implicit modulemap import that works with a Dynamic framework, but fails for a Static Lib?

Cannot build framework with objective-c static library AND Swift file
 
 
Q