-
Notifications
You must be signed in to change notification settings - Fork 3
197 lines (176 loc) · 7.25 KB
/
Copy pathrelease-nif.yml
File metadata and controls
197 lines (176 loc) · 7.25 KB
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
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
name: Release NIF
# Builds the precompiled NIF for each supported (variant × target)
# cell and publishes the resulting tarballs. Two triggers:
#
# * `push` on a bare-semver tag (e.g. 0.3.0) — the publication run.
# Tarballs and matching `.sha256` sidecars land on a draft GitHub
# release named after the tag, at the URL mix.exs's
# `compile.emily_nif` step fetches from.
#
# * `workflow_dispatch` — manual rebuild against any branch. Useful
# for debugging or reproducing a release without cutting a new
# tag. Artefacts go to workflow run storage (retention 90 days);
# no GitHub release is touched.
#
# Consumers verify each tarball against the SHA256 pinned in
# `native_checksums.txt`, which ships inside the hex package (and is
# thus covered by Hex's package hash in the consumer's mix.lock — a
# trust root independent of the mutable GitHub release). The maintainer
# regenerates that file with `mix emily.publish` once the release is
# public; the `.sha256` sidecars uploaded here are informational only.
# See MAINTAINING.md.
on:
push:
tags: ['[0-9]+.[0-9]+.[0-9]+*']
workflow_dispatch:
permissions:
contents: write
jobs:
# Resolves the target version once and — on tag trigger only —
# creates the draft release up front so the matrix cells don't race
# on `gh release create`. On workflow_dispatch the release step is
# a no-op; there's no tag to create one against.
prepare-release:
runs-on: ubuntu-latest
outputs:
version: ${{ steps.version.outputs.version }}
steps:
- uses: actions/checkout@v6
- name: Resolve version
id: version
run: |
if [[ "${{ github.event_name }}" == "push" ]]; then
VERSION="${GITHUB_REF_NAME}"
else
VERSION=$(grep -m1 '@version' mix.exs | awk -F'"' '{print $2}')
if [[ -z "$VERSION" ]]; then
echo "error: could not parse @version from mix.exs" >&2
exit 1
fi
fi
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
echo "Resolved version: $VERSION"
- name: Create or reuse draft release
if: github.event_name == 'push'
env:
GH_TOKEN: ${{ github.token }}
TAG: ${{ steps.version.outputs.version }}
REPO: ${{ github.repository }}
run: |
if ! gh release view "$TAG" --repo "$REPO" >/dev/null 2>&1; then
gh release create "$TAG" \
--repo "$REPO" \
--draft \
--title "Emily $TAG" \
--notes "Precompiled NIF tarballs for macOS arm64 (AOT + JIT variants)."
fi
build:
needs: prepare-release
strategy:
fail-fast: false
matrix:
include:
# AOT runs on macos-14 so the metallib works back to older
# macOS; JIT runs on macos-26 because MLX's NAX kernel
# sources transitively include
# <MetalPerformancePrimitives/MetalPerformancePrimitives.h>,
# first shipped in the macOS 26.2 SDK.
- { variant: aot, target: macos-arm64, runs-on: macos-14, min: "14.0" }
- { variant: jit, target: macos-arm64, runs-on: macos-26, min: "26.2" }
runs-on: ${{ matrix.runs-on }}
env:
MIX_ENV: dev
EMILY_MLX_VARIANT: ${{ matrix.variant }}
VERSION: ${{ needs.prepare-release.outputs.version }}
# Pin the cache to the path the actions/cache step below
# populates. Without it, mix.exs's macOS default of
# DARWIN_USER_CACHE_DIR (`/private/var/folders/...`) misses
# the restored cache and rebuilds MLX from scratch every run.
EMILY_CACHE: ~/Library/Caches/emily
steps:
- uses: actions/checkout@v6
- uses: erlef/setup-beam@v1
with:
version-file: .tool-versions
version-type: strict
- name: Cache deps
uses: actions/cache@v5
with:
path: deps
key: deps-${{ runner.os }}-${{ hashFiles('mix.lock') }}
restore-keys: deps-${{ runner.os }}-
# MLX install dir keyed by the NIF sources, Makefile, build
# script, and the pinned MLX version in mix.exs.
- name: Cache MLX install
uses: actions/cache@v5
with:
path: ~/Library/Caches/emily
key: mlx-release-${{ runner.os }}-${{ matrix.variant }}-${{ hashFiles('c_src/**', 'Makefile', 'mix.exs', 'scripts/build-mlx.sh') }}
restore-keys: mlx-release-${{ runner.os }}-${{ matrix.variant }}-
- run: mix deps.get
- run: mix compile
# Guard the shipped artifact: assert it declares the pinned macOS floor
# and imports no above-floor libSystem symbols before it's packaged.
- name: Verify NIF macOS floor
run: bash scripts/verify-nif-floor.sh _build/dev/lib/emily/priv/libemily.so "${{ matrix.min }}"
- name: Package NIF tarball
id: pack
env:
VARIANT: ${{ matrix.variant }}
TARGET: ${{ matrix.target }}
run: |
ASSET="emily-nif-${VERSION}-${VARIANT}-${TARGET}.tar.gz"
PRIV="_build/dev/lib/emily/priv"
if [[ ! -d "$PRIV" ]]; then
echo "error: expected $PRIV after mix compile" >&2
exit 1
fi
# Flat tarball (no wrapping dir) — mix.exs's fetch_nif/1
# extracts with `tar -xzf ... -C priv` and expects files at
# the top level.
(cd "$PRIV" && tar czf "$GITHUB_WORKSPACE/$ASSET" libemily.* mlx.metallib)
shasum -a 256 "$ASSET" > "${ASSET}.sha256"
echo "asset=$ASSET" >> "$GITHUB_OUTPUT"
{
echo "## ${VARIANT}-${TARGET}"
echo ""
echo '```'
cat "${ASSET}.sha256"
echo '```'
} >> "$GITHUB_STEP_SUMMARY"
# On tag push, publish to the draft GitHub release so consumers
# can fetch. On workflow_dispatch, stash as workflow artefacts
# for maintainer collection — no release side-effects.
- name: Upload to draft release
if: github.event_name == 'push'
env:
GH_TOKEN: ${{ github.token }}
TAG: ${{ needs.prepare-release.outputs.version }}
REPO: ${{ github.repository }}
ASSET: ${{ steps.pack.outputs.asset }}
run: gh release upload "$TAG" "$ASSET" "${ASSET}.sha256" --repo "$REPO" --clobber
- name: Upload as workflow artefact
if: github.event_name == 'workflow_dispatch'
uses: actions/upload-artifact@v4
with:
name: ${{ steps.pack.outputs.asset }}
path: |
${{ steps.pack.outputs.asset }}
${{ steps.pack.outputs.asset }}.sha256
retention-days: 90
# Once every matrix cell has uploaded its tarball + sidecar to the
# draft release, flip the release out of draft so consumers can
# fetch. `needs: build` requires all matrix cells to succeed; the
# event guard keeps workflow_dispatch runs (which don't touch a
# release) from triggering this step.
publish-release:
needs: [prepare-release, build]
if: github.event_name == 'push'
runs-on: ubuntu-latest
steps:
- name: Mark release as published
env:
GH_TOKEN: ${{ github.token }}
TAG: ${{ needs.prepare-release.outputs.version }}
REPO: ${{ github.repository }}
run: gh release edit "$TAG" --repo "$REPO" --draft=false