Skip to content

Commit d698682

Browse files
committed
docs: rewrite readme for clarity and scannability
1 parent d47e4f5 commit d698682

1 file changed

Lines changed: 38 additions & 91 deletions

File tree

README.md

Lines changed: 38 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -1,63 +1,42 @@
11
# GenerateInterface
22

3-
A command-line tool that automatically generates Swift interface modules from compiled module interfaces using SourceKit and SwiftSyntax.
3+
Auto-generate Swift interface modules from compiled modules using SourceKit and SwiftSyntax - eliminating unnecessary recompilation in modular iOS codebases.
44

5-
## The Problem
5+
## Why?
66

7-
In large modular iOS codebases, modules often depend on each other's full implementations even when they only need access to the public API. This means changing an implementation detail in one module can trigger recompilation of all dependent modules, slowing down builds significantly.
7+
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.
88

9-
**Interface modules** solve this by extracting a module's public API surface into a separate, lightweight module. Dependents import the interface module instead of the full implementation, so implementation changes no longer cascade rebuilds across the dependency graph.
9+
Maintaining these by hand is tedious and error-prone. This tool automates the entire process.
1010

11-
Creating and maintaining these interface modules by hand is tedious and error-prone. This tool automates the process.
11+
## What it does
1212

13-
## What It Does
14-
15-
Given a module name and its compiler arguments, the tool:
16-
17-
1. **Extracts the module interface** via SourceKit (the same engine Xcode uses for code completion and indexing)
18-
2. **Rewrites the interface** using SwiftSyntax - strips private/internal imports (prefixed with `_`), removes `some`/`any` type erasure wrappers, simplifies member type syntax, filters out builder classes, and merges duplicate extensions
19-
3. **Replaces declarations** with their original source versions when available (preserving doc comments, attributes, and formatting)
20-
4. **Creates the interface module directory** with a `Sources/` folder
21-
5. **Rewrites import statements** across the codebase (`import Module` -> `import ModuleInterface`)
22-
6. **Updates Project.swift** to register the new interface module with the correct dependencies (Tuist-specific)
13+
- Extracts the public module interface via SourceKit (`source.request.editor.open.interface`)
14+
- Rewrites the interface with SwiftSyntax - strips internal imports, removes `some`/`any` wrappers, simplifies types, filters builder classes, merges duplicate extensions
15+
- Replaces declarations with original source versions when available (preserving doc comments, attributes, formatting)
16+
- Creates the interface module directory with `Sources/`
17+
- Rewrites `import Module` to `import ModuleInterface` across the codebase
18+
- Updates `Project.swift` to register the new module with correct dependencies (Tuist-specific)
2319

2420
## Requirements
2521

26-
- macOS 13+
27-
- Xcode (for SourceKit)
28-
- Swift 5.10+
22+
- macOS 13+, Swift 5.10+, Xcode (for SourceKit)
2923

30-
## Installation
24+
## Quick start
3125

3226
```bash
33-
# Build from source
3427
swift build -c release
35-
36-
# Or use the Makefile (builds for arm64)
37-
make build
28+
# or: make build
3829
```
3930

40-
## Try it out
41-
42-
A sample project is included to test the tool without needing a real Xcode workspace or Tuist:
31+
A sample project is included:
4332

4433
```bash
4534
cd SampleProject
46-
47-
# Preview the generated interface (dry run, no files written)
48-
./run-sample.sh
49-
50-
# Run the full pipeline (creates interface module, rewrites Project.swift and imports)
51-
./run-sample.sh --write
35+
./run-sample.sh # dry run - preview generated interface
36+
./run-sample.sh --write # full pipeline - creates interface module, rewrites imports
5237
```
5338

54-
The script builds the tool, compiles a sample Swift module via SPM, extracts compiler arguments, and runs the tool. Use `--write` to execute the full pipeline, or omit it to preview with `--print-only`.
55-
56-
After running with `--write`, you'll see:
57-
- `libraries/business/UserProfileInterface/` created with `Sources/`
58-
- `Project.swift` updated with the new interface module declaration
59-
60-
To reset the sample project after a full run:
39+
Reset after a full run:
6140

6241
```bash
6342
git checkout SampleProject/Project.swift
@@ -69,7 +48,6 @@ rm -rf SampleProject/libraries/business/UserProfileInterface
6948
### Direct invocation
7049

7150
```bash
72-
# SourceKit requires the Xcode toolchain frameworks in the dynamic library path
7351
export DYLD_FRAMEWORK_PATH="$(xcode-select -p)/Toolchains/XcodeDefault.xctoolchain/usr/lib"
7452

