360 Secure SkillHub
360 安全技能中心

rez-package-definition

该skill讲解如何编写Rez package.py包定义文件,覆盖标准属性、requires/build_requires/private_build_requires依赖声明、variants、tools、@early/@late函数、字符串展开及包配置覆盖。典型用于编写、审查或调试包定义,声明运行时/构建依赖并配置commands()环境。输入为package.py文件,输出为可被rez-build/rez-env解析和安装的包定义及运行时环境配置。

loonghao
loonghao
热度 13.68
最近更新 2026-10-02 02:22:40
分类:technical-documentation应用场景:package-definition / rez / authoring / reviewing ...

安装技能

复制下面的话术发给你的 Agent,它会通过 secure-skills 检索并安装该技能。

执行命令安装此 skill: npx secure-skills-cli add rez-package-definition
Metadata
name
rez-package-definition
description
Authoring Rez package.py files — standard attributes, early/late binding functions, requires/variants/build_requires, string expansion and package overrides. Use when writing, reviewing or debugging a package definition file, or when the user asks how to declare dependencies, variants, tools or build-time requirements. Covers Rez 3.4.0.

Rez package definition (package.py)

One-sentence summary: every variable in package.py becomes an attribute of the package; requires declares dependencies and commands() 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 sys does not create a sys attribute);
  • plain functions (except commands, @early and @late functions);
  • 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:

  1. requires
  2. build_requires — transitive: build requirements of all packages in the env are included
  3. private_build_requires — not transitive
  4. 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 this object exposes package attributes only.
  • No rez environment variables are accessible.
  • Do not reference other @early or @late attributes — an error is raised.
  • CWD during evaluation is the directory containing your package.py.
  • @early functions are evaluated multiple times: once pre-build and once per variant. The pre-build value (with building == False) is what lands in the installed package.
  • Therefore: make sure the function returns the runtime value when building is False.
@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 @early custom attribute, then do only the runtime-dependent part in @late. This keeps resolves cheap.
  • in_context() returns True when the package is part of a resolved context (e.g. iterating a ResolvedContext) and False when 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

  • requires uses ranges (python-2.7+<3) rather than unpinned names when pinning matters.
  • Build-only deps are in private_build_requires, not requires.
  • @early() returns the correct value when building is False.
  • @late() performs imports inside the function body.
  • variants entries that must be discriminated are pinned in the request, not left ambiguous.
  • commands() uses POSIX forward slashes, even on Windows.
  • No os.pathsep or hardcoded a:b path 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