Files
dependabot[bot] 350230cd07 deps(actions): bump the github-actions group with 9 updates (#354)
Bumps the github-actions group with 9 updates:

| Package | From | To |
| --- | --- | --- |
| [actions/setup-python](https://github.com/actions/setup-python) | `6.2.0` | `6.3.0` |
| [taiki-e/install-action](https://github.com/taiki-e/install-action) | `2.82.1` | `2.82.5` |
| [actions/cache](https://github.com/actions/cache) | `5.0.5` | `6.1.0` |
| [actions/setup-go](https://github.com/actions/setup-go) | `6.4.0` | `6.5.0` |
| [r-lib/actions/setup-r](https://github.com/r-lib/actions) | `2.12.0` | `2.12.1` |
| [r-lib/actions/setup-r-dependencies](https://github.com/r-lib/actions) | `2.12.0` | `2.12.1` |
| [actions/setup-java](https://github.com/actions/setup-java) | `5.3.0` | `5.4.0` |
| [actions/attest-build-provenance](https://github.com/actions/attest-build-provenance) | `4.1.0` | `4.1.1` |
| [zizmorcore/zizmor-action](https://github.com/zizmorcore/zizmor-action) | `0.5.6` | `0.5.7` |


Updates `actions/setup-python` from 6.2.0 to 6.3.0
- [Release notes](https://github.com/actions/setup-python/releases)
- [Commits](https://github.com/actions/setup-python/compare/a309ff8b426b58ec0e2a45f0f869d46889d02405...ece7cb06caefa5fff74198d8649806c4678c61a1)

Updates `taiki-e/install-action` from 2.82.1 to 2.82.5
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/taiki-e/install-action/compare/8b3c737da4b541bf0fb5a3e0488ff20535badac9...bffeee26d4db9be238a4ea78d8826604ebcb594d)

Updates `actions/cache` from 5.0.5 to 6.1.0
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](https://github.com/actions/cache/compare/27d5ce7f107fe9357f9df03efb73ab90386fccae...55cc8345863c7cc4c66a329aec7e433d2d1c52a9)

Updates `actions/setup-go` from 6.4.0 to 6.5.0
- [Release notes](https://github.com/actions/setup-go/releases)
- [Commits](https://github.com/actions/setup-go/compare/4a3601121dd01d1626a1e23e37211e3254c1c06c...924ae3a1cded613372ab5595356fb5720e22ba16)

Updates `r-lib/actions/setup-r` from 2.12.0 to 2.12.1
- [Release notes](https://github.com/r-lib/actions/releases)
- [Changelog](https://github.com/r-lib/actions/blob/v2-branch/NEWS.md)
- [Commits](https://github.com/r-lib/actions/compare/a51a8012b0aab7c32ef9d19bf54da93f3254335e...d3c5be51b12e724e68f33216ca3c148b66d5f0b6)

Updates `r-lib/actions/setup-r-dependencies` from 2.12.0 to 2.12.1
- [Release notes](https://github.com/r-lib/actions/releases)
- [Changelog](https://github.com/r-lib/actions/blob/v2-branch/NEWS.md)
- [Commits](https://github.com/r-lib/actions/compare/a51a8012b0aab7c32ef9d19bf54da93f3254335e...d3c5be51b12e724e68f33216ca3c148b66d5f0b6)

Updates `actions/setup-java` from 5.3.0 to 5.4.0
- [Release notes](https://github.com/actions/setup-java/releases)
- [Commits](https://github.com/actions/setup-java/compare/ad2b38190b15e4d6bdf0c97fb4fca8412226d287...1bcf9fb12cf4aa7d266a90ae39939e61372fe520)

Updates `actions/attest-build-provenance` from 4.1.0 to 4.1.1
- [Release notes](https://github.com/actions/attest-build-provenance/releases)
- [Changelog](https://github.com/actions/attest-build-provenance/blob/main/RELEASE.md)
- [Commits](https://github.com/actions/attest-build-provenance/compare/a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32...0f67c3f4856b2e3261c31976d6725780e5e4c373)

Updates `zizmorcore/zizmor-action` from 0.5.6 to 0.5.7
- [Release notes](https://github.com/zizmorcore/zizmor-action/releases)
- [Commits](https://github.com/zizmorcore/zizmor-action/compare/5f14fd08f7cf1cb1609c1e344975f152c7ee938d...192e21d79ab29983730a13d1382995c2307fbcaa)

---
updated-dependencies:
- dependency-name: actions/setup-python
  dependency-version: 6.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: github-actions
- dependency-name: taiki-e/install-action
  dependency-version: 2.82.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions
- dependency-name: actions/cache
  dependency-version: 6.1.0
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
- dependency-name: actions/setup-go
  dependency-version: 6.5.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: github-actions
- dependency-name: r-lib/actions/setup-r
  dependency-version: 2.12.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions
- dependency-name: r-lib/actions/setup-r-dependencies
  dependency-version: 2.12.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions
- dependency-name: actions/setup-java
  dependency-version: 5.4.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: github-actions
- dependency-name: actions/attest-build-provenance
  dependency-version: 4.1.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions
- dependency-name: zizmorcore/zizmor-action
  dependency-version: 0.5.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-03 18:44:18 +02:00

1168 lines
48 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: 30 # backstop: cap a wedged job instead of GitHub's 6h default (headroom for slow registry/package installs)
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- name: Install Rust toolchain
uses: ./.github/actions/setup-rust
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: 30 # backstop: cap a wedged job instead of GitHub's 6h default (headroom for slow registry/package installs)
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
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@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.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@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.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: 30 # backstop: cap a wedged job instead of GitHub's 6h default (headroom for slow registry/package installs)
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- name: Install Rust toolchain
uses: ./.github/actions/setup-rust
with:
components: clippy
- name: Set up Python
id: setup_python
continue-on-error: true
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.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@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.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: 30 # backstop: cap a wedged job instead of GitHub's 6h default (headroom for slow registry/package installs)
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@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- name: Install Rust ${{ matrix.toolchain }}
uses: ./.github/actions/setup-rust
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: 30 # backstop: cap a wedged job instead of GitHub's 6h default (headroom for slow registry/package installs)
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- name: Install Rust toolchain
uses: ./.github/actions/setup-rust
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@bffeee26d4db9be238a4ea78d8826604ebcb594d # v2.82.5
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@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
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: 30 # backstop: cap a wedged job instead of GitHub's 6h default (headroom for slow registry/package installs)
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
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: 30 # backstop: cap a wedged job instead of GitHub's 6h default (headroom for slow registry/package installs)
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- name: Install nightly Rust
uses: ./.github/actions/setup-rust
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@bffeee26d4db9be238a4ea78d8826604ebcb594d # v2.82.5
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: 30 # backstop: cap a wedged job instead of GitHub's 6h default (headroom for slow registry/package installs)
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@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- name: Install Rust toolchain
uses: ./.github/actions/setup-rust
- 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@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.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@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.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: 30 # backstop: cap a wedged job instead of GitHub's 6h default (headroom for slow registry/package installs)
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- name: Install Rust toolchain (with wasm target)
uses: ./.github/actions/setup-rust
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@bffeee26d4db9be238a4ea78d8826604ebcb594d # v2.82.5
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
- name: Build WASM package (nodejs target) for the golden suite
run: wasm-pack build bindings/wasm --target nodejs --release --out-dir pkg
- name: Golden parity — all 514 indicators vs the Rust reference
run: node --test bindings/wasm/tests/golden.test.js
node:
name: Node ${{ matrix.node-version }} on ${{ matrix.os }}
runs-on: ${{ matrix.os }}
timeout-minutes: 30 # backstop: cap a wedged job instead of GitHub's 6h default (headroom for slow registry/package installs)
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
node-version: ["22", "24"]
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- name: Install Rust toolchain
uses: ./.github/actions/setup-rust
- 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
# npm's own fetch-retry (npm_config_fetch_retries) rides out per-request
# blips; wrap the whole `npm ci` once more so a longer registry hiccup
# retries the install instead of failing the job.
- name: Install Node dependencies
uses: nick-fields/retry@ad984534de44a9489a53aefd81eb77f87c70dc60 # v4.0.0
with:
timeout_minutes: 10
max_attempts: 3
retry_wait_seconds: 20
command: cd bindings/node && npm ci
shell: bash
- 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: 30 # backstop: cap a wedged job instead of GitHub's 6h default (headroom for slow registry/package installs)
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- name: Install Rust toolchain
uses: ./.github/actions/setup-rust
- 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@bffeee26d4db9be238a4ea78d8826604ebcb594d # v2.82.5
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: 30 # backstop: cap a wedged job instead of GitHub's 6h default (headroom for slow registry/package installs)
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- name: Install Rust toolchain
uses: ./.github/actions/setup-rust
- 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
# Cache the restored NuGet packages so dotnet test/build resolve from the
# local store instead of hitting nuget.org every run. No packages.lock.json
# exists, so key on the project files; never block the job on a slow restore.
- name: Cache NuGet packages
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
continue-on-error: true
timeout-minutes: 6
with:
path: ~/.nuget/packages
key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj') }}
restore-keys: |
${{ runner.os }}-nuget-
# 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
# dotnet test restores from nuget.org first; retry so a transient restore
# blip retries instead of failing the job (the cached packages above make
# the retry cheap).
- name: Test the C# binding
uses: nick-fields/retry@ad984534de44a9489a53aefd81eb77f87c70dc60 # v4.0.0
with:
timeout_minutes: 15
max_attempts: 2
retry_wait_seconds: 20
command: dotnet test bindings/csharp/Wickra.Tests/Wickra.Tests.csproj -c Release
shell: bash
- 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: 30 # backstop: cap a wedged job instead of GitHub's 6h default (headroom for slow registry/package installs)
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
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@924ae3a1cded613372ab5595356fb5720e22ba16 # v6.5.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@924ae3a1cded613372ab5595356fb5720e22ba16 # v6.5.0
with:
go-version: "stable"
cache: false
- name: Install Rust toolchain
uses: ./.github/actions/setup-rust
- 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: 30 # backstop: cap a wedged job instead of GitHub's 6h default (headroom for slow registry/package installs)
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@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- name: Install Rust toolchain
uses: ./.github/actions/setup-rust
- 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@d3c5be51b12e724e68f33216ca3c148b66d5f0b6 # v2
with:
r-version: "release"
use-public-rspm: true
# Install the R dependencies via setup-r-dependencies: it restores a cached
# package library (actions/cache) and pulls RSPM *binaries* instead of
# compiling testthat / knitr and their deps from source — the slow, flaky
# path that previously blew past the job timeout on the ubuntu runner.
- name: Install R dependencies (cached binaries)
uses: r-lib/actions/setup-r-dependencies@d3c5be51b12e724e68f33216ca3c148b66d5f0b6 # v2
with:
working-directory: bindings/r
extra-packages: |
any::testthat
any::knitr
- 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)'
# Full R CMD check, gated on the documentation problems r-universe surfaces
# (undocumented exported objects, codoc mismatches) that R CMD INSTALL above
# does not catch — exactly what shipped stale to r-universe with the 0.9.3
# data layer. Ubuntu-only; these checks are platform-independent. Vignettes
# are skipped here (building them needs pandoc and the vignette is exercised
# in the next step), so the two --no-build-vignettes warnings are ignored.
- name: R CMD check (documentation & consistency, like r-universe)
if: matrix.os == 'ubuntu-latest'
shell: bash
run: |
export WICKRA_INCLUDE_DIR="${WICKRA_INCLUDE_DIR//\\//}"
export WICKRA_LIB_DIR="${WICKRA_LIB_DIR//\\//}"
R CMD build bindings/r --no-build-vignettes --no-manual
R CMD check wickra_*.tar.gz --no-manual --no-vignettes --no-tests || true
log=$(find . -maxdepth 2 -name 00check.log | head -1)
echo "::group::00check.log"; cat "$log"; echo "::endgroup::"
problems=$(grep -E '\.\.\. (WARNING|ERROR)' "$log" \
| grep -vE "checking (files in .vignettes.|package vignettes)" || true)
if [ -n "$problems" ]; then
echo "::error::R CMD check found problems (run roxygen2::roxygenise() in bindings/r if the docs are stale):"
echo "$problems"
exit 1
fi
echo "R CMD check: documentation and consistency clean."
- 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.
# knitr is installed by the cached setup-r-dependencies step above.
run: |
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: 30 # backstop: cap a wedged job instead of GitHub's 6h default (headroom for slow registry/package installs)
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- name: Install Rust toolchain
uses: ./.github/actions/setup-rust
- 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@1bcf9fb12cf4aa7d266a90ae39939e61372fe520 # v5.4.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@1bcf9fb12cf4aa7d266a90ae39939e61372fe520 # v5.4.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.
# Maven resolves plugins/deps from the network on a cache miss; retry so a
# transient Central blip retries instead of failing the job.
- name: Test and install the Java binding
uses: nick-fields/retry@ad984534de44a9489a53aefd81eb77f87c70dc60 # v4.0.0
with:
timeout_minutes: 20
max_attempts: 2
retry_wait_seconds: 20
command: mvn -B -f bindings/java install
shell: bash
- 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
# Build a Python wheel inside both the manylinux and the musllinux container,
# mirroring the Linux wheel build in release.yml. The 3-OS Python jobs build
# natively on the runner, which already ships system OpenSSL, so they cannot
# catch a build-time gap that only exists inside the slim release containers —
# exactly what broke the 0.9.3 Linux wheels (the live-binance data layer links
# native-tls -> openssl-sys, and the containers provide no OpenSSL: manylinux
# lacks the headers, musllinux cross-compiles against a musl sysroot without
# OpenSSL at all). The wheels are built with the `vendored-tls` feature, which
# statically compiles OpenSSL from source. This job exercises both container
# builds on every PR, so the same class of breakage now fails CI, not release.
python-wheel-container-smoke:
name: Python wheel (${{ matrix.manylinux }} smoke)
runs-on: ubuntu-latest
timeout-minutes: 30 # backstop: vendored OpenSSL adds a from-source compile
strategy:
fail-fast: false
matrix:
manylinux: [auto, musllinux_1_2]
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- name: Sync root README into bindings/python so the build matches release
run: cp README.md bindings/python/README.md
- uses: PyO3/maturin-action@e83996d129638aa358a18fbd1dfb82f0b0fb5d3b # v1.51.0
with:
working-directory: bindings/python
target: x86_64
# Keep in sync with release.yml: vendored OpenSSL for the Linux wheels.
args: --release --out dist --features vendored-tls
manylinux: ${{ matrix.manylinux }}
# Building OpenSSL from source needs Perl modules (IPC::Cmd,
# Time::Piece, ...) the minimal manylinux (CentOS 7) image lacks.
# perl-core pulls the full distribution; the explicit names document
# the ones OpenSSL's Configure has required. The musllinux cross image
# ships a complete Perl and has no yum, so this is a no-op there.
before-script-linux: |
if command -v yum >/dev/null 2>&1; then yum install -y perl-core perl-IPC-Cmd perl-Data-Dumper perl-Time-Piece; fi
# 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.
# Non-blocking external-link heads-up on PRs. The authoritative check is the
# scheduled links.yml; here it only surfaces link rot early without ever
# gating a PR — external sites flake (rate limits, transient outages, moves),
# so `continue-on-error` keeps a third-party hiccup from blocking the merge.
# Redirects are reported as warnings (lychee only fails on hard errors).
links:
name: External links (non-blocking)
runs-on: ubuntu-latest
continue-on-error: true
timeout-minutes: 10
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- uses: lycheeverse/lychee-action@8646ba30535128ac92d33dfc9133794bfdd9b411 # v2
with:
args: --config lychee.toml --no-progress --root-dir "${{ github.workspace }}" "*.md" "bindings/*/README.md"
fail: true