{"id":49923601,"url":"https://github.com/doubleailes/rer","last_synced_at":"2026-05-16T22:04:17.060Z","repository":{"id":358032645,"uuid":"708744381","full_name":"doubleailes/rer","owner":"doubleailes","description":"A rez implementation in Rust","archived":false,"fork":false,"pushed_at":"2026-05-15T11:34:56.000Z","size":451,"stargazers_count":0,"open_issues_count":9,"forks_count":0,"subscribers_count":3,"default_branch":"main","last_synced_at":"2026-05-15T11:35:46.210Z","etag":null,"topics":["animation","environment-configuration","package-management","resolved-environments","rez","standalone-environments","vfx"],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/doubleailes.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2023-10-23T09:54:41.000Z","updated_at":"2026-05-15T11:31:45.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/doubleailes/rer","commit_stats":null,"previous_names":["doubleailes/rer"],"tags_count":9,"template":false,"template_full_name":null,"purl":"pkg:github/doubleailes/rer","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/doubleailes%2Frer","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/doubleailes%2Frer/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/doubleailes%2Frer/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/doubleailes%2Frer/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/doubleailes","download_url":"https://codeload.github.com/doubleailes/rer/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/doubleailes%2Frer/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33120450,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-16T18:38:32.183Z","status":"ssl_error","status_checked_at":"2026-05-16T18:38:29.903Z","response_time":115,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["animation","environment-configuration","package-management","resolved-environments","rez","standalone-environments","vfx"],"created_at":"2026-05-16T22:04:16.103Z","updated_at":"2026-05-16T22:04:17.052Z","avatar_url":"https://github.com/doubleailes.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# rer\n\n![rer.logo](https://github.com/doubleailes/rer/blob/main/docs/content/images/res_v01.png)\n\n**r.e.r** stands for the French phrase *\"Rez En Rust\"* — a pun on the Parisian\nsuburban train, the [réseau express régional d'Île-de-France](https://en.wikipedia.org/wiki/R%C3%A9seau_Express_R%C3%A9gional).\n\n## What it is\n\n`rer` is a Rust reimplementation of the solver hotpath of\n[rez](https://github.com/AcademySoftwareFoundation/rez), the VFX/animation\npackage manager.\n\nThe solver is a **faithful port of rez's own phase-based backtracking solver**\n(`rez/src/rez/solver.py`) — not a different algorithm dressed up to look\nsimilar. It reproduces rez's mechanics exactly: weak (`~`) and conflict (`!`)\nrequirement semantics, variant selection order, the extract / intersect /\nreduce / split cycle, and implicit backtracking. The goal is output that\nmatches rez **1:1**, not merely \"a valid resolve\".\n\nThe end goal is a **hybrid integration**: a Rust library callable from Python\nvia PyO3 that accelerates rez resolves while leaving the rest of rez untouched.\n\n### Status\n\n- ✅ **Solver** — complete and rez-faithful.\n- ✅ **Validated 1:1** against rez's bundled 188-case benchmark dataset\n  on **solve status**, the **resolved `(name, version)` set**, **and the\n  variant index** rez picked for each. The differential test enforces all\n  three; a divergence on any is a release blocker.\n- ✅ **Fast** — see the [Benchmark](#benchmark) section below for a local\n  apples-to-apples measurement against rez 3.3.0.\n- ✅ **Python bridge** — `pyrer.solve(...)` runs the ported solver (the\n  `rer-python` crate ships to PyPI as `pyrer` — `rer` is taken;\n  `pip install pyrer`, `import pyrer`).\n\n## Workspace\n\nA virtual Cargo workspace — use `-p \u003ccrate\u003e` for crate-specific commands.\n\n| Crate | Role |\n|---|---|\n| **`rer-version`** | `RerVersion` (rez token ordering) and `VersionRange` (rez range semantics over [`version-ranges`](https://crates.io/crates/version-ranges)). |\n| **`rer-resolver`** | The solver. `rez_solver` is the rez port — `Solver`, `ResolvePhase`, `PackageScope`, the variant structures, `Requirement`/`RequirementList`. `PackageData` is the in-memory unit of the package repository. |\n| **`rer-python`** | PyO3 bridge (Python import name `pyrer`, PyPI distribution `pyrer`), built into wheels by maturin. |\n| **`examples`** | `rez_benchmark_dataset` — a timing report. |\n\n`rer` works on an **in-memory package repository** (`family → version →\n{requires, variants}`); it does not read the filesystem itself — the host\n(rez) hands the loaded data in.\n\n## Using it from Rust\n\n```rust\nuse std::rc::Rc;\nuse rer_resolver::rez_solver::{Requirement, Solver, SolverStatus};\nuse rer_resolver::PackageData;\n\nlet mut repo = std::collections::HashMap::new();\n// app-1.0.0 requires lib-2; lib has 1.0.0 and 2.0.0\nrepo.insert(\"app\".into(), [(\"1.0.0\".to_string(), PackageData {\n    requires: vec![\"lib-2\".into()], variants: vec![],\n})].into_iter().collect());\nrepo.insert(\"lib\".into(), [\n    (\"1.0.0\".to_string(), PackageData::default()),\n    (\"2.0.0\".to_string(), PackageData::default()),\n].into_iter().collect());\n\nlet reqs = vec![Requirement::parse(\"app\")];\nlet mut solver = Solver::new(reqs, Rc::new(repo)).unwrap();\nsolver.solve();\nassert_eq!(solver.status(), SolverStatus::Solved);\n// resolves to app-1.0.0 + lib-2.0.0\n```\n\n## Using it from Python\n\n```bash\npython -m venv .venv \u0026\u0026 . .venv/bin/activate\npip install maturin\ncd crates/rer-python \u0026\u0026 maturin develop\n```\n\n```python\nimport pyrer\n\npackages = [\n    pyrer.PackageData(\"app\", \"1.0.0\", requires=[\"lib-2\"]),\n    pyrer.PackageData(\"lib\", \"1.0.0\"),\n    pyrer.PackageData(\"lib\", \"2.0.0\"),\n]\nresult = pyrer.solve([\"app\"], packages)\n\nprint(result.status)                         # \"solved\"\nfor v in result.resolved_packages:\n    print(v.name, v.version, v.variant_index, v.uri)\n# app 1.0.0 None app/1.0.0/package.py\n# lib 2.0.0 None lib/2.0.0/package.py\n```\n\n`solve()` reports failures and bad input via `result.status`\n(`\"solved\"` / `\"failed\"` / `\"error\"`), never as a Python exception —\nexcept a `TypeError` if `packages` isn't a list of `PackageData`.\n\n### Wiring `pyrer` behind `rez`\n\n`pyrer` only does the solve; `rez` still does package discovery, env\nconstruction, and the whole `ResolvedContext` lifecycle. To plug\n`pyrer` in behind a normal `rez env` / `ResolvedContext` flow:\n\n1. Walk rez's package paths into `pyrer.PackageData` objects — once\n   per process, reusable across many solves. `PackageData.from_rez(pkg)`\n   does the per-package conversion (stringifies `version` and each\n   `Requirement`) so the integration shim is one line:\n\n   ```python\n   import pyrer\n   from rez.packages import iter_package_families\n\n   def build_pyrer_packages(package_paths):\n       for fam in iter_package_families(paths=package_paths):\n           for pkg in fam.iter_packages():\n               yield pyrer.PackageData.from_rez(pkg)\n   ```\n\n2. Call `pyrer.solve(requests, list(build_pyrer_packages(paths)))`\n   instead of `rez.solver.Solver.solve()`.\n\n3. Read `result.resolved_packages` — each entry already has `.name`,\n   `.version`, `.variant_index`, `.requires` and a rez-shaped `.uri`.\n   If a downstream consumer needs the full rez `Variant` (for\n   `commands`, `tools`, etc.), look it up with\n   `rez.packages.get_package(rv.name, rv.version).get_variant(rv.variant_index or 0)`.\n\nThe\n[Wiring `pyrer` into `rez`](https://doubleailes.github.io/rer/docs/getting-started/rez-integration/)\nguide has the full walkthrough — a minimal monkey-patch of\n`Resolver._solve`, a fallback for configs `pyrer` doesn't model yet\n(`@early` / `@late` requires, custom orderers / filters), and a\nsanity-check loop for diffing `pyrer` against rez on your own repo.\nBoth `version_priority` and `intersection_priority` variant-select\nmodes are supported — pass `variant_select_mode=\"intersection_priority\"`\nto `pyrer.solve` (or wire it through from `rez.config`).\n\n## Building \u0026 testing\n\n```bash\ncargo build                          # build all crates\ncargo test                           # unit + integration tests\ncargo bench                          # benchmarks (rer-version)\n```\n\n### The rez benchmark\n\nThe 1:1 differential test against rez's bundled benchmark is `#[ignore]`d (the\nfull release run takes several minutes). It needs the `rez` git submodule:\n\n```bash\ngit submodule update --init\npython scripts/prepare_benchmark_data.py     # -\u003e data_set/benchmark_*.json\ncargo test --release -p rer-resolver --test test_rez_benchmark -- --ignored\ncargo run  --release -p examples --example rez_benchmark_dataset   # timing report\n```\n\nThe `benchmark` CI workflow runs this on every PR touching the resolver.\n\n## Benchmark\n\n`rer` and `rez` are timed on the **same machine** running the **same\n188-case workload** (`rez/src/rez/data/benchmarking/`). Both runs use one\ncore and the default solver configuration.\n\n### Reference run, 2026-05-15\n\n```text\nmachine: Intel(R) Xeon(R) CPU E5-2699 v4 @ 2.20 GHz, 32 cores\nOS:      Linux 5.14.0 (glibc 2.34)\n```\n\nThe headline comparison uses **CPython 3.13** for `rez` — that's the\nfastest current CPython and is the realistic target for modern rez\ndeployments. We include a CPython 3.9 row too because rez's solver is\nsensitive to interpreter speed and a lot of VFX studios still run 3.9.\n\n| Implementation                          | Total (188 cases) | Mean / case | Median | p95 | Max |\n|---|---:|---:|---:|---:|---:|\n| **rez 3.3.0** on CPython 3.13           | 221.43 s | 1 177 ms |   587 ms | —      | 5 770 ms |\n| **rer 0.1.0-rc.6** (no Python in loop)  |  11.35 s |    60 ms |    30 ms | 181 ms |   247 ms |\n| **speedup vs rez on 3.13**              |  **19.5×** | **19.5×** | **19.9×** | —      | **23.4×** |\n| *(rez 3.3.0 on CPython 3.9 — for ref)*  | *405.17 s* | *2 152 ms* | *1 162 ms* | —    | *9 399 ms* |\n\n`rer` solved 187 of 188 requests with the same `(name, version)` set as `rez`\non every solve; the one failed request fails on both sides. The differential\ntest confirms this on every PR (`cargo test --release -p rer-resolver --test\ntest_rez_benchmark -- --ignored`).\n\n### How to reproduce\n\n```bash\n# 1. rez 3.3.0 (vendored as a submodule), on CPython 3.13 via uv\ngit submodule update --init\nuv python install 3.13\nuv venv --python 3.13 /tmp/rez-bench-venv\nuv pip install --python /tmp/rez-bench-venv/bin/python ./rez\n/tmp/rez-bench-venv/bin/rez-benchmark --out /tmp/rez-bench-out\ncat /tmp/rez-bench-out/summary.json   # records hardware + total_run_time\n\n# 2. rer, same machine\npython3 scripts/prepare_benchmark_data.py   # fixtures from the rez submodule\ncargo run --release -p examples --example rez_benchmark_dataset\n```\n\n### Historical context\n\n`rez/metrics/benchmarking/artifacts/2022.11.16-3.7-2.112.0/summary.json`\nrecords a published rez run on a 2-core Azure VM in 382.68 s — useful as\nupstream context, but **not directly comparable** to a `rer` run on\ndifferent hardware. The numbers above are the apples-to-apples comparison.\n\n### Where the speedup comes from\n\nThe solver itself is a faithful port; the wall-clock difference is a Rust\nimplementation of an algorithm that was tuned for Python. Notable\ncontributions, in rough order of impact on this benchmark:\n\n| | Change | Effect |\n|---|---|---|\n| 1 | Variant cache shared across solves | -20 % |\n| 2 | `is_subset` → length compare on the extract hot-path | -30 % |\n| 3 | Pre-filter pending reduction pairs by `fam_requires` | -25 % |\n| 4 | `Rc\u003cstr\u003e` for package family names | -15 % |\n| 5 | `Rc\u003cRanges\u003e` inside `VersionRange` | -11 % |\n| 6 | `mimalloc` as the global allocator in the bench binary | -13 % |\n| 7 | Skip `extract` on scopes known to be exhausted | -5 % |\n\nCompounding from 43.0 s (post-port baseline) down to 11.35 s.\n\n## Release notes \u0026 stability\n\n- [`CHANGELOG.md`](CHANGELOG.md) — what changed in every release.\n- [Stability commitments](https://doubleailes.github.io/rer/docs/engineering/stability/)\n  — what 1.0 commits us to (supported Pythons, supported rez range,\n  semver scope, what is and isn't modelled).\n\n## License\n\nSee [LICENSE](LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdoubleailes%2Frer","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdoubleailes%2Frer","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdoubleailes%2Frer/lists"}