Auto-generate Swift interface modules from compiled modules using SourceKit and SwiftSyntax - eliminating unnecessary recompilation in modular iOS codebases.
In large modular iOS projects, changing an implementation detail in one module triggers recompilation of every dependent module. Interface modules break this chain by exposing only the public API surface - dependents import the lightweight interface instead of the full implementation.
Maintaining these by hand is tedious and error-prone. This tool automates the entire process.
- Extracts the public module interface via SourceKit (
source.request.editor.open.interface) - Rewrites the interface with SwiftSyntax - strips internal imports, removes
some/anywrappers, simplifies types, filters builder classes, merges duplicate extensions - Replaces declarations with original source versions when available (preserving doc comments, attributes, formatting)
- Creates the interface module directory with
Sources/ - Rewrites
import Moduletoimport ModuleInterfaceacross the codebase - Updates
Project.swiftto register the new module with correct dependencies (Tuist-specific)
- macOS 13+, Swift 5.10+, Xcode (for SourceKit)
swift build -c release
# or: make buildA sample project is included:
cd SampleProject
./run-sample.sh # dry run - preview generated interface
./run-sample.sh --write # full pipeline - creates interface module, rewrites importsReset after a full run:
git checkout SampleProject/Project.swift
rm -rf SampleProject/libraries/business/UserProfileInterfaceexport DYLD_FRAMEWORK_PATH="$(xcode-select -p)/Toolchains/XcodeDefault.xctoolchain/usr/lib"
./generateInterface \
"/path/to/Project.swift" \
"MyModule" \
"/path/to/modules" \
"/path/to/compiler-args.txt"Arguments:
projectSwiftPath- TuistProject.swiftfilemoduleName- module to generate an interface formodulesPath- root directory containing all modulescompilerArgsPath- file with Swift compiler arguments (one per line)
Flags:
--print-only- preview generated interface without writing files
Extract from Xcode build settings:
xcodebuild -workspace App.xcworkspace \
-scheme "MyModule" -arch arm64 \
-sdk iphonesimulator -configuration "Debug" \
-showBuildSettingsForIndex -json 2>/dev/null > build_settings.jsonParse swiftASTCommandArguments, removing -module-name and the module name:
jq -r '.MyModule | to_entries[0].value.swiftASTCommandArguments[]' build_settings.json \
| grep -v -e '-module-name' -e '^MyModule$' \
> compiler-args.txtThe included generateInterface.rb automates compiler argument extraction:
ruby generateInterface.rb MyModule
ruby generateInterface.rb MyModule --print-onlyThe wrapper runs xcodebuild -showBuildSettingsForIndex, extracts and caches compiler arguments, then invokes the tool.
Environment variables: WORKSPACE (default: App.xcworkspace), TOOL_PATH (default: tools/generateInterface), MODULES_PATH (default: libraries).
- SourceKit returns the full public interface of a compiled module (same as Xcode's "Generated Interface" view)
- SwiftSyntax parses and rewrites the interface at the AST level - structurally correct transformations, not string replacements
- ProjectRewriter manipulates Tuist
Module(...)declarations to register the new interface module and update dependencies
Project.swiftrewriting is specific to Tuist projects usingModule(...)declarations withkind,moduleDependencies, andfeaturesparameters - adaptProjectRewriter.swiftfor other conventions- Ruby wrapper assumes an Xcode workspace-based project
- Builder class filtering is project-specific behavior
MIT - see LICENSE.