06 Package Commands

zhangly 2021-03-15 15:23:38
Categories: > Tags:

概述

程序包定义文件(package.py)通常定义命令的部分。
这是一个例子,用于确定如何配置环境和软件包的Python函数:

def commands():
  env.PYTHONPATH.append("{root}/python")
  env.PATH.append("{root}/bin")

这是一个典型例子,程序包将其路径添加到PYTHONPATH,并将其工具添加到PATH。
{root} 字符串代表软件包的安装目录。

配置rez环境后,解析列表中的每个软件包都将其命令部分解释并转换为shell代码。
(bash或其它,这取决于系统平台。)shell代码是源代码,用于配置环境。
在已配置的环境中,变量REZ_CONTEXT_FILE指向此shell代码文件,

然后命令rez-context-interper会对其进行打印输出。

在commands函数中使用的python API称为rex(rez执行语言)。
你可以使用此API执行一些常见操作,包括设置环境变量。

注意:如果需要导入任何python模块在命令部分中使用,则import语句必须在该函数中。

执行顺序

包的commands执行顺序取决于两个因素,请求包的顺序和包之间的依赖关系。

比如一个名为"maya_anim_tool"的包,肉眼可见的它是一个maya插件,它依赖于maya。
因此,将优先解释执行maya的命令,这是因为maya插件可能取决于maya设置的某些环境变量。
maya会初始化MAYA_PLUG_IN_PATH变量,然后"maya_anim_tool"会添加到这里面。

一个例子:
rez-env maya_anim_tool-1.3+ PyYAML-3.10 maya-2015

假设PyYAML依赖于python,而maya_anim_tool依赖于maya,则命令的执行顺序为:

添加环境变量

对于类PATH的环境变量,可以添加到此变量前面,或后面。
比如向后添加:
env.PATH.append("{root}/bin")

不过,对于任何给定变量的第一个append/prepend操作实际上会覆盖该变量。
因为考虑到了"PYTHONPATH"在系统级别上已经配置了一些PATH变量。

例如,PyQt在系统变量的"PYTHONPATH"上,同时又使用rez-env设置了不同版本的PyQt,
那么在已配置版本的环境中导入它,仍然会不正确的导入系统版本。

Tips
PATH比较特殊,它不是简单的覆盖,因为这样会丢失一些重要的系统路径,比如无法使用lscd等命令。
通常这种情况下,在解释完所有命令后,系统路径将附加回PATH。

rez将来的新特性可以为变量指定各种模式,例如一种模式会将原始(pre-rez)值附加回结果值。

支持的字符引用

可引用的对象字符

commands部分可用的任何对象都可以作为字符引用,该字符会传递给rex函数,例如:

appendenv("PATH", "{root}/bin")

在这里"{root}“将解释为root的值,root的值是软件包的安装路径(也可以使用"this.root”)。

它的代码和下述是同样的意思,只是下述的代码更冗长:

import os.path
appendenv("PATH", os.path.join(root, "bin"))

通过env对象还支持下面这种写法:

env.FOO_LIC = "{this.root}/lic"

环境变量引用字符

支持语法是$FOO和${FOO}。

Literal函数

你可以使用literal函数,来将对象或环境变量的特殊字符注释掉。比如:

env.TEST = literal("this {root} will not expand")

它还有个扩展功能是,类似格式化字符的用法,将字符和变量组合起来:

env.DESC = literal("the value of {root} is").expandable("{root}")

expandvars函数

字符引用通常只在将字符传递给rex函数或env对象的时候发生。

例如一个简单的语句"var = {root}/bin",这里的"{root}"就不会被解释成安装包路径。

但是可以使用expandvars函数来启用字符引用:

var = expandvars("{root}/bin")

预载和后置命令

我们可以为程序包设置pre_commands或post_commands。

执行顺序为:

预构建命令

在构建一个软件包的时候是不会执行commands的,原因是软件包不在其自身的构建环境中。

但是有时候需要在构建的时候运行命令,例如将一些环境变量传递到构建系统。

pre_build_commands函数就可以实现。

预测试命令

在运行包测试的环境中执行一些额外的配置很有用。

可以定义pre_test_commands函数来执行此操作。

一个例子

这是一个包定义的示例,其中的commands部分非常冗长:

name = "foo"

version = "1.0.0"

requires = [
    "python-2.7",
    "~maya-2015"
]

def commands():
    import os.path  # imports MUST be inline to the function

    # add python module, executables
    env.PYTHONPATH.append("{this.root}/python")
    env.PATH.append("{this.root}/bin")

    # show include path if a build is occurring
    if building:
        env.FOO_INCLUDE_PATH = "{this.root}/include"

    # debug support to point at local config
    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 is in use then include the maya plugin part of this package
    if "maya" in resolve:
        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")

    # license file per major version
    env.FOO_LIC = "/lic/foo_{this.version.major}.lic"

可使用的对象

commands函数中(包括pre_commands和post_commands)可以使用各种对象和函数。
例如,env是类似dict对象,表示目标环境中构造的所有环境变量。
以下是可用对象和功能:

alias(Function)

创建一个命令别名。

alias("nukex", "Nuke -x")

base(String)

this.base: 与this.root类似,但不包含变量的子路径(如果有的话)。

build(Dict-like object)

if build.install:
	info("An installation is taking place")

这个对象只能在"pre_build_commands"函数中使用。有以下字段:

building(Boolean)

if building:
    env.FOO_INCLUDE_PATH = "{root}/include"

如果正在进行构建(通常用rez-build工具完成),则此布尔变量为True,否则为False。
常见的用法是,一个包会使用这个判断来设置环境变量,这些变量只在构建过程中生效。
比如上述的例子。

