Skip to content

Commit 59d620f

Browse files
committed
feat: add sample project and improve usage docs
Add a self-contained SampleProject with a minimal SPM-based module for end-to-end testing without needing Xcode or Tuist. Update README with compiler args extraction guide, DYLD_FRAMEWORK_PATH requirement, and a "Try it out" section.
1 parent 9f693ef commit 59d620f

5 files changed

Lines changed: 204 additions & 0 deletions

File tree

README.md

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,25 @@ swift build -c release
3737
make build
3838
```
3939

40+
## Try it out
41+
42+
A sample project is included to test the tool end-to-end without needing a real Xcode workspace or Tuist:
43+
44+
```bash
45+
cd SampleProject
46+
./run-sample.sh
47+
```
48+
49+
This builds the tool, compiles a sample Swift module via SPM, extracts compiler arguments, and runs the tool with `--print-only` to display the generated interface.
50+
4051
## Usage
4152

4253
### Direct invocation
4354

4455
```bash
56+
# SourceKit requires the Xcode toolchain frameworks in the dynamic library path
57+
export DYLD_FRAMEWORK_PATH="$(xcode-select -p)/Toolchains/XcodeDefault.xctoolchain/usr/lib"
58+
4559
./generateInterface \
4660
"/path/to/Project.swift" \
4761
"MyModule" \
@@ -58,6 +72,46 @@ make build
5872
**Flags:**
5973
- `--print-only` - preview the generated interface without writing any files
6074

75+
### Getting compiler arguments
76+
77+
The tool needs Swift compiler arguments to invoke SourceKit. You can extract these from Xcode's build settings:
78+
79+
```bash
80+
# Dump build settings as JSON
81+
xcodebuild -workspace App.xcworkspace \
82+
-scheme "MyModule" \
83+
-arch arm64 \
84+
-sdk iphonesimulator \
85+
-configuration "Debug" \
86+
-showBuildSettingsForIndex -json 2>/dev/null > build_settings.json
87+
```
88+
89+
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:
90+
91+
```bash
92+
# Using jq as an example (the Ruby wrapper does this automatically)
93+
jq -r '
94+
.MyModule | to_entries[0].value.swiftASTCommandArguments[]
95+
' build_settings.json \
96+
| grep -v -e '-module-name' -e '^MyModule$' \
97+
> compiler-args.txt
98+
```
99+
100+
### Verifying it works
101+
102+
Use `--print-only` to preview the generated interface without writing any files:
103+
104+
```bash
105+
./generateInterface \
106+
"/path/to/Project.swift" \
107+
"MyModule" \
108+
"/path/to/modules" \
109+
"/path/to/compiler-args.txt" \
110+
--print-only
111+
```
112+
113+
This prints the rewritten module interface to stdout so you can inspect it before committing to file changes.
114+
61115
### With the Ruby wrapper
62116

63117
The included `generateInterface.rb` script automates compiler argument extraction from Xcode build settings:

