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

Enhancements for shell plugins #698

Merged
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
16 changes: 10 additions & 6 deletions src/rez/bind/hello_world.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from rez.package_maker__ import make_package
from rez.vendor.version.version import Version
from rez.utils.lint_helper import env
from rez.util import create_executable_script
from rez.util import create_executable_script, ExecutableScriptMode
from rez.bind._utils import make_dirs, check_version
import os.path

Expand All @@ -26,10 +26,10 @@ def hello_world_source():

p = OptionParser()
p.add_option("-q", dest="quiet", action="store_true",
help="quiet mode")
help="quiet mode")
p.add_option("-r", dest="retcode", type="int", default=0,
help="exit with a non-zero return code")
opts,args = p.parse_args()
help="exit with a non-zero return code")
opts, args = p.parse_args()

if not opts.quiet:
print("Hello Rez World!")
Expand All @@ -43,7 +43,12 @@ def bind(path, version_range=None, opts=None, parser=None):
def make_root(variant, root):
binpath = make_dirs(root, "bin")
filepath = os.path.join(binpath, "hello_world")
create_executable_script(filepath, hello_world_source)

create_executable_script(
filepath,
hello_world_source,
py_script_mode=ExecutableScriptMode.platform_specific
)

with make_package("hello_world", path, make_root=make_root) as pkg:
pkg.version = version
Expand All @@ -52,7 +57,6 @@ def make_root(variant, root):

return pkg.installed_variants


# Copyright 2013-2016 Allan Johns.
#
# This library is free software: you can redistribute it and/or
Expand Down
8 changes: 8 additions & 0 deletions src/rez/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,13 @@ def schema(cls):
return Or(*(x.name for x in RezToolsVisibility))


class ExecutableScriptMode_(Str):
@cached_class_property
def schema(cls):
from rez.util import ExecutableScriptMode
return Or(*(x.name for x in ExecutableScriptMode))


class OptionalStrOrFunction(Setting):
schema = Or(None, basestring, callable)

Expand Down Expand Up @@ -308,6 +315,7 @@ def _parse_env_var(self, value):
"documentation_url": Str,
"suite_visibility": SuiteVisibility_,
"rez_tools_visibility": RezToolsVisibility_,
"create_executable_script_mode": ExecutableScriptMode_,
"suite_alias_prefix_char": Char,
"package_definition_python_path": OptionalStr,
"tmpdir": OptionalStr,
Expand Down
23 changes: 15 additions & 8 deletions src/rez/rex.py
Original file line number Diff line number Diff line change
Expand Up @@ -446,6 +446,17 @@ class ActionInterpreter(object):
"""
expand_env_vars = False

# RegEx that captures environment variables (generic form).
# Extend/override to regex formats that can capture environment formats
# in other interpreters like shells if needed
ENV_VAR_REGEX = re.compile(
"|".join([
"\\${([^\\{\\}]+?)}", # ${ENVVAR}
"\\$([a-zA-Z_]+[a-zA-Z0-9_]*?)", # $ENVVAR
])
)


def get_output(self, style=OutputStyle.file):
"""Returns any implementation specific data.

