Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions .github/actions/setup-java/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
name: Set up Java
description: >-
Installs a Temurin JDK and enables Gradle build caching. Used by every
Java-touching job so toolchain setup lives in one place. The SDK targets Java
11 bytecode via `--release 11`, so any JDK >= 11 works; we use 21 to match the
publish workflow.

inputs:
java-version:
description: "Temurin JDK major version to install."
required: false
default: "21"

runs:
using: composite
steps:
- name: Install JDK + Gradle cache
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: ${{ inputs.java-version }}
cache: gradle
36 changes: 36 additions & 0 deletions .github/workflows/ci-java.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
name: CI Java

# Reusable workflow: build the JNI cdylib (via Gradle's cargoBuild) and run the
# Java SDK's full `gradle build` — compile, Spotless + Checkstyle, and the JUnit
# suite across the runtime, processor, and test-support subprojects. Called by
# ci.yml when Java-relevant paths change.
on:
workflow_call:

permissions:
contents: read

env:
CARGO_HTTP_MULTIPLEXING: "false"
CARGO_NET_RETRY: "10"

jobs:
java-test:
name: Java SDK Tests
runs-on: ubuntu-latest
steps:
- name: Check out repository
uses: actions/checkout@v7

- name: Set up Rust
uses: ./.github/actions/setup-rust

- name: Set up Java + Gradle
uses: ./.github/actions/setup-java

# `gradle build` runs cargoBuild (the JNI cdylib), staging the native
# library into the jar, then compile + Spotless + Checkstyle + tests for
# every subproject. --no-daemon keeps the runner clean.
- name: Build and test the Java SDK
working-directory: sdks/java
run: ./gradlew build --no-daemon
52 changes: 52 additions & 0 deletions .github/workflows/ci-node.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
name: CI Node

# Reusable workflow: build the napi addon + TypeScript + dashboard SPA, then
# typecheck, lint, and test the Node SDK. Called by ci.yml when Node-relevant
# paths change.
on:
workflow_call:

permissions:
contents: read

env:
CARGO_HTTP_MULTIPLEXING: "false"
CARGO_NET_RETRY: "10"

jobs:
node-test:
name: Node SDK Tests
runs-on: ubuntu-latest
steps:
- name: Check out repository
uses: actions/checkout@v7

- name: Set up Rust
uses: ./.github/actions/setup-rust

- name: Set up Node + pnpm
uses: ./.github/actions/setup-node

- name: Install dashboard dependencies
working-directory: dashboard
run: pnpm install --frozen-lockfile

- name: Build addon, TypeScript, and dashboard SPA
working-directory: sdks/node
run: |
pnpm run build:native
pnpm run build:ts
pnpm -C ../../dashboard exec tsr generate # route tree is gitignored
pnpm run build:dashboard

- name: Typecheck
working-directory: sdks/node
run: pnpm typecheck

- name: Lint
working-directory: sdks/node
run: pnpm lint

- name: Run Node test suite
working-directory: sdks/node
run: pnpm test
101 changes: 101 additions & 0 deletions .github/workflows/ci-python.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
name: CI Python

# Reusable workflow: Python lint (Ruff + mypy) and the cross-platform pytest
# matrix. Called by ci.yml when Python-relevant paths change.
on:
workflow_call:

permissions:
contents: read

env:
CARGO_HTTP_MULTIPLEXING: "false"
CARGO_NET_RETRY: "10"

jobs:
lint:
name: Lint (Python)
runs-on: ubuntu-latest
steps:
- name: Check out repository
uses: actions/checkout@v7

- name: Set up Python + uv
uses: ./.github/actions/setup-python

- name: Lint Python with Ruff
working-directory: sdks/python
run: uv run ruff check taskito/ tests/

- name: Check Python formatting with Ruff
working-directory: sdks/python
run: uv run ruff format --check taskito/ tests/

- name: Type-check Python with mypy
working-directory: sdks/python
run: uv run mypy taskito/ tests/ --no-incremental

test:
name: Python Tests (${{ matrix.os }} / Python ${{ matrix.python-version }})
needs: lint
runs-on: ${{ matrix.os }}
env:
# setup-python on macOS upgrades certifi via pip; pip's HTTP cache
# occasionally fails to deserialize across runner image versions and
# surfaces as a noisy "Cache entry deserialization failed" annotation.
# Disabling pip's cache eliminates the warning at zero cost (we use uv
# for actual package installs).
PIP_NO_CACHE_DIR: "1"
strategy:
matrix:
os: [ubuntu-latest]
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
include:
- os: macos-15
python-version: "3.14"
- os: macos-15
python-version: "3.10"
- os: windows-2025
python-version: "3.14"
- os: windows-2025
python-version: "3.10"
steps:
- name: Point vendored OpenSSL at Strawberry Perl (Windows)
if: runner.os == 'Windows'
shell: bash
# The postgres feature pulls openssl-sys (vendored), whose Configure
# runs perl. Under `shell: bash` the incomplete MSYS perl shadows the
# runner's Strawberry Perl and lacks modules OpenSSL needs
# (Locale::Maketext::Simple), so pin openssl-src to Strawberry Perl.
# Must precede the maturin build that `uv sync` triggers below.
run: echo "OPENSSL_SRC_PERL=C:/Strawberry/perl/bin/perl.exe" >> "$GITHUB_ENV"

