ratio-matrix 0.5.0

Ratio's matrix data library.
Documentation
# This justfile contains jobs for a mixed Rust/Python project, but can be used for sole Rust
# projects as well, as forwarding commands check whether HAS_PYTHON is set to a value to determine
# whether the Python build, test, lint, etc. jobs must be run in conjunction with their Rust
# counterparts, which are always run.
# Version 2025-05-08

set dotenv-load
set export

# Shorthand for the justfile directory.
HERE := env_var_or_default("HERE", justfile_directory())

# Container image to use when running commands in a dev container.
IMAGE := env_var_or_default("IMAGE", "registry.gitlab.com/ratio-case-os/docker/rust-ci")
# The tag. Switch to "cross" for cross compilation.
TAG := env_var_or_default("TAG", "latest")
# Compilation targets if required.
TARGETS := env("TARGETS", "x86_64-unknown-linux-gnu x86_64-apple-darwin x86_64-pc-windows-msvc aarch64-unknown-linux-gnu aarch64-apple-darwin aarch64-pc-windows-msvc")

# Optional Rust crate to target within a workspace.
CRATE := env_var_or_default("CRATE", "")

# String of flame scripts (space separated) to run and calculate flamegraphs for.
FLAMES := env_var_or_default("FLAMES", "")
# Flamegraph output directory.
FLAMES_DIR := env_var_or_default("FLAMES_DIR", HERE + "/target/flames")

# Whether this project has Python bindings.
HAS_PYTHON := env_var_or_default("HAS_PYTHON", "")
# The Python crate name.
PYTHON_CRATE := env_var_or_default("PYTHON_CRATE", CRATE + "-py")
# The directory containing the Python bindings crate.
PYTHON_DIR := env_var_or_default("PYTHON_DIR", HERE / PYTHON_CRATE)
# The Python wheels output directory.
WHEELS_DIR := env_var_or_default("WHEELS_DIR", HERE + "/target/wheels")

# Show the recipe list.
default:
  @just --list --justfile {{justfile()}}

# Pull the dev container image from the registry.
pull-container:
  podman pull {{IMAGE}}:{{TAG}}

# Run a dev container with the project directory mounted.
run-container args="" cmd="bash":
  podman run --privileged --rm -it -v {{HERE}}:/home/ratio/work:z {{args}} {{IMAGE}}:{{TAG}} {{cmd}}

# Run a recipe in a dev container.
in-container recipe="":
  podman run --privileged --rm -it -v {{HERE}}:/home/ratio/work:z {{IMAGE}}:{{TAG}} just {{recipe}}

@install:
  if [[ -n "{{HAS_PYTHON}} "]]; just install-py; fi

# Install the Python bindings environment.
install-py *args:
  cd {{PYTHON_DIR}} && \
  uv lock && \
  uv sync --all-extras --all-groups && \
  uvx maturin develop --uv && \
  uv run python -m maturin_import_hook site install

# Build package in release mode.
@build: build-rs
  if [[ -n "{{HAS_PYTHON}}" ]]; just build-py; fi

# Build Rust crate(s) for all available targets.
build-rs:
  cargo build --release --all-targets --color always {{ if CRATE != "" { "-p " + CRATE } else { "" } }}

# Build Python bindings for all set targets.
build-py *args:
  rm -rf {{WHEELS_DIR}}
  for i in {{TARGETS}}; uvx maturin build --quiet --release --zig --target "$i"; done

# Create flamegraph of certain scripts for performance measurement.
flamegraph *args:
  #!/bin/bash
  mkdir -p {{FLAMES_DIR}}
  for i in {{FLAMES}}; \
  do CARGO_PROFILE_RELEASE_DEBUG=true cargo flamegraph -o "{{FLAMES_DIR}}/$i.svg" --no-inline --unit-test -- "$i" --color always {{args}} || true; \
  done
  rm -f perf.data
  rm -f perf.data.old
alias flame := flamegraph

# Lint both the Rust code, license usage, and Python bindings.
@lint: lint-rs lint-deny
  if [[ -n "{{HAS_PYTHON}}" ]]; then just lint-py; fi

# Lint the Rust code.
lint-rs *args:
  cargo clippy --all-targets --color always {{ if CRATE != "" { "-p " + CRATE } else { "" } }} {{args}}
  cargo +nightly fmt --check

# Lint the Python bindings if any.
lint-py *args:
  then ruff check {{PYTHON_DIR}} {{args}}

# Lint project dependencies and licenses
lint-deny:
  cargo deny check

# Fix the Rust code and Python bindings if any.
@fix: fix-rs
  if [[ -n "{{HAS_PYTHON}}" ]]; then just fix-py; fi

# Fix the Rust code.
fix-rs *args:
  cargo clippy --all-targets --fix --allow-dirty --allow-staged --color always {{args}}
  cargo +nightly fmt --all

# Fix the Python bindings if any.
fix-py *args:
  if [[ -n "{{HAS_PYTHON}}" ]]; then ruff format {{PYTHON_DIR}} {{args}}; fi

# Run a single test cycle for the project.
@test: test-rs-coverage
  if [[ -n "{{HAS_PYTHON}}" ]]; then just test-py; fi

# Run Rust code tests.
test-rs *args:
  cargo test {{ if CRATE != "" { "-p " + CRATE } else { "" } }} {{args}}

# Run the Python bindings tests.
test-py *args:
  cd {{PYTHON_DIR}} && uv run pytest {{args}}

# Run Rust tests with coverage.
test-rs-coverage *args:
  cargo tarpaulin --tests --doc --color always --locked --out Xml --output-dir target/tarpaulin {{ if CRATE != "" { "-p " + CRATE } else { "" } }} {{args}}

# Run Rust tests in watch mode, or the Python suite if the bindings are enabled.
@watch *args:
  if [[ -n "{{HAS_PYTHON}}" ]]; then just watch-py; else just watch-rs; fi

# Run Rust tests continuously.
watch-rs *args:
  bacon -j test {{ if CRATE != "" { "-p " + CRATE } else { "" } }} {{args}}

# Run Python bindings test continuously.
watch-py *args:
  bacon -j pytest {{args}}

# Build and publish both the Rust crate(s) as well as the Python bindings.
@publish: publish-rs
 if [[ -n "{{HAS_PYTHON}}" ]]; then just publish-py; fi

# Build and publish the Rust crate.
publish-rs *args: build-rs
  cargo publish --color always {{ if CRATE != "" { "-p " + CRATE } else { "" } }} {{args}}

# Build and publish the Python bindings.
publish-py *args:
  uv publish {{args}} {{WHEELS_DIR}}/*

# Check for outdated packages.
outdated: outdated-rs

# Check for outdated packages for the Rust code.
outdated-rs *args:
  cargo outdated --color always {{ if CRATE != "" { "-p " + CRATE } else { "" } }} {{args}}

# Upgrade all dependencies.
@upgrade: upgrade-rs
  if [[ -n "{{HAS_PYTHON}}" ]]; then just update-py; fi

# Upgrade Rust crate dependencies.
upgrade-rs *args:
  cargo upgrade {{ if CRATE != "" { "-p " + CRATE } else { "" } }} {{args}}

# Update Python lockfile.
update-py *args:
  cd {{PYTHON_DIR}} && uv lock