Source code for rezplugins.build_system.custom

# SPDX-License-Identifier: Apache-2.0
# Copyright Contributors to the Rez Project


"""
Package-defined build command
"""
try:
    from builtins import str
    from builtins import map
except ImportError:
    pass
from pipes import quote
import functools
import os.path
import sys
import os

from rez.build_system import BuildSystem
from rez.build_process import BuildType
from rez.utils.execution import create_forwarding_script
from rez.packages import get_developer_package
from rez.resolved_context import ResolvedContext
from rez.shells import create_shell
from rez.exceptions import PackageMetadataError
from rez.utils.colorize import heading, Printer
from rez.utils.logging_ import print_warning
from rez.vendor.six import six
from rez.config import config

basestring = six.string_types[0]


[docs]class CustomBuildSystem(BuildSystem): """This build system runs the 'build_command' defined in a package.py. For example, consider the package.py snippet: build_commands = "bash {root}/build.sh {install}" This will run the given bash command in the build path - this is typically located somewhere under the 'build' dir under the root dir containing the package.py. The following variables are available for expansion: * root: The source directory (the one containing the package.py). * install: 'install' if an install is occurring, or the empty string ('') otherwise; * build_path: The build path (this will also be the cwd); * install_path: Full path to install destination; * name: Name of the package getting built; * variant_index: Index of the current variant getting built, or an empty string ('') if no variants are present. * version: Package version currently getting built. """
[docs] @classmethod def name(cls): return "custom"
[docs] @classmethod def is_valid_root(cls, path, package=None): if package is None: try: package = get_developer_package(path) except PackageMetadataError: return False return (getattr(package, "build_command", None) is not None)
def __init__(self, working_dir, opts=None, package=None, write_build_scripts=False, verbose=False, build_args=[], child_build_args=[]): super(CustomBuildSystem, self).__init__( working_dir, opts=opts, package=package, write_build_scripts=write_build_scripts, verbose=verbose, build_args=build_args, child_build_args=child_build_args)
[docs] @classmethod def bind_cli(cls, parser, group): """ Uses a 'parse_build_args.py' file to add options, if found. """ try: with open("./parse_build_args.py") as f: source = f.read() except: return # detect what extra args have been added before_args = set(x.dest for x in parser._actions) try: exec(source, {"parser": group}) except Exception as e: print_warning("Error in ./parse_build_args.py: %s" % str(e)) after_args = set(x.dest for x in parser._actions) extra_args = after_args - before_args # store extra args onto parser so we can get to it in self.build() setattr(parser, "_rezbuild_extra_args", list(extra_args))
[docs] def build(self, context, variant, build_path, install_path, install=False, build_type=BuildType.local): """Perform the build. Note that most of the func args aren't used here - that's because this info is already passed to the custom build command via environment variables. """ ret = {} if self.write_build_scripts: # write out the script that places the user in a build env build_env_script = os.path.join(build_path, "build-env") create_forwarding_script( build_env_script, module=("build_system", "custom"), func_name="_FWD__spawn_build_shell", working_dir=self.working_dir, build_path=build_path, variant_index=variant.index, install=install, install_path=install_path ) ret["success"] = True ret["build_env_script"] = build_env_script return ret # get build command command = self.package.build_command # False just means no build command if command is False: ret["success"] = True return ret def expand(txt): # we need a shell here because build_command may reference vars like # {root}, and that may require path normalization. sh = create_shell() return txt.format( build_path=sh.normalize_path(build_path), install_path=sh.normalize_path(install_path), root=sh.normalize_path(self.package.root), install="install" if install else '', name=self.package.name, variant_index=variant.index if variant.index is not None else '', version=self.package.version ).strip() if isinstance(command, basestring): if self.build_args: command = command + ' ' + ' '.join(map(quote, self.build_args)) command = expand(command) cmd_str = command else: # list command = command + self.build_args command = list(map(expand, command)) cmd_str = ' '.join(map(quote, command)) if self.verbose: pr = Printer(sys.stdout) pr("Running build command: %s" % cmd_str, heading) # run the build command post_actions_callback = functools.partial( self.add_pre_build_commands, variant=variant, build_type=build_type, install=install, build_path=build_path, install_path=install_path ) def _actions_callback(executor): self._add_build_actions( executor, context=context, package=self.package, variant=variant, build_type=build_type, install=install, build_path=build_path, install_path=install_path ) if self.opts: # write args defined in ./parse_build_args.py out as env vars extra_args = getattr(self.opts.parser, "_rezbuild_extra_args", []) for key, value in list(vars(self.opts).items()): if key in extra_args: varname = "__PARSE_ARG_%s" % key.upper() # do some value conversions if isinstance(value, bool): value = 1 if value else 0 elif isinstance(value, (list, tuple)): value = list(map(str, value)) value = list(map(quote, value)) value = ' '.join(value) executor.env[varname] = value retcode, _, _ = context.execute_shell( command=command, block=True, cwd=build_path, actions_callback=_actions_callback, post_actions_callback=post_actions_callback ) ret["success"] = (not retcode) return ret
@classmethod def _add_build_actions(cls, executor, context, package, variant, build_type, install, build_path, install_path=None): cls.add_standard_build_actions( executor=executor, context=context, variant=variant, build_type=build_type, install=install, build_path=build_path, install_path=install_path )
def _FWD__spawn_build_shell(working_dir, build_path, variant_index, install, install_path=None): # This spawns a shell that the user can run the build command in directly context = ResolvedContext.load(os.path.join(build_path, "build.rxt")) package = get_developer_package(working_dir) variant = package.get_variant(variant_index) config.override("prompt", "BUILD>") actions_callback = functools.partial( CustomBuildSystem._add_build_actions, context=context, package=package, variant=variant, build_type=BuildType.local, install=install, build_path=build_path, install_path=install_path ) post_actions_callback = functools.partial( CustomBuildSystem.add_pre_build_commands, variant=variant, build_type=BuildType.local, install=install, build_path=build_path, install_path=install_path ) retcode, _, _ = context.execute_shell( block=True, cwd=build_path, actions_callback=actions_callback, post_actions_callback=post_actions_callback ) sys.exit(retcode)
[docs]def register_plugin(): return CustomBuildSystem