Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Installer updates #662

Merged
merged 9 commits into from
Jul 20, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 81 additions & 2 deletions INSTALL.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,82 @@
# Installation
## Installation Script

See https://github.com/nerdvegas/rez/wiki/Getting-Started#installation
To install rez, download the source. Then from the root directory, run:

```
]$ python ./install.py
```

This installs rez to `/opt/rez`. See `install.py -h` for how to install to a
different location.

Once the installation is complete, a message tells you how to run it:

```
SUCCESS! To activate Rez, add the following path to $PATH:
/opt/rez/bin/rez

You may also want to source the completion script (for bash):
source /opt/rez/completion/complete.sh
```

> [[media/icons/warning.png]] Do _not_ move the installation - re-install to a new
> location if you want to change the install path. If you want to install rez for
> multiple operating systems, perform separate installs for each of those systems.


## Installation Via Pip

It is possible to install rez with pip, like so:

```
]$ pip install rez
```

However, this comes with a caveat - rez command line tools _are not guaranteed
to work correctly_ once inside a rez environment (ie after using the `rez-env`
command). The reasons are given in the next section.

Pip installation is adequate however, if all you require is the rez API, or you
don't require its command line tools to be available within a resolved environment.

Note that running pip-installed rez command line tools will print a warning like so:

```
Pip-based rez installation detected. Please be aware that rez command line tools
are not guaranteed to function correctly in this case. See
https://github.com/nerdvegas/rez/wiki/Installation#why-not-pip-for-production
for futher details.
```


## Why Not Pip For Production?

Rez is not a normal python package. Although it can successfully be installed
using standard mechanisms such as pip, this comes with a number of caveats.
Specifically:

* When within a rez environment (ie after using the `rez-env` command), the rez
command line tools are not guaranteed to function correctly;
* When within a rez environment, other packages' tools (that were also installed
with pip) remain visible, but are not guaranteed to work.

When you enter a rez environment, the rez packages in the resolve configure
that environment as they see fit. For example, it is not uncommon for a python
package to append to PYTHONPATH. Environment variables such as PYTHONPATH
affect the behaviour of tools, including rez itself, and this can cause it to
Copy link
Contributor

@mottosso mottosso Jul 12, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would possibly be addressed by the above as well.

crash or behave abnormally.

When you use the `install.py` script to install rez, some extra steps are taken
to avoid this problem. Specifically:

* Rez is installed into a virtualenv so that it operates standalone;
* The rez tools are shebanged with `python -E`, in order to protect them from
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the --isolated method works, the python -E is embedded into Rez, which means we could skip this particular step.

environment variables that affect python's behaviour;
* The rez tools are stored in their own directory, so that other unrelated tools
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And finally, due to explicit control over the environment, this would also go away. Pof!

are not visible.

Due to the way standard wheel-based python installations work, it simply is not
possible to perform these extra steps without using a custom installation script.
Wheels do not give the opportunity to run post-installation code; neither do
they provide functionality for specifying interpreter arguments to be added for
any given entry point.
3 changes: 0 additions & 3 deletions bin/_rez-complete

This file was deleted.

3 changes: 0 additions & 3 deletions bin/_rez_fwd

This file was deleted.

3 changes: 0 additions & 3 deletions bin/bez

This file was deleted.

3 changes: 0 additions & 3 deletions bin/rez

This file was deleted.

3 changes: 0 additions & 3 deletions bin/rez-bind

This file was deleted.

3 changes: 0 additions & 3 deletions bin/rez-build

This file was deleted.

3 changes: 0 additions & 3 deletions bin/rez-config

This file was deleted.

3 changes: 0 additions & 3 deletions bin/rez-context

This file was deleted.

3 changes: 0 additions & 3 deletions bin/rez-cp

This file was deleted.

3 changes: 0 additions & 3 deletions bin/rez-depends

This file was deleted.

3 changes: 0 additions & 3 deletions bin/rez-diff