SampleProject/Project.swift

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import ProjectDescription
2+
3+
extension Module {
4+
static let userProfile = Module(
5+
name: "UserProfile",
6+
kind: .business,
7+
moduleDependencies: [
8+
.core.extensionKit,
9+
.core.oxide
10+
],
11+
features: [
12+
.tests(
13+
moduleDependencies: [.core.typography]
14+
),
15+
.snapshotTests(),
16+
.testSupport(targetDependencies: [
17+
.testSupportTarget(of: .core.ribs)
18+
])
19+
]
20+
)
21+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// swift-tools-version: 5.10
2+
import PackageDescription
3+
4+
let package = Package(
5+
name: "UserProfile",
6+
platforms: [.macOS(.v13)],
7+
products: [
8+
.library(name: "UserProfile", targets: ["UserProfile"]),
9+
],
10+
targets: [
11+
.target(name: "UserProfile"),
12+
]
13+
)
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import Foundation
2+
3+
/// A repository for managing user profiles.
4+
public protocol UserProfileRepository {
5+
func fetchProfile(id: String) async throws -> UserProfile
6+
func updateProfile(_ profile: UserProfile) async throws
7+
}
8+
9+
/// A user profile.
10+
public struct UserProfile: Sendable {
11+
public let id: String
12+
public let name: String
13+
public let email: String
14+
public let createdAt: Date
15+
16+
public init(id: String, name: String, email: String, createdAt: Date) {
17+
self.id = id
18+
self.name = name
19+
self.email = email
20+
self.createdAt = createdAt
21+
}
22+
}
23+
24+
/// Account status for a user profile.
25+
public enum AccountStatus: String, CaseIterable, Sendable {
26+
case active
27+
case inactive
28+
case suspended
29+
}
30+
31+
extension UserProfile {
32+
/// The user's display name, falling back to email.
33+
public var displayName: String {
34+
name.isEmpty ? email : name
35+
}
36+
}
37+
38+
/// Creates a placeholder profile for previews.
39+
public func makePreviewUserProfile() -> UserProfile {
40+
UserProfile(
41+
id: "preview",
42+
name: "Jane Doe",
43+
email: "jane@example.com",
44+
createdAt: Date()
45+
)
46+
}

SampleProject/run-sample.sh

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
#!/bin/bash
2+
set -euo pipefail
3+
4+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
5+
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
6+
MODULE_NAME="UserProfile"
7+
PACKAGE_DIR="$SCRIPT_DIR/libraries/business/UserProfile"
8+
9+
echo "=== Step 1: Build the generateInterface tool ==="
10+
cd "$REPO_ROOT"
11+
swift build -c debug 2>&1 | tail -1
12+
TOOL_PATH="$(swift build -c debug --show-bin-path)/generateInterface"
13+
echo "Tool built at: $TOOL_PATH"
14+
15+
echo ""
16+
echo "=== Step 2: Build the sample UserProfile module ==="
17+
cd "$PACKAGE_DIR"
18+
19+
# Clean and rebuild with verbose output to capture the swiftc invocation
20+
swift package clean 2>/dev/null || true
21+
VERBOSE_OUTPUT=$(swift build -v 2>&1)
22+
23+
echo "Module built."
24+
25+
echo ""
26+
echo "=== Step 3: Extract compiler arguments ==="
27+
28+
# Extract the swiftc line that compiles the UserProfile module
29+
SWIFTC_LINE=$(echo "$VERBOSE_OUTPUT" | grep "swiftc.*-module-name UserProfile " | head -1)
30+
31+
if [ -z "$SWIFTC_LINE" ]; then
32+
echo "ERROR: Could not find swiftc invocation for UserProfile module."
33+
echo "Verbose build output:"
34+
echo "$VERBOSE_OUTPUT"
35+
exit 1
36+
fi
37+
38+
# Extract arguments: remove the swiftc binary path prefix, split into one-per-line,
39+
# then remove -module-name and the module name itself (as the Ruby wrapper does)
40+
ARGS_FILE=$(mktemp /tmp/compiler-args.XXXXXX)
41+
42+
echo "$SWIFTC_LINE" \
43+
| sed 's|^[^ ]*/swiftc ||' \
44+
| tr ' ' '\n' \
45+
| grep -v -e '^-module-name$' -e "^${MODULE_NAME}$" \
46+
| grep -v '^$' \
47+
> "$ARGS_FILE"
48+
49+
echo "Compiler arguments written to: $ARGS_FILE"
50+
echo "$(wc -l < "$ARGS_FILE" | tr -d ' ') arguments extracted."
51+
52+
echo ""
53+
echo "=== Step 4: Run generateInterface with --print-only ==="
54+
55+
# SourceKit requires the Xcode toolchain frameworks to be in the dynamic library path
56+
TOOLCHAIN_LIB="$(xcode-select -p)/Toolchains/XcodeDefault.xctoolchain/usr/lib"
57+
export DYLD_FRAMEWORK_PATH="$TOOLCHAIN_LIB:${DYLD_FRAMEWORK_PATH:-}"
58+
59+
"$TOOL_PATH" \
60+
"$SCRIPT_DIR/Project.swift" \
61+
"$MODULE_NAME" \
62+
"$SCRIPT_DIR/libraries" \
63+
"$ARGS_FILE" \
64+
--print-only
65+
66+
# Clean up
67+
rm -f "$ARGS_FILE"
68+
69+
echo ""
70+
echo "=== Done! ==="

0 commit comments

Comments
 (0)