Skip to content

Commit 55b01ae

Browse files
alexey1312claude
andcommitted
feat: add Windows CI support with Swift 6.3 and swift-jinja migration
- Add Windows build to ci.yml and release.yml using Swift 6.3 - Replace Stencil with swift-jinja for cross-platform compatibility - Add JinjaSupport shared module for template rendering - Make XcodeProj conditional (#if !os(Windows)) in Package.swift - Wrap XcodeProjectWriter in #if canImport(XcodeProj) (6 export files) - Use #if canImport(FoundationNetworking) instead of #if os(Linux) - Pin swift-resvg to 0.45.1-swift.15 (Windows artifactbundle) - Delete standalone windows-test.yml - Update docs: CLAUDE.md, linux-compat.md, README.md Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent a932958 commit 55b01ae

35 files changed

Lines changed: 397 additions & 290 deletions

‎.claude/rules/linux-compat.md‎

Lines changed: 56 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
# Linux Compatibility
1+
# Linux & Windows Compatibility
22

3-
This rule covers Linux-specific workarounds and differences from macOS.
3+
This rule covers Linux- and Windows-specific workarounds and differences from macOS.
44

5-
The project builds on Linux (Ubuntu 22.04 LTS / Jammy). Key differences from macOS:
5+
The project builds on Linux (Ubuntu 22.04 LTS / Jammy) and Windows (Swift 6.3). Key differences from macOS:
66

77
## Required Import for Networking
88

@@ -44,8 +44,56 @@ func testSomePngOperation() throws {
4444

4545
## Platform-Specific Features
4646

47-
| Feature | macOS | Linux |
48-
| ------------ | ------------ | ------------------------ |
49-
| HEIC encoding| ImageIO | Falls back to PNG |
50-
| libpng tests | Full support | Build tests first |
51-
| Foundation | Full | Some APIs missing/broken |
47+
| Feature | macOS | Linux | Windows |
48+
| ------------ | ------------ | ------------------------ | ------------------------ |
49+
| HEIC encoding| ImageIO | Falls back to PNG | Falls back to PNG |
50+
| libpng tests | Full support | Build tests first | Not tested |
51+
| Foundation | Full | Some APIs missing/broken | Some APIs missing/broken |
52+
| XcodeProj | Full | Full | Not available |
53+
| Swift version| 6.2+ | 6.2+ | 6.3 required |
54+
55+
## Windows Support
56+
57+
### Swift Version
58+
59+
Windows requires Swift 6.3 (development snapshot) due to `swift-resvg` artifactbundle compatibility.
60+
CI uses `compnerd/gha-setup-swift@v0.3.0` with `swift-6.3-branch`.
61+
62+
### Conditional Dependencies (Package.swift)
63+
64+
`#if` inside array literals does NOT work in SPM Package.swift. Use variable + `#if` append pattern:
65+
66+
```swift
67+
var packageDependencies: [Package.Dependency] = [...]
68+
#if !os(Windows)
69+
packageDependencies.append(.package(url: "https://github.com/tuist/XcodeProj.git", from: "8.27.0"))
70+
#endif
71+
```
72+
73+
### XcodeProj Exclusion
74+
75+
XcodeProj is Apple-only (depends on PathKit/AEXML). On Windows:
76+
- Dependency excluded via `#if !os(Windows)` in Package.swift
77+
- `XcodeProjectWriter` wrapped in `#if canImport(XcodeProj)` (6 files in Export/)
78+
- Xcode project manipulation silently skipped on Windows
79+
80+
### FoundationNetworking / FoundationXML
81+
82+
Use `#if canImport()` instead of `#if os(Linux)` — covers both Linux and Windows:
83+
84+
```swift
85+
#if canImport(FoundationNetworking)
86+
import FoundationNetworking
87+
#endif
88+
89+
#if canImport(FoundationXML)
90+
import FoundationXML
91+
#endif
92+
```
93+
94+
### SPM Artifactbundle on Windows
95+
96+
SPM on Windows has library naming differences:
97+
- Unix linkers auto-prepend `lib` prefix (`-lresvg` finds `libresvg.a`)
98+
- Windows `lld-link` does NOT prepend prefix (`resvg.lib` must exist as-is)
99+
- Swift 6.3 allows `.lib` files without `lib` prefix in artifactbundle info.json

‎.github/workflows/ci.yml‎

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,3 +125,28 @@ jobs:
125125
- name: Run tests
126126
# Build tests separately to avoid hangs from concurrent build + test execution
127127
run: swift test --skip-build --parallel 2>&1 | xcsift
128+
129+
build-windows:
130+
name: Build (Windows)
131+
runs-on: windows-latest
132+
needs: lint
133+
steps:
134+
- uses: actions/checkout@v6
135+
136+
- name: Install Swift
137+
uses: compnerd/gha-setup-swift@v0.3.0
138+
with:
139+
branch: swift-6.3-branch
140+
tag: 6.3-DEVELOPMENT-SNAPSHOT-2026-02-21-a
141+
142+
- name: Swift version
143+
run: swift --version
144+
145+
- name: Resolve dependencies
146+
run: swift package resolve
147+
148+
- name: Build (Debug)
149+
run: swift build
150+
151+
- name: Build (Release)
152+
run: swift build -c release

‎.github/workflows/release.yml‎

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@ jobs:
2828
archive-name: exfig-linux-x64
2929
container: swift:6.2-jammy
3030
extra-flags: --static-swift-stdlib -Xlinker -lcurl -Xlinker -lxml2 -Xlinker -lssl -Xlinker -lcrypto -Xlinker -lz
31+
- os: windows-latest
32+
platform: windows-x64
33+
build-path: x86_64-unknown-windows-msvc/release
34+
archive-name: exfig-windows-x64
3135

3236

3337
runs-on: ${{ matrix.os }}
@@ -45,6 +49,13 @@ jobs:
4549
with:
4650
lfs: true
4751

52+
- name: Install Swift (Windows)
53+
if: matrix.platform == 'windows-x64'
54+
uses: compnerd/gha-setup-swift@v0.3.0
55+
with:
56+
branch: swift-6.3-branch
57+
tag: 6.3-DEVELOPMENT-SNAPSHOT-2026-02-21-a
58+
4859
- name: Select Xcode 26.1 (macOS)
4960
if: matrix.platform == 'macos'
5061
run: sudo xcode-select -s /Applications/Xcode_26.1.1.app/Contents/Developer
@@ -54,7 +65,8 @@ jobs:
5465
run: |
5566
apt-get install -y libcurl4-openssl-dev libxml2-dev libssl-dev
5667
57-
- name: Set version from tag
68+
- name: Set version from tag (Unix)
69+
if: matrix.platform != 'windows-x64'
5870
run: |
5971
VERSION="${GITHUB_REF#refs/tags/}"
6072
FILE="Sources/ExFigCLI/ExFigCommand.swift"
@@ -64,6 +76,14 @@ jobs:
6476
sed -i "s/static let version = \".*\"/static let version = \"$VERSION\"/" "$FILE"
6577
fi
6678
79+
- name: Set version from tag (Windows)
80+
if: matrix.platform == 'windows-x64'
81+
shell: pwsh
82+
run: |
83+
$version = $env:GITHUB_REF -replace '^refs/tags/', ''
84+
$file = "Sources/ExFigCLI/ExFigCommand.swift"
85+
(Get-Content $file) -replace 'static let version = ".*"', "static let version = `"$version`"" | Set-Content $file
86+
6787
- name: Build release binary (macOS)
6888
if: matrix.platform == 'macos'
6989
run: |
@@ -83,6 +103,10 @@ jobs:
83103
if: matrix.platform == 'linux-x64'
84104
run: swift build -c release ${{ matrix.extra-flags }}
85105

106+
- name: Build release binary (Windows)
107+
if: matrix.platform == 'windows-x64'
108+
run: swift build -c release
109+
86110
- name: Create release archive (macOS)
87111
if: matrix.platform == 'macos'
88112
run: |
@@ -109,6 +133,20 @@ jobs:
109133
cp LICENSE dist/
110134
cd dist && tar -czf ../${{ matrix.archive-name }}.tar.gz -- *
111135
136+
- name: Create release archive (Windows)
137+
if: matrix.platform == 'windows-x64'
138+
shell: pwsh
139+
run: |
140+
New-Item -ItemType Directory -Force -Path dist
141+
Copy-Item ".build/${{ matrix.build-path }}/exfig.exe" "dist/ExFig.exe"
142+
Copy-Item ".build/${{ matrix.build-path }}/exfig_AndroidExport.resources" "dist/" -Recurse -ErrorAction SilentlyContinue
143+
Copy-Item ".build/${{ matrix.build-path }}/exfig_XcodeExport.resources" "dist/" -Recurse -ErrorAction SilentlyContinue
144+
Copy-Item ".build/${{ matrix.build-path }}/exfig_FlutterExport.resources" "dist/" -Recurse -ErrorAction SilentlyContinue
145+
Copy-Item ".build/${{ matrix.build-path }}/exfig_WebExport.resources" "dist/" -Recurse -ErrorAction SilentlyContinue
146+
Copy-Item ".build/${{ matrix.build-path }}/exfig_ExFigCLI.resources" "dist/" -Recurse -ErrorAction SilentlyContinue
147+
Copy-Item "LICENSE" "dist/"
148+
Compress-Archive -Path "dist/*" -DestinationPath "${{ matrix.archive-name }}.zip"
149+
112150
- name: Upload artifact
113151
uses: actions/upload-artifact@v4
114152
with:
@@ -200,6 +238,7 @@ jobs:
200238
files: |
201239
artifacts/exfig-macos/exfig-macos.zip
202240
artifacts/exfig-linux-x64/exfig-linux-x64.tar.gz
241+
artifacts/exfig-windows-x64/exfig-windows-x64.zip
203242
.pkl-out/exfig@*/*
204243
205244
update-homebrew:

‎.github/workflows/windows-test.yml‎

Lines changed: 0 additions & 36 deletions
This file was deleted.

‎CLAUDE.md‎

Lines changed: 30 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -106,21 +106,21 @@ pkl eval --format json <file.pkl> # Package URI requires published package
106106

107107
## Project Context
108108

109-
| Aspect | Details |
110-
| --------------- | ---------------------------------------------------------------------------------- |
111-
| Language | Swift 6.2, macOS 13.0+ |
112-
| Package Manager | Swift Package Manager |
113-
| CLI Framework | swift-argument-parser |
114-
| Config Format | PKL (Programmable, Scalable, Safe) |
115-
| Templates | Jinja2 (swift-jinja) |
116-
| Required Env | `FIGMA_PERSONAL_TOKEN` |
117-
| Config Files | `exfig.pkl` (PKL configuration) |
118-
| Tooling | mise (`./bin/mise` self-contained, no global install needed) |
119-
| Platforms | macOS 13+ (primary), Linux/Ubuntu 22.04 (CI) - see `.claude/rules/linux-compat.md` |
109+
| Aspect | Details |
110+
| --------------- | -------------------------------------------------------------------------------------------------- |
111+
| Language | Swift 6.2, macOS 13.0+ |
112+
| Package Manager | Swift Package Manager |
113+
| CLI Framework | swift-argument-parser |
114+
| Config Format | PKL (Programmable, Scalable, Safe) |
115+
| Templates | Jinja2 (swift-jinja) |
116+
| Required Env | `FIGMA_PERSONAL_TOKEN` |
117+
| Config Files | `exfig.pkl` (PKL configuration) |
118+
| Tooling | mise (`./bin/mise` self-contained, no global install needed) |
119+
| Platforms | macOS 13+ (primary), Linux/Ubuntu 22.04, Windows (Swift 6.3) - see `.claude/rules/linux-compat.md` |
120120

121121
## Architecture
122122

123-
Twelve modules in `Sources/`:
123+
Thirteen modules in `Sources/`:
124124

125125
| Module | Purpose |
126126
| --------------- | --------------------------------------------------------- |
@@ -137,6 +137,7 @@ Twelve modules in `Sources/`:
137137
| `FlutterExport` | Flutter export (Dart code, SVG/PNG assets) |
138138
| `WebExport` | Web/React export (CSS variables, JSX icons) |
139139
| `SVGKit` | SVG parsing, ImageVector/VectorDrawable generation |
140+
| `JinjaSupport` | Shared Jinja2 template rendering helpers |
140141

141142
**Data flow:** CLI -> PKL config parsing -> FigmaAPI fetch -> ExFigCore processing -> Platform plugin -> Export module -> File write
142143

@@ -326,22 +327,22 @@ NooraUI.formatLink("url", useColors: true) // underlined primary
326327

327328
## Dependencies
328329

329-
| Package | Version | Purpose |
330-
| --------------------- | ------- | ------------------------------- |
331-
| swift-argument-parser | 1.5.0+ | CLI framework |
332-
| swift-collections | 1.2.x | Ordered collections |
333-
| swift-jinja | 2.0.0+ | Jinja2 template engine |
334-
| XcodeProj | 8.27.0+ | Xcode project manipulation |
335-
| swift-log | 1.6.0+ | Logging |
336-
| Rainbow | 4.2.0+ | Terminal colors |
337-
| libwebp | 1.4.1+ | WebP encoding |
338-
| libpng | 1.6.45+ | PNG decoding |
339-
| swift-custom-dump | 1.3.0+ | Test assertions |
340-
| Noora | 0.54.0+ | Terminal UI design system |
341-
| swift-resvg | 0.45.1 | SVG parsing/rendering |
342-
| swift-docc-plugin | 1.4.5+ | DocC documentation |
343-
| swift-yyjson | 0.5.0+ | High-performance JSON codec |
344-
| pkl-swift | 0.7.2+ | PKL config evaluation & codegen |
330+
| Package | Version | Purpose |
331+
| --------------------- | --------------- | -------------------------------------------------- |
332+
| swift-argument-parser | 1.5.0+ | CLI framework |
333+
| swift-collections | 1.2.x | Ordered collections |
334+
| swift-jinja | 2.0.0+ | Jinja2 template engine |
335+
| XcodeProj | 8.27.0+ | Xcode project manipulation (macOS/Linux only) |
336+
| swift-log | 1.6.0+ | Logging |
337+
| Rainbow | 4.2.0+ | Terminal colors |
338+
| libwebp | 1.4.1+ | WebP encoding |
339+
| libpng | 1.6.45+ | PNG decoding |
340+
| swift-custom-dump | 1.3.0+ | Test assertions |
341+
| Noora | 0.54.0+ | Terminal UI design system |
342+
| swift-resvg | 0.45.1-swift.15 | SVG parsing/rendering (Windows requires Swift 6.3) |
343+
| swift-docc-plugin | 1.4.5+ | DocC documentation |
344+
| swift-yyjson | 0.5.0+ | High-performance JSON codec |
345+
| pkl-swift | 0.7.2+ | PKL config evaluation & codegen |
345346

346347
## Troubleshooting
347348

@@ -379,7 +380,7 @@ Contextual documentation is in `.claude/rules/`:
379380
| `cache-granular.md` | Experimental granular node-level cache |
380381
| `api-reference.md` | Figma API endpoints, response mapping |
381382
| `gotchas.md` | Swift 6 concurrency, SwiftLint, rate limits |
382-
| `linux-compat.md` | Linux-specific workarounds |
383+
| `linux-compat.md` | Linux/Windows platform workarounds |
383384
| `testing-workflow.md` | Testing guidelines, commit format |
384385
| `pkl-codegen.md` | pkl-swift generated types, enum bridging, codegen |
385386
| `Sources/*/CLAUDE.md` | Module-specific patterns, modification checklists |

‎Package.resolved‎

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)