This file was deleted.

3 changes: 0 additions & 3 deletions bin/rez-env

This file was deleted.

3 changes: 0 additions & 3 deletions bin/rez-gui

This file was deleted.

3 changes: 0 additions & 3 deletions bin/rez-help

This file was deleted.

3 changes: 0 additions & 3 deletions bin/rez-interpret

This file was deleted.

3 changes: 0 additions & 3 deletions bin/rez-memcache

This file was deleted.

3 changes: 0 additions & 3 deletions bin/rez-pip

This file was deleted.

3 changes: 0 additions & 3 deletions bin/rez-plugins

This file was deleted.

3 changes: 0 additions & 3 deletions bin/rez-python

This file was deleted.

3 changes: 0 additions & 3 deletions bin/rez-release

This file was deleted.

3 changes: 0 additions & 3 deletions bin/rez-search

This file was deleted.

3 changes: 0 additions & 3 deletions bin/rez-selftest

This file was deleted.

3 changes: 0 additions & 3 deletions bin/rez-status

This file was deleted.

3 changes: 0 additions & 3 deletions bin/rez-suite

This file was deleted.

3 changes: 0 additions & 3 deletions bin/rez-test

This file was deleted.

3 changes: 0 additions & 3 deletions bin/rez-view

This file was deleted.

3 changes: 0 additions & 3 deletions bin/rez-yaml2py

This file was deleted.

3 changes: 0 additions & 3 deletions bin/rezolve

This file was deleted.

