4a12f60a88
* ci: test the Node binding on the 22/24 LTS (18/20 are EOL) Node 18 is EOL and 20 reaches EOL; move the binding CI matrix from [18, 20] to [22, 24] (both LTS), bump the fixed Node setups in ci.yml/release.yml to 22, and raise package.json engines to >= 20. The N-API 8 binary is ABI-stable across Node versions, so no per-version build is needed. The `npm test` script (`node --test __tests__/`) breaks on Node 22 — it resolves `__tests__` as a module — so switch to `node --test` (auto- discovery). Verified locally on Node 22: build + 584 tests green. * build(node): sync package-lock engines.node to >= 20 * ci(node): drop the __tests__/ path arg so node --test auto-discovers On Node 22+, `node --test __tests__/` resolves the directory as a single module and fails with one unrunnable subtest. `node --test` (no path) auto-discovers every *.test.js under the package, matching the package.json test script. Verified locally: 584 tests pass.
1032 lines
42 KiB
YAML
1032 lines
42 KiB
YAML
name: CI
|
|
|
|
on:
|
|
push:
|
|
branches: [main]
|
|
pull_request:
|
|
branches: [main]
|
|
|
|
# Least-privilege default for the auto-injected GITHUB_TOKEN. None of the CI
|
|
# jobs write back to the repo — coverage uploads via CODECOV_TOKEN, everything
|
|
# else is build/test/lint — so a read-only token is sufficient (OpenSSF
|
|
# Scorecard: Token-Permissions).
|
|
permissions:
|
|
contents: read
|
|
|
|
env:
|
|
CARGO_TERM_COLOR: always
|
|
RUSTFLAGS: "-D warnings"
|
|
# Network-flake resilience: retry transient registry/DNS failures at the tool
|
|
# level so a blip fetching crates.io / npm / PyPI inside any build step (cargo,
|
|
# napi, maturin, wasm-pack, npm ci, pip) retries automatically instead of
|
|
# failing the job and needing a manual re-run. Cargo treats "couldn't resolve
|
|
# host" / connect / timeout as spurious and retries with backoff; 10 attempts
|
|
# ride out a transient DNS blip on a runner. Complements the setup-action /
|
|
# cache retries (which only covered toolchain download + cache restore).
|
|
CARGO_NET_RETRY: "10"
|
|
CARGO_NET_GIT_FETCH_WITH_CLI: "true"
|
|
npm_config_fetch_retries: "5"
|
|
npm_config_fetch_retry_maxtimeout: "120000"
|
|
PIP_RETRIES: "5"
|
|
PIP_DEFAULT_TIMEOUT: "120"
|
|
|
|
jobs:
|
|
rust:
|
|
name: Rust ${{ matrix.os }}
|
|
runs-on: ${{ matrix.os }}
|
|
timeout-minutes: 20 # backstop: cap a wedged job instead of GitHub's 6h default (slowest real job ~5 min)
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
os: [ubuntu-latest, macos-latest, windows-latest]
|
|
steps:
|
|
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
|
with:
|
|
persist-credentials: false
|
|
|
|
- name: Install Rust toolchain
|
|
uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable branch, 2026-03-27
|
|
with:
|
|
components: rustfmt, clippy
|
|
|
|
- name: Cache cargo
|
|
uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
|
|
continue-on-error: true # cache is an optimisation; never block on a stuck/slow restore
|
|
timeout-minutes: 6
|
|
|
|
- name: Warm cargo registry (retry transient DNS/registry flakes)
|
|
shell: bash
|
|
# CARGO_NET_RETRY rides out short blips, but a longer runner DNS outage
|
|
# outlasts cargo's rapid in-process retries: the crates.io index fetch
|
|
# the first cargo step does ("Could not resolve host: index.crates.io")
|
|
# then fails the whole job. Pre-fetch the dependency graph here with real
|
|
# backoff so clippy/build/test resolve from the warmed local cache.
|
|
run: |
|
|
for attempt in 1 2 3 4 5; do
|
|
if cargo fetch; then exit 0; fi
|
|
echo "::warning::cargo fetch failed (attempt $attempt/5) — likely a registry/DNS flake; retrying in $((attempt * 20))s..."
|
|
sleep $((attempt * 20))
|
|
done
|
|
echo "::error::cargo fetch still failing after 5 attempts"
|
|
exit 1
|
|
|
|
- name: Format check
|
|
run: cargo fmt --all -- --check
|
|
|
|
- name: Clippy (workspace, all targets)
|
|
run: cargo clippy -p wickra-core -p wickra -p wickra-data -p wickra-wasm --all-targets -- -D warnings
|
|
|
|
- name: Build
|
|
run: cargo build -p wickra-core -p wickra -p wickra-data --verbose
|
|
|
|
- name: Tests (default features)
|
|
run: cargo test -p wickra-core -p wickra -p wickra-data --verbose
|
|
|
|
- name: Tests (live-binance feature)
|
|
run: cargo test -p wickra-data --features live-binance --verbose
|
|
|
|
- name: Compile benches (smoke)
|
|
run: cargo build -p wickra --benches --verbose
|
|
|
|
- name: Compile examples
|
|
# All runnable examples now live in the dedicated wickra-examples crate
|
|
# (examples/rust/src/bin/*.rs) which enables the live-binance feature
|
|
# on its wickra-data dep, so a single --bins build covers backtest,
|
|
# live_binance, fetch_btcusdt, multi_timeframe, parallel_assets and
|
|
# streaming.
|
|
run: cargo build -p wickra-examples --bins
|
|
|
|
# Syntax/parse smoke for the non-Rust examples. The Rust examples are built
|
|
# in the `rust` job above (`cargo build -p wickra-examples --bins`); the Node,
|
|
# browser-WASM and Python examples otherwise have no build gate, so a broken
|
|
# edit could land unnoticed. This is a parse-only smoke — actually running the
|
|
# examples needs the built native binding / wasm module / wheel, which the
|
|
# binding jobs provide separately.
|
|
examples-smoke:
|
|
name: Examples (syntax smoke)
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 20 # backstop: cap a wedged job instead of GitHub's 6h default (slowest real job ~5 min)
|
|
steps:
|
|
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
|
with:
|
|
persist-credentials: false
|
|
|
|
- name: Set up Node
|
|
id: setup_node
|
|
continue-on-error: true
|
|
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
|
with:
|
|
node-version: "22"
|
|
|
|
- name: Wait before Node retry
|
|
if: steps.setup_node.outcome == 'failure'
|
|
shell: bash
|
|
run: |
|
|
echo "::warning::setup-node failed (likely CDN flake), waiting 30s before retry..."
|
|
sleep 30
|
|
|
|
- name: Set up Node (retry)
|
|
if: steps.setup_node.outcome == 'failure'
|
|
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
|
with:
|
|
node-version: "22"
|
|
|
|
- name: Set up Python
|
|
id: setup_python
|
|
continue-on-error: true
|
|
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
|
with:
|
|
python-version: "3.12"
|
|
|
|
- name: Wait before Python retry
|
|
if: steps.setup_python.outcome == 'failure'
|
|
shell: bash
|
|
run: |
|
|
echo "::warning::setup-python failed (likely CDN flake), waiting 30s before retry..."
|
|
sleep 30
|
|
|
|
- name: Set up Python (retry)
|
|
if: steps.setup_python.outcome == 'failure'
|
|
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
|
with:
|
|
python-version: "3.12"
|
|
|
|
- name: Node examples — syntax check
|
|
run: |
|
|
shopt -s nullglob
|
|
count=0
|
|
for f in examples/node/*.js examples/wasm/*.js; do
|
|
echo "node --check $f"
|
|
node --check "$f"
|
|
count=$((count + 1))
|
|
done
|
|
echo "checked $count Node/WASM .js files"
|
|
|
|
- name: WASM demo module scripts — syntax check
|
|
# The .html demos embed an ES module; extract it and parse-check so a
|
|
# broken edit to the in-page strategy logic fails CI.
|
|
run: |
|
|
shopt -s nullglob
|
|
count=0
|
|
for f in examples/wasm/*.html; do
|
|
node -e 'const fs=require("fs");const h=fs.readFileSync(process.argv[1],"utf8");const m=h.match(/<script type="module">([\s\S]*?)<\/script>/);if(!m){console.error("no <script type=module> in "+process.argv[1]);process.exit(1);}fs.writeFileSync("module-check.mjs",m[1]);' "$f"
|
|
echo "node --check (module of) $f"
|
|
node --check module-check.mjs
|
|
count=$((count + 1))
|
|
done
|
|
rm -f module-check.mjs
|
|
echo "checked $count WASM .html module scripts"
|
|
|
|
- name: Python examples — byte-compile
|
|
run: |
|
|
shopt -s nullglob
|
|
count=0
|
|
for f in examples/python/*.py; do
|
|
echo "py_compile $f"
|
|
python -m py_compile "$f"
|
|
count=$((count + 1))
|
|
done
|
|
echo "compiled $count Python files"
|
|
|
|
# Clippy for the Python and Node bindings. These are kept out of the main
|
|
# `rust` job because PyO3 / napi build scripts need a Python interpreter and
|
|
# a Node toolchain on PATH, which the 3-OS matrix job does not provision.
|
|
# Ubuntu-only is sufficient: the lints are platform-independent.
|
|
clippy-bindings:
|
|
name: Clippy bindings
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 20 # backstop: cap a wedged job instead of GitHub's 6h default (slowest real job ~5 min)
|
|
steps:
|
|
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
|
with:
|
|
persist-credentials: false
|
|
|
|
- name: Install Rust toolchain
|
|
uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable branch, 2026-03-27
|
|
with:
|
|
components: clippy
|
|
|
|
- name: Set up Python
|
|
id: setup_python
|
|
continue-on-error: true
|
|
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
|
|
with:
|
|
python-version: "3.12"
|
|
|
|
- name: Wait before Python retry
|
|
if: steps.setup_python.outcome == 'failure'
|
|
shell: bash
|
|
run: |
|
|
echo "::warning::setup-python failed (likely CDN flake), waiting 30s before retry..."
|
|
sleep 30
|
|
|
|
- name: Set up Python (retry)
|
|
if: steps.setup_python.outcome == 'failure'
|
|
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
|
|
with:
|
|
python-version: "3.12"
|
|
|
|
- name: Set up Node
|
|
id: setup_node
|
|
continue-on-error: true
|
|
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
|
with:
|
|
node-version: "22"
|
|
|
|
- name: Wait before Node retry
|
|
if: steps.setup_node.outcome == 'failure'
|
|
shell: bash
|
|
run: |
|
|
echo "::warning::setup-node failed (likely CDN flake), waiting 30s before retry..."
|
|
sleep 30
|
|
|
|
- name: Set up Node (retry)
|
|
if: steps.setup_node.outcome == 'failure'
|
|
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
|
with:
|
|
node-version: "22"
|
|
|
|
- name: Cache cargo
|
|
uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
|
|
continue-on-error: true # cache is an optimisation; never block on a stuck/slow restore
|
|
timeout-minutes: 6
|
|
|
|
- name: Warm cargo registry (retry transient DNS/registry flakes)
|
|
shell: bash
|
|
# See the rust job: pre-fetch with backoff so the clippy index update
|
|
# can't fail the job on a transient "Could not resolve host" DNS blip.
|
|
run: |
|
|
for attempt in 1 2 3 4 5; do
|
|
if cargo fetch; then exit 0; fi
|
|
echo "::warning::cargo fetch failed (attempt $attempt/5) — likely a registry/DNS flake; retrying in $((attempt * 20))s..."
|
|
sleep $((attempt * 20))
|
|
done
|
|
echo "::error::cargo fetch still failing after 5 attempts"
|
|
exit 1
|
|
|
|
- name: Clippy (bindings, all targets)
|
|
run: cargo clippy -p wickra-node -p wickra-python --all-targets -- -D warnings
|
|
|
|
# Verify the crates still build and test on their declared minimum supported
|
|
# Rust version. The workspace pins rust-version = "1.86" — that floor is
|
|
# set by criterion 0.8.2 (the bench dev-dep), which itself rolled past the
|
|
# clap_lex 1.1.0 / edition2024 / Rust 1.85 floor and now needs 1.86; the
|
|
# earlier rayon-core 1.13.0 (1.80) and clap_lex (1.85) requirements are
|
|
# subsumed. bindings/node pins rust-version = "1.88" because napi-build
|
|
# 2.3.2 requires it (and that subsumes the older 1.77 floor needed for
|
|
# `cargo::` directives). Without this job an accidental use of a newer
|
|
# API would only surface for downstream users.
|
|
msrv:
|
|
name: ${{ matrix.name }}
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 20 # backstop: cap a wedged job instead of GitHub's 6h default (slowest real job ~5 min)
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
include:
|
|
- name: MSRV workspace (Rust 1.86)
|
|
toolchain: "1.86"
|
|
packages: "-p wickra-core -p wickra -p wickra-data"
|
|
- name: MSRV node binding (Rust 1.88)
|
|
toolchain: "1.88"
|
|
packages: "-p wickra-node"
|
|
steps:
|
|
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
|
with:
|
|
persist-credentials: false
|
|
|
|
- name: Install Rust ${{ matrix.toolchain }}
|
|
uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable branch, 2026-03-27
|
|
with:
|
|
toolchain: ${{ matrix.toolchain }}
|
|
|
|
- name: Cache cargo
|
|
uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
|
|
continue-on-error: true # cache is an optimisation; never block on a stuck/slow restore
|
|
timeout-minutes: 6
|
|
|
|
- name: Warm cargo registry (retry transient DNS/registry flakes)
|
|
shell: bash
|
|
# See the rust job: pre-fetch with backoff so the first cargo step can't
|
|
# fail the job on a transient "Could not resolve host" DNS blip.
|
|
run: |
|
|
for attempt in 1 2 3 4 5; do
|
|
if cargo fetch; then exit 0; fi
|
|
echo "::warning::cargo fetch failed (attempt $attempt/5) — likely a registry/DNS flake; retrying in $((attempt * 20))s..."
|
|
sleep $((attempt * 20))
|
|
done
|
|
echo "::error::cargo fetch still failing after 5 attempts"
|
|
exit 1
|
|
|
|
- name: Build on MSRV
|
|
run: cargo build ${{ matrix.packages }} --verbose
|
|
|
|
- name: Test on MSRV
|
|
run: cargo test ${{ matrix.packages }} --verbose
|
|
|
|
# Code coverage for the pure-Rust crates, uploaded to Codecov.
|
|
coverage:
|
|
name: Coverage
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 20 # backstop: cap a wedged job instead of GitHub's 6h default (slowest real job ~5 min)
|
|
steps:
|
|
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
|
with:
|
|
persist-credentials: false
|
|
|
|
- name: Install Rust toolchain
|
|
uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable branch, 2026-03-27
|
|
with:
|
|
components: llvm-tools-preview
|
|
|
|
- name: Cache cargo
|
|
uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
|
|
continue-on-error: true # cache is an optimisation; never block on a stuck/slow restore
|
|
timeout-minutes: 6
|
|
|
|
- name: Install cargo-llvm-cov
|
|
uses: taiki-e/install-action@59012be0884e296ca2da49b530610e72c49039ad # v2.81.6
|
|
timeout-minutes: 10 # fail fast on a stuck download instead of hanging the job
|
|
with:
|
|
tool: cargo-llvm-cov
|
|
|
|
- name: Generate coverage (lcov)
|
|
run: >
|
|
cargo llvm-cov
|
|
-p wickra-core -p wickra -p wickra-data
|
|
--features wickra-data/live-binance
|
|
--lcov --output-path lcov.info
|
|
|
|
- name: Upload to Codecov
|
|
uses: codecov/codecov-action@e79a6962e0d4c0c17b229090214935d2e33f8354 # v6.0.1
|
|
with:
|
|
files: lcov.info
|
|
fail_ci_if_error: false
|
|
env:
|
|
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
|
|
|
# Supply-chain audit: security advisories, license policy, banned crates,
|
|
# and source restrictions. Configured by deny.toml at the repo root.
|
|
supply-chain:
|
|
name: Supply-chain (cargo-deny)
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 20 # backstop: cap a wedged job instead of GitHub's 6h default (slowest real job ~5 min)
|
|
steps:
|
|
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
|
with:
|
|
persist-credentials: false
|
|
|
|
- name: cargo-deny
|
|
uses: EmbarkStudios/cargo-deny-action@bb137d7af7e4fb67e5f82a49c4fce4fad40782fe # v2.0.20
|
|
with:
|
|
command: check
|
|
|
|
# Time-boxed fuzz smoke. Each target runs for ~30 s with libfuzzer; any panic
|
|
# fails the job. The goal is to catch a regression in the harness (e.g. a
|
|
# newly added indicator that panics on a particular input shape), not to
|
|
# discover novel bugs — long fuzz campaigns should be run on dedicated
|
|
# infrastructure with persistent corpora.
|
|
fuzz-smoke:
|
|
name: Fuzz (smoke)
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 20 # backstop: cap a wedged job instead of GitHub's 6h default (slowest real job ~5 min)
|
|
steps:
|
|
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
|
with:
|
|
persist-credentials: false
|
|
|
|
- name: Install nightly Rust
|
|
uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable branch, 2026-03-27
|
|
with:
|
|
toolchain: nightly
|
|
|
|
- name: Cache cargo
|
|
uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
|
|
continue-on-error: true # cache is an optimisation; never block on a stuck/slow restore
|
|
timeout-minutes: 6
|
|
with:
|
|
workspaces: fuzz
|
|
|
|
- name: Install cargo-fuzz
|
|
# Use the prebuilt binary from taiki-e/install-action instead of
|
|
# `cargo install cargo-fuzz --locked`. The latter resolves the
|
|
# version graph from cargo-fuzz's own Cargo.lock, which pins to
|
|
# rustix 0.36.5 — a version that still uses internal `rustc_*`
|
|
# attributes the modern nightly compiler rejects, so the install
|
|
# never gets off the ground. The prebuilt binary avoids the entire
|
|
# transitive-dep compile.
|
|
uses: taiki-e/install-action@59012be0884e296ca2da49b530610e72c49039ad # v2.81.6
|
|
timeout-minutes: 10 # fail fast on a stuck download instead of hanging the job
|
|
with:
|
|
tool: cargo-fuzz
|
|
|
|
- name: Fuzz csv_reader (30 s)
|
|
run: cargo +nightly fuzz run --target x86_64-unknown-linux-gnu csv_reader -- -max_total_time=30
|
|
working-directory: fuzz
|
|
|
|
- name: Fuzz binance_envelope (30 s)
|
|
run: cargo +nightly fuzz run --target x86_64-unknown-linux-gnu binance_envelope -- -max_total_time=30
|
|
working-directory: fuzz
|
|
|
|
- name: Fuzz indicator_update (30 s)
|
|
run: cargo +nightly fuzz run --target x86_64-unknown-linux-gnu indicator_update -- -max_total_time=30
|
|
working-directory: fuzz
|
|
|
|
- name: Fuzz indicator_update_candle (30 s)
|
|
run: cargo +nightly fuzz run --target x86_64-unknown-linux-gnu indicator_update_candle -- -max_total_time=30
|
|
working-directory: fuzz
|
|
|
|
- name: Fuzz tick_aggregator (30 s)
|
|
run: cargo +nightly fuzz run --target x86_64-unknown-linux-gnu tick_aggregator -- -max_total_time=30
|
|
working-directory: fuzz
|
|
|
|
python:
|
|
name: Python ${{ matrix.python-version }} on ${{ matrix.os }}
|
|
runs-on: ${{ matrix.os }}
|
|
timeout-minutes: 20 # backstop: cap a wedged job instead of GitHub's 6h default (slowest real job ~5 min)
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
os: [ubuntu-latest, macos-latest, windows-latest]
|
|
python-version: ["3.9", "3.11", "3.12", "3.13"]
|
|
steps:
|
|
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
|
with:
|
|
persist-credentials: false
|
|
|
|
- name: Install Rust toolchain
|
|
uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable branch, 2026-03-27
|
|
|
|
- name: Cache cargo
|
|
uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
|
|
continue-on-error: true # cache is an optimisation; never block on a stuck/slow restore
|
|
timeout-minutes: 6
|
|
|
|
# setup-python downloads the interpreter from the Actions tool cache /
|
|
# nodejs CDN and occasionally hangs or 5xx's on the Windows runners.
|
|
# Run it with continue-on-error, then retry once after a backoff so a
|
|
# single CDN flake does not fail the whole job (see also: GitHub
|
|
# Actions runner-images#7061).
|
|
- name: Set up Python
|
|
id: setup_python
|
|
continue-on-error: true
|
|
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
|
with:
|
|
python-version: ${{ matrix.python-version }}
|
|
cache: pip
|
|
cache-dependency-path: .github/requirements/ci-dev-*.txt
|
|
|
|
- name: Wait before Python retry
|
|
if: steps.setup_python.outcome == 'failure'
|
|
shell: bash
|
|
run: |
|
|
echo "::warning::setup-python failed (likely CDN flake), waiting 30s before retry..."
|
|
sleep 30
|
|
|
|
- name: Set up Python (retry)
|
|
if: steps.setup_python.outcome == 'failure'
|
|
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
|
with:
|
|
python-version: ${{ matrix.python-version }}
|
|
cache: pip
|
|
cache-dependency-path: .github/requirements/ci-dev-*.txt
|
|
|
|
- name: Install Python dev dependencies
|
|
shell: bash
|
|
run: |
|
|
python -m pip install --upgrade pip
|
|
# Hash-locked dev tooling (OpenSSF Scorecard PinnedDependencies).
|
|
# Split by Python version: numpy ships no single release with wheels
|
|
# for both cp39 and cp313 (<=2.0.2 has cp39 only, >=2.1 drops cp39).
|
|
if [ "${{ matrix.python-version }}" = "3.9" ]; then
|
|
python -m pip install --require-hashes -r .github/requirements/ci-dev-py39.txt
|
|
else
|
|
python -m pip install --require-hashes -r .github/requirements/ci-dev-py3.txt
|
|
fi
|
|
|
|
- name: Build wheel
|
|
working-directory: bindings/python
|
|
run: maturin build --release --out dist
|
|
|
|
- name: Install wheel
|
|
shell: bash
|
|
working-directory: bindings/python
|
|
# --no-index forces pip to ignore PyPI; --no-deps skips re-resolving
|
|
# numpy (already installed in the previous step). Without --no-index
|
|
# pip prefers the PyPI 0.2.x wheel over our freshly built one when
|
|
# platform tags overlap (e.g. macOS arm64), so tests would run
|
|
# against the released package and miss any new symbols the PR adds.
|
|
run: python -m pip install --no-index --find-links dist --force-reinstall --no-deps wickra
|
|
|
|
- name: Run Python tests
|
|
working-directory: bindings/python
|
|
run: pytest -v
|
|
|
|
wasm:
|
|
name: WASM build
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 20 # backstop: cap a wedged job instead of GitHub's 6h default (slowest real job ~5 min)
|
|
steps:
|
|
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
|
with:
|
|
persist-credentials: false
|
|
|
|
- name: Install Rust toolchain (with wasm target)
|
|
uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable branch, 2026-03-27
|
|
with:
|
|
targets: wasm32-unknown-unknown
|
|
|
|
- name: Cache cargo
|
|
uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
|
|
continue-on-error: true # cache is an optimisation; never block on a stuck/slow restore
|
|
timeout-minutes: 6
|
|
|
|
- name: Install wasm-pack
|
|
# jetli/wasm-pack-action@v0.4.0 with no `version:` input installs an
|
|
# old wasm-pack (~0.10.x) whose `build` subcommand does not yet accept
|
|
# `--features`, so `wasm-pack build … --features panic-hook` fails
|
|
# with "Found argument '--features' which wasn't expected". Use the
|
|
# same taiki-e prebuilt-binary installer we already use for
|
|
# cargo-llvm-cov and cargo-fuzz; it tracks the latest wasm-pack
|
|
# release, which has `--features` as a top-level flag (since 0.12).
|
|
uses: taiki-e/install-action@59012be0884e296ca2da49b530610e72c49039ad # v2.81.6
|
|
timeout-minutes: 10 # fail fast on a stuck download instead of hanging the job
|
|
with:
|
|
tool: wasm-pack
|
|
|
|
- name: Build WASM package
|
|
run: wasm-pack build bindings/wasm --target web --release --features panic-hook
|
|
|
|
- name: Run WASM tests
|
|
run: wasm-pack test --node bindings/wasm
|
|
|
|
- name: Verify generated artefacts
|
|
run: |
|
|
test -f bindings/wasm/pkg/wickra_wasm.js
|
|
test -f bindings/wasm/pkg/wickra_wasm_bg.wasm
|
|
test -f bindings/wasm/pkg/wickra_wasm.d.ts
|
|
|
|
node:
|
|
name: Node ${{ matrix.node-version }} on ${{ matrix.os }}
|
|
runs-on: ${{ matrix.os }}
|
|
timeout-minutes: 20 # backstop: cap a wedged job instead of GitHub's 6h default (slowest real job ~5 min)
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
os: [ubuntu-latest, macos-latest, windows-latest]
|
|
node-version: ["22", "24"]
|
|
steps:
|
|
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
|
with:
|
|
persist-credentials: false
|
|
|
|
- name: Install Rust toolchain
|
|
uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable branch, 2026-03-27
|
|
|
|
- name: Cache cargo
|
|
uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
|
|
continue-on-error: true # cache is an optimisation; never block on a stuck/slow restore
|
|
timeout-minutes: 6
|
|
|
|
# setup-node downloads Node from nodejs.org and we've seen it fail on
|
|
# Windows runners with "Attempting to download 18..." followed by a
|
|
# silent hang or curl error. Retry once after a backoff so a single
|
|
# CDN flake does not fail the whole job.
|
|
- name: Set up Node
|
|
id: setup_node
|
|
continue-on-error: true
|
|
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
|
with:
|
|
node-version: ${{ matrix.node-version }}
|
|
cache: npm
|
|
cache-dependency-path: bindings/node/package-lock.json
|
|
|
|
- name: Wait before Node retry
|
|
if: steps.setup_node.outcome == 'failure'
|
|
shell: bash
|
|
run: |
|
|
echo "::warning::setup-node failed (likely CDN flake), waiting 30s before retry..."
|
|
sleep 30
|
|
|
|
- name: Set up Node (retry)
|
|
if: steps.setup_node.outcome == 'failure'
|
|
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
|
with:
|
|
node-version: ${{ matrix.node-version }}
|
|
cache: npm
|
|
cache-dependency-path: bindings/node/package-lock.json
|
|
|
|
- name: Install Node dependencies
|
|
working-directory: bindings/node
|
|
run: npm ci
|
|
|
|
- name: Build native module
|
|
working-directory: bindings/node
|
|
# --platform puts the target triple into the filename so the loader's
|
|
# `wickra.<target>.node` lookup finds the freshly built binary instead
|
|
# of falling back to the per-platform npm subpackage (which doesn't
|
|
# exist yet for win32-x64-msvc).
|
|
run: npx napi build --platform --release
|
|
|
|
# The Node test process has wedged on macOS runners (the step hung for
|
|
# 1h+ while the same tests passed in ~1.5 min elsewhere). Wrap it so a
|
|
# hung attempt is killed after 6 min and retried once, rather than
|
|
# running up to the job-level backstop. A normal run is under a minute.
|
|
- name: Run Node tests
|
|
uses: nick-fields/retry@ad984534de44a9489a53aefd81eb77f87c70dc60 # v4.0.0
|
|
with:
|
|
timeout_minutes: 6
|
|
max_attempts: 2
|
|
command: cd bindings/node && node --test
|
|
shell: bash
|
|
|
|
c-abi:
|
|
name: C ABI on ${{ matrix.os }}
|
|
runs-on: ${{ matrix.os }}
|
|
timeout-minutes: 20 # backstop: cap a wedged job instead of GitHub's 6h default (slowest real job ~5 min)
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
os: [ubuntu-latest, macos-latest, windows-latest]
|
|
steps:
|
|
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
|
with:
|
|
persist-credentials: false
|
|
|
|
- name: Install Rust toolchain
|
|
uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable branch, 2026-03-27
|
|
|
|
- name: Cache cargo
|
|
uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
|
|
continue-on-error: true # cache is an optimisation; never block on a stuck/slow restore
|
|
timeout-minutes: 6
|
|
|
|
- name: Install cbindgen
|
|
uses: taiki-e/install-action@59012be0884e296ca2da49b530610e72c49039ad # v2.81.6
|
|
timeout-minutes: 10 # fail fast on a stuck download instead of hanging the job
|
|
with:
|
|
tool: cbindgen
|
|
|
|
- name: Build the C ABI library (cdylib + staticlib)
|
|
run: cargo build -p wickra-c --release
|
|
|
|
- name: Rust unit tests
|
|
run: cargo test -p wickra-c
|
|
|
|
# The generated header is platform-independent, so checking drift on one OS
|
|
# is enough — and avoids a spurious CRLF/LF diff on the Windows runner.
|
|
- name: Check the committed header is in sync with cbindgen
|
|
if: runner.os == 'Linux'
|
|
shell: bash
|
|
run: |
|
|
cbindgen --config bindings/c/cbindgen.toml --crate wickra-c --output bindings/c/include/wickra.h
|
|
if ! git diff --quiet -- bindings/c/include/wickra.h; then
|
|
echo "::error::bindings/c/include/wickra.h is out of sync — run cbindgen and commit the result"
|
|
git --no-pager diff -- bindings/c/include/wickra.h
|
|
exit 1
|
|
fi
|
|
|
|
# The real cross-language test: a foreign C consumer links the generated
|
|
# header + the compiled library and runs. If this passes on all three OSes,
|
|
# every C-capable language can link the same way.
|
|
- name: Build and run the C smoke example (CMake + ctest)
|
|
shell: bash
|
|
run: |
|
|
cmake -S examples/c -B examples/c/build
|
|
cmake --build examples/c/build --config Release
|
|
ctest --test-dir examples/c/build -C Release --output-on-failure
|
|
|
|
csharp:
|
|
name: C# on ${{ matrix.os }}
|
|
runs-on: ${{ matrix.os }}
|
|
timeout-minutes: 20 # backstop: cap a wedged job instead of GitHub's 6h default (slowest real job ~5 min)
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
os: [ubuntu-latest, macos-latest, windows-latest]
|
|
steps:
|
|
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
|
with:
|
|
persist-credentials: false
|
|
|
|
- name: Install Rust toolchain
|
|
uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable branch, 2026-03-27
|
|
|
|
- name: Cache cargo
|
|
uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
|
|
continue-on-error: true # cache is an optimisation; never block on a stuck/slow restore
|
|
timeout-minutes: 6
|
|
|
|
# The binding links against the C ABI hub at runtime; build it first so the
|
|
# DllImportResolver finds target/release/wickra.{dll,so,dylib}. .NET 8 SDK is
|
|
# preinstalled on the GitHub runners, so no setup-dotnet step is needed.
|
|
- name: Build the C ABI library
|
|
run: cargo build -p wickra-c --release
|
|
|
|
- name: .NET info
|
|
run: dotnet --info
|
|
|
|
- name: Test the C# binding
|
|
run: dotnet test bindings/csharp/Wickra.Tests/Wickra.Tests.csproj -c Release
|
|
|
|
- name: Build the C# examples
|
|
shell: bash
|
|
run: |
|
|
for d in streaming backtest multi_timeframe parallel_assets \
|
|
strategy_rsi_mean_reversion strategy_macd_adx strategy_bollinger_squeeze \
|
|
fetch_btcusdt live_binance; do
|
|
dotnet build "examples/csharp/$d" -c Release
|
|
done
|
|
|
|
# Run only the offline examples (fetch_btcusdt / live_binance need network).
|
|
- name: Run the offline C# examples
|
|
shell: bash
|
|
run: |
|
|
for d in streaming backtest multi_timeframe parallel_assets \
|
|
strategy_rsi_mean_reversion strategy_macd_adx strategy_bollinger_squeeze; do
|
|
dotnet run --project "examples/csharp/$d" -c Release --no-build
|
|
done
|
|
|
|
go:
|
|
name: Go on ${{ matrix.os }}
|
|
runs-on: ${{ matrix.os }}
|
|
timeout-minutes: 20 # backstop: cap a wedged job instead of GitHub's 6h default (slowest real job ~5 min)
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
os: [ubuntu-latest, macos-latest, windows-latest]
|
|
steps:
|
|
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
|
with:
|
|
persist-credentials: false
|
|
|
|
# Go is not reliably on PATH on every runner image, so install it
|
|
# explicitly. cache: false — the cargo build dominates and the modules
|
|
# have no external Go deps worth caching.
|
|
- name: Set up Go
|
|
id: setup-go
|
|
continue-on-error: true
|
|
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
|
|
with:
|
|
go-version: "stable"
|
|
cache: false
|
|
|
|
- name: Retry Go setup (CDN flake)
|
|
if: steps.setup-go.outcome == 'failure'
|
|
shell: bash
|
|
run: |
|
|
echo "::warning::setup-go failed (likely CDN flake), waiting 30s before retry..."
|
|
sleep 30
|
|
|
|
- name: Set up Go (retry)
|
|
if: steps.setup-go.outcome == 'failure'
|
|
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
|
|
with:
|
|
go-version: "stable"
|
|
cache: false
|
|
|
|
- name: Install Rust toolchain
|
|
uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable branch, 2026-03-27
|
|
|
|
- name: Cache cargo
|
|
uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
|
|
continue-on-error: true # cache is an optimisation; never block on a stuck/slow restore
|
|
timeout-minutes: 6
|
|
|
|
# The binding links against the C ABI hub via cgo; build it first and stage
|
|
# the platform library under bindings/go/lib so cgo's LDFLAGS find it. Go is
|
|
# preinstalled on the GitHub runners, so no setup-go step is needed.
|
|
- name: Build the C ABI library
|
|
run: cargo build -p wickra-c --release
|
|
|
|
- name: Vendored header in sync with the C ABI
|
|
shell: bash
|
|
# bindings/go/include/wickra.h is a committed copy of the cbindgen header
|
|
# (the parent ../c/include is outside the Go module, so it must be
|
|
# vendored). Fail if it drifts from the source of truth.
|
|
run: |
|
|
if ! diff -u bindings/c/include/wickra.h bindings/go/include/wickra.h; then
|
|
echo "::error::bindings/go/include/wickra.h is stale — copy bindings/c/include/wickra.h over it"
|
|
exit 1
|
|
fi
|
|
|
|
- name: Stage the native library
|
|
shell: bash
|
|
# Stage into lib/<goos>_<goarch>/ to match the per-platform cgo LDFLAGS.
|
|
# CI builds the host target, so RUNNER_OS/ARCH give the right directory
|
|
# (note macos-latest is arm64).
|
|
run: |
|
|
case "$RUNNER_ARCH" in
|
|
X64) arch=amd64 ;;
|
|
ARM64) arch=arm64 ;;
|
|
*) echo "::error::unsupported RUNNER_ARCH '$RUNNER_ARCH'"; exit 1 ;;
|
|
esac
|
|
case "$RUNNER_OS" in
|
|
Linux) dir="linux_$arch"; lib=target/release/libwickra.so ;;
|
|
macOS) dir="darwin_$arch"; lib=target/release/libwickra.dylib ;;
|
|
Windows) dir="windows_$arch"; lib=target/release/wickra.dll ;;
|
|
esac
|
|
mkdir -p "bindings/go/lib/$dir"
|
|
cp "$lib" "bindings/go/lib/$dir/"
|
|
echo "WICKRA_GO_LIBDIR=$PWD/bindings/go/lib/$dir" >> "$GITHUB_ENV"
|
|
|
|
- name: Go info
|
|
run: go version
|
|
|
|
- name: Check gofmt
|
|
shell: bash
|
|
run: |
|
|
unformatted="$(gofmt -l bindings/go examples/go)"
|
|
if [ -n "$unformatted" ]; then
|
|
echo "gofmt needed on:"; echo "$unformatted"; exit 1
|
|
fi
|
|
|
|
- name: Vet and test the Go binding
|
|
shell: bash
|
|
# On Windows there is no rpath; the loader resolves wickra.dll via PATH
|
|
# (WICKRA_GO_LIBDIR is the per-platform staged lib dir). Linux/macOS use
|
|
# the rpath baked by the per-platform cgo LDFLAGS.
|
|
run: |
|
|
export PATH="$WICKRA_GO_LIBDIR:$PATH"
|
|
cd bindings/go
|
|
go vet ./...
|
|
go test ./...
|
|
|
|
- name: Build the Go examples
|
|
shell: bash
|
|
run: |
|
|
export PATH="$WICKRA_GO_LIBDIR:$PATH"
|
|
cd examples/go
|
|
go build ./...
|
|
|
|
# Run only the offline examples (fetch_btcusdt / live_binance need network).
|
|
- name: Run the offline Go examples
|
|
shell: bash
|
|
run: |
|
|
export PATH="$WICKRA_GO_LIBDIR:$PATH"
|
|
cd examples/go
|
|
for d in streaming backtest multi_timeframe parallel_assets \
|
|
strategy_rsi_mean_reversion strategy_macd_adx strategy_bollinger_squeeze; do
|
|
go run "./$d"
|
|
done
|
|
|
|
r:
|
|
name: R on ${{ matrix.os }}
|
|
runs-on: ${{ matrix.os }}
|
|
timeout-minutes: 20 # backstop: cap a wedged job instead of GitHub's 6h default (slowest real job ~5 min)
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
os: [ubuntu-latest, macos-latest, windows-latest]
|
|
env:
|
|
WICKRA_INCLUDE_DIR: ${{ github.workspace }}/bindings/c/include
|
|
WICKRA_LIB_DIR: ${{ github.workspace }}/target/release
|
|
steps:
|
|
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
|
with:
|
|
persist-credentials: false
|
|
|
|
- name: Install Rust toolchain
|
|
uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable branch, 2026-03-27
|
|
|
|
- name: Cache cargo
|
|
uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
|
|
continue-on-error: true
|
|
timeout-minutes: 6
|
|
|
|
# The binding compiles a thin .Call glue layer against the C ABI hub; build
|
|
# the library first. On Windows configure.win bundles a renamed copy
|
|
# (wickra_abi.dll) so the package's own wickra.dll does not collide with it.
|
|
- name: Build the C ABI library
|
|
run: cargo build -p wickra-c --release
|
|
|
|
- name: Set up R
|
|
uses: r-lib/actions/setup-r@a51a8012b0aab7c32ef9d19bf54da93f3254335e # v2
|
|
with:
|
|
r-version: "release"
|
|
use-public-rspm: true
|
|
|
|
# Use the repos configured by setup-r (use-public-rspm) so Linux installs
|
|
# binary packages — building testthat's deps from source is slow and flaky.
|
|
- name: Install test dependency
|
|
run: Rscript -e 'install.packages("testthat")'
|
|
|
|
- name: Install and test the R binding
|
|
shell: bash
|
|
# github.workspace is a backslash path on Windows; configure(.win) (sh) and
|
|
# mingw need forward slashes. WICKRA_*_DIR makes configure use the locally
|
|
# built C ABI (dev override) instead of downloading the release asset, so
|
|
# CI is version-independent. Deliberately do NOT set LD_LIBRARY_PATH /
|
|
# DYLD_LIBRARY_PATH: the lib is bundled into the package and must resolve
|
|
# via the rpath ($ORIGIN / @loader_path) baked by configure — that is the
|
|
# self-contained install path real users (and r-universe) get.
|
|
run: |
|
|
export WICKRA_INCLUDE_DIR="${WICKRA_INCLUDE_DIR//\\//}"
|
|
export WICKRA_LIB_DIR="${WICKRA_LIB_DIR//\\//}"
|
|
R CMD INSTALL bindings/r
|
|
Rscript -e 'library(testthat); library(wickra); test_dir("bindings/r/tests/testthat", stop_on_failure = TRUE)'
|
|
|
|
- name: Build the vignette code
|
|
shell: bash
|
|
# The getting-started vignette runs at R CMD check time on r-universe /
|
|
# CRAN (with pandoc); this job only INSTALLs, so execute the vignette's R
|
|
# chunks here (knit, no pandoc needed) to catch a broken example before it
|
|
# reaches the published build.
|
|
run: |
|
|
Rscript -e 'install.packages("knitr", repos = Sys.getenv("RSPM", unset = "https://cloud.r-project.org"))'
|
|
Rscript -e 'knitr::knit("bindings/r/vignettes/getting-started.Rmd", output = tempfile(fileext = ".md"), quiet = TRUE); cat("vignette code OK\n")'
|
|
|
|
- name: Run the offline R examples
|
|
shell: bash
|
|
# No loader-path exports: the installed package is self-contained (bundled
|
|
# lib + rpath), so the examples exercise exactly what end users run.
|
|
run: |
|
|
cd examples/r
|
|
for f in streaming backtest multi_timeframe parallel_assets \
|
|
strategy_rsi_mean_reversion strategy_macd_adx strategy_bollinger_squeeze; do
|
|
Rscript "$f.R"
|
|
done
|
|
|
|
# fetch_btcusdt / live_binance need the network (and jsonlite / websocket);
|
|
# build-check that they parse without running them.
|
|
- name: Parse the network R examples
|
|
run: Rscript -e 'invisible(lapply(c("examples/r/fetch_btcusdt.R", "examples/r/live_binance.R"), parse)); cat("network R examples parse OK\n")'
|
|
|
|
java:
|
|
name: Java on ${{ matrix.os }}
|
|
runs-on: ${{ matrix.os }}
|
|
timeout-minutes: 20 # backstop: cap a wedged job instead of GitHub's 6h default (slowest real job ~5 min)
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
os: [ubuntu-latest, macos-latest, windows-latest]
|
|
steps:
|
|
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
|
with:
|
|
persist-credentials: false
|
|
|
|
- name: Install Rust toolchain
|
|
uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable branch, 2026-03-27
|
|
|
|
- name: Cache cargo
|
|
uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
|
|
continue-on-error: true # cache is an optimisation; never block on a stuck/slow restore
|
|
timeout-minutes: 6
|
|
|
|
# The binding links the C ABI hub at runtime through the Java FFM API; build
|
|
# it first so WickraNative's development fallback finds
|
|
# target/release/wickra.{so,dylib,dll}. The FFM API is final since JDK 22; we
|
|
# build on the 25 LTS (22 is EOL; the pom pins bytecode to release 22 so the
|
|
# runtime floor stays Java 22). Installed explicitly (runners ship 17/21).
|
|
- name: Build the C ABI library
|
|
run: cargo build -p wickra-c --release
|
|
|
|
- name: Set up JDK 25
|
|
id: setup-java
|
|
continue-on-error: true
|
|
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
|
|
with:
|
|
distribution: temurin
|
|
java-version: "25"
|
|
cache: maven
|
|
|
|
- name: Retry JDK setup (CDN flake)
|
|
if: steps.setup-java.outcome == 'failure'
|
|
shell: bash
|
|
run: |
|
|
echo "::warning::setup-java failed (likely CDN flake), waiting 30s before retry..."
|
|
sleep 30
|
|
|
|
- name: Set up JDK 25 (retry)
|
|
if: steps.setup-java.outcome == 'failure'
|
|
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
|
|
with:
|
|
distribution: temurin
|
|
java-version: "25"
|
|
cache: maven
|
|
|
|
- name: Java info
|
|
run: java -version
|
|
|
|
# `install` runs the archetype test suite (the real FFI boundary check) and
|
|
# installs the binding to the local repo so the examples can resolve it.
|
|
- name: Test and install the Java binding
|
|
run: mvn -B -f bindings/java install
|
|
|
|
- name: Build the Java examples
|
|
run: mvn -B -f examples/java compile
|
|
|
|
# Run only the offline examples (fetch_btcusdt / live_binance need network).
|
|
- name: Run the offline Java examples
|
|
shell: bash
|
|
run: |
|
|
for cls in Streaming Backtest MultiTimeframe ParallelAssets \
|
|
StrategyRsiMeanReversion StrategyMacdAdx StrategyBollingerSqueeze; do
|
|
mvn -B -q -f examples/java exec:exec -Dexec.mainClass="org.wickra.examples.$cls"
|
|
done
|
|
|
|
# The cross-library benchmark has moved to a dedicated scheduled workflow
|
|
# (.github/workflows/bench.yml) — see audit finding R10. It runs nightly
|
|
# at 03:00 UTC and on-demand via `workflow_dispatch`, and is no longer on
|
|
# the every-push / every-PR critical path.
|