swift-interface-gen is a command-line tool designed to generate clean, compilable Swift interfaces (.swiftinterface and .swift) directly from macOS/iOS Text-Based Stub (.tbd) files.
This tool is primarily used for reverse-engineering and reconstructing the public Swift APIs of private Apple frameworks, allowing developers to link against them and explore their capabilities in standalone test programs.
- Automated Demangling: Leverages the native Swift demangler (
_stdlib_demangle) to accurately reconstruct types, methods, and properties from mangled symbols. - Zero-Config Dynamic Inference: Automatically infers complex multi-parameter generics, dotted protocol namespaces, and concrete types without relying on hardcoded lists.
- Local Framework Bundling: Generates a complete, self-contained
.frameworkstructure (including.swiftmoduleand.swiftinterface) that can be seamlessly passed to the Swift compiler using standard-Fflags. - Mock Executability: Generates safe mock implementations (e.g., empty initializers
{}, safe default return values like[]ornil, andfatalError()for complex logic) so that client code can layout and instantiate types without runtime crash triggers. - Recursive Dependency Resolution: Automatically parses imports from target APIs, resolves target-level cross-module dependencies (e.g.
ModelCatalog->FeatureFlags), and recursively builds dependencies in correct pipeline order usingorchestrate.py. - Dynamic Stub Module Generation: Dynamically detects external types not present in the SDK's Swift interface, generates dedicated mock source files for them (e.g.
FeatureFlags.swift), and compiles them into local mock frameworks. - Assembly-Based Symbol Alignment (100% Matching): Resolves missing expected symbols (like re-exported or inlined compiler symbols) by assembling stub sources (
stubs_{name}.s) to object code (stubs_{name}.o) usingclangand linking via-Xlinkerwith-exported_symbols_list. This achieves 100% symbol alignment with 0 missing/extra symbols, preventing Swift compiler IR-gen collisions.
The easiest way to use the tool is via the included orchestrate.py tool, which automates the entire compilation and verification process.
-
Create a Test File (e.g.,
test_TokenGenerationCore.swift):import TokenGenerationCore import Foundation print("Starting TokenGenerationCore test") // Reference layouts and APIs from TokenGenerationCore print("Types verified")
-
Run the Orchestration Tool: Pass the target framework name and your test file.
./orchestrate.py TokenGenerationCore test_TokenGenerationCore.swift
The script will automatically:
- Parse
TokenGenerationCore.tbdto extract its dependencies and stubs. - Recursively resolve and build all dependencies (like
ModelCatalog) and stub modules (likePromptKit) underLocalFrameworks/. - Compile
TokenGenerationCore's.swiftinterfaceand its dynamic library with assembly stubs for 100% exact symbol matching. - Compile and execute your test binary using the built local frameworks.
Note
Automated Dependency Ordering:
The orchestrate.py pipeline completely replaces automate.sh. It resolves the entire dependency graph (including nested targets like CoreAICompiler -> CoreAICommon) and builds them automatically in the correct topology. You do not need to build dependency frameworks manually.
If you prefer to run the steps manually:
cd SwiftInterfaceGen/Sources/SwiftInterfaceGen
clang++ -O3 -std=c++11 -c DemangleWrapper.cpp -o DemangleWrapper.o
swiftc -parse-as-library main.swift Parser.swift Model.swift Config.swift String+RegexFree.swift TreeNode.swift DemangleWrapper.o -lc++ -o ../../../swift-interface-gen
cd ../../.././swift-interface-gen /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/System/Library/PrivateFrameworks/ModelCatalog.framework/ModelCatalog.tbd > ModelCatalogInterface.swiftmkdir -p LocalFrameworks/ModelCatalog.framework/Modules/ModelCatalog.swiftmodule
# Emit Module Interface
swiftc -emit-module -module-name ModelCatalog ModelCatalogInterface.swift \
-enable-experimental-feature NonescapableTypes -enable-experimental-feature Lifetimes \
-enable-library-evolution -language-mode 6 -F LocalFrameworks \
-sdk /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk \
-emit-module-interface-path LocalFrameworks/ModelCatalog.framework/Modules/ModelCatalog.swiftmodule/arm64-apple-macos.swiftinterface \
-o LocalFrameworks/ModelCatalog.framework/Modules/ModelCatalog.swiftmodule/arm64-apple-macos.swiftmodule
# Compile Mock Dynamic Library
swiftc -emit-library -o LocalFrameworks/ModelCatalog.framework/ModelCatalog \
-enable-experimental-feature NonescapableTypes -enable-experimental-feature Lifetimes \
ModelCatalogInterface.swift -enable-library-evolution -module-name ModelCatalog \
-F LocalFrameworks -sdk /Library/Developer/CommandLineTools/SDKs/MacOSX.sdkswiftc -F LocalFrameworks test_ModelCatalog.swift \
-enable-experimental-feature NonescapableTypes -enable-experimental-feature Lifetimes \
-sdk /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk -language-mode 6 \
-o test_run
# Run against the local framework
DYLD_FRAMEWORK_PATH=LocalFrameworks ./test_runThis tool has been fully refactored to achieve 100% dynamic inference. There is no longer a need for config.json or manual override files.
All generic parameter counts, namespaces, type definitions, protocol conformances, and concrete/protocol type classifications are inferred automatically at runtime by scanning and parsing the demangled binary symbols and referencing the SDK's metadata headers.
For backward compatibility, the --config parameter is still accepted but operates as a no-op.
When compiling client code against the reconstructed mock framework, the Swift compiler links against the generated mock dynamic library (e.g., LocalFrameworks/ModelCatalog.framework/ModelCatalog).
At runtime, the dynamic linker (dyld) resolves this dependency based on environment variables and library paths. This provides two distinct execution modes:
To run your test program using only the mock implementation (e.g., verifying type layouts, initializing classes without triggering system side effects or network requests):
DYLD_FRAMEWORK_PATH=LocalFrameworks ./test_run- How it works:
dyldprioritizesLocalFrameworksand loads your mock dynamic library. Methods invoked on mock instances will return mock default values (likenilor empty arrays) or trigger afatalError()if they contain complex logic, without executing system code.
To run your test program against the actual system implementation (e.g., calling live XPC services or secure system daemons):
./test_run- How it works: Without
DYLD_FRAMEWORK_PATH=LocalFrameworks,dyldfalls back to the system's dynamic linker search paths and loads the real system framework from/System/Library/PrivateFrameworks/.... - Benefit: This allows you to check types and compile your code locally against the mock framework without needing private framework headers, while executing the actual system implementation at runtime.
When writing integration tests that actually invoke methods requiring XPC services or secure enclaves (rather than just inspecting types), you must link against the real system framework and sign your binary with private entitlements.
codesign --force --options runtime --entitlements ModelCatalog.entitlements -s - test_integration_runamfi_get_out_of_my_way=1 boot argument). On standard macOS configurations, the kernel will immediately kill (Killed: 9) binaries attempting to impersonate system entitlements.