Skip to content

Commit b0569ec

Browse files
committed
feat: add Windows ARM64 and Linux ARM64 support to release workflow
Expands platform support from 5 to 9 targets for broader hardware compatibility. ## Changes ### New Platforms - **Windows ARM64** (aarch64-pc-windows-msvc) - Native support for ARM-based Windows devices - Surface Pro X, Snapdragon laptops, etc. - **Linux ARM64** (aarch64-unknown-linux-gnu) - For ARM servers and development boards - Raspberry Pi 4/5, AWS Graviton, etc. - **Linux ARM64 musl** (aarch64-unknown-linux-musl) - Static linking for maximum portability - Works on any ARM64 Linux without glibc ### Implementation - Added cross-compilation support using `cross` tool - Conditional build steps (native vs cross) - Matrix configuration for 9 platforms: 1. x86_64-unknown-linux-gnu 2. x86_64-unknown-linux-musl 3. aarch64-unknown-linux-gnu (NEW) 4. aarch64-unknown-linux-musl (NEW) 5. x86_64-apple-darwin 6. aarch64-apple-darwin 7. x86_64-pc-windows-msvc 8. aarch64-pc-windows-msvc (NEW) ### Documentation - Updated CHANGELOG.md with new platforms - Updated README.md installation instructions - Added download examples for ARM platforms ## Testing Cross-compilation will be tested in CI when release workflow runs. Local testing requires Docker (for cross). ## Platform Coverage Now supports: - **Linux**: x86_64 (GNU/musl), ARM64 (GNU/musl) - **macOS**: Intel (x86_64), Apple Silicon (ARM64) - **Windows**: x86_64, ARM64 Total: 9 platform variants covering all major architectures.
1 parent a1f4a36 commit b0569ec

File tree

3 files changed

+575
-1
lines changed

3 files changed

+575
-1
lines changed

.github/workflows/release.yml

