Install
openclaw skills install @loonghao/rez-package-commandsWriting the Rez commands() section and rex environment API — env object, setenv/appendenv/prependenv/unsetenv/alias/source/error, string expansion, literal/expandable/expandvars, execution order and build-time branching. Use when a package must set, append or prepend environment variables, expose tools, source scripts, or behave differently during a build. Covers Rez 3.4.0.
openclaw skills install @loonghao/rez-package-commandsOne-sentence summary:
commands()is Python that configures the environment; it is translated into shell code, not executed as Python at runtime.
Skill scope: the commands() body. For the surrounding package.py see rez-package-definition.
commands() is not plain Python at run time. Rez interprets it and emits target-shell code
(bash, zsh, csh, tcsh, sh, pwsh). Consequences:
print() inside commands() does not write to the user's terminal in the way you expect;
use info() / error().env objectThe env object is the common case and reads naturally:
def commands():
env.PATH.append("{root}/bin")
env.PYTHONPATH.append("{root}/python")
env.FOO_LIC = "{this.root}/lic" # plain assignment == setenv
env.MAYA_PLUG_IN_PATH.prepend("{root}/maya")
env.SOME_VAR.unset()
Equivalent function-style API:
env form | rex function | Effect |
|---|---|---|
env.X = v | setenv("X", v) | set (overwrite) |
env.X.append(v) | appendenv("X", v) | append to path-like var |
env.X.prepend(v) | prependenv("X", v) | prepend to path-like var |
env.X.unset() | unsetenv("X") | unset |
| — | alias("name", "value") | create a shell alias |
| — | source("path") | source a script |
| — | error("msg") | raise a user-visible error |
| — | info("msg") | print a message |
| — | defined("VAR") | True if the env var is set |
| — | expandvars("{root}/bin") | expand immediately |
The first append/prepend on a given variable actually overwrites it. This is
deliberate: otherwise a system PyQt already on PYTHONPATH would still win over the version you
just resolved.
PATH is special-cased — it is not clobbered, because you would lose ls/cd. The system paths
(from a non-interactive shell's default PATH) are appended back after all commands are
interpreted.
Three flavors, and the difference matters:
| Package command | Equivalent bash |
|---|---|
env.FOO = literal("${USER}") | export FOO='${USER}' |
env.FOO = expandable("${USER}") | export FOO="${USER}" |
env.FOO = expandvars("${USER}") | export FOO="jbloggs" |
{root}, {this.root}, {this.version.major}) and env-var expansion
($FOO, ${FOO}) normally happen automatically when a string reaches a rex call or env.var = "{root}/bin" does not expand — wrap it: var = expandvars("{root}/bin").literal() inhibits expansion; chain with .expandable() for mixed content:env.DESC = literal("the value of {root} is").expandable("{root}")
def commands():
env.PATH.append("{root}/bin") # forward slash, even on Windows
Rez normalizes to the target shell. It knows which variables are paths via the pathed_env_vars
config setting — by default any variable ending in PATH.
Never use os.pathsep or hardcoded a:b lists. On Git-for-Windows (git-bash) the shell's path
separator differs from the underlying system's, so these break portability.
Two rules, in this precedence:
A was requested before B, A's commands run before B's.A requires B — then B runs first.So for rez-env maya_anim_tool-1.3+ PyYAML-3.10 maya-2015, where maya_anim_tool requires maya
and PyYAML requires python:
maya → maya_anim_tool → python → PyYAML
This is why a Maya plugin can safely append to MAYA_PLUG_IN_PATH — Maya initialized it first.
Objects available in commands():
| Object | Use |
|---|---|
root / this.root | install path of current variant |
base / this.base | parent of the variant dirs |
this.version | version, with .major / .minor / .patch |
building | True during a build |
resolve | mapping of resolved packages; "maya" in resolve |
ephemerals | mapping of ephemeral packages |
in_context() | True when part of a resolved context |
defined(name) | env var presence |
A realistic example:
def commands():
import os.path # imports MUST be inline
env.PYTHONPATH.append("{this.root}/python")
env.PATH.append("{this.root}/bin")
if building: # expose headers only to builds
env.FOO_INCLUDE_PATH = "{this.root}/include"
if defined("DEBUG_FOO"):
conf_file = os.path.expanduser("~/.foo/config")
else:
conf_file = "{this.root}/config"
env.FOO_CONFIG_FILE = conf_file
if "maya" in resolve: # only wire up maya bits when maya is present
env.MAYA_PLUG_IN_PATH.append("{this.root}/maya/plugins")
if resolve.maya.version.minor == "sp3":
error("known issue with GL renderer in service pack 3, beware")
env.FOO_LIC = "/lic/foo_{this.version.major}.lic"
buildingDependency packages use building to expose build-only information:
# in boost's package.py
def commands():
if building:
env.CMAKE_MODULE_PATH.append("{root}/cmake") # ships FindBoost.cmake
commands()is never executed for the package actually being built. Usepre_build_commands()when you need code to run for the package under construction.
pre_build_commands() — runs for the package being built, before the build.pre_test_commands() — runs before the package's tests.Both take the same rex API as commands().
os.pathsep, no hardcoded :-joined paths.building / in_context() / "pkg" in resolve rather than assuming.append on a variable is understood to overwrite.literal() used where a $VAR must survive into the shell unresolved.rez-env mypkg --verbose # more solver/command detail
rez-context --interpret # show interpreted shell code
rez-test mypkg # run the package's own tests
rez-env mypkg -- printenv MY_VAR # one-shot check without a subshell