Skip to content

Commit 5430aa6

Browse files
hellovaiclaudegithub-advanced-security[bot]
authored
feat: Add Windows support for Go BAML client (#2619)
Implements full Windows support for the Go client library, which previously only worked on macOS and Linux. The client uses CGO to dynamically load the Rust-compiled BAML runtime library. Key changes: - Split platform-specific code using Go build tags (lib_unix.go, lib_windows.go) - Implement Windows dynamic loading via LoadLibraryW/GetProcAddress syscalls - Update CI/CD to build Windows CFFI libraries with correct DLL naming - Add Windows-specific path handling and cache directories - Create GitHub Actions workflows for Windows testing - Add comprehensive test coverage for all platforms Platform-specific library naming: - Windows: baml_cffi.dll (no "lib" prefix) - macOS: libbaml_cffi.dylib - Linux: libbaml_cffi.so Note: Cross-compilation from Unix to Windows requires a Windows cross-compiler due to CGO requirements. Native builds on each platform are recommended. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> --------- Signed-off-by: hellovai <vaibhavtheory@gmail.com> Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
1 parent 276f878 commit 5430aa6

13 files changed

Lines changed: 1530 additions & 19 deletions

File tree

‎.github/workflows/build-cli-release.reusable.yaml‎

Lines changed: 10 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -156,32 +156,32 @@ jobs:
156156
157157
${{ env.CARGO }} build --release -p baml_cffi ${{ env.TARGET_FLAGS }}
158158
working-directory: engine
159-
# Skip this step on Windows runners
160-
if: matrix._.os != 'windows-2022'
161159

162160
# Determine CFFI library name and artifact suffix based on OS
163161
- name: Set CFFI Lib Info
164162
id: cffi_info
165-
# Skip this step on Windows runners
166-
if: matrix._.os != 'windows-2022'
167163
shell: bash
168164
run: |
169165
target="${{ matrix._.target }}"
170166
if [[ "${{ matrix._.os }}" == *"windows"* ]]; then
171-
# This case is currently excluded by the 'if' condition, but kept for completeness
172-
base_name="libbaml_cffi.dll"
167+
base_name="baml_cffi.dll" # Note: no "lib" prefix on Windows
173168
suffix="windows"
169+
extension="dll"
174170
elif [[ "${{ matrix._.os }}" == *"macos"* ]]; then
175171
base_name="libbaml_cffi.dylib"
176172
suffix="darwin"
173+
extension="dylib"
177174
else # Linux
178175
base_name="libbaml_cffi.so"
179176
suffix="linux"
177+
extension="so"
178+
fi
179+
# Windows DLLs don't use lib prefix
180+
if [[ "${{ matrix._.os }}" == *"windows"* ]]; then
181+
new_name="baml_cffi-${target}.${extension}"
182+
else
183+
new_name="libbaml_cffi-${target}.${extension}"
180184
fi
181-
# Extract extension
182-
extension="${base_name##*.}"
183-
# Construct new name: libbaml_cffi-${target}.${extension}
184-
new_name="libbaml_cffi-${target}.${extension}"
185185
186186
echo "lib_name=${base_name}" >> $GITHUB_OUTPUT
187187
echo "new_lib_name=${new_name}" >> $GITHUB_OUTPUT
@@ -204,8 +204,6 @@ jobs:
204204
205205
# Rename the CFFI library file
206206
- name: Rename CFFI Library
207-
# Skip this step on Windows runners
208-
if: matrix._.os != 'windows-2022'
209207
shell: bash
210208
run: |
211209
set -x # enable debug printing
@@ -236,8 +234,6 @@ jobs:
236234
# Upload the CFFI library for this specific target
237235
- name: Upload CFFI Library Artifact
238236
uses: actions/upload-artifact@v4
239-
# Skip this step on Windows runners
240-
if: matrix._.os != 'windows-2022'
241237
with:
242238
# Artifact name uses the target name for uniqueness (as decided previously)
243239
name: libbaml-cffi-${{ matrix._.target }}
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
name: Quick Test - Go Windows
2+
3+
on:
4+
workflow_dispatch:
5+
inputs:
6+
debug_enabled:
7+
type: boolean
8+
description: 'Run with debug logging'
9+
required: false
10+
default: true
11+
12+
jobs:
13+
test-windows-quick:
14+
name: Quick Windows Test
15+
runs-on: windows-2022
16+
timeout-minutes: 15
17+
18+
steps:
19+
- name: Checkout code
20+
uses: actions/checkout@v4
21+
22+
- name: Setup Rust
23+
uses: dtolnay/rust-toolchain@stable
24+
with:
25+
toolchain: stable
26+
27+
- name: Setup Go
28+
uses: actions/setup-go@v5
29+
with:
30+
go-version: '1.22'
31+
32+
- name: Setup Protoc (Windows - Quick)
33+
shell: bash
34+
run: |
35+
# Download protoc directly to avoid rate limiting
36+
PROTOC_VERSION="27.1"
37+
curl -LO "https://github.com/protocolbuffers/protobuf/releases/download/v${PROTOC_VERSION}/protoc-${PROTOC_VERSION}-win64.zip"
38+
unzip -q protoc-${PROTOC_VERSION}-win64.zip -d $HOME/.local
39+
echo "$HOME/.local/bin" >> $GITHUB_PATH
40+
rm protoc-${PROTOC_VERSION}-win64.zip
41+
42+
- name: Install protoc-gen-go
43+
run: go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.34.1
44+
shell: bash
45+
46+
- name: Cache Rust build
47+
uses: actions/cache@v4
48+
with:
49+
path: |
50+
engine/target
51+
~/.cargo/registry
52+
~/.cargo/git
53+
key: ${{ runner.os }}-rust-${{ hashFiles('**/Cargo.lock') }}
54+
restore-keys: |
55+
${{ runner.os }}-rust-
56+
57+
- name: Build CFFI Library (Debug mode for speed)
58+
run: cargo build -p baml_cffi
59+
working-directory: engine
60+
shell: bash
61+
62+
- name: Run Windows-specific Go Tests
63+
run: |
64+
$env:BAML_LIBRARY_PATH = "${{ github.workspace }}\engine\target\debug\baml_cffi.dll"
65+
if ("${{ inputs.debug_enabled }}" -eq "true") {
66+
$env:BAML_LOG = "DEBUG"
67+
}
68+
go test -v -run "TestWindows" ./...
69+
working-directory: engine/language_client_go
70+
shell: powershell
71+
72+
- name: Run All Go Tests
73+
if: success()
74+
run: |
75+
$env:BAML_LIBRARY_PATH = "${{ github.workspace }}\engine\target\debug\baml_cffi.dll"
76+
go test -v ./...
77+
working-directory: engine/language_client_go
78+
shell: powershell
79+
80+
- name: Verify DLL Naming
81+
run: |
82+
Write-Host "Checking DLL naming convention..."
83+
$dll = Get-Item "${{ github.workspace }}\engine\target\debug\baml_cffi.dll"
84+
if ($dll.Name -match '^lib') {
85+
Write-Error "Windows DLL should not have 'lib' prefix"
86+
exit 1
87+
}
88+
Write-Host "✓ DLL naming is correct: $($dll.Name)"
89+
shell: powershell
Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
name: Test Go Client Windows Support
2+
3+
permissions:
4+
contents: read
5+
6+
on:
7+
pull_request:
8+
branches: [ canary, main ]
9+
paths:
10+
- 'engine/language_client_go/**'
11+
- 'engine/language_client_cffi/**'
12+
- 'engine/baml-lib/**'
13+
- '.github/workflows/test-go-windows.yml'
14+
push:
15+
branches: [ hellovai/go-windows-support ]
16+
workflow_dispatch:
17+
18+
jobs:
19+
test-windows:
20+
name: Test Windows Go Client
21+
runs-on: windows-2022
22+
23+
steps:
24+
- name: Checkout code
25+
uses: actions/checkout@v4
26+
27+
- name: Setup Rust
28+
uses: dtolnay/rust-toolchain@stable
29+
with:
30+
toolchain: stable
31+
32+
- name: Cache Rust dependencies
33+
uses: Swatinem/rust-cache@v2
34+
with:
35+
workspaces: engine
36+
37+
- name: Setup Go
38+
uses: actions/setup-go@v5
39+
with:
40+
go-version: '1.22'
41+
cache: false # Disable cache to avoid path issues
42+
43+
- name: Setup Protoc (Windows)
44+
shell: bash
45+
run: |
46+
# Download protoc directly to avoid rate limiting
47+
PROTOC_VERSION="27.1"
48+
curl -LO "https://github.com/protocolbuffers/protobuf/releases/download/v${PROTOC_VERSION}/protoc-${PROTOC_VERSION}-win64.zip"
49+
unzip -q protoc-${PROTOC_VERSION}-win64.zip -d $HOME/.local
50+
echo "$HOME/.local/bin" >> $GITHUB_PATH
51+
rm protoc-${PROTOC_VERSION}-win64.zip
52+
53+
- name: Install protoc-gen-go
54+
run: |
55+
go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.34.1
56+
echo "$HOME/go/bin" >> $GITHUB_PATH
57+
shell: bash
58+
59+
- name: Build CFFI Library
60+
run: |
61+
cargo build --release -p baml_cffi
62+
working-directory: engine
63+
shell: bash
64+
65+
- name: List built artifacts
66+
run: |
67+
echo "Listing release directory:"
68+
dir engine\target\release\*.dll
69+
shell: cmd
70+
71+
- name: Set BAML Library Path
72+
run: |
73+
echo "BAML_LIBRARY_PATH=${{ github.workspace }}\engine\target\release\baml_cffi.dll" >> $GITHUB_ENV
74+
shell: bash
75+
76+
- name: Run Go Tests
77+
run: |
78+
go test -v ./...
79+
working-directory: engine/language_client_go
80+
env:
81+
BAML_LOG: DEBUG
82+
83+
- name: Build Go Package
84+
run: |
85+
go build ./...
86+
working-directory: engine/language_client_go
87+
88+
test-unix:
89+
name: Test Unix Go Client
90+
strategy:
91+
fail-fast: false # Don't cancel other jobs if one fails
92+
matrix:
93+
os: [ubuntu-latest, macos-latest]
94+
runs-on: ${{ matrix.os }}
95+
96+
steps:
97+
- name: Checkout code
98+
uses: actions/checkout@v4
99+
100+
- name: Setup Rust
101+
uses: dtolnay/rust-toolchain@stable
102+
with:
103+
toolchain: stable
104+
105+
- name: Cache Rust dependencies
106+
uses: Swatinem/rust-cache@v2
107+
with:
108+
workspaces: engine
109+
110+
- name: Setup Go
111+
uses: actions/setup-go@v5
112+
with:
113+
go-version: '1.22'
114+
cache: false # Disable cache to avoid path issues
115+
116+
- name: Setup Protoc (Unix)
117+
run: |
118+
# Download protoc directly to avoid rate limiting
119+
PROTOC_VERSION="27.1"
120+
if [[ "$RUNNER_OS" == "Linux" ]]; then
121+
PROTOC_ZIP="protoc-${PROTOC_VERSION}-linux-x86_64.zip"
122+
else
123+
PROTOC_ZIP="protoc-${PROTOC_VERSION}-osx-universal_binary.zip"
124+
fi
125+
curl -LO "https://github.com/protocolbuffers/protobuf/releases/download/v${PROTOC_VERSION}/${PROTOC_ZIP}"
126+
unzip -q ${PROTOC_ZIP} -d $HOME/.local
127+
echo "$HOME/.local/bin" >> $GITHUB_PATH
128+
rm ${PROTOC_ZIP}
129+
130+
- name: Install protoc-gen-go
131+
run: |
132+
go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.34.1
133+
134+
- name: Build CFFI Library
135+
run: |
136+
cargo build --release -p baml_cffi
137+
working-directory: engine
138+
139+
- name: List built artifacts
140+
run: |
141+
echo "Listing release directory:"
142+
ls -la engine/target/release/libbaml_cffi.* || true
143+
ls -la engine/target/release/baml_cffi.* || true
144+
145+
- name: Set BAML Library Path (Linux)
146+
if: runner.os == 'Linux'
147+
run: |
148+
echo "BAML_LIBRARY_PATH=${{ github.workspace }}/engine/target/release/libbaml_cffi.so" >> $GITHUB_ENV
149+
150+
- name: Set BAML Library Path (macOS)
151+
if: runner.os == 'macOS'
152+
run: |
153+
echo "BAML_LIBRARY_PATH=${{ github.workspace }}/engine/target/release/libbaml_cffi.dylib" >> $GITHUB_ENV
154+
155+
- name: Run Go Tests
156+
run: |
157+
go test -v ./...
158+
working-directory: engine/language_client_go
159+
env:
160+
BAML_LOG: DEBUG
161+
162+
- name: Build Go Package
163+
run: |
164+
go build ./...
165+
working-directory: engine/language_client_go
166+
167+
cross-compile-test:
168+
name: Test Cross-Compilation
169+
runs-on: ubuntu-latest
170+
171+
steps:
172+
- name: Checkout code
173+
uses: actions/checkout@v4
174+
175+
- name: Setup Go
176+
uses: actions/setup-go@v5
177+
with:
178+
go-version: '1.22'
179+
cache: false # Disable cache to avoid path issues
180+
181+
- name: Test Cross-Compilation to Windows (CGO disabled)
182+
run: |
183+
# This is expected to fail due to CGO requirements
184+
# We're testing that the error message is clear
185+
if GOOS=windows go build ./... 2>&1 | grep -q "build constraints exclude all Go files"; then
186+
echo "✓ Cross-compilation correctly fails with expected CGO error"
187+
exit 0
188+
else
189+
echo "✗ Unexpected cross-compilation behavior"
190+
exit 1
191+
fi
192+
working-directory: engine/language_client_go
193+
194+
- name: Verify Unix Build Still Works
195+
run: |
196+
go build ./...
197+
working-directory: engine/language_client_go

‎engine/language_client_cffi/build.rs‎

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -406,10 +406,16 @@ fn main() -> std::io::Result<()> {
406406
.write_to_file(out_path.clone());
407407
if std::env::var("CI").is_ok() && res {
408408
let new_content = std::fs::read_to_string(&out_path).unwrap();
409-
println!("New header content: \n==============\n{new_content}");
410-
println!("\n\n");
411-
println!("Old header content: \n==============\n{outpath_content}");
412-
panic!("cbindgen generated a diff");
409+
// Normalize line endings for comparison (Windows CRLF vs Unix LF)
410+
let normalized_old = outpath_content.replace("\r\n", "\n");
411+
let normalized_new = new_content.replace("\r\n", "\n");
412+
413+
if normalized_old != normalized_new {
414+
println!("New header content: \n==============\n{new_content}");
415+
println!("\n\n");
416+
println!("Old header content: \n==============\n{outpath_content}");
417+
panic!("cbindgen generated a diff");
418+
}
413419
}
414420
}
415421

0 commit comments

Comments
 (0)