diff --git a/install.py b/install.py index b8d7f6f6b..6e68be67c 100644 --- a/install.py +++ b/install.py @@ -16,6 +16,9 @@ src_path = os.path.join(source_path, "src") sys.path.insert(0, src_path) +# Note: The following imports are carefully selected, they will work even +# though rez is not yet built. +# from rez.utils._version import _rez_version from rez.cli._entry_points import get_specifications from rez.backport.shutilwhich import which @@ -98,6 +101,56 @@ def copy_completion_scripts(dest_dir): return None +def install(dest_dir, print_welcome=False): + """Install rez into the given directory. + + Args: + dest_dir (str): Full path to the install directory. + """ + print("installing rez to %s..." % dest_dir) + + # create the virtualenv + create_environment(dest_dir) + + # install rez from source + install_rez_from_source(dest_dir) + + # patch the rez binaries + patch_rez_binaries(dest_dir) + + # copy completion scripts into venv + completion_path = copy_completion_scripts(dest_dir) + + # mark venv as production rez install. Do not remove - rez uses this! + _, _, _, venv_bin_dir = path_locations(dest_dir) + dest_bin_dir = os.path.join(venv_bin_dir, "rez") + validation_file = os.path.join(dest_bin_dir, ".rez_production_install") + with open(validation_file, 'w') as f: + f.write(_rez_version) + + # done + if print_welcome: + print() + print("SUCCESS! To activate Rez, add the following path to $PATH:") + print(dest_bin_dir) + + if completion_path: + print('') + shell = os.getenv('SHELL') + + if shell: + shell = os.path.basename(shell) + ext = "csh" if "csh" in shell else "sh" # Basic selection logic + + print("You may also want to source the completion script (for %s):" % shell) + print("source {0}/complete.{1}".format(completion_path, ext)) + else: + print("You may also want to source the relevant completion script from:") + print(completion_path) + + print('') + + def install_rez_from_source(dest_dir): _, py_executable = get_py_venv_executable(dest_dir) @@ -105,19 +158,60 @@ def install_rez_from_source(dest_dir): run_command([py_executable, "-m", "pip", "install", "."]) +def install_as_rez_package(repo_path): + """Installs rez as a rez package. + + Note that this can be used to install new variants of rez into an existing + rez package (you may require multiple platform installations for example). + + Args: + repo_path (str): Full path to the package repository to install into. + """ + from tempfile import mkdtemp + + # do a temp production (venv-based) rez install + tmpdir = mkdtemp(prefix="rez-install-") + install(tmpdir) + + try: + # This extracts a rez package from the installation. See + # rez.utils.installer.install_as_rez_package for more details. + # + args = ( + os.path.join(tmpdir, "bin", "python"), "-E", "-c", + r"from rez.utils.installer import install_as_rez_package;" + r"install_as_rez_package('%s')" % repo_path + ) + print(subprocess.check_output(args)) + + finally: + # cleanup temp install + try: + shutil.rmtree(tmpdir) + except: + pass + + if __name__ == "__main__": - parser = argparse.ArgumentParser("Rez installer") + parser = argparse.ArgumentParser( + "Rez installer", description="Install rez in a production ready, " + "standalone Python virtual environment.") parser.add_argument( '-v', '--verbose', action='count', dest='verbose', default=0, help="Increase verbosity.") parser.add_argument( '-s', '--keep-symlinks', action="store_true", default=False, - help="Don't run realpath on the passed DEST_DIR to resolve symlinks; " + help="Don't run realpath on the passed DIR to resolve symlinks; " "ie, the baked script locations may still contain symlinks") parser.add_argument( - "DIR", default="/opt/rez", nargs='?', + '-p', '--as-rez-package', action="store_true", + help="Install rez as a rez package. Note that this installs the API " + "only (no cli tools), and DIR is expected to be the path to a rez " + "package repository (and will default to ~/packages instead).") + parser.add_argument( + "DIR", nargs='?', help="Destination directory. If '{version}' is present, it will be " - "expanded to the rez version. Default: %(default)s") + "expanded to the rez version. Default: /opt/rez") opts = parser.parse_args() @@ -128,49 +222,24 @@ def install_rez_from_source(dest_dir): ) # determine install path - dest_dir = opts.DIR.format(version=_rez_version) + if opts.DIR: + path = opts.DIR + elif opts.as_rez_package: + path = "~/packages" + else: + path = "/opt/rez" + + if opts.as_rez_package: + dest_dir = path + else: + dest_dir = path.format(version=_rez_version) + dest_dir = os.path.expanduser(dest_dir) if not opts.keep_symlinks: dest_dir = os.path.realpath(dest_dir) - print("installing rez to %s..." % dest_dir) - - # create the virtualenv - create_environment(dest_dir) - - # install rez from source - install_rez_from_source(dest_dir) - - # patch the rez binaries - patch_rez_binaries(dest_dir) - - # copy completion scripts into venv - completion_path = copy_completion_scripts(dest_dir) - - # mark venv as production rez install. Do not remove - rez uses this! - _, _, _, venv_bin_dir = path_locations(dest_dir) - dest_bin_dir = os.path.join(venv_bin_dir, "rez") - validation_file = os.path.join(dest_bin_dir, ".rez_production_install") - with open(validation_file, 'w') as f: - f.write(_rez_version) - - # done - print() - print("SUCCESS! To activate Rez, add the following path to $PATH:") - print(dest_bin_dir) - - if completion_path: - print('') - shell = os.getenv('SHELL') - - if shell: - shell = os.path.basename(shell) - ext = "csh" if "csh" in shell else "sh" # Basic selection logic - - print("You may also want to source the completion script (for %s):" % shell) - print("source {0}/complete.{1}".format(completion_path, ext)) - else: - print("You may also want to source the relevant completion script from:") - print(completion_path) - - print('') + # perform the installation + if opts.as_rez_package: + install_as_rez_package(dest_dir) + else: + install(dest_dir, print_welcome=True) diff --git a/src/rez/utils/installer.py b/src/rez/utils/installer.py new file mode 100644 index 000000000..a4837b24f --- /dev/null +++ b/src/rez/utils/installer.py @@ -0,0 +1,41 @@ +from __future__ import print_function + +import rez +from rez.package_maker import make_package +from rez.system import system +import os.path +import sys +import shutil + + +def install_as_rez_package(repo_path): + """Install the current rez installation as a rez package. + + Note: This is very similar to 'rez-bind rez', however rez-bind is intended + for deprecation. Rez itself is a special case. + + Args: + repo_path (str): Repository to install the rez package into. + """ + def commands(): + env.PYTHONPATH.append('{this.root}') + + def make_root(variant, root): + # copy source + rez_path = rez.__path__[0] + site_path = os.path.dirname(rez_path) + rezplugins_path = os.path.join(site_path, "rezplugins") + + shutil.copytree(rez_path, os.path.join(root, "rez")) + shutil.copytree(rezplugins_path, os.path.join(root, "rezplugins")) + + variant = system.variant + variant.append("python-{0.major}.{0.minor}".format(sys.version_info)) + + with make_package("rez", repo_path, make_root=make_root) as pkg: + pkg.version = rez.__version__ + pkg.commands = commands + pkg.variants = [variant] + + print('') + print("Success! Rez was installed to %s/rez/%s" % (repo_path, rez.__version__))