130 changes: 62 additions & 68 deletions install.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,65 +4,53 @@
"""
from __future__ import print_function

import argparse
import os
import sys
import shutil
import os.path
import textwrap
import subprocess
from optparse import OptionParser


source_path = os.path.dirname(os.path.realpath(__file__))
bin_path = os.path.join(source_path, "bin")
#bin_path = os.path.join(source_path, "bin")
src_path = os.path.join(source_path, "src")
sys.path.insert(0, src_path)

from rez.utils._version import _rez_version
from rez.cli._entry_points import get_specifications
from rez.backport.shutilwhich import which
from build_utils.virtualenv.virtualenv import Logger, create_environment, \
path_locations
from build_utils.distlib.scripts import ScriptMaker
from rez.vendor.distlib.scripts import ScriptMaker

from build_utils.virtualenv.virtualenv import create_environment, path_locations

class fake_entry(object):
code_template = textwrap.dedent(
"""
from rez.cli.{module} import run
run({target})
""").strip() + '\n'

def __init__(self, name):
self.name = name
def get_py_venv_executable(dest_dir):
# get virtualenv's python executable
_, _, _, venv_bin_dir = path_locations(dest_dir)

def get_script_text(self):
module = "_main"
target = ""
if self.name == "bez":
module = "_bez"
elif self.name == "_rez_fwd": # TODO rename this binary
target = "'forward'"
elif self.name not in ("rez", "rezolve"):
target = "'%s'" % self.name.split('-', 1)[-1]
return self.code_template.format(module=module, target=target)
env = {
"PATH": venv_bin_dir,
"PATHEXT": os.environ.get("PATHEXT", "")
}

return venv_bin_dir, which("python", env=env)

class _ScriptMaker(ScriptMaker):
def __init__(self, *nargs, **kwargs):
super(_ScriptMaker, self).__init__(*nargs, **kwargs)
self.variants = set(('',))

def _get_script_text(self, entry):
return entry.get_script_text()
def run_command(args, cwd=source_path):
if opts.verbose:
print("running in %s: %s" % (cwd, " ".join(args)))
p = subprocess.Popen(args, cwd=source_path)
p.wait()


def patch_rez_binaries(dest_dir):
bin_names = os.listdir(bin_path)
_, _, _, venv_bin_path = path_locations(dest_dir)
venv_py_executable = which("python", env={"PATH":venv_bin_path,
"PATHEXT":os.environ.get("PATHEXT", "")})
venv_bin_path, py_executable = get_py_venv_executable(dest_dir)

# delete rez bin files written by setuptools
for name in bin_names:
specs = get_specifications()

# delete rez bin files written into virtualenv
for name in specs.keys():
filepath = os.path.join(venv_bin_path, name)
if os.path.isfile(filepath):
os.remove(filepath)
Expand All @@ -75,13 +63,22 @@ def patch_rez_binaries(dest_dir):
shutil.rmtree(dest_bin_path)
os.makedirs(dest_bin_path)

maker = _ScriptMaker(bin_path, dest_bin_path)
maker.executable = venv_py_executable
options = dict(interpreter_args=["-E"])
maker = ScriptMaker(
# note: no filenames are referenced in any specifications, so
# source_dir is unused
source_dir=None,
target_dir=dest_bin_path
)

maker.executable = py_executable

for name in bin_names:
entry = fake_entry(name)
maker._make_script(entry, [], options=options)
maker.make_multiple(
specifications=specs.values(),
# the -E arg is crucial - it means rez cli tools still work within a
# rez-resolved env, even if PYTHONPATH or related env-vars would have
# otherwise changed rez's behaviour
options=dict(interpreter_args=["-E"])
)


def copy_completion_scripts(dest_dir):
Expand All @@ -103,52 +100,48 @@ def copy_completion_scripts(dest_dir):
return None


def install_rez_from_source(dest_dir):
_, py_executable = get_py_venv_executable(dest_dir)

# install via pip
run_command([py_executable, "-m", "pip", "install", "."])


if __name__ == "__main__":
usage = ("usage: %prog [options] DEST_DIR ('{version}' in DEST_DIR will "
"expand to Rez version)")
parser = OptionParser(usage=usage)
parser.add_option(
parser = argparse.ArgumentParser("Rez installer")
parser.add_argument(
'-v', '--verbose', action='count', dest='verbose', default=0,
help="Increase verbosity.")
parser.add_option(
parser.add_argument(
'-s', '--keep-symlinks', action="store_true", default=False,
help="Don't run realpath on the passed DEST_DIR to resolve symlinks; "
"ie, the baked script locations may still contain symlinks")
opts, args = parser.parse_args()
parser.add_argument(
"DIR", default="/opt/rez", nargs='?',
help="Destination directory. If '{version}' is present, it will be "
"expanded to the rez version. Default: %(default)s")

opts = parser.parse_args()

if " " in os.path.realpath(__file__):
err_str = "\nThe absolute path of install.py cannot contain spaces due to setuptools limitation.\n" \
"Please move installation files to another location or rename offending folder(s).\n"
parser.error(err_str)
parser.error(
"\nThe absolute path of install.py cannot contain spaces due to setuptools limitation.\n"
"Please move installation files to another location or rename offending folder(s).\n"
)

# determine install path
if len(args) != 1:
parser.error("expected DEST_DIR")

dest_dir = args[0].format(version=_rez_version)
dest_dir = opts.DIR.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)

# make virtualenv verbose
log_level = Logger.level_for_integer(2 - opts.verbose)
logger = Logger([(log_level, sys.stdout)])

# create the virtualenv
create_environment(dest_dir)

# install rez from source
_, _, _, venv_bin_dir = path_locations(dest_dir)
py_executable = which("python", env={"PATH":venv_bin_dir,
"PATHEXT":os.environ.get("PATHEXT",
"")})
args = [py_executable, "setup.py", "install"]
if opts.verbose:
print("running in %s: %s" % (source_path, " ".join(args)))
p = subprocess.Popen(args, cwd=source_path)
p.wait()
install_rez_from_source(dest_dir)

# patch the rez binaries
patch_rez_binaries(dest_dir)
Expand All @@ -157,6 +150,7 @@ def copy_completion_scripts(dest_dir):
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:
Expand Down
Loading