- name: Check out repository
uses: actions/checkout@v7

- name: Set up Python + uv
uses: ./.github/actions/setup-python
with:
python-version: ${{ matrix.python-version }}

- name: Set up Rust
uses: ./.github/actions/setup-rust
with:
# Only the non-Linux runners save the cache here; the Linux Cargo
# cache is written by the Rust lint job.
save-if: ${{ matrix.os != 'ubuntu-latest' }}

- name: Build native extension with maturin
uses: PyO3/maturin-action@v1.51.0
with:
command: develop
args: --release --features extension-module,postgres,redis,native-async,workflows,mesh
maturin-version: v1.13.3
working-directory: sdks/python

- name: Run Python test suite
working-directory: sdks/python
# The pytest_unconfigure hook in tests/conftest.py calls
# ``os._exit(0)`` on a clean run to bypass CPython finalization and
# avoid the PyO3 daemon-thread SIGABRT we used to paper over here.
run: uv run python -m pytest tests/ -v --junitxml=test-results.xml
127 changes: 127 additions & 0 deletions .github/workflows/ci-rust.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
name: CI Rust

# Reusable workflow: Rust lint + the SQLite/PostgreSQL/Redis test matrices.
# Called by ci.yml when Rust-relevant paths change. See ci.yml for the env
# rationale (Cargo HTTP/1.1 + retry budget) — reusable workflows do not inherit
# the caller's env, so it is repeated here.
on:
workflow_call:

permissions:
contents: read

env:
CARGO_HTTP_MULTIPLEXING: "false"
CARGO_NET_RETRY: "10"

jobs:
lint:
name: Lint (Rust)
runs-on: ubuntu-latest
steps:
- name: Check out repository
uses: actions/checkout@v7

- name: Set up Python + uv
uses: ./.github/actions/setup-python

- name: Set up Rust
uses: ./.github/actions/setup-rust
with:
components: rustfmt, clippy
save-if: "true" # the Rust lint job is the sole Cargo cache writer

- name: Check Rust formatting
run: cargo fmt --all --check

- name: Run Clippy
run: cargo clippy --all-targets --all-features -- -D warnings

- name: Generic crates stay binding-free
# The engine crates (core/workflows/mesh) must remain binding-agnostic so
# the Python, Node, and Java shells can all reuse them. A pyo3, napi, OR
# jni dep would let binding-specific code creep back in. cargo tree is
# authoritative — you cannot `use pyo3`/`use napi`/`use jni` without
# depending on it. See crates/taskito-core/BINDING_CONTRACT.md.
run: |
set -euo pipefail
for crate in taskito-core taskito-workflows taskito-mesh; do
# Capture first so a `cargo tree` failure aborts (set -e) instead of
# being swallowed by the pipe and silently passing the check.
tree_output="$(cargo tree -p "$crate" -e normal --all-features)"
for binding in pyo3 napi jni; do
if grep -qi "$binding" <<<"$tree_output"; then
echo "::error::$binding is in the dependency tree of $crate — generic crates must stay binding-agnostic"
exit 1
fi
done
done
echo "Generic crates are free of pyo3, napi, and jni."

rust-test:
name: Rust Tests (SQLite)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: ./.github/actions/rust-prelude

# $pythonLocation is exported to the job env by setup-python at runtime.
- name: Run Rust test suite
run: LD_LIBRARY_PATH="$pythonLocation/lib" cargo test --workspace

- name: Run mesh crate tests
run: LD_LIBRARY_PATH="$pythonLocation/lib" cargo test -p taskito-mesh

- name: Check build with native-async features
run: cargo check --workspace --features native-async

- name: Check build with mesh feature
run: cargo check --workspace --features mesh

rust-test-postgres:
name: Rust Tests (PostgreSQL)
runs-on: ubuntu-latest
services:
postgres:
image: postgres:16-alpine
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: test
POSTGRES_DB: taskito_test
ports:
- 5432:5432
options: >-
--health-cmd "pg_isready -U postgres"
--health-interval 5s
--health-timeout 5s
--health-retries 10
steps:
- uses: actions/checkout@v7
- uses: ./.github/actions/rust-prelude

- name: Run Rust test suite (PostgreSQL backend)
run: LD_LIBRARY_PATH="$pythonLocation/lib" cargo test --workspace --features postgres,workflows
env:
TASKITO_POSTGRES_TEST_URL: postgres://postgres:test@localhost:5432/taskito_test

rust-test-redis:
name: Rust Tests (Redis)
runs-on: ubuntu-latest
services:
redis:
image: redis:7-alpine
ports:
- 6379:6379
options: >-
--health-cmd "redis-cli ping"
--health-interval 5s
--health-timeout 3s
--health-retries 10
steps:
- uses: actions/checkout@v7
- uses: ./.github/actions/rust-prelude

- name: Run Rust test suite (Redis backend)
run: LD_LIBRARY_PATH="$pythonLocation/lib" cargo test --workspace --features redis,workflows
env:
TASKITO_REDIS_TEST_URL: redis://localhost:6379/15
Loading
Loading