Rez package definition (package.py)
One-sentence summary: every variable in
package.pybecomes an attribute of the package;requiresdeclares dependencies andcommands()configures the runtime environment.
Skill scope: the definition file itself. For the commands() body see rez-package-commands;
for requests/version syntax see rez-core-concepts; for builds see rez-cli. For the decisions behind
versions, variants, range width and the build/release loop see rez-package-authoring.
Minimal package
name = "sequence"
version = "2.1.2"
description = "Sequence detection library."
authors = ["ajohns"]
tools = ["lsq", "cpq"]
requires = ["python-2.6+<3", "argparse"]
def commands():
env.PATH.append("{root}/bin")
env.PYTHONPATH.append("{root}/python")
uuid = "6c43d533-92bb-4f8b-b812-7020bf54d3f1"
The file lives at the root of each package install:
/packages/inhouse/foo/1.0.0/package.py.
What becomes a package attribute
Every module-level variable becomes an attribute — including custom ones. These do not:
- Python modules (
import sysdoes not create asysattribute); - plain functions (except
commands,@earlyand@latefunctions); - names with a leading double underscore;
- any build-only attribute.
That list is exactly as wide as it reads, and no wider. A class is not a module and not a
function, so from pathlib import Path at module scope does create a Path attribute — and it is
serialized into the installed package.py as Path = <class 'pathlib.Path'>, which no longer
parses. Same for a class defined in the file and for any object instance. Values derived from a
stripped module survive too: sys_path = sys.prefix is kept, frozen on the build machine. See
rez-package-pitfalls for the errors and the correct form.
Standard attributes
| Attribute | Type | Purpose |
|---|---|---|
name |
str |
package name (required) |
version |
str |
version (required) |
description |
str |
general description, no version details |
authors |
list[str] |
ordered, major contributor first |
requires |
list[str] |
runtime dependencies |
build_requires |
list[str] |
build-time deps, transitive |
private_build_requires |
list[str] |
build-time deps, not transitive |
tools |
list[str] |
entry points exposed into the environment |
variants |
list[list[str]] |
variant definitions (see rez-core-concepts) |
hashed_variants |
bool |
install variants under a hash |
commands() |
function | runtime environment configuration |
uuid |
str |
stable identity across renames |
has_plugins / plugin_for |
bool / str |
plugin discovery via rez-plugins |
cachable |
bool |
allow package caching |
config |
dict |
override rez settings during build/release |
Arbitrary custom attributes are allowed and are often the cleanest way to pass
build-computed data to commands().
requires vs build_requires vs private_build_requires
The build environment requirement list is assembled in this order:
requiresbuild_requires— transitive: build requirements of all packages in the env are includedprivate_build_requires— not transitive- the current variant's requirements
Use private_build_requires for doc generators (doxygen, sphinx), build utilities and
statically linked libraries. Use build_requires for header-only C++ libraries, where consumers
of your headers also need the header at build time.
Early binding functions (@early)
Evaluated at build time; the returned value is baked into the installed package.py.
@early()
def authors():
import subprocess
p = subprocess.Popen("git shortlog -sn | cut -f2",
shell=True, stdout=subprocess.PIPE)
out, _ = p.communicate()
return out.strip().split("\n")
Rules and gotchas:
- Only package attributes are visible — the implicit
thisobject exposes package attributes only. - No rez environment variables are accessible.
- Do not reference other
@earlyor@lateattributes — an error is raised. - CWD during evaluation is the directory containing your
package.py. @earlyfunctions are evaluated multiple times: once pre-build and once per variant. The pre-build value (withbuilding == False) is what lands in the installed package.- Therefore: make sure the function returns the runtime value when
buildingisFalse.
@early()
def requires():
if building:
return ["python-2"]
else:
return ["runtimeonly-1.2", "python-2"]
Objects available during early evaluation: this, building, build_variant_index,
build_variant_requires.
Late binding functions (@late)
Remain as functions in the installed package; evaluated lazily on first access and then cached.
Allowed for: requires, build_requires, private_build_requires, tools, help, and any
arbitrary attribute.
@late()
def tools():
import os # imports MUST be inside the function
result = this._tools
if os.getenv("_USER_ROLE") != "superuser":
result = set(result) - set(["delete-all", "mod-things"])
return list(result)
@early()
def _tools():
import os
return os.listdir("./bin") # relative: not installed yet at build time
- Split the work: compute what you can at build time into an
@earlycustom attribute, then do only the runtime-dependent part in@late. This keeps resolves cheap. in_context()returnsTruewhen the package is part of a resolved context (e.g. iterating aResolvedContext) andFalsewhen merely iterating packages (e.g.rez-search). Guard context-dependent logic with it.
commands()is late bound but is never decorated with@early/@late.
String expansion
{root} expands to the install location of the package; {this.root} is equivalent.
Available in commands() and in rex calls. See rez-package-commands for literal(),
expandable() and expandvars().
Package config overrides
A package can override rez settings during its own build/release:
with scope("config") as c:
c.release_packages_path = "/software/packages/apps"
Overrides only apply while the package is being built or released, so only these are useful:
packages_path, local_packages_path, release_packages_path, the build_system /
release_hook / release_vcs plugin settings, package_definition_python_path,
and package_filter.
Useful for routing internally- and externally-developed packages to different release paths.
Preprocessing
package.py can be preprocessed before evaluation — for example to add a version timestamp or
to generate attributes from an external source. See the "Package Preprocessing" section of the
Rez package-definition docs: the package_preprocess_function setting installs a global
preprocessor, and rez-build --view-pre prints the preprocessed result.
Prefer the simplest thing that works: an @early() function usually covers the same ground with
far less machinery.
Review checklist for a package.py
-
requiresuses ranges (python-2.7+<3) rather than unpinned names when pinning matters. - Build-only deps are in
private_build_requires, notrequires. -
@early()returns the correct value whenbuildingisFalse. -
@late()performs imports inside the function body. -
variantsentries that must be discriminated are pinned in the request, not left ambiguous. -
commands()uses POSIX forward slashes, even on Windows. - No
os.pathsepor hardcodeda:bpath lists.
Validate before release
rez-build --install # build + install locally
rez-env mypackage # resolve in a fresh shell
rez-search --validate mypackage
Narrow the output — read the specific resolve, not the whole build log:
rez-context --so # source order of the resolve
