2026-09-18 12:03:43 -04:00
|
|
|
name: ci
|
|
|
|
|
|
|
|
|
|
# Deliberately not triggered by pull_request.
|
|
|
|
|
#
|
|
|
|
|
# The forge this runs on is public and takes pull requests from strangers, and a
|
|
|
|
|
# runner executes whatever the workflow checks out. Triggering on pull_request
|
|
|
|
|
# would mean a stranger's first contribution runs code on the runner host before
|
|
|
|
|
# anyone has read it. Triggering only on push means the runner only ever executes
|
|
|
|
|
# code that someone with push rights put there.
|
|
|
|
|
#
|
|
|
|
|
# The cost is that a pull request gets no automatic check. Run it by pushing the
|
|
|
|
|
# branch yourself once you have read the diff.
|
|
|
|
|
on:
|
|
|
|
|
push:
|
2026-09-18 12:51:45 -04:00
|
|
|
# Branches only. A tag push does not match a branch filter, so release.yml
|
|
|
|
|
# owns tags and this owns everything else.
|
2026-09-18 12:03:43 -04:00
|
|
|
branches: ['**']
|
|
|
|
|
|
|
|
|
|
jobs:
|
|
|
|
|
test:
|
|
|
|
|
runs-on: docker
|
|
|
|
|
container:
|
|
|
|
|
image: rust:1.98-bookworm
|
|
|
|
|
steps:
|
2026-09-18 12:50:56 -04:00
|
|
|
# Not actions/checkout. That is a JavaScript action: the runner executes it
|
|
|
|
|
# with `node` *inside this container*, and a Rust image carries no node, so
|
|
|
|
|
# the job dies at the first step with "node: executable file not found in
|
|
|
|
|
# $PATH". actions/cache and actions/upload-artifact are the same. Using an
|
|
|
|
|
# image that also carries node would mean installing Rust on every run;
|
|
|
|
|
# cloning by hand costs four lines and keeps the lean image.
|
|
|
|
|
- name: Checkout
|
|
|
|
|
run: |
|
|
|
|
|
git init -q .
|
|
|
|
|
git remote add origin "$GITHUB_SERVER_URL/$GITHUB_REPOSITORY"
|
|
|
|
|
git fetch -q --depth 1 origin "$GITHUB_SHA"
|
|
|
|
|
git checkout -q FETCH_HEAD
|
2026-09-18 12:03:43 -04:00
|
|
|
|
|
|
|
|
# No `cargo fmt --check`. The tables in this crate are aligned by hand —
|
|
|
|
|
# MONTHS, JURISDICTIONS — and rustfmt wants one entry per line, which loses
|
|
|
|
|
# the shape that makes them readable. A gate that fights the house style is
|
|
|
|
|
# a gate that gets ignored.
|
|
|
|
|
- name: Lint
|
|
|
|
|
run: |
|
|
|
|
|
rustup component add clippy
|
|
|
|
|
cargo clippy --all-targets -- -D warnings
|
2026-09-18 12:33:56 -04:00
|
|
|
# The binding code only exists under the feature, so a bare clippy
|
|
|
|
|
# run never looks at src/python.rs.
|
|
|
|
|
cargo clippy --features python --all-targets -- -D warnings
|
2026-09-18 14:46:25 -04:00
|
|
|
cargo clippy --features capi --all-targets -- -D warnings
|
2026-09-18 14:55:31 -04:00
|
|
|
rustup target add wasm32-unknown-unknown
|
|
|
|
|
cargo clippy --lib --features wasm --target wasm32-unknown-unknown -- -D warnings
|
2026-09-18 12:03:43 -04:00
|
|
|
|
|
|
|
|
- name: Rust tests
|
2026-09-18 14:46:25 -04:00
|
|
|
run: |
|
|
|
|
|
cargo test
|
|
|
|
|
cargo test --features capi
|
|
|
|
|
|
|
|
|
|
# The header and the LuaJIT cdef are checked against the library by the
|
|
|
|
|
# Rust tests; this checks that a C compiler agrees, which they cannot.
|
|
|
|
|
- name: C ABI
|
|
|
|
|
run: |
|
|
|
|
|
apt-get update && apt-get install -y --no-install-recommends gcc luajit
|
|
|
|
|
cargo build --release --features capi
|
|
|
|
|
cc -Wall -Wextra -Werror -Iinclude examples/c/demo.c \
|
|
|
|
|
-Ltarget/release -ltypikon -o /tmp/demo
|
|
|
|
|
LD_LIBRARY_PATH=target/release /tmp/demo rocor
|
|
|
|
|
LD_LIBRARY_PATH=target/release /tmp/demo --list
|
|
|
|
|
TYPIKON_LIB=target/release/libtypikon.so \
|
|
|
|
|
luajit examples/lua/statusline.lua rocor
|
2026-09-18 12:03:43 -04:00
|
|
|
|
|
|
|
|
# The CLI and the bindings are the same engine, so they have to agree. This
|
|
|
|
|
# is the check that caught the bindings resolving their own default
|
|
|
|
|
# jurisdiction while the CLI read the configured one.
|
|
|
|
|
- name: Python bindings
|
|
|
|
|
run: |
|
|
|
|
|
apt-get update && apt-get install -y --no-install-recommends python3 python3-venv python3-dev
|
|
|
|
|
python3 -m venv /tmp/venv
|
|
|
|
|
/tmp/venv/bin/pip install --quiet maturin pytest mypy
|
|
|
|
|
cargo build --release
|
|
|
|
|
/tmp/venv/bin/maturin build --release --out dist
|
|
|
|
|
/tmp/venv/bin/pip install --quiet --no-index --find-links dist typikon
|
|
|
|
|
/tmp/venv/bin/python -m pytest tests/python -q
|
2026-09-18 13:46:57 -04:00
|
|
|
# The bindings resolve the default jurisdiction themselves, and once
|
|
|
|
|
# read only the config file while the CLI read the environment first.
|
|
|
|
|
# The two then answered for different churches on the same machine.
|
|
|
|
|
cli=$(TYPIKON_JURISDICTION=rocor ./target/release/typikon \
|
|
|
|
|
--date 2026-03-18 --format tsv --fields jurisdiction | tail -1)
|
|
|
|
|
py=$(TYPIKON_JURISDICTION=rocor /tmp/venv/bin/python -c "import typikon, datetime; print(typikon.describe(datetime.date(2026, 3, 18)).jurisdiction)")
|
|
|
|
|
test "$cli" = "$py" || { echo "cli=$cli python=$py"; exit 1; }
|
2026-09-18 12:03:43 -04:00
|
|
|
|
|
|
|
|
- name: Type stubs
|
|
|
|
|
run: |
|
|
|
|
|
cat > /tmp/typecheck.py <<'EOF'
|
|
|
|
|
import datetime, typikon
|
|
|
|
|
d: typikon.Day = typikon.describe(datetime.date(2026, 4, 12))
|
|
|
|
|
day: datetime.date = d.date
|
|
|
|
|
feast: str | None = d.feast
|
|
|
|
|
n: int = d.days_to_pascha
|
|
|
|
|
EOF
|
|
|
|
|
/tmp/venv/bin/python -m mypy --strict /tmp/typecheck.py
|
2026-09-18 13:46:57 -04:00
|
|
|
|
2026-09-18 14:55:31 -04:00
|
|
|
# The npm package is the same engine again, so it has to agree with the
|
|
|
|
|
# command line again. The test compares a year of days field by field
|
|
|
|
|
# for four jurisdictions, and type-checks the published .d.ts the way the
|
|
|
|
|
# Python bindings are type-checked.
|
|
|
|
|
- name: JavaScript package
|
|
|
|
|
run: |
|
|
|
|
|
apt-get update && apt-get install -y --no-install-recommends nodejs npm
|
|
|
|
|
version=$(awk '/^name = "wasm-bindgen"$/{getline; gsub(/[",]/,""); print $3; exit}' Cargo.lock)
|
|
|
|
|
curl -sSfL "https://github.com/rustwasm/wasm-bindgen/releases/download/$version/wasm-bindgen-$version-x86_64-unknown-linux-musl.tar.gz" \
|
|
|
|
|
| tar xz -C /tmp
|
|
|
|
|
export PATH="/tmp/wasm-bindgen-$version-x86_64-unknown-linux-musl:$PATH"
|
|
|
|
|
npm install --global typescript@5
|
|
|
|
|
cargo build --release
|
|
|
|
|
npm/build.sh
|
|
|
|
|
node npm/test.mjs
|
|
|
|
|
|
2026-09-18 13:46:57 -04:00
|
|
|
- name: README examples
|
|
|
|
|
run: |
|
|
|
|
|
cargo build --release
|
|
|
|
|
python3 tests/readme-examples.py
|
2026-09-18 16:08:41 -04:00
|
|
|
|
|
|
|
|
# Every binding is the same engine reached by a different door, so the
|
|
|
|
|
# answers have to be identical, and the only way to know is to ask them
|
|
|
|
|
# all the same question. Twelve days chosen where they could plausibly
|
|
|
|
|
# differ, twenty-two fields each, against what the command line says.
|
|
|
|
|
#
|
|
|
|
|
# Chromium is installed for this: the web build is the one artifact whose
|
|
|
|
|
# real home is somewhere Node cannot stand in for, and loading it with
|
|
|
|
|
# Node's bytes proves the glue, not the fetch.
|
|
|
|
|
- name: Every binding agrees
|
|
|
|
|
run: |
|
|
|
|
|
apt-get install -y --no-install-recommends chromium
|
|
|
|
|
version=$(awk '/^name = "wasm-bindgen"$/{getline; gsub(/[",]/,""); print $3; exit}' Cargo.lock)
|
|
|
|
|
export PATH="/tmp/wasm-bindgen-$version-x86_64-unknown-linux-musl:$PATH"
|
|
|
|
|
tests/parity.sh
|