command(Function)

command("rm -rf ~/.foo_plugin")

运行任意的shell命令。注意不能用这个函数来获取返回值,因为命令并没有运行。
只要在包被解释并转换为shell语言之后才会执行它们的命令。
如果有需要的话,尽可能在Python中执行简单的操作(例如文件操作等)。
它可以立即生效,且跨平台使用。如下:

def commands():
    import shutil
    import os.path
    path = os.path.expanduser("~/.foo_plugin")
    if os.path.exists(path):
        shutil.rmtree(path)

comment(Function)

if "nuke" in resolve:
    comment("note: taking over 'nuke' binary!")
    alias("nuke", "foo_nuke_replacer")

在转换后的shell脚本代码中创建注释行。当用户使用命令rez-context —interpret或查看环境变量"REZ_CONTEXT_FILE"所引用的文件时,可以看到这个注释行。

defined(Function)

if defined("REZ_MAYA_VERSION"):
    env.FOO_MAYA = 1

这个布尔函数将返回是否设置了这个环境变量。

env(Dict-like object)

env.FOO_DEBUG = 1
env["BAH_LICENSE"] = "/lic/bah.lic"

env对象表示所配置的环境变量字典。需要注意的是,这和python的os.environ返回的字典不一样。
env只表示正在配置的环境,如果之前的程序通过env对象设置了变量,则只能在env中看到变量,
os.environ是看不到的,因为os.environ并不会随之更新。

env对象还提供以下功能:

ephemerals(Dict-like object)

if "foo.cli" in ephemerals:
    info("Foo cli option is being specified!")

代表已解析完成环境中的临时字典。字典里每一项都是一个字符串(例如fooc.li-1),使用get_range来测试intersects函数(一个布尔函数,如果给定对象的版本在给定版本范围内,则返回True),下面是一个用法示例:

if intersects(ephemerals.get_range("foo.cli", "1"), "1"):
    info("Enabling foo cli tools")
    env.PATH.append("{root}/bin")

error(Function)

if "PyQt" in resolve:
    error("The floob package has problems running in combo with PyQt")

打印一个标准错误,只有打印作用,不会阻止环境构建(要阻止使用stop命令)。

getenv(Function)

if getenv("REZ_MAYA_VERSION") == "2016.sp1":
    pass

获取环境变量的值,如果变量不存在,触发"RexUndefinedVariableError"。

implicit(Dict-like object)

if "platform" in implicits:
    pass

类似于request对象(request表示程序包请求列表组成的字典),不同的是它仅包含由implicit_packages配置定义的包请求。

info(Function)

info("floob version is %s" % resolve.floob.version)

打印一个标准输出。

intersects(Function)

if intersects(resolve.maya, "2019+"):
    info("Maya 2019 or greater is present")

一个布尔函数,如果给定对象的版本在给定的版本范围内,则返回True。

可查询的有效对象包括:

注意:不要这样做if intersects(ephemerals.get("foo.cli", "0"), "1"): ...
如果不存在"foo.cli",这将把返回的默认值0与范围1进行比较,它会返回True。

在测试临时对象的交集时,请使用get_range:
if intersects(ephemerals.get_range("foo.cli", "0"), "1"): ...

literal(Function)

将特殊字符注释掉(使其失效)。
env.FOO = literal("this {root} will not expand")

也可以像这样将文字和可扩展字符链接起来:
env.FOO = literal("the value of {root} is").expandable("{root}")

request(Dict-like object)

if "maya" in request:
    info("maya was asked for!")

表示包请求列表的字典,以包的名称为键,比如下面这条命令。

]$ rez-env maya-2015 maya_utils-1.2+<2 !corelib-1.4.4

会生成如下的对象:

{
    "maya": "maya-2015",
    "maya_utils": "maya_utils-1.2+<2",
    "corelib": "!corelib-1.4.4"
}

使用get_range函数来测试intersects函数:

if intersects(request.get_range("maya", "0"), "2019"):
    info("maya 2019.* was asked for!")

resolve(Dict-like object)

if "maya" in resolve:
    info("Maya version is %s", resolve.maya.version)
    # ..or resolve["maya"].version

表示已解析环境中的包列表字典。每个键为包的名称,值为包对象。

root(String)

this.root
包的安装目录,如果程序包包含变量,则此路径包含变量的子路径。

setenv(Function)

setenv("FOO_PLUGIN_PATH", "{root}/plugins")
将环境变量设置为给定的值。相当于env.FOO = "BAH"

source(Function)

source("{root}/scripts/init.sh")
执行一个shell脚本。类似于commands,此函数不会有返回值。

stop(Function)

stop("The value should be %s", expected_value)
引发异常并阻止继续解析。当检测到无法配置有效环境的错误时,使用它。

system(System object)

if system.platform == "windows":
    ...

该对象提供系统信息,比如当前平台,架构或操作系统。
查看这个文件可以获取更多信息:https://github.com/nerdvegas/rez/blob/master/src/rez/system.py

test(Dict-like object)

if test.name == "unit":
    info("My unit test is about to run yay")

这个对象仅在"pre_test_commands"函数中生效。具有以下的字段:

this(Package object)

import os.path
env.PATH.append(os.path.join(this.root, "bin"))

表示当前包,下面是一些常用的命令:

undefined(Function)

if undefined("REZ_MAYA_VERSION"):
    info("maya is not present")

一个布尔函数,环境变量如果没有被定义则返回True,与defined刚好相反。

unsetenv(Function)

unsetenv("FOO_LIC_SERVER")
删除指定的环境变量,如果没有这个环境变量,则该函数不执行任何操作。

version(Version object)

版本对象。可以见上述的this.version。