A fast, lightweight environment resolver for VFX and animation pipelines. Think Rez, but Rust-powered and simpler.
- YAML-based package definitions -- simple, readable, version-controlled
- Dependency resolution -- automatic recursive resolution with version constraints (exact, minimum, range, alternatives)
- Two package layouts -- flat YAML files or nested
{name}/{version}/package.yamldirectories - Command aliases -- packages define named commands;
anvil runresolves them automatically - Environment variable expansion --
${VAR},${PACKAGE_ROOT},${VERSION},${NAME},~/tilde expansion - Platform variants -- per-platform requirements and environment overrides (Linux, macOS, Windows)
- Aliases -- named package sets for common configurations
- Lockfiles -- pin resolved versions for reproducible environments across machines
- Saved contexts -- export a fully resolved environment to JSON for render farms, CI, or sharing
- Per-project config --
.anvil.yamlin the project root, merged with user/studio config - Conflict detection -- warns when packages silently override each other's variables
- Pre/post hooks -- run scripts before/after resolution or command execution (license checks, logging, etc.)
- Package filters -- include/exclude packages by glob pattern per-project or per-config
- Shell completions -- tab completion for bash, zsh, fish, PowerShell
- Wrapper scripts -- generate executable wrappers for resolved commands; add to
$PATHfor seamless tool access - Package publishing --
anvil publishto copy validated packages to shared repositories - Scan caching -- cached package scans with automatic invalidation for fast repeated calls
- Cross-platform -- Windows, Linux, macOS with shell-specific output (bash, zsh, fish, PowerShell, cmd)
- Fast -- written in Rust, resolves in milliseconds, single binary with no runtime dependencies
cargo install anvil-envOr build from source:
git clone https://github.com/voidreamer/anvil.git
cd anvil
cargo build --release
# Binary at target/release/anvilAnvil supports two layouts. Use whichever fits your workflow -- both can coexist in the same directory.
Flat files (recommended for simplicity):
mkdir -p ~/packages
cat > ~/packages/maya-2024.yaml << 'EOF'
name: maya
version: "2024"
description: Autodesk Maya 2024
requires:
- python-3.10
environment:
MAYA_VERSION: "2024"
MAYA_LOCATION: /usr/autodesk/maya2024
PATH: ${MAYA_LOCATION}/bin:${PATH}
PYTHONPATH: ${PACKAGE_ROOT}/scripts:${PYTHONPATH}
commands:
maya: ${MAYA_LOCATION}/bin/maya
mayapy: ${MAYA_LOCATION}/bin/mayapy
EOFNested directories (useful when packages bundle scripts, addons, or other files):
~/packages/
maya/
2024/
package.yaml
scripts/
modules/
2025/
package.yaml
Create ~/.anvil.yaml:
package_paths:
- ~/packages
- /studio/shared/packages
default_shell: bash
# Named package sets for common workflows
aliases:
maya-anim:
- maya-2024
- animbot-2.0
- studio-tools# Show resolved environment variables
anvil env maya-2024
# Launch maya using the command alias defined in the package
anvil run maya-2024 -- maya
# Launch with multiple packages and arguments
anvil run maya-2024 arnold-7.2 -- maya -file scene.ma
# Start an interactive shell with packages loaded
anvil shell maya-2024 arnold-7.2
# Use an alias to resolve a whole group of packages
anvil run maya-anim -- mayaA package is a YAML file that declares a name, version, and optionally: dependencies, environment variables, command aliases, and platform variants.
name: houdini # Required: package name
version: "20.5" # Required: version string
description: SideFX Houdini 20.5 # Optional: human-readable description
requires: # Optional: dependencies (version-constrained)
- python-3.11
environment: # Optional: environment variables to set
HOUDINI_VERSION: "${VERSION}"
HFS: ${PACKAGE_ROOT}
PATH: ${HFS}/bin:${PATH}
PYTHONPATH: ${HFS}/python/lib/python3.11/site-packages:${PYTHONPATH}
commands: # Optional: named command aliases
houdini: ${HFS}/bin/houdini
hython: ${HFS}/bin/hython
hcustom: ${HFS}/bin/hcustom
variants: # Optional: platform-specific overrides
- platform: linux
environment:
HFS: /opt/hfs20.5
LD_LIBRARY_PATH: ${HFS}/dsolib:${LD_LIBRARY_PATH}
- platform: macos
environment:
HFS: /Applications/Houdini/Houdini20.5/Frameworks/Houdini.framework/Versions/20.5/Resources
DYLD_LIBRARY_PATH: ${HFS}/../Libraries:${DYLD_LIBRARY_PATH}
- platform: windows
environment:
HFS: C:/Program Files/Side Effects Software/Houdini 20.5Only name and version are required:
name: studio-tools
version: "1.0"
environment:
PYTHONPATH: ${PACKAGE_ROOT}/python:${PYTHONPATH}Flat files -- YAML files directly in a package path. The filename is for your convenience; the name and version inside the file are what anvil uses:
~/packages/
maya-2024.yaml
arnold-7.2.yaml
python-3.11.yaml
Nested directories -- the traditional {name}/{version}/package.yaml layout. Better when a package includes associated files (scripts, addons, libraries):
~/packages/
maya/
2024/
package.yaml
scripts/
modules/
arnold/
7.2/
package.yaml
bin/
lib/
Both layouts can coexist in the same package path directory. Anvil scans .yaml/.yml files as flat packages and subdirectories as nested packages in a single pass.
Used in the requires field and when requesting packages from the CLI:
| Format | Meaning |
|---|---|
maya-2024 |
Exactly version 2024 |
maya-2024+ |
Version 2024 or higher |
maya-2024..2025 |
Versions 2024 through 2025 (inclusive) |
python-3.10|3.11 |
Version 3.10 or 3.11 |
maya |
Any available version (highest wins) |
When multiple versions match a constraint, the highest version is selected. Versions are compared as semantic versions when possible, falling back to string comparison.
Package names and versions are split on the last - in the request string, but only when the suffix starts with a digit. This means hyphenated names like studio-blender-tools work correctly -- anvil treats the whole string as the name and resolves any version. To request a specific version: studio-blender-tools-1.0.0.
Package environment values are expanded in this order:
${PACKAGE_ROOT}-- absolute path to the package directory${VERSION}-- the package's version string${NAME}-- the package's name${ANY_VAR}-- any variable from previously resolved packages or the current environment~/prefix -- expanded to the user's home directory
When multiple packages are resolved, their environments are merged in dependency order. Each package sees the environment from all previously resolved packages, so later packages can reference variables set by earlier ones.
Conflict detection: If two packages both set the same variable and the later one does not reference ${VAR} (i.e., it overwrites rather than appends), anvil emits a warning. This catches accidental overrides while allowing common append patterns like PATH: .../bin:${PATH}.
Packages can define named commands in the commands field:
commands:
houdini: ${HFS}/bin/houdini
hython: ${HFS}/bin/hython
kick: ${PACKAGE_ROOT}/bin/kickWhen you use anvil run, the first argument after -- is looked up in the merged command map of all resolved packages. If it matches a defined alias, it's replaced with the fully expanded path before execution:
# "houdini" is resolved to /opt/hfs20.5/bin/houdini (or platform equivalent)
anvil run houdini-20.5 -- houdini -scene myfile.hip
# Commands that don't match any alias pass through unchanged
anvil run houdini-20.5 -- /usr/bin/env hython myscript.pyCommand values support the same variable expansion as environment values (${PACKAGE_ROOT}, ${VERSION}, etc.), and are expanded against the fully resolved environment.
Variants apply platform-specific overrides. The requires list is extended (merged with the base list), and environment values overwrite the base values for matching keys.
Supported platforms: linux, windows, macos.
variants:
- platform: linux
requires:
- gcc-11
environment:
LD_LIBRARY_PATH: ${PACKAGE_ROOT}/lib:${LD_LIBRARY_PATH}
- platform: macos
requires:
- clang-14
- platform: windows
requires:
- msvc-2022
environment:
PATH: ${PACKAGE_ROOT}/bin;${PATH}Resolve packages and print the resulting environment.
anvil env maya-2024 arnold-7.2 # KEY=VALUE format
anvil env maya-2024 --export # Shell export statements
anvil env maya-2024 --json # JSON objectUseful for debugging, piping into other tools, or generating env files for IDE integration.
Run a command with the resolved environment. If the command name matches a command alias defined by any resolved package, it's automatically expanded to the full path.
# "maya" is resolved from the package's commands: field
anvil run maya-2024 -- maya
# Multiple packages, with arguments passed through
anvil run maya-2024 arnold-7.2 -- maya -file scene.ma
# Add extra environment variables with -e
anvil run maya-2024 -e MAYA_DEBUG=1 -e CUSTOM=value -- mayaExits with the command's exit code.
Start an interactive shell with packages loaded. Adds [anvil] to the prompt.
anvil shell maya-2024 arnold-7.2
anvil shell maya-2024 --shell zshShell detection priority: --shell flag > default_shell from config > $SHELL > bash.
On Unix, the shell replaces the current process (exec). On Windows, it spawns a child process and waits.
List available packages or versions of a specific package.
anvil list # All package names
anvil list maya # All versions of mayaShow details for a specific package: name, version, description, dependencies, environment, and commands.
anvil info maya-2024
anvil info houdini-20.5Check that package definitions are valid and all dependencies can be resolved.
anvil validate # Validate all packages
anvil validate maya-2024 # Validate one packageResolve packages and pin the exact versions to anvil.lock. Subsequent anvil env, run, and shell commands will prefer locked versions when the lockfile is present.
# Create a lockfile
anvil lock maya-2024 arnold-7.2
# Now any resolution will use pinned versions
anvil env maya-2024 # uses versions from anvil.lock
anvil run maya-2024 -- maya # same
# Re-resolve and update the lockfile
anvil lock maya-2024 arnold-7.2 --updateThe lockfile is a YAML file that can be committed to version control for reproducible environments across the team.
Save a fully resolved environment to a JSON file. The context can be loaded later to run commands or start shells without re-resolving -- useful for render farms, CI pipelines, or sharing exact environments.
# Save a context
anvil context save maya-2024 arnold-7.2 -o render.ctx.json
# Inspect it
anvil context show render.ctx.json
anvil context show render.ctx.json --json
anvil context show render.ctx.json --export
# Run a command using the saved environment
anvil context run render.ctx.json -- maya -batch -file scene.ma
# Start a shell with the saved environment
anvil context shell render.ctx.jsonScaffold a new package definition with a template.
# Create a nested package directory
anvil init my-tools # my-tools/1.0.0/package.yaml
anvil init my-tools --version 2.0 # my-tools/2.0/package.yaml
# Create a flat YAML file
anvil init my-tools --flat # my-tools-1.0.0.yamlGenerate shell completions for tab completion.
# Bash (add to ~/.bashrc)
eval "$(anvil completions bash)"
# Zsh (add to ~/.zshrc)
eval "$(anvil completions zsh)"
# Fish
anvil completions fish | source
# PowerShell
anvil completions powershell | Out-String | Invoke-ExpressionGenerate executable wrapper scripts for all commands defined by the resolved packages. Each wrapper calls anvil run under the hood, so it always resolves correctly (respects lockfiles, config, etc.).
# Generate wrappers for all commands in houdini + its dependencies
anvil wrap houdini-20.5 --dir ~/tools/fx
# Creates: ~/tools/fx/houdini, ~/tools/fx/hython, ~/tools/fx/python, ...
# Add to PATH and use directly
export PATH="$HOME/tools/fx:$PATH"
houdini -scene myfile.hipThis is how you create suites -- generate wrappers for a department's tool set, add the directory to $PATH, and artists get seamless access to all tools without knowing about anvil.
Copy a validated package to a shared package repository.
# Publish from a package directory (nested layout)
anvil publish /studio/packages --path ~/dev/my-tool
# Publish as a flat YAML file
anvil publish /studio/packages --path ~/dev/my-tool --flat
# Publish from current directory
cd ~/dev/my-tool
anvil publish /studio/packagesRefuses to overwrite existing packages. Validates the package before copying.
Anvil looks for a global (user-level) configuration in this order:
$ANVIL_CONFIGenvironment variable (if set)~/.anvil.yaml~/.config/anvil/config.yaml
If no config file is found, anvil uses default package paths (see below).
Anvil also searches for a .anvil.yaml file in the current directory and its ancestors. If found, the project config is merged with the global config:
package_paths-- project paths are prepended (higher priority than global)aliases-- project aliases are added; same-name aliases override the global onesdefault_shell-- project value wins if setplatform-- project platform paths are prepended per-platform
This allows each project/show to define its own package locations and aliases without modifying the user's global config:
# /projects/myshow/.anvil.yaml
package_paths:
- /projects/myshow/packages
aliases:
show-tools:
- maya-2024
- myshow-assets-1.0
- myshow-pipeline-2.3# Package search paths (supports ${VAR} expansion and ~/ tilde expansion)
package_paths:
- ~/packages # Local/dev packages (highest priority)
- /studio/packages # Shared studio packages
- ${STUDIO_ROOT}/packages # Variable-based paths
# Default shell for 'anvil shell' (optional, defaults to $SHELL or bash)
default_shell: zsh
# Named package sets (optional)
# Use as: anvil run maya-anim -- maya
aliases:
maya-anim:
- maya-2024
- animbot-2.0
- studio-tools
maya-light:
- maya-2024
- arnold-7.2
- light-tools
studio-blender:
- blender-4.2
- studio-blender-tools
- studio-python
# Platform-specific additional package paths (optional)
# These extend the base package_paths list on matching platforms
platform:
linux:
package_paths:
- /mnt/shared/packages
windows:
package_paths:
- P:/packages
macos:
package_paths:
- /Volumes/shared/packages
# Lifecycle hooks (optional)
# Shell commands run at specific points during resolution/execution
hooks:
pre_resolve:
- echo "Resolving packages..."
post_resolve:
- /studio/scripts/log_resolution.sh
pre_run:
- /studio/scripts/check_license.sh
post_run:
- /studio/scripts/cleanup.sh
# Package filters (optional)
# When include is set, only matching packages are visible
# Exclude is applied after include. Patterns use glob syntax (*, ?)
filters:
include:
- "maya-*"
- "arnold-*"
- "studio-*"
exclude:
- "*-dev"
- "test-*"Hooks are shell commands that run at specific lifecycle points:
| Hook | When | Fails on error? |
|---|---|---|
pre_resolve |
Before package resolution | Yes |
post_resolve |
After resolution, with resolved env | Yes |
pre_run |
Before command execution in anvil run |
Yes |
post_run |
After command finishes in anvil run |
No (best-effort) |
If a pre-hook exits non-zero, the operation is aborted. Hooks receive the resolved environment as their environment.
Filters control which packages are visible. Useful for restricting a project to only approved packages:
# Only show Maya and Arnold packages, but hide dev versions
filters:
include: ["maya-*", "arnold-*"]
exclude: ["*-dev"]When include is non-empty, only matching packages pass. exclude is applied after include. Patterns support * (any chars) and ? (single char).
| Variable | Purpose |
|---|---|
ANVIL_CONFIG |
Override config file location |
ANVIL_PACKAGES |
Additional package paths (colon-separated) |
RUST_LOG |
Control log verbosity (e.g., RUST_LOG=debug anvil env maya) |
If no config file is found, anvil searches these directories for packages:
- Paths from
$ANVIL_PACKAGES(colon-separated) $HOME/packages$HOME/.local/share/anvil/packages/opt/packages
Anvil is designed as a practical alternative to Rez for studios that need fast, reliable environment resolution without the operational overhead.
| Anvil | Rez | |
|---|---|---|
| Language | Rust (single static binary) | Python |
| Startup time | Milliseconds | Seconds (Python bootstrap + imports) |
| Package format | YAML | Python (package.py) |
| Runtime dependencies | None | Python 3.7+, pip, platform bindings |
| Resolution strategy | Greedy (highest matching version) | SAT solver with backtracking |
| Installation | cargo install anvil-env or download binary |
pip install + rez bind + config |
| Learning curve | 6 commands, YAML only | Many subsystems, Python API |
| Config surface | 1 YAML file, 3 env vars | Multiple config files, dozens of settings |
- Zero bootstrap -- no Python runtime, no virtual environments, no
rez bind, no platform bindings. Copy the binary and go. - Instant startup -- sub-millisecond resolution means no lag when launching tools or shells. Artists don't wait.
- Simple packages -- YAML files that any TD can write, review in PRs, and store alongside code in version control.
- Low ops burden -- a single binary and a directory of YAML files. No database, no daemon, no package server required.
- Flat-file packages -- for simple packages (wrappers, environment configs), a single YAML file is enough. No directory hierarchy needed.
- Cross-platform first -- native Windows, Linux, macOS support with per-platform variants and shell-specific output (bash, zsh, fish, PowerShell, cmd).
- SAT solver -- handles complex constraint satisfaction that greedy resolution cannot (important for large dependency graphs with conflicts).
- Build system --
rez-build/rez-releasefor building compiled packages with cmake/make integration. - Package repository -- centralized package server with memcached integration for cached resolution.
Anvil is built for studios and teams where Rez's complexity isn't justified by the scale of the dependency graph. If your packages number in the dozens (not thousands), if your version constraints are straightforward, and if you value fast iteration and simple ops over advanced constraint solving -- Anvil is the right tool.
This includes solo TDs, small studios, and medium studios that want to ship a working pipeline without dedicating engineering time to maintaining a package management system.
Planned features, roughly in priority order. Contributions welcome.
-
anvil context-- save and restore resolved environments to a file (likerez-env --output) - Lockfiles -- pin resolved versions for reproducible environments across machines and CI
- Per-project config --
.anvil.yamlin the project root, merged with user/studio config - Conflict warnings -- detect and warn when multiple packages set the same environment variable
-
anvil init-- scaffold a new package definition from a template - Resolution caching -- cache filesystem scan results to skip re-traversal on repeated calls
- Pre/post hooks -- run scripts before or after resolution, shell entry, or command execution
- Package filters -- include or exclude packages by pattern, label, or path
- Shell completions -- tab completion for bash, zsh, fish, PowerShell
-
anvil publish-- publish validated packages to shared package repositories -
anvil wrap-- generate wrapper scripts for resolved commands; create tool suites for departments - Remote package sources -- fetch packages from HTTP, S3, or GCS endpoints
- Backtracking resolver -- upgrade to a solver that backtracks on conflicts for complex dependency graphs
- Package server -- lightweight HTTP service for centralized package hosting and discovery
- Standalone wrappers -- wrapper scripts that embed the environment (no anvil needed at runtime)
- Web dashboard -- visibility into available packages, resolution results, and usage across the studio
- Audit logging -- track who resolved what, when, for compliance and debugging
cargo build --release # Optimized binary (LTO, stripped)
cargo test # Run tests
cargo fmt # Format code
cargo clippy # Lint
# Debug logging
RUST_LOG=debug cargo run -- env maya-2024MIT