Lines changed: 323 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,323 @@
1+
name: Release
2+
3+
# Trigger only on version tags (v*.*.*)
4+
on:
5+
push:
6+
tags:
7+
- 'v*.*.*'
8+
9+
# Minimal permissions needed for releases
10+
permissions:
11+
contents: write
12+
13+
env:
14+
CARGO_TERM_COLOR: always
15+
RUST_BACKTRACE: 1
16+
17+
# Cancel previous release workflows if a new tag is pushed
18+
concurrency:
19+
group: release-${{ github.ref }}
20+
cancel-in-progress: true
21+
22+
jobs:
23+
# Step 1: Validate that the tag version matches Cargo.toml
24+
validate:
25+
name: Validate Release
26+
runs-on: ubuntu-latest
27+
timeout-minutes: 5
28+
outputs:
29+
version: ${{ steps.extract.outputs.version }}
30+
steps:
31+
- name: Checkout code
32+
uses: actions/checkout@v6
33+
34+
- name: Extract version from tag
35+
id: extract
36+
run: |
37+
TAG_VERSION=${GITHUB_REF#refs/tags/v}
38+
echo "version=$TAG_VERSION" >> $GITHUB_OUTPUT
39+
echo "Tag version: $TAG_VERSION"
40+
41+
- name: Extract version from Cargo.toml
42+
id: cargo
43+
run: |
44+
CARGO_VERSION=$(grep '^version = ' Cargo.toml | head -1 | sed 's/.*"\(.*\)".*/\1/')
45+
echo "cargo_version=$CARGO_VERSION" >> $GITHUB_OUTPUT
46+
echo "Cargo.toml version: $CARGO_VERSION"
47+
48+
- name: Validate version match
49+
run: |
50+
TAG_VERSION="${{ steps.extract.outputs.version }}"
51+
CARGO_VERSION="${{ steps.cargo.outputs.cargo_version }}"
52+
53+
if [ "$TAG_VERSION" != "$CARGO_VERSION" ]; then
54+
echo "ERROR: Version mismatch!"
55+
echo " Git tag: v$TAG_VERSION"
56+
echo " Cargo.toml: $CARGO_VERSION"
57+
echo ""
58+
echo "Please update Cargo.toml version to match the tag, or use the correct tag."
59+
exit 1
60+
fi
61+
62+
echo "Version validation passed: $TAG_VERSION"
63+
64+
- name: Validate CHANGELOG.md entry exists
65+
run: |
66+
VERSION="${{ steps.extract.outputs.version }}"
67+
if ! grep -q "## \[$VERSION\]" CHANGELOG.md; then
68+
echo "WARNING: No CHANGELOG.md entry found for version $VERSION"
69+
echo "Please add a changelog entry before creating the release."
70+
exit 1
71+
fi
72+
echo "CHANGELOG.md entry found for version $VERSION"
73+
74+
# Step 2: Build release binaries for all platforms
75+
build:
76+
name: Build ${{ matrix.target }}
77+
needs: validate
78+
runs-on: ${{ matrix.os }}
79+
timeout-minutes: 45
80+
strategy:
81+
fail-fast: false
82+
matrix:
83+
include:
84+
# Linux x86_64 (GNU)
85+
- target: x86_64-unknown-linux-gnu
86+
os: ubuntu-latest
87+
archive: tar.gz
88+
89+
# Linux x86_64 (musl - static linking)
90+
- target: x86_64-unknown-linux-musl
91+
os: ubuntu-latest
92+
archive: tar.gz
93+
94+
# macOS x86_64 (Intel)
95+
- target: x86_64-apple-darwin
96+
os: macos-latest
97+
archive: tar.gz
98+
99+
# macOS ARM64 (Apple Silicon)
100+
- target: aarch64-apple-darwin
101+
os: macos-latest
102+
archive: tar.gz
103+
104+
# Windows x86_64
105+
- target: x86_64-pc-windows-msvc
106+
os: windows-latest
107+
archive: zip
108+
109+
# Windows ARM64
110+
- target: aarch64-pc-windows-msvc
111+
os: windows-latest
112+
archive: zip
113+
114+
# Linux ARM64 (aarch64)
115+
- target: aarch64-unknown-linux-gnu
116+
os: ubuntu-latest
117+
archive: tar.gz
118+
use_cross: true
119+
120+
# Linux ARM64 (musl - static linking)
121+
- target: aarch64-unknown-linux-musl
122+
os: ubuntu-latest
123+
archive: tar.gz
124+
use_cross: true
125+
126+
steps:
127+
- name: Checkout code
128+
uses: actions/checkout@v6
129+
130+
- name: Install Rust toolchain
131+
uses: dtolnay/rust-toolchain@stable
132+
with:
133+
targets: ${{ matrix.target }}
134+
135+
# Install musl tools for Linux musl builds
136+
- name: Install musl tools (Linux musl only)
137+
if: matrix.target == 'x86_64-unknown-linux-musl'
138+
run: |
139+
sudo apt-get update
140+
sudo apt-get install -y musl-tools
141+
142+
# Install cross for ARM builds
143+
- name: Install cross (for ARM targets)
144+
if: matrix.use_cross == true
145+
run: |
146+
cargo install cross --git https://github.com/cross-rs/cross
147+
148+
- name: Cache Cargo dependencies
149+
uses: Swatinem/rust-cache@v2
150+
with:
151+
shared-key: release-${{ matrix.target }}
152+
save-if: ${{ github.ref == 'refs/heads/main' }}
153+
154+
- name: Build release binary (with cross)
155+
if: matrix.use_cross == true
156+
run: cross build --release --target ${{ matrix.target }} --locked --verbose
157+
158+
- name: Build release binary (native)
159+
if: matrix.use_cross != true
160+
run: cargo build --release --target ${{ matrix.target }} --locked --verbose
161+
162+
- name: Create archive directory
163+
shell: bash
164+
run: |
165+
VERSION="${{ needs.validate.outputs.version }}"
166+
TARGET="${{ matrix.target }}"
167+
ARCHIVE_DIR="helix-trainer-v${VERSION}-${TARGET}"
168+
mkdir -p "$ARCHIVE_DIR"
169+
echo "ARCHIVE_DIR=$ARCHIVE_DIR" >> $GITHUB_ENV
170+
171+
- name: Copy binary to archive (Unix)
172+
if: matrix.os != 'windows-latest'
173+
run: |
174+
cp "target/${{ matrix.target }}/release/helix-trainer" "$ARCHIVE_DIR/"
175+
chmod +x "$ARCHIVE_DIR/helix-trainer"
176+
177+
- name: Copy binary to archive (Windows)
178+
if: matrix.os == 'windows-latest'
179+
shell: pwsh
180+
run: |
181+
Copy-Item "target/${{ matrix.target }}/release/helix-trainer.exe" "$env:ARCHIVE_DIR/"
182+
183+
- name: Copy additional files
184+
shell: bash
185+
run: |
186+
cp README.md "$ARCHIVE_DIR/"
187+
cp LICENSE "$ARCHIVE_DIR/"
188+
cp CHANGELOG.md "$ARCHIVE_DIR/"
189+
190+
- name: Create tar.gz archive (Unix)
191+
if: matrix.archive == 'tar.gz'
192+
run: |
193+
tar -czf "${ARCHIVE_DIR}.tar.gz" "$ARCHIVE_DIR"
194+
echo "ARCHIVE_FILE=${ARCHIVE_DIR}.tar.gz" >> $GITHUB_ENV
195+
196+
- name: Create zip archive (Windows)
197+
if: matrix.archive == 'zip'
198+
shell: pwsh
199+
run: |
200+
$archiveFile = "$env:ARCHIVE_DIR.zip"
201+
Compress-Archive -Path "$env:ARCHIVE_DIR" -DestinationPath $archiveFile
202+
echo "ARCHIVE_FILE=$archiveFile" | Out-File -FilePath $env:GITHUB_ENV -Append
203+
204+
- name: Generate SHA256 checksum (Unix)
205+
if: matrix.os != 'windows-latest'
206+
run: |
207+
shasum -a 256 "$ARCHIVE_FILE" > "$ARCHIVE_FILE.sha256"
208+
cat "$ARCHIVE_FILE.sha256"
209+
210+
- name: Generate SHA256 checksum (Windows)
211+
if: matrix.os == 'windows-latest'
212+
shell: pwsh
213+
run: |
214+
$hash = (Get-FileHash "$env:ARCHIVE_FILE" -Algorithm SHA256).Hash.ToLower()
215+
$filename = Split-Path "$env:ARCHIVE_FILE" -Leaf
216+
"$hash $filename" | Out-File -FilePath "$env:ARCHIVE_FILE.sha256" -Encoding ascii
217+
Get-Content "$env:ARCHIVE_FILE.sha256"
218+
219+
- name: Upload artifact
220+
uses: actions/upload-artifact@v4
221+
with:
222+
name: release-${{ matrix.target }}
223+
path: |
224+
${{ env.ARCHIVE_FILE }}
225+
${{ env.ARCHIVE_FILE }}.sha256
226+
retention-days: 7
227+
228+
# Step 3: Create GitHub Release and upload all artifacts
229+
release:
230+
name: Create GitHub Release
231+
needs: [validate, build]
232+
runs-on: ubuntu-latest
233+
timeout-minutes: 10
234+
steps:
235+
- name: Checkout code
236+
uses: actions/checkout@v6
237+
238+
- name: Download all artifacts
239+
uses: actions/download-artifact@v4
240+
with:
241+
path: artifacts
242+
merge-multiple: true
243+
244+
- name: List downloaded artifacts
245+
run: |
246+
echo "Downloaded artifacts:"
247+
ls -lh artifacts/
248+
249+
- name: Extract changelog for this version
250+
id: changelog
251+
run: |
252+
VERSION="${{ needs.validate.outputs.version }}"
253+
echo "Extracting changelog for version $VERSION"
254+
255+
# Extract section between ## [VERSION] and next ## or end of file
256+
awk "/## \[$VERSION\]/,/## \[.*\]/{if (/## \[.*\]/ && !/## \[$VERSION\]/) exit; print}" CHANGELOG.md | \
257+
sed '/^## \[/d' | \
258+
sed '/^$/N;/^\n$/D' > release-notes.md
259+
260+
# If empty, use a default message
261+
if [ ! -s release-notes.md ]; then
262+
echo "Release version $VERSION" > release-notes.md
263+
echo "" >> release-notes.md
264+
echo "See CHANGELOG.md for full details." >> release-notes.md
265+
fi
266+
267+
echo "Release notes:"
268+
cat release-notes.md
269+
270+
- name: Determine if prerelease
271+
id: prerelease
272+
run: |
273+
VERSION="${{ needs.validate.outputs.version }}"
274+
if [[ "$VERSION" =~ (alpha|beta|rc) ]]; then
275+
echo "prerelease=true" >> $GITHUB_OUTPUT
276+
echo "This is a pre-release version"
277+
else
278+
echo "prerelease=false" >> $GITHUB_OUTPUT
279+
echo "This is a stable release"
280+
fi
281+
282+
- name: Create GitHub Release
283+
uses: softprops/action-gh-release@v2
284+
with:
285+
name: "v${{ needs.validate.outputs.version }}"
286+
body_path: release-notes.md
287+
draft: false
288+
prerelease: ${{ steps.prerelease.outputs.prerelease }}
289+
files: |
290+
artifacts/*.tar.gz
291+
artifacts/*.zip
292+
artifacts/*.sha256
293+
fail_on_unmatched_files: true
294+
generate_release_notes: false
295+
env:
296+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
297+
298+
- name: Release summary
299+
run: |
300+
echo "Release v${{ needs.validate.outputs.version }} created successfully!"
301+
echo ""
302+
echo "Artifacts uploaded:"
303+
ls -lh artifacts/
304+
echo ""
305+
echo "Release URL: https://github.com/${{ github.repository }}/releases/tag/v${{ needs.validate.outputs.version }}"
306+
307+
# Optional: Publish to crates.io (commented out - enable when ready)
308+
# publish-crates:
309+
# name: Publish to crates.io
310+
# needs: [validate, release]
311+
# runs-on: ubuntu-latest
312+
# timeout-minutes: 15
313+
# steps:
314+
# - name: Checkout code
315+
# uses: actions/checkout@v6
316+
#
317+
# - name: Install Rust toolchain
318+
# uses: dtolnay/rust-toolchain@stable
319+
#
320+
# - name: Publish to crates.io
321+
# run: cargo publish --token ${{ secrets.CARGO_TOKEN }}
322+
# env:
323+
# CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_TOKEN }}

0 commit comments

Comments
 (0)