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
3 changes: 3 additions & 0 deletions .github/package-filters/rs-packages-direct.yml
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,9 @@ wasm-sdk:
platform-wallet:
- packages/rs-platform-wallet/**

platform-wallet-storage:
- packages/rs-platform-wallet-storage/**

platform-wallet-ffi:
- packages/rs-platform-wallet-ffi/**

Expand Down
4 changes: 4 additions & 0 deletions .github/package-filters/rs-packages-no-workflows.yml
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,10 @@ platform-wallet: &platform_wallet
- *platform_encryption
- *sdk

platform-wallet-storage:
- packages/rs-platform-wallet-storage/**
- *platform_wallet

platform-wallet-ffi: &platform_wallet_ffi
- packages/rs-platform-wallet-ffi/**
- *platform_wallet
Expand Down
5 changes: 5 additions & 0 deletions .github/package-filters/rs-packages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,11 @@ platform-wallet: &platform_wallet
- *platform_encryption
- *sdk

platform-wallet-storage:
- .github/workflows/tests*
- packages/rs-platform-wallet-storage/**
- *platform_wallet

platform-wallet-ffi: &platform_wallet_ffi
- .github/workflows/tests*
- packages/rs-platform-wallet-ffi/**
Expand Down
62 changes: 62 additions & 0 deletions .github/scripts/check-wallet-closure.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
#!/usr/bin/env python3
"""Verify the wallet reverse-dependency closure.

The wallet-only CI fast path (.github/workflows/tests-rs-wallet.yml) compiles
and tests only the wallet crates plus their known dependents. That is sound
only while the set of workspace crates depending (transitively) on the wallet
crates is exactly EXPECTED_DEPENDENTS below.

This script runs on BOTH Rust CI paths: on the full workspace path it fails
the PR that introduces a new dependent (the moment the invariant breaks), and
on the wallet fast path it protects against a stale scoped package list.

On failure: extend the scoped --package lists in
.github/workflows/tests-rs-wallet.yml, add the new dependent to
EXPECTED_DEPENDENTS here, and reconsider whether the fast path is still sound.
"""

import json
import subprocess
import sys

WALLET_CRATES = {"platform-wallet", "platform-wallet-ffi", "platform-wallet-storage"}
EXPECTED_DEPENDENTS = {"rs-unified-sdk-ffi"}


def main() -> int:
meta = json.loads(
subprocess.check_output(
["cargo", "metadata", "--format-version", "1", "--no-deps", "--locked"]
)
)
deps = {p["name"]: {d["name"] for d in p["dependencies"]} for p in meta["packages"]}

# Transitive closure of workspace crates that depend on a wallet crate
reaches_wallet = set(WALLET_CRATES)
changed = True
while changed:
changed = False
for name, d in deps.items():
if name not in reaches_wallet and d & reaches_wallet:
reaches_wallet.add(name)
changed = True

dependents = reaches_wallet - WALLET_CRATES
if dependents != EXPECTED_DEPENDENTS:
print(
f"Wallet reverse-dependency closure changed: "
f"expected {sorted(EXPECTED_DEPENDENTS)}, got {sorted(dependents)}"
)
print(
"Update the scoped --package lists in "
".github/workflows/tests-rs-wallet.yml and EXPECTED_DEPENDENTS in "
".github/scripts/check-wallet-closure.py to cover the new dependents."
)
return 1

print(f"Closure OK: only {sorted(EXPECTED_DEPENDENTS)} depends on the wallet crates")
return 0


if __name__ == "__main__":
sys.exit(main())
213 changes: 213 additions & 0 deletions .github/workflows/tests-rs-wallet.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
# Fast path for same-repo PRs that touch ONLY the platform-wallet crates.
#
# `tests.yml`'s `changes` job sets `rs-scope=wallet` when the run is a
# same-repo pull request and every changed file lives under one of the wallet
# crate directories (Swift sources are ignored — they are not part of the
# Rust workspace). Fork PRs and push/schedule runs always take the full
# `tests-rs-workspace.yml` path. In the wallet case this workflow runs in
# place of the full one, compiling and testing only the wallet crates plus
# their sole in-workspace dependent (`rs-unified-sdk-ffi`) instead of the
# whole workspace.
#
# Safety: nothing in the workspace depends on the wallet crates except
# `rs-unified-sdk-ffi` — an invariant enforced by the "Verify wallet
# reverse-dependency closure" step below, so a new dependent added elsewhere
# fails this job until the scoped package lists are updated. Scoped clippy
# compiles that dependent, preserving the downstream compile check the full
# `clippy --workspace` provides. Formatting, unused-dep, immutable-structure
# checks, and wallet-scoped doctests mirror the full job so nothing merges
# through the fast path that the full path would have rejected.
#
# Coverage upload is intentionally omitted (codecov never gates the merge —
# `fail_ci_if_error: false`). Known trade-offs: no Ubuntu backup jobs exist
# here (if `UBUNTU_BACKUP_ENABLED` is turned on, wallet PRs still depend on
# the mac runner being online), and the scoped `-p` builds feature-unify
# shared deps differently than `--workspace` builds, so the shared target/
# carries an extra artifact flavor.
on:
workflow_call:
inputs:
doctests-changed:
description: Whether doc comments with code examples have changed
type: boolean
default: false

jobs:
test-mac:
name: Wallet tests (macOS)
runs-on: [self-hosted, macOS, ARM64]
if: >-
github.event_name != 'pull_request'
|| github.event.pull_request.head.repo.full_name == github.repository
|| github.event.pull_request.head.repo.owner.login == 'thepastaclaw'
# Matches the full workspace job: after a disk prune deletes target/, the
# scoped build still cold-compiles ~77% of the workspace dependency graph
# (dpp, dash-sdk, drive, dashcore, the orchard/halo2 stack) in two profiles.
timeout-minutes: 30
steps:
- name: Check out repo
uses: actions/checkout@v4
with:
clean: false

- name: Prune macOS runner disk before tests
run: |
for path in ../target-backup-before-*-clean-* target/llvm-cov-target; do
if [ -e "$path" ]; then
echo "Removing stale runner artifact: $path"
rm -rf "$path"
fi
done

TARGET_MAX_MB=120000
MIN_FREE_MB=60000
SIZE=$(du -sm target 2>/dev/null | awk '{print $1}' || echo 0)
FREE=$(df -m . | awk 'NR == 2 {print $4}')
SIZE=${SIZE:-0}
FREE=${FREE:-0}

echo "target/ size: ${SIZE}MB"
echo "available disk: ${FREE}MB"
if [ "$SIZE" -gt "$TARGET_MAX_MB" ] || [ "$FREE" -lt "$MIN_FREE_MB" ]; then
echo "target/ exceeds ${TARGET_MAX_MB}MB or disk is below ${MIN_FREE_MB}MB free; removing target/"
rm -rf target
fi

- name: Install build dependencies
run: |
echo "/opt/homebrew/bin" >> $GITHUB_PATH
echo "/opt/homebrew/opt/llvm/bin" >> $GITHUB_PATH
brew list cmake &>/dev/null || brew install cmake
brew list gmp &>/dev/null || brew install gmp
brew list llvm &>/dev/null || brew install llvm

- name: Setup Rust
uses: ./.github/actions/rust
with:
cache: false
components: rustfmt, clippy

# Enforce the invariant the scoped --package lists below rely on: the
# only workspace crate depending (transitively) on the wallet crates is
# rs-unified-sdk-ffi. The same check runs on the full workspace path,
# which is what catches the PR that introduces a new dependent (that PR
# necessarily touches a non-wallet crate and takes the full path); this
# copy protects against a stale scoped package list.
- name: Verify wallet reverse-dependency closure
run: python3 .github/scripts/check-wallet-closure.py

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

- name: Find unused dependencies
run: |
cargo install cargo-machete 2>/dev/null || true
cargo machete

- name: Detect immutable structure changes
if: github.event_name == 'pull_request'
env:
GH_TOKEN: ${{ github.token }}
run: |
CHANGED_RS=$(gh pr view ${{ github.event.pull_request.number }} --json files --jq '[.files[].path] | map(select(test("\\.rs$"))) | .[]')
if [ -z "$CHANGED_RS" ]; then
echo "No .rs files changed — skipping"
exit 0
fi

# Brace-aware extractor: captures from @tag through the matching closing brace
extract_tagged_block() {
local tag="$1"
awk -v tag="$tag" '
$0 ~ tag { found=1; depth=0 }
found {
for (i=1; i<=length($0); i++) {
c = substr($0,i,1)
if (c == "{") depth++
if (c == "}") depth--
}
print
if (found && depth <= 0 && index($0, "}")) { found=0 }
}
'
}

git fetch origin ${{ github.event.pull_request.base.ref }} --depth=1
for file in $CHANGED_RS; do
if [ ! -f "$file" ]; then continue; fi
BASE_CONTENT=$(git show origin/${{ github.event.pull_request.base.ref }}:"$file" 2>/dev/null || true)
if [ -z "$BASE_CONTENT" ]; then continue; fi

BASE_APPEND=$(echo "$BASE_CONTENT" | extract_tagged_block "@append_only")
PR_APPEND=$(extract_tagged_block "@append_only" < "$file")
BASE_IMMUTABLE=$(echo "$BASE_CONTENT" | extract_tagged_block "@immutable")
PR_IMMUTABLE=$(extract_tagged_block "@immutable" < "$file")

if [ -n "$BASE_APPEND" ]; then
# Check for deletions, ignoring comment-only lines and the tag itself
DELETIONS=$(diff <(echo "$BASE_APPEND") <(echo "$PR_APPEND") | grep "^<" | grep -v "@append_only" | grep -v "^< *$" | grep -v "^< *///" | grep -v "^< *//" || true)
if [ -n "$DELETIONS" ]; then
echo "Deletions detected in @append_only structures in $file"
echo "$DELETIONS"
exit 1
fi
fi
if [ -n "$BASE_IMMUTABLE" ]; then
CHANGES=$(diff <(echo "$BASE_IMMUTABLE") <(echo "$PR_IMMUTABLE") | grep -E "^[<>]" | grep -v "^[<>] *$" | grep -v "^[<>] *///" | grep -v "^[<>] *//" | grep -v "@immutable" || true)
if [ -n "$CHANGES" ]; then
echo "Code changes detected in @immutable structures in $file"
echo "$CHANGES"
exit 1
fi
fi
done
echo "No immutable/append_only structure violations found"

