forked from eryx-org/eryx
-
Notifications
You must be signed in to change notification settings - Fork 1
945 lines (818 loc) · 33.9 KB
/
Copy pathci.yml
File metadata and controls
945 lines (818 loc) · 33.9 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
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
name: CI
on:
push:
branches: [main]
# Release tags (pushed by release-plz via the GitHub App token) so the
# docker job can publish a version-tagged image, e.g. sd2k/eryx-server:0.5.0.
tags: ['eryx-v[0-9]+.[0-9]+.[0-9]+']
pull_request:
branches: [main]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
CARGO_TERM_COLOR: always
RUST_BACKTRACE: 1
jobs:
# Detect which paths changed to skip unnecessary jobs.
# Each downstream job has an explicit 'github.event_name == push' override
# so that all jobs always run on pushes to main.
changes:
name: Detect Changes
runs-on: ubuntu-latest
permissions:
pull-requests: read
outputs:
core: ${{ steps.filter.outputs.core }}
server: ${{ steps.filter.outputs.server }}
python: ${{ steps.filter.outputs.python }}
book: ${{ steps.filter.outputs.book }}
examples: ${{ steps.filter.outputs.examples }}
js: ${{ steps.filter.outputs.js }}
demo: ${{ steps.filter.outputs.demo }}
steps:
- uses: actions/checkout@v7
- uses: dorny/paths-filter@v4
id: filter
with:
filters: |
core:
- 'crates/eryx-core/**'
- 'crates/eryx/**'
- 'crates/eryx-vfs/**'
- 'crates/eryx-wasm-runtime/**'
- 'crates/eryx-runtime/**'
- 'crates/eryx-precompile/**'
- 'Cargo.lock'
- 'Cargo.toml'
- '.github/workflows/ci.yml'
- '.github/actions/**'
# Tool versions and the cargo-rail config drive what every Rust
# job actually runs. Without these, a Renovate bump that touches
# only mise.toml skips all of CI and merges green — which is how
# cargo-rail 0.8.1 -> 0.21.0 (#297) landed and broke main.
- 'mise.toml'
- '.config/rail.toml'
server:
- 'crates/eryx-server/**'
python:
- 'crates/eryx-python/**'
book:
- 'book/**'
examples:
- 'examples/**'
js:
- 'js/**'
demo:
- 'demo/**'
# Fast check for dependency drift - runs in parallel with build
unify-check:
name: Dependency Check
runs-on: ubuntu-latest
needs: changes
if: >-
github.event_name == 'push'
|| needs.changes.outputs.core == 'true'
steps:
- uses: actions/checkout@v7
- uses: ./.github/actions/setup-rust
- name: Check dependency unification
run: cargo rail unify --check
# Build eryx-wasm-runtime and precompile it - other jobs depend on this.
# Runs when core paths change, or when any downstream job needs WASM artifacts.
build-eryx-runtime:
name: Build eryx-runtime
runs-on: ubuntu-latest
needs: changes
if: >-
github.event_name == 'push'
|| needs.changes.outputs.core == 'true'
|| needs.changes.outputs.python == 'true'
|| needs.changes.outputs.book == 'true'
|| needs.changes.outputs.examples == 'true'
|| needs.changes.outputs.server == 'true'
|| needs.changes.outputs.js == 'true'
|| needs.changes.outputs.demo == 'true'
steps:
- uses: actions/checkout@v7
- name: Cache WASM runtime artifacts
id: wasm-cache
uses: actions/cache@v6
with:
path: |
crates/eryx-runtime/runtime.wasm
crates/eryx-runtime/runtime.cwasm
crates/eryx-runtime/prebuilt/
crates/eryx-wasm-runtime/target/liberyx_runtime.so
# NOTE: this key does not cover the cwasm's CPU-feature configuration,
# which comes from `ERYX_CPU_FEATURES` below and the flag lists in
# crates/eryx/src/wasm.rs (compiled into the `precompile` example).
# Changing either without busting the version prefix silently reuses a
# cwasm built with the old settings. Bumped v2 -> v3 for the VNNI flags
# added under wasmtime 48.
key: wasm-runtime-v3-${{ hashFiles('crates/eryx-wasm-runtime/src/**', 'crates/eryx-wasm-runtime/clock_stubs.c', 'crates/eryx-wasm-runtime/Cargo.toml', 'crates/eryx-runtime/build.rs', 'crates/eryx-runtime/wit/runtime.wit', 'crates/eryx-runtime/libs/*.zst', 'Cargo.lock') }}
- name: Touch cached artifacts
if: steps.wasm-cache.outputs.cache-hit == 'true'
run: |
# Touch all cached files so mise/make consider them current
touch crates/eryx-runtime/runtime.wasm
touch crates/eryx-runtime/runtime.cwasm
touch crates/eryx-runtime/prebuilt/*
touch crates/eryx-wasm-runtime/target/liberyx_runtime.so
- name: Upload WASM artifacts (cache hit)
if: steps.wasm-cache.outputs.cache-hit == 'true'
uses: actions/upload-artifact@v7
with:
name: wasm-runtime
path: |
crates/eryx-runtime/runtime.wasm
crates/eryx-runtime/runtime.cwasm
crates/eryx-runtime/prebuilt/
crates/eryx-wasm-runtime/target/liberyx_runtime.so
retention-days: 1
- uses: ./.github/actions/setup-rust
if: steps.wasm-cache.outputs.cache-hit != 'true'
- name: Configure Rust cache
if: steps.wasm-cache.outputs.cache-hit != 'true'
uses: Swatinem/rust-cache@v2
- name: Install nightly toolchain
run: rustup toolchain install nightly --component rust-src
- name: Force rebuild (cache may have stale timestamps)
if: steps.wasm-cache.outputs.cache-hit != 'true'
run: |
# Touch ALL sources to ensure mise doesn't skip build due to cached timestamps
# The Rust cache may restore .rlib files with newer timestamps than sources
find crates/eryx-wasm-runtime/src -name "*.rs" -exec touch {} +
touch crates/eryx-wasm-runtime/clock_stubs.c
touch crates/eryx-wasm-runtime/Cargo.toml
# Remove outputs (mise checks mtime, not existence)
rm -f crates/eryx-runtime/runtime.wasm crates/eryx-runtime/runtime.cwasm
# Clean nested build-std target dirs to avoid stale std artifacts
# when the nightly toolchain updates between runs
rm -rf target/release/build/eryx-runtime-*/out/wasm-runtime-target
- name: Build eryx-runtime
if: steps.wasm-cache.outputs.cache-hit != 'true'
run: mise run build-eryx-runtime
- name: Copy prebuilt late-linking artifacts
if: steps.wasm-cache.outputs.cache-hit != 'true'
run: |
# Find the OUT_DIR that actually contains the built artifacts
# (not just any OUT_DIR - the preinit build creates a new one without .so files)
OUT_DIR=$(find target/release -path "*/eryx-runtime-*/out/liberyx_runtime.so" -type f | head -1 | xargs dirname)
echo "Found OUT_DIR: $OUT_DIR"
mkdir -p crates/eryx-runtime/prebuilt
cp "$OUT_DIR/liberyx_runtime.so.zst" crates/eryx-runtime/prebuilt/
cp "$OUT_DIR/liberyx_bindings.so.zst" crates/eryx-runtime/prebuilt/
# Also copy liberyx_runtime.so for eryx-wasm-runtime tests now, before preinit build
mkdir -p crates/eryx-wasm-runtime/target
cp "$OUT_DIR/liberyx_runtime.so" crates/eryx-wasm-runtime/target/
ls -la crates/eryx-runtime/prebuilt/
ls -la crates/eryx-wasm-runtime/target/
- name: Set up python-stdlib for preinit
if: steps.wasm-cache.outputs.cache-hit != 'true'
run: mise run setup-eryx-runtime-tests
- name: Precompile WASM with preinit
if: steps.wasm-cache.outputs.cache-hit != 'true'
run: |
# Use preinit feature for faster sandbox instantiation in downstream jobs
# (pre-initializes Python interpreter in the snapshot)
cargo run -p eryx --example precompile --features preinit --release
env:
ERYX_PRECOMPILE_BOOTSTRAP: "1"
# Use x86-64-v2 for portable cwasm across runner types
ERYX_CPU_FEATURES: "x86-64-v2"
- name: Upload WASM artifacts (cache miss)
if: steps.wasm-cache.outputs.cache-hit != 'true'
uses: actions/upload-artifact@v7
with:
name: wasm-runtime
path: |
crates/eryx-runtime/runtime.wasm
crates/eryx-runtime/runtime.cwasm
crates/eryx-runtime/prebuilt/
crates/eryx-wasm-runtime/target/liberyx_runtime.so
retention-days: 1
lint:
name: Lint
runs-on: ubuntu-latest
needs: [changes, build-eryx-runtime]
if: >-
github.event_name == 'push'
|| needs.changes.outputs.core == 'true'
|| needs.changes.outputs.server == 'true'
steps:
- uses: actions/checkout@v7
- name: Install protoc
uses: arduino/setup-protoc@v3
with:
version: "36.0"
repo-token: ${{ secrets.GITHUB_TOKEN }}
- name: Download WASM artifacts
uses: actions/download-artifact@v8
with:
name: wasm-runtime
path: crates/
- uses: ./.github/actions/mark-wasm-current
- uses: ./.github/actions/setup-rust
- name: Configure Rust cache
uses: Swatinem/rust-cache@v2
- name: Check formatting
run: cargo fmt --all -- --check
- name: Run clippy
# Use --all-features now that prebuilt artifacts are available
run: cargo clippy --workspace --all-targets --all-features -- -D warnings
test:
name: Test
runs-on: ubuntu-latest
needs: [changes, build-eryx-runtime]
if: >-
github.event_name == 'push'
|| needs.changes.outputs.core == 'true'
|| needs.changes.outputs.server == 'true'
steps:
- uses: actions/checkout@v7
- name: Download WASM artifacts
uses: actions/download-artifact@v8
with:
name: wasm-runtime
path: crates/
- uses: ./.github/actions/mark-wasm-current
- name: Set up eryx-wasm-runtime test artifacts
run: |
# Extract python stdlib for eryx-wasm-runtime tests (from checked-in archive)
mkdir -p crates/eryx-wasm-runtime/tests/python-stdlib
tar -xf crates/eryx/python-stdlib.tar.zst -C crates/eryx-wasm-runtime/tests/
# Create empty site-packages directory (tests expect it)
mkdir -p crates/eryx-wasm-runtime/tests/site-packages
ls -la crates/eryx-wasm-runtime/target/
ls crates/eryx-wasm-runtime/tests/python-stdlib/ | head -10
- name: Install protoc
uses: arduino/setup-protoc@v3
with:
version: "36.0"
repo-token: ${{ secrets.GITHUB_TOKEN }}
# No apt anywhere in this workflow, deliberately. Beyond the dpkg-lock
# hang that took protoc off apt in #337, `azure.archive.ubuntu.com` is
# slow often enough to matter: on #339 `apt-get update` pulled 11.4 MB of
# package indexes at 47 kB/s, taking 4m01s, then spent another 38s and
# four `Ign:` retries fetching mold before falling back to
# archive.ubuntu.com — 4m51s against this step's 5m timeout, for a 2.5 MB
# linker. setup-mold fetches a prebuilt release from GitHub instead, the
# same trade as arduino/setup-protoc above.
#
# Nothing else was needed from apt: the runner image already ships clang
# and gcc, and `-fuse-ld=mold` works with the default gcc driver, so no
# `linker =` override is required either.
- name: Install mold linker
uses: rui314/setup-mold@v1
- name: Configure mold linker
run: |
mkdir -p .cargo
cat >> .cargo/config.toml << 'EOF'
[target.x86_64-unknown-linux-gnu]
rustflags = ["-C", "link-arg=-fuse-ld=mold"]
EOF
- uses: ./.github/actions/setup-rust
- name: Configure Rust cache
uses: Swatinem/rust-cache@v2
- name: Run tests
# Use --all-features to test everything. This works because:
# - runtime.wasm and runtime.cwasm are provided by artifacts
# - prebuilt/liberyx_*.so.zst are provided for late-linking feature
# - liberyx_runtime.so and python-stdlib are provided for eryx-wasm-runtime tests
# Use --profile ci for longer timeouts on slow WASM component tests
run: cargo nextest run --workspace --all-features --profile ci
msrv:
name: MSRV
runs-on: ubuntu-latest
needs: changes
if: >-
github.event_name == 'push'
|| needs.changes.outputs.core == 'true'
steps:
- uses: actions/checkout@v7
- name: Install mise
uses: jdx/mise-action@v4
with:
mise_toml: |
[tools]
rust = "1.98"
- name: Configure Rust cache
uses: Swatinem/rust-cache@v2
- name: Check MSRV
# Exclude crates that require WASM artifacts or WASI SDK
run: cargo check --workspace --exclude eryx-python --exclude eryx-precompile --exclude eryx-server
platform-check:
name: ${{ matrix.os == 'windows-latest' && 'Windows' || 'macOS' }} Check
runs-on: ${{ matrix.os }}
needs: changes
if: >-
github.event_name == 'push'
|| needs.changes.outputs.core == 'true'
strategy:
matrix:
os: [windows-latest, macos-latest]
steps:
- uses: actions/checkout@v7
- name: Configure Rust cache
uses: Swatinem/rust-cache@v2
- name: Install Rust
# Install the pinned toolchain rather than trusting the runner image's
# preinstalled Rust, which lags: the macos-latest image shipped 1.97.1
# while the workspace MSRV was 1.98.0, so `cargo check` refused to build
# ("rustc 1.97.1 is not supported by the following packages"). Reading
# mise.toml keeps this following the same pin as every other job, which
# Renovate bumps as one "Rust toolchain" group.
shell: bash
run: |
RUST_VERSION=$(sed -n 's/^rust = { version = "\([^"]*\)".*/\1/p' mise.toml)
if [ -z "$RUST_VERSION" ]; then
echo "could not read the rust pin from mise.toml" >&2
exit 1
fi
rustup toolchain install "$RUST_VERSION" --profile minimal
rustup default "$RUST_VERSION"
rustup show active-toolchain
- name: Check compilation
# Exclude crates that need WASI SDK, Python, or WASM artifacts
run: cargo check --workspace --exclude eryx-python --exclude eryx-precompile --exclude eryx-wasm-runtime --exclude eryx-server
doc:
name: Documentation
runs-on: ubuntu-latest
needs: [changes, build-eryx-runtime]
if: >-
github.event_name == 'push'
|| needs.changes.outputs.core == 'true'
steps:
- uses: actions/checkout@v7
- name: Install protoc
uses: arduino/setup-protoc@v3
with:
version: "36.0"
repo-token: ${{ secrets.GITHUB_TOKEN }}
- name: Download WASM artifacts
uses: actions/download-artifact@v8
with:
name: wasm-runtime
path: crates/
- uses: ./.github/actions/mark-wasm-current
- uses: ./.github/actions/setup-rust
- name: Configure Rust cache
uses: Swatinem/rust-cache@v2
- name: Build documentation
# Use --all-features now that prebuilt artifacts are available
run: cargo doc --workspace --no-deps --all-features
env:
RUSTDOCFLAGS: -D warnings
# Simulate docs.rs build (no WASM artifacts needed)
docsrs:
name: docs.rs
runs-on: ubuntu-latest
needs: changes
if: >-
github.event_name == 'push'
|| needs.changes.outputs.core == 'true'
steps:
- uses: actions/checkout@v7
- name: Install nightly toolchain
run: rustup toolchain install nightly
- name: Configure Rust cache
uses: Swatinem/rust-cache@v2
- name: Build docs (docs.rs simulation)
run: DOCS_RS=1 cargo +nightly doc -p eryx -p eryx-runtime --all-features --no-deps
env:
RUSTDOCFLAGS: "--cfg docsrs -D warnings"
# Check that all feature combinations compile
check-features:
name: Check Feature Combinations
runs-on: ubuntu-latest
needs: [changes, build-eryx-runtime]
if: >-
github.event_name == 'push'
|| needs.changes.outputs.core == 'true'
steps:
- uses: actions/checkout@v7
- name: Download WASM artifacts
uses: actions/download-artifact@v8
with:
name: wasm-runtime
path: crates/
- uses: ./.github/actions/mark-wasm-current
- uses: ./.github/actions/setup-rust
- name: Configure Rust cache
uses: Swatinem/rust-cache@v2
- name: Check all feature combinations
# cargo-all-features checks the powerset of feature combinations.
# The Cargo.toml config skips preinit/native-extensions for local dev (no artifacts),
# but in CI we have artifacts, so we check those separately.
run: |
echo "=== Checking feature powerset (excluding preinit/native-extensions) ==="
cargo check-all-features -- -p eryx
echo "=== Checking features that require runtime artifacts ==="
cargo check -p eryx --features preinit
cargo check -p eryx --features embedded,preinit
cargo check -p eryx --features native-extensions
cargo check -p eryx --features embedded,native-extensions
# Run examples to ensure they don't bitrot
examples:
name: Examples
runs-on: ubuntu-latest
needs: [changes, build-eryx-runtime]
if: >-
github.event_name == 'push'
|| needs.changes.outputs.core == 'true'
|| needs.changes.outputs.examples == 'true'
steps:
- uses: actions/checkout@v7
- name: Download WASM artifacts
uses: actions/download-artifact@v8
with:
name: wasm-runtime
path: crates/
- uses: ./.github/actions/mark-wasm-current
- uses: ./.github/actions/setup-rust
- name: Configure Rust cache
uses: Swatinem/rust-cache@v2
- name: Run examples
run: |
echo "=== simple ==="
cargo run --package eryx --example simple --features embedded --release
echo "=== parallel_callbacks ==="
cargo run --package eryx --example parallel_callbacks --features embedded --release
echo "=== with_tracing ==="
cargo run --package eryx --example with_tracing --features embedded --release
echo "=== error_handling ==="
cargo run --package eryx --example error_handling --features embedded --release
echo "=== resource_limits ==="
cargo run --package eryx --example resource_limits --features embedded --release
echo "=== custom_library ==="
cargo run --package eryx --example custom_library --features embedded --release
echo "=== session_reuse ==="
cargo run --package eryx --example session_reuse --features embedded --release
echo "=== runtime_callbacks ==="
cargo run --package eryx --example runtime_callbacks --features embedded --release
echo "=== precompile ==="
cargo run --package eryx --example precompile --features embedded --release
echo "=== embedded_runtime ==="
cargo run --package eryx --example embedded_runtime --features embedded --release
echo "=== trace_events ==="
cargo run --package eryx --example trace_events --features embedded --release
# Test Python bindings
python-bindings:
name: Python Bindings
runs-on: ubuntu-latest
needs: [changes, build-eryx-runtime]
if: >-
github.event_name == 'push'
|| needs.changes.outputs.core == 'true'
|| needs.changes.outputs.python == 'true'
steps:
- uses: actions/checkout@v7
- name: Download WASM artifacts
uses: actions/download-artifact@v8
with:
name: wasm-runtime
path: crates/
- uses: ./.github/actions/mark-wasm-current
- uses: ./.github/actions/setup-rust
- name: Configure Rust cache
uses: Swatinem/rust-cache@v2
- uses: actions/setup-python@v7
with:
python-version: "3.14"
- name: Install uv
uses: astral-sh/setup-uv@v10.0.1
- name: Build and install eryx-python with dev dependencies
working-directory: crates/eryx-python
run: |
uv venv ../../.venv
source ../../.venv/bin/activate
uv pip install maturin
maturin develop --release
uv export --extra dev --no-dev --no-hashes --no-emit-project --frozen | uv pip install -r -
- name: Run Python tests
working-directory: crates/eryx-python
run: |
source ../../.venv/bin/activate
pytest tests/ -v
- name: Run Python examples
working-directory: crates/eryx-python
run: |
source ../../.venv/bin/activate
echo "=== simple.py ==="
python examples/simple.py
echo "=== error_handling.py ==="
python examples/error_handling.py
echo "=== resource_limits.py ==="
python examples/resource_limits.py
# Test book examples
book-tests:
name: Book Tests
runs-on: ubuntu-latest
needs: [changes, build-eryx-runtime]
if: >-
github.event_name == 'push'
|| needs.changes.outputs.core == 'true'
|| needs.changes.outputs.book == 'true'
|| needs.changes.outputs.python == 'true'
steps:
- name: Free disk space
run: |
sudo rm -rf /usr/share/dotnet /usr/local/lib/android /opt/ghc /opt/hostedtoolcache/CodeQL
sudo docker image prune --all --force
- uses: actions/checkout@v7
- name: Download WASM artifacts
uses: actions/download-artifact@v8
with:
name: wasm-runtime
path: crates/
- uses: ./.github/actions/mark-wasm-current
- uses: ./.github/actions/setup-rust
- name: Configure Rust cache
uses: Swatinem/rust-cache@v2
- name: Install mdbook tools
run: |
cargo binstall -y mdbook
cargo binstall -y mdbook-langtabs
- name: Build project
run: |
# `mdbook test -L <dir>` points rustdoc at a whole directory instead of
# naming artifacts, so that directory must hold exactly one copy of
# each crate. Two separate things break that in target/debug/deps:
#
# 1. Cargo legitimately compiles some crates twice in one build --
# once for the target graph and once for the host graph (build
# scripts, proc macros and their dependencies), which
# resolver = "2" deliberately does not feature-unify. Both land in
# target/debug/deps and rustdoc cannot choose:
# error[E0464]: multiple candidates for `rlib` dependency `serde`
# Rust 1.97 produces two more such units than 1.92 did, which is
# why this surfaced on the toolchain bump and not before.
# 2. The Rust cache is keyed per job, so artifacts from earlier runs
# under a different Cargo.lock linger alongside current ones.
#
# Building with an explicit --target fixes (1): target-side artifacts
# go to target/<triple>/debug/deps and the host graph stays out.
# Assembling the link directory from cargo's own artifact list fixes
# (2): only what this build resolved to is copied in.
TRIPLE=$(rustc -vV | sed -n 's/^host: //p')
echo "building for $TRIPLE"
cargo clean -p eryx -p eryx-runtime --target "$TRIPLE" 2>/dev/null || true
cargo build -p eryx --features embedded,macros --target "$TRIPLE" \
--message-format=json-render-diagnostics \
> "$RUNNER_TEMP/cargo-build.json"
BOOK_DEPS=target/book-deps
rm -rf "$BOOK_DEPS" && mkdir -p "$BOOK_DEPS"
jq -r 'select(.reason == "compiler-artifact") | .filenames[]?' \
"$RUNNER_TEMP/cargo-build.json" \
| grep "/$TRIPLE/debug/deps/" | grep -E '\.(rlib|rmeta)$' \
| while read -r f; do cp -n "$f" "$BOOK_DEPS/"; done
# Cargo reports the *root* package's rlib at its uplifted path
# (target/<triple>/debug/liberyx.rlib) rather than the hashed one
# under deps/, so the loop above copies eryx's .rmeta but not its
# .rlib. rustdoc loads the hashed .rmeta and then looks for a .rlib
# with the matching hash beside it -- the unhashed uplifted copy does
# not satisfy it, and every example fails with
# error: crate `eryx` required to be available in rlib format
# Pick up the sibling .rlib for each .rmeta already copied. Keying off
# the rmeta keeps this restricted to crates cargo actually named, so
# leftovers from an earlier Cargo.lock still cannot slip in.
for m in "$BOOK_DEPS"/*.rmeta; do
[ -e "$m" ] || continue
sib="target/$TRIPLE/debug/deps/$(basename "$m" .rmeta).rlib"
[ -e "$sib" ] && cp -n "$sib" "$BOOK_DEPS/" || true
done
# Proc macros are host artifacts, so --target keeps them out of the
# target directory -- but rustdoc still needs them to expand derives,
# or every example using one fails with
# error[E0463]: can't find crate for `serde_derive`
# They are dylibs, so they cannot collide with the rlibs above.
jq -r 'select(.reason == "compiler-artifact")
| select(.target.kind[]? == "proc-macro") | .filenames[]?' \
"$RUNNER_TEMP/cargo-build.json" \
| while read -r f; do cp -n "$f" "$BOOK_DEPS/"; done
echo "assembled $(ls "$BOOK_DEPS" | wc -l) artifacts into $BOOK_DEPS"
- name: Test Rust code blocks
run: mdbook test -L ../target/book-deps
working-directory: book
- uses: actions/setup-python@v7
with:
python-version: '3.14'
- name: Create virtualenv and install dependencies
run: |
python -m venv .venv
source .venv/bin/activate
pip install maturin pytest
- name: Build and install eryx-python
working-directory: crates/eryx-python
run: |
source ../../.venv/bin/activate
maturin develop --release
- name: Test Python code blocks
working-directory: book
run: |
source ../.venv/bin/activate
python scripts/test_examples.py
# Deploy book to GitHub Pages (only on main)
deploy-book:
name: Deploy Book
runs-on: ubuntu-latest
needs: book-tests
if: github.ref == 'refs/heads/main'
permissions:
contents: write
pages: write
id-token: write
steps:
- uses: actions/checkout@v7
with:
fetch-depth: 0 # Need full history for version detection
- name: Install cargo-binstall
run: curl -L --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/cargo-bins/cargo-binstall/main/install-from-binstall-release.sh | bash
- name: Install mdbook tools
run: |
cargo binstall -y mdbook
cargo binstall -y mdbook-langtabs
- name: Detect version
id: version
run: |
VERSION=$(grep '^version = ' Cargo.toml | head -1 | sed 's/version = "\(.*\)"/\1/')
echo "version=v$VERSION" >> $GITHUB_OUTPUT
echo "Detected version: v$VERSION"
- name: Build book
working-directory: book
run: mdbook build
- name: Prepare deployment directory
run: |
mkdir -p deploy/latest/book
mkdir -p deploy/${{ steps.version.outputs.version }}/book
cp -r book/book/* deploy/latest/book/
cp -r book/book/* deploy/${{ steps.version.outputs.version }}/book/
echo "docs.eryx.run" > deploy/CNAME
cat > deploy/index.html << 'HTMLEOF'
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Eryx Documentation</title>
<meta http-equiv="refresh" content="0; url=latest/book/">
</head>
<body>
<p>Redirecting to <a href="latest/book/">latest documentation</a>...</p>
</body>
</html>
HTMLEOF
- name: Setup Pages
uses: actions/configure-pages@v6
- name: Upload artifact
uses: actions/upload-pages-artifact@v5
with:
path: 'deploy'
- name: Deploy to GitHub Pages
uses: actions/deploy-pages@v5
# Deploy browser demo to Cloudflare Pages.
# - On main: production deployment (custom domain)
# - On PRs: preview deployment (<branch>.eryx-demo.pages.dev)
deploy-demo:
name: Deploy Demo (Cloudflare)
runs-on: ubuntu-latest
needs: [changes, build-eryx-runtime]
if: >-
github.event.pull_request.head.repo.fork != true
&& github.actor != 'dependabot[bot]'
&& github.ref_type != 'tag'
&& (github.event_name == 'push'
|| needs.changes.outputs.core == 'true'
|| needs.changes.outputs.js == 'true'
|| needs.changes.outputs.demo == 'true')
permissions:
contents: read
deployments: write
pull-requests: write
steps:
- uses: actions/checkout@v7
- name: Download WASM artifacts
uses: actions/download-artifact@v8
with:
name: wasm-runtime
path: crates/
- uses: ./.github/actions/mark-wasm-current
- uses: ./.github/actions/setup-rust
- name: Configure Rust cache
uses: Swatinem/rust-cache@v2
- name: Install wasm32-unknown-unknown target
run: rustup target add wasm32-unknown-unknown
- name: Build JavaScript bindings
working-directory: js
run: mise run build
- name: Build linker WASM + copy base libs
run: |
mise run build-linker-wasm
mise run copy-base-libs
- name: Download example packages for demo
working-directory: js/demo/public
run: |
mkdir -p examples
curl -fSL -o examples/humanize-4.15.0-py3-none-any.whl \
"https://files.pythonhosted.org/packages/c5/7b/bca5613a0c3b542420cf92bd5e5fb8ebd5435ce1011a091f66bb7693285e/humanize-4.15.0-py3-none-any.whl"
curl -fSL -o examples/numpy-wasi.tar.gz \
"https://github.com/eryx-org/eryx/releases/download/demo-assets/numpy-wasi.tar.gz"
ls -lh examples/
- name: Install eryx dependencies
run: npm ci --prefix js/eryx
- name: Build demo site
working-directory: js/demo
run: |
npm install
npx vite build
- name: Deploy to Cloudflare Pages
id: deploy
uses: cloudflare/wrangler-action@v4
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
command: pages deploy js/demo/dist --project-name=eryx-demo --branch=${{ github.head_ref || github.ref_name }} --commit-dirty=true
- name: Comment preview URL on PR
if: github.event_name == 'pull_request'
env:
GH_TOKEN: ${{ github.token }}
run: |
BODY="🌐 **Demo preview:** ${{ steps.deploy.outputs.pages-deployment-alias-url }}
📌 **This deploy:** ${{ steps.deploy.outputs.deployment-url }}"
gh pr comment ${{ github.event.pull_request.number }} \
--body "$BODY" --edit-last || \
gh pr comment ${{ github.event.pull_request.number }} \
--body "$BODY"
# Gate job for the "Deploy Demo" required status check.
# The actual deploy is skipped for fork PRs (no secrets), but GitHub
# treats skipped required checks as failing. This job passes when the
# deploy is skipped, so fork/dependabot PRs aren't blocked.
deploy-demo-status:
name: Deploy Demo
runs-on: ubuntu-latest
needs: [deploy-demo]
if: always()
steps:
- name: Check deploy result
run: |
result="${{ needs.deploy-demo.result }}"
if [ "$result" = "success" ] || [ "$result" = "skipped" ]; then
echo "Deploy demo: $result"
else
echo "Deploy demo: $result"
exit 1
fi
# Build Docker image for the gRPC server.
# Uses the pre-built runtime.cwasm from build-eryx-runtime to skip the
# expensive WASM precompilation step inside Docker. Only pushes on main.
docker:
name: Docker
runs-on: ubuntu-latest
needs: [changes, build-eryx-runtime]
if: >-
github.event.pull_request.head.repo.fork != true
&& (github.event_name == 'push'
|| needs.changes.outputs.core == 'true'
|| needs.changes.outputs.server == 'true')
permissions:
contents: read
steps:
- uses: actions/checkout@v7
- name: Download WASM artifacts
uses: actions/download-artifact@v8
with:
name: wasm-runtime
path: crates/
- name: Prepare runtime.cwasm for Docker build context
run: cp crates/eryx-runtime/runtime.cwasm crates/eryx-server/runtime.cwasm
- name: Log in to Docker Hub
if: github.event_name == 'push'
uses: docker/login-action@v4
with:
username: sd2k
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Docker meta
id: meta
uses: docker/metadata-action@v6
with:
images: sd2k/eryx-server
# On main: sha + branch + latest. On a release tag (eryx-v0.5.0):
# the version tags 0.5.0 and 0.5 (type=match strips the eryx-v prefix).
# latest stays tied to main and is not moved by tag builds.
tags: |
type=sha,prefix=
type=ref,event=branch
type=raw,value=latest,enable={{is_default_branch}}
type=match,pattern=\d+.\d+.\d+,group=0
type=match,pattern=\d+.\d+,group=0
- name: Build and push
uses: docker/build-push-action@v7
with:
context: .
file: crates/eryx-server/Dockerfile
build-args: |
RUNTIME_SOURCE=custom
RUNTIME_CWASM=crates/eryx-server/runtime.cwasm
push: ${{ github.event_name == 'push' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}