Expand Down Expand Up @@ -877,12 +888,6 @@ class NamespaceFormatter(Formatter):
across shells, and avoids some problems with non-curly-braced variables in
some situations.
"""
# Note: the regex used here matches more than just posix environment variable
# names, because special shell expansion characters may be present.
ENV_VAR_REF_1 = "\\${([^\\{\\}]+?)}" # ${ENVVAR}
ENV_VAR_REF_2 = "\\$([a-zA-Z_]+[a-zA-Z0-9_]*?)" # $ENVVAR
ENV_VAR_REF = "%s|%s" % (ENV_VAR_REF_1, ENV_VAR_REF_2)
ENV_VAR_REGEX = re.compile(ENV_VAR_REF)

def __init__(self, namespace):
Formatter.__init__(self)
Expand All @@ -894,7 +899,9 @@ def escape_envvar(matchobj):
value = next((x for x in matchobj.groups() if x is not None))
return "${{%s}}" % value

format_string_ = re.sub(self.ENV_VAR_REGEX, escape_envvar, format_string)
regex = kwargs.get("regex") or ActionInterpreter.ENV_VAR_REGEX

format_string_ = re.sub(regex, escape_envvar, format_string)

# for recursive formatting, where a field has a value we want to expand,
# add kwargs to namespace, so format_field can use them...
Expand Down Expand Up @@ -1254,7 +1261,7 @@ def get_output(self, style=OutputStyle.file):
return self.manager.get_output(style=style)

def expand(self, value):
return self.formatter.format(str(value))
return self.formatter.format(str(value), regex=self.interpreter.ENV_VAR_REGEX)


# Copyright 2013-2016 Allan Johns.
Expand Down
20 changes: 19 additions & 1 deletion src/rez/rezconfig.py
Original file line number Diff line number Diff line change
Expand Up @@ -676,6 +676,25 @@
# If not zero, truncates all package changelogs to only show the last N commits
max_package_changelog_revisions = 0

# Default option on how to create scripts with util.create_executable_script.
# In order to support both windows and other OS it is recommended to set this
# to 'both'.
#
# Possible modes:
# - single:
# Creates the requested script only.
# - py:
# Create .py script that will allow launching scripts on windows,
# if the shell adds .py to PATHEXT. Make sure to use PEP-397 py.exe
# as default application for .py files.
# - platform_specific:
# Will create py script on windows and requested on other platforms
# - both:
# Creates the requested file and a .py script so that scripts can be
# launched without extension from windows and other systems.
create_executable_script_mode = "single"


###############################################################################
# Rez-1 Compatibility
###############################################################################
Expand Down Expand Up @@ -875,7 +894,6 @@
}



###############################################################################
###############################################################################
# GUI
Expand Down
72 changes: 67 additions & 5 deletions src/rez/shells.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import os
import os.path
import pipes
import re


basestring = six.string_types[0]
Expand Down Expand Up @@ -42,7 +43,6 @@ def create_shell(shell=None, **kwargs):
class Shell(ActionInterpreter):
"""Class representing a shell, such as bash or tcsh.
"""

schema_dict = {
"prompt": basestring}

Expand Down Expand Up @@ -75,6 +75,22 @@ def __init__(self):
def _addline(self, line):
self._lines.append(line)

def convert_tokens(self, value):
"""
Converts any token like ${VAR} and $VAR to shell specific form.
Uses the ENV_VAR_REGEX to correctly parse tokens.

Args:
value: str to convert

Returns:
str with shell specific variables
"""
return self.ENV_VAR_REGEX.sub(
lambda m: "".join(self.get_key_token(g) for g in m.groups() if g),
value
)

def get_output(self, style=OutputStyle.file):
if style == OutputStyle.file:
script = '\n'.join(self._lines) + '\n'
Expand Down Expand Up @@ -164,7 +180,46 @@ def spawn_shell(self, context_file, tmpdir, rcfile=None, norc=False,
"""
raise NotImplementedError

def join(self, command):
@classmethod
def get_key_token(cls, key):
"""
Encodes the environment variable into the shell specific form.
Shells might implement multiple forms, but the most common/safest
should be returned here.

Args:
key: Variable name to encode

Returns:
str of encoded token form
"""
return cls.get_all_key_tokens(key)[0]

@classmethod
def get_all_key_tokens(cls, key):
bfloch marked this conversation as resolved.
Show resolved Hide resolved
"""
Encodes the environment variable into the shell specific forms.
Shells might implement multiple forms, but the most common/safest
should be always returned at index 0.

Args:
key: Variable name to encode

Returns:
list of str with encoded token forms
"""
raise NotImplementedError

@classmethod
def line_terminator(cls):
"""
Returns:
str: default line terminator
"""
raise NotImplementedError

@classmethod
def join(cls, command):
"""
Args:
command:
Expand All @@ -175,6 +230,7 @@ def join(self, command):
"""
raise NotImplementedError


class UnixShell(Shell):
"""
A base class for common *nix shells, such as bash and tcsh.
Expand All @@ -189,6 +245,7 @@ class UnixShell(Shell):
last_command_status = '$?'
syspaths = None


#
# startup rules
#
Expand Down Expand Up @@ -400,12 +457,17 @@ def comment(self, value):
def shebang(self):
self._addline("#!%s" % self.executable)

def get_key_token(self, key):
return "${%s}" % key
@classmethod
def get_all_key_tokens(cls, key):
return ["${%s}" % key, "$%s" % key]

def join(self, command):
@classmethod
def join(cls, command):
return shlex_join(command)

@classmethod
def line_terminator(cls):
return "\n"

# Copyright 2013-2016 Allan Johns.
#
Expand Down
Loading