# Scoped clippy over the wallet crates plus `rs-unified-sdk-ffi`, the only
# in-workspace crate that depends on them — this preserves the downstream
# compile check the full `clippy --workspace` would otherwise provide.
- name: Clippy lints (wallet crates + dependents)
run: |
cargo clippy \
--package platform-wallet \
--package platform-wallet-storage \
--package platform-wallet-ffi \
--package rs-unified-sdk-ffi \
--all-features \
--locked \
-- --no-deps -D warnings

# Mirrors the workspace job's non-shielded step for the wallet crates:
# same package subset, same `not test(~shield)` filter (shielded wallet
# tests are not run there either).
- name: Run wallet tests (non-shielded)
run: |
cargo nextest run \
--package platform-wallet \
--package platform-wallet-storage \
--package platform-wallet-ffi \
--all-features \
--locked \
-E 'not test(~shield)'
env:
RUST_MIN_STACK: 4194304
CARGO_PROFILE_DEV_DEBUG: "0"
CARGO_PROFILE_DEV_CODEGEN_UNITS: "256"

# Wallet-only PRs can only change doc examples in the wallet crates, so
# scoped doctests are equivalent to the full job's `--workspace --doc`
# run for this scope. (The standalone rs-doctests job in tests.yml is
# only an Ubuntu backup gated on UBUNTU_BACKUP_ENABLED.)
- name: Run doctests (wallet crates)
if: ${{ inputs.doctests-changed }}
run: |
cargo test --doc \
--package platform-wallet \
--package platform-wallet-storage \
--package platform-wallet-ffi \
--all-features \
--locked
env:
RUST_MIN_STACK: 4194304
CARGO_PROFILE_DEV_DEBUG: "0"
CARGO_PROFILE_DEV_CODEGEN_UNITS: "256"
7 changes: 7 additions & 0 deletions .github/workflows/tests-rs-workspace.yml
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,13 @@ jobs:
- name: Check formatting
run: cargo fmt --check --all

# Fails the PR that introduces a new workspace dependent of the wallet
# crates, keeping the wallet-only fast path (tests-rs-wallet.yml) sound:
# such a PR always takes this full path, so the check must live here to
# fire at the moment the invariant breaks.
- name: Verify wallet reverse-dependency closure
run: python3 .github/scripts/check-wallet-closure.py

- name: Clippy lints
run: |
cargo clippy \
Expand Down
Loading
Loading