How to Generate a Swift Interface from Objective-C Header via Command Line?

I’m trying to automate the process of generating a Swift interface from an Objective-C header file using the command line.

Note: In the example below, Foo is intentionally not defined to demonstrate the errors I’m seeing.

Given this header file:
#import <Foundation/Foundation.h>

@interface Bar : NSObject

- (nullable Foo *)getFoo;

@end

In Xcode, I can open the header, click “Counterparts”, then select the Swift 5 interface, and get something like:

import Foundation

open class Bar : NSObject {

 open func getFoo() -> Any!

}

This is exactly what I want, but I’d like to automate it from the command line.

What I’ve Tried:

I’m using the following command:

/Applications/Xcode_26.0.0.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/swift-synthesize-interface \

-sdk "$(xcrun --sdk iphoneos --show-sdk-path)" \

-target arm64-apple-ios26.0 \

-module-name Module \

-I "$PWD/Module" \

-Xcc -fmodule-map-file=$PWD/Module/module.modulemap \

-o "$PWD/generated-interface.swift"

Where module.modulemap points to the header above.

The problem:
I get a lot of compiler errors like:

:1:9: note: in file included from :1:

import "objc2swift.h"

    ^

/path/to/your/project/objc2swift.h:4:13: error: expected a type

- (nullable Foo *)getFoo;

            ^

:0: error: could not build Objective-C module 'Module'

Couldn't load module 'Module' in the current SDK and search paths.

Other Options

I’ve also looked into SourceKit LSP but it doesn’t have support for source.request.editor.open.interface.header, looks like only SourceKit itself has that.

Question

Is there a way to generate the Swift interface from the command line, in an error-friendly way?

Any advice would be greatly appreciated!

I think the problem here is that the header isn't complete; Foo isn't declared anywhere in this file or in any files that it imports, so the error reported by ClangImporter is correct. If I add a forward declaration to your example:

#import <Foundation/Foundation.h>

@class Foo;

@interface Bar : NSObject

- (nullable Foo *)getFoo;

@end

Then the command line you provided generates the expected output:

import Foundation

open class Bar : NSObject {

    open func getFoo() -> Foo?
}

Why does this work in Xcode, then? I'm not quite as familiar with the SourceKit request that synthesizes interfaces, but I assume that it uses a mode that is more forgiving of ClangImporter errors and recovers from them. In your case, you can see that it's doing so by replacing the missing type with Any.

1 Like

Thanks for the explanation!

Do you know if swift-synthesize-interface has a "forgiving" mode like you mentioned, where it substitutes missing types with Any instead of failing? Or is there another way to get this behavior from the command line?

None that I'm aware of. When I implemented the feature, the goal was to make it possible to synthesize the interfaces of C modules that were already submitted to a code base and were known to build correctly, since it was meant for use cases outside of IDEs.

1 Like