Skip to content

Development

How to build, test, and contribute to ryll. For macOS-specific setup see development-macos.md; for AI coding assistant conventions see AGENTS.md.

The project includes a devcontainer for consistent builds:

# Build debug version
make build

# Build release version
make release

# Run tests
make test

# Run linting (rustfmt + clippy)
make lint

# Run linting with auto-fix
make lint-fix

# Start a test QEMU SPICE server (UEFI latency guest, downloads on first run)
make test-qemu

# Stop the test QEMU instance
make test-qemu-stop

Building with a local Rust installation

If you have Rust installed locally with the required dependencies:

cargo build --release

Required system dependencies (Debian/Ubuntu):

apt-get install -y \
    libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev libxcb1-dev \
    libx11-dev libxkbcommon-dev libgl1-mesa-dev libegl1-mesa-dev \
    libwayland-dev libssl-dev pkg-config

macOS (Apple Silicon): No additional system libraries are needed -- just Xcode Command Line Tools and Rust. See development-macos.md for full setup instructions.

Cargo features

ryll ships several default-on Cargo features that can be opted out at build time:

  • gui (default-on) — eframe, egui, arboard, rfd, and the whole interactive UI. Disabling it produces a --headless / --web-only binary that does not link the X11/Wayland/ winit runtime and the runtime image drops libgl1, libx11-6, libxcb1, libxkbcommon0, libwayland-client0. Running such a binary without --headless or --web exits with a clear "this binary was built without the gui feature" message.
  • audio (default-on) — cpal, opus-decoder, rtrb, and the SPICE playback channel in shakenfist-spice-renderer. Disabling it drops libasound2 from the runtime image and skips the SPICE playback channel at connect time (the rest of the session is unaffected).
  • capture (default-on) — pcap-file + etherparse + mp4 for --capture recording.
  • digest-decode (default-off) — adds the shakenfist-visual-digest crate as a git dependency and enables a polling task that scans the primary surface for a QR-encoded visual digest and emits a digest_updated control-socket event on each frame counter change. Built only for the kerbside test harness; not in production ryll.

The slim test-harness binary is built with cargo build --release --no-default-features -p ryll. See control-socket-protocol.md for the surface_drawn and digest_updated event shapes.

Pre-commit hooks

The project uses pre-commit hooks to enforce code quality:

# Install pre-commit hooks
pre-commit install

# Run checks manually on all files
pre-commit run --all-files

# Or use the script directly
./scripts/check-rust.sh check   # Check mode
./scripts/check-rust.sh fix     # Auto-fix mode

The pre-commit hooks run:

  • rustfmt - Code formatting
  • clippy - Linting with warnings as errors
  • shellcheck - Shell script linting

Review tracking

Whole-file human review state (REVIEWS.md, .vscode/*.weaudit*) is maintained with tools/review-tracking.sh, a wrapper around the shared helper in the shakenfist/development repository. It is run by hand, not from git hooks: prune after a pull to discard reviews of files that have since changed, stamp before committing new review marks, regen to rebuild REVIEWS.md, and next to pick an unreviewed file.

CI and automation

GitHub Actions CI builds and tests ryll on Linux (x86_64 + aarch64), macOS (Apple Silicon), and Windows (x86_64 + aarch64) on every push to develop and on pull requests. Linux x86_64 jobs run on self-hosted runners with the build wrapped in the devcontainer (via the same Makefile targets used locally); macOS, Windows, and aarch64 Linux use GitHub-hosted runners because we own no matching hardware. PRs also receive an automated code review via Claude Code. Changes that only touch code-review artifacts (REVIEWS.md, .vscode/*.weaudit*, .vscode/review-scope.toml) skip the CI and CodeQL workflows entirely; the supply-chain content scanners still run on them.

Workflows in .github/workflows/:

Workflow Purpose
ci.yml Lint, fuzz smoke, build, test (multi-platform), automated PR review
manual-build.yml On-demand binary builds of arbitrary branches
release.yml Build and publish release artifacts
codeql-analysis.yml CodeQL security scanning
supply-chain.yml Dependency advisories, license policy, secret scanning, bidi/unicode checks
renovate.yml Automated dependency updates (hourly)
export-repo-config.yml Daily repository configuration export
pr-re-review.yml Bot-triggered PR re-review (@shakenfist-bot please re-review)
pr-address-comments.yml Bot-triggered comment addressing (@shakenfist-bot please address comments)
pr-retest.yml Bot-triggered CI re-run (@shakenfist-bot please retest)

Key dependencies

  • eframe/egui - Immediate mode GUI
  • tokio - Async runtime
  • tokio-rustls - TLS support
  • clap - CLI parsing
  • rsa/sha1 - Authentication encryption
  • image - JPEG decoding (via the image crate with jpeg feature)
  • cpal - Cross-platform audio output
  • rtrb - Lock-free ring buffer for audio sample passing
  • opus-decoder - Pure-Rust Opus audio decoding
  • openh264 - H.264 encoding in shakenfist-spice-renderer (the encoder pipeline). Capture mode in ryll consumes it transitively via the renderer.
  • nusb - USB device access (pure Rust, no libusb)
  • dav-server - WebDAV server (RFC 4918, LocalFs backend)
  • hyper - HTTP/1.1 framing for WebDAV byte-stream transport
  • webrtc = "0.17.1" - DTLS/SRTP/ICE/SCTP/STUN stack for shakenfist-spice-webrtc (browser-bridge crate; also pulls in rtp = "0.17.1" for H.264 RTP packetisation)
  • opus = "0.3" - libopus bindings for the synthetic Opus pump in the webrtc crate; audiopus_sys builds libopus from source in the devcontainer
  • ctrlc - Cross-platform Ctrl+C handling for graceful shutdown

📝 Report an issue with this page