7553
./generateInterface \
@@ -80,88 +58,57 @@ export DYLD_FRAMEWORK_PATH="$(xcode-select -p)/Toolchains/XcodeDefault.xctoolcha
8058
```
8159

8260
**Arguments:**
83-
- `projectSwiftPath` - path to the Tuist `Project.swift` file
84-
- `moduleName` - name of the module to generate an interface for
61+
- `projectSwiftPath` - Tuist `Project.swift` file
62+
- `moduleName` - module to generate an interface for
8563
- `modulesPath` - root directory containing all modules
86-
- `compilerArgsPath` - path to a file containing Swift compiler arguments (one per line)
64+
- `compilerArgsPath` - file with Swift compiler arguments (one per line)
8765

8866
**Flags:**
89-
- `--print-only` - preview the generated interface without writing any files
67+
- `--print-only` - preview generated interface without writing files
9068

9169
### Getting compiler arguments
9270

93-
The tool needs Swift compiler arguments to invoke SourceKit. You can extract these from Xcode's build settings:
71+
Extract from Xcode build settings:
9472

9573
```bash
96-
# Dump build settings as JSON
9774
xcodebuild -workspace App.xcworkspace \
98-
-scheme "MyModule" \
99-
-arch arm64 \
100-
-sdk iphonesimulator \
101-
-configuration "Debug" \
75+
-scheme "MyModule" -arch arm64 \
76+
-sdk iphonesimulator -configuration "Debug" \
10277
-showBuildSettingsForIndex -json 2>/dev/null > build_settings.json
10378
```
10479

105-
The JSON output is keyed by module name, then by source file path. Each entry contains a `swiftASTCommandArguments` array. Extract those arguments, remove `-module-name` and the module name itself, and write one argument per line to a file:
80+
Parse `swiftASTCommandArguments`, removing `-module-name` and the module name:
10681

10782
```bash
108-
# Using jq as an example (the Ruby wrapper does this automatically)
109-
jq -r '
110-
.MyModule | to_entries[0].value.swiftASTCommandArguments[]
111-
' build_settings.json \
83+
jq -r '.MyModule | to_entries[0].value.swiftASTCommandArguments[]' build_settings.json \
11284
| grep -v -e '-module-name' -e '^MyModule$' \
11385
> compiler-args.txt
11486
```
11587

116-
### Verifying it works
117-
118-
Use `--print-only` to preview the generated interface without writing any files:
119-
120-
```bash
121-
./generateInterface \
122-
"/path/to/Project.swift" \
123-
"MyModule" \
124-
"/path/to/modules" \
125-
"/path/to/compiler-args.txt" \
126-
--print-only
127-
```
128-
129-
This prints the rewritten module interface to stdout so you can inspect it before committing to file changes.
130-
131-
### With the Ruby wrapper
88+
### Ruby wrapper
13289

133-
The included `generateInterface.rb` script automates compiler argument extraction from Xcode build settings:
90+
The included `generateInterface.rb` automates compiler argument extraction:
13491

13592
```bash
13693
ruby generateInterface.rb MyModule
13794
ruby generateInterface.rb MyModule --print-only
13895
```
13996

140-
The wrapper:
141-
1. Runs `xcodebuild -showBuildSettingsForIndex` for the module's scheme
142-
2. Extracts Swift compiler arguments from the build settings
143-
3. Caches build settings to avoid repeated Xcode calls
144-
4. Invokes the Swift tool with the extracted arguments
145-
146-
Configure the wrapper by setting these environment variables:
147-
- `WORKSPACE` - Xcode workspace name (default: `App.xcworkspace`)
148-
- `TOOL_PATH` - path to the built `generateInterface` binary (default: `tools/generateInterface`)
149-
- `MODULES_PATH` - path to the modules directory (default: `libraries`)
150-
151-
## How It Works
97+
The wrapper runs `xcodebuild -showBuildSettingsForIndex`, extracts and caches compiler arguments, then invokes the tool.
15298

153-
The core pipeline uses two Apple frameworks:
99+
Environment variables: `WORKSPACE` (default: `App.xcworkspace`), `TOOL_PATH` (default: `tools/generateInterface`), `MODULES_PATH` (default: `libraries`).
154100

155-
- **SourceKittenFramework** - sends a `source.request.editor.open.interface` request to Xcode's SourceKit daemon, which returns the full public interface of a compiled module (the same text you see in Xcode's "Generated Interface" view)
156-
- **SwiftSyntax** - parses and rewrites the generated interface at the AST level, ensuring transformations are structurally correct rather than fragile string replacements
101+
## How it works
157102

158-
The `ProjectRewriter` manipulates Tuist's `Module(...)` declarations via SwiftSyntax to add the new interface module definition, update dependency lists, and wire up test support targets.
103+
- **SourceKit** returns the full public interface of a compiled module (same as Xcode's "Generated Interface" view)
104+
- **SwiftSyntax** parses and rewrites the interface at the AST level - structurally correct transformations, not string replacements
105+
- **ProjectRewriter** manipulates Tuist `Module(...)` declarations to register the new interface module and update dependencies
159106

160107
## Limitations
161108

162-
- The `Project.swift` rewriting is specific to a Tuist project structure using `Module(...)` declarations with `kind`, `moduleDependencies`, and `features` parameters. You may need to adapt `ProjectRewriter.swift` for your project's conventions.
163-
- The Ruby wrapper assumes an Xcode workspace-based project. Adjust if using a different build system.
164-
- Builder class filtering (`classes inheriting from Builder are excluded`) is project-specific behavior.
109+
- `Project.swift` rewriting is specific to Tuist projects using `Module(...)` declarations with `kind`, `moduleDependencies`, and `features` parameters - adapt `ProjectRewriter.swift` for other conventions
110+
- Ruby wrapper assumes an Xcode workspace-based project
111+
- Builder class filtering is project-specific behavior
165112

166113
## License
167114

0 commit comments

Comments
 (0)