1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
name: Release
on:
push:
tags:
- "v*" # Trigger on version tags, e.g., v1.0.0, v0.1.0
workflow_dispatch:
inputs:
version:
description: 'Version tag for manual release (e.g., v0.1.0-test)'
required: true
type: string
publish_to_crates_io:
description: 'Also publish to crates.io (leave unchecked for test/manual releases)'
required: false
type: boolean
default: false
permissions:
contents: write
jobs:
release:
name: Build and Release
runs-on: ${{ matrix.os }}
strategy:
# Let the other target finish (and upload its asset) even if one leg fails.
# Matters more now that the Windows leg cross-compiles instead of using its own OS runner.
fail-fast: false
matrix:
include:
# `primary: true` marks the one leg that generates the release notes/changelog -
# otherwise each matrix leg would generate and append its own copy (duplicate text).
- os: ubuntu-latest
target: x86_64-unknown-linux-musl
executable_name: ime
asset_name: ime-linux-amd64
cross: false
primary: true
# Windows binary is cross-compiled from Ubuntu via cargo-xwin instead of running
# on windows-latest. This avoids the slower/more expensive Windows runner entirely.
- os: ubuntu-latest
target: x86_64-pc-windows-msvc
executable_name: ime.exe
asset_name: ime-windows-amd64.exe
cross: true
primary: false
steps:
- name: Checkout repository
uses: actions/checkout@v7
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.target }}
# Caches ~/.cargo/registry, ~/.cargo/git and target/, keyed on Cargo.lock.
# The `key` input keeps the two matrix legs (musl vs windows-msvc) in separate caches.
- name: Cache cargo registry and build artifacts
uses: Swatinem/rust-cache@v2
with:
key: ${{ matrix.target }}
# mold links noticeably faster than the default linker. Only applies to the native
# musl build; the cross-compiled Windows build below uses lld-link via clang-cl instead.
- name: Install mold linker
if: ${{ !matrix.cross }}
uses: rui314/setup-mold@v1
- name: Install musl-tools (Linux target)
if: ${{ !matrix.cross }}
run: sudo apt-get update && sudo apt-get install -y musl-tools
# clang/lld/llvm ship preinstalled on the ubuntu-latest runner image, so cargo-xwin
# (which drives clang-cl under the hood) needs no extra apt packages to cross-build MSVC.
- name: Cache cargo-xwin binary
if: matrix.cross
id: cargo-xwin-cache
uses: actions/cache@v6
with:
path: ~/.cargo/bin/cargo-xwin
# Bump the "v1" suffix to force a rebuild if you ever want to update cargo-xwin.
key: cargo-xwin-bin-v1
- name: Install cargo-xwin (Windows cross target)
if: matrix.cross && steps.cargo-xwin-cache.outputs.cache-hit != 'true'
run: cargo install --locked cargo-xwin
# cargo-xwin downloads the MS CRT/Windows SDK on first use (several hundred MB).
# Caching this directory means only the very first run pays that download cost.
- name: Cache Windows SDK/CRT (xwin)
if: matrix.cross
uses: actions/cache@v6
with:
path: /home/runner/.xwin-cache
# Bump the "v1" suffix if you ever need to force a fresh SDK download.
key: xwin-sdk-v1
- name: Build binary (native)
if: ${{ !matrix.cross }}
run: cargo build --release --target ${{ matrix.target }}
- name: Build binary (cross-compiled via cargo-xwin)
if: matrix.cross
env:
XWIN_CACHE_DIR: /home/runner/.xwin-cache
run: cargo xwin build --release --target ${{ matrix.target }}
- name: Rename binary for upload
shell: bash
run: |
mv target/${{ matrix.target }}/release/${{ matrix.executable_name }} target/${{ matrix.target }}/release/${{ matrix.asset_name }}
- name: Create Release and Upload Asset
uses: softprops/action-gh-release@v3
with:
tag_name: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.version || github.ref_name }}
files: target/${{ matrix.target }}/release/${{ matrix.asset_name }}
# Only the primary leg generates notes; the other just uploads its asset
# to the release that already exists by the time it gets here (or vice versa).
generate_release_notes: ${{ matrix.primary }}
publish-crate:
name: Publish to crates.io
runs-on: ubuntu-latest
needs: release # Only publish after successful build and GitHub release
# crates.io publishes are permanent and can't be un-published, so a manual
# (workflow_dispatch) run only publishes if the checkbox was explicitly ticked.
# Once the tag-push trigger above is re-enabled, tag pushes always publish.
if: github.event_name != 'workflow_dispatch' || github.event.inputs.publish_to_crates_io == 'true'
steps:
- name: Checkout repository
uses: actions/checkout@v7
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
# `cargo publish` builds the crate to verify it, so caching pays off here too.
- name: Cache cargo registry and build artifacts
uses: Swatinem/rust-cache@v2
with:
key: publish-crate
- name: Publish to crates.io
run: cargo publish --token ${{ secrets.CARGO_REGISTRY_TOKEN }}