diff --git a/CHANGELOG.md b/CHANGELOG.md index 5f65dc99f..2e2281638 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,23 @@ # Change Log +## 2.40.4 (2019-12-11) + +**Notes** + +- Enable support for the --rcfile argument for powershell. + The file specified after this flag is injected right before the info "You are now in a rez-configured environment." in the target_file +- Enable support for the --stdin argument for powershell. + +Testing this new feature has been activated by doing the following changes: +- the test test_shells.test_rcfile now adds an extension to the rcfile created + +- the function rez.util.create_executable_script has been changed to work on linux and windows + +- the binding rez.bind.hello_world.py has been changed to work on linux and windows + +- the decorator rez.tests.utils.shell_dependent, has been edited to stop skipping everything once a shell has been excluded + (replaced self.skipTest by continue) + ## 2.40.3 (2019-08-15) [Source](https://github.com/nerdvegas/rez/tree/2.40.3) | [Diff](https://github.com/nerdvegas/rez/compare/2.40.2...2.40.3) diff --git a/src/rez/bind/hello_world.py b/src/rez/bind/hello_world.py index 7c11bfece..f3435502c 100644 --- a/src/rez/bind/hello_world.py +++ b/src/rez/bind/hello_world.py @@ -12,7 +12,10 @@ from rez.utils.lint_helper import env from rez.util import create_executable_script from rez.bind._utils import make_dirs, check_version +from rez.system import system +import os import os.path +import inspect def commands(): @@ -21,19 +24,59 @@ def commands(): def hello_world_source(): - import sys - from optparse import OptionParser + if os.name == "nt": + code = \ + """ + @echo off - p = OptionParser() - p.add_option("-q", dest="quiet", action="store_true", - 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() + set quiet=0 + set retcode=0 + + :loop + IF NOT "%1"=="" ( + IF "%1"=="-q" ( + set quiet=1 + SHIFT + ) + IF "%1"=="-r" ( + set retcode=%2 + SHIFT + SHIFT + ) + GOTO :loop + ) + + IF %quiet%==0 ( + echo Hello Rez World! + ) + exit %retcode% + """ + return inspect.cleandoc(code) - if not opts.quiet: - print("Hello Rez World!") - sys.exit(opts.retcode) + else: + code = \ + """ + set -e + + retcode=0 + + while [ "$1" != "" ]; do + case $1 in + -q | --quiet ) quiet=1 + ;; + -r | --retcode ) retcode="$2" + shift + ;; + esac + shift + done + + if [ -z $quiet ]; then + echo Hello Rez World! + fi + exit $retcode + """ + return inspect.cleandoc(code) def bind(path, version_range=None, opts=None, parser=None): @@ -43,8 +86,11 @@ 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) - + program = "bash" + if os.name == "nt": + program = "cmd" + create_executable_script(filepath, hello_world_source(), program=program) + with make_package("hello_world", path, make_root=make_root) as pkg: pkg.version = version pkg.tools = ["hello_world"] diff --git a/src/rez/tests/test_shells.py b/src/rez/tests/test_shells.py index 841cbd444..319ba5041 100644 --- a/src/rez/tests/test_shells.py +++ b/src/rez/tests/test_shells.py @@ -133,7 +133,8 @@ def test_rcfile(self): rcfile, _, _, command = sh.startup_capabilities(rcfile=True, command=True) if rcfile and command: - f, path = tempfile.mkstemp() + # Force extension (mainly needed for windows) + f, path = tempfile.mkstemp(prefix="rcfile_", suffix=".{}".format(sh.file_extension())) os.write(f, "hello_world\n") os.close(f) diff --git a/src/rez/tests/util.py b/src/rez/tests/util.py index a5e45c18a..18a1a80a2 100644 --- a/src/rez/tests/util.py +++ b/src/rez/tests/util.py @@ -189,7 +189,8 @@ def wrapper(self, *args, **kwargs): for shell in shells: if exclude and shell in exclude: - self.skipTest("This test does not run on %s shell." % shell) + print("This test does not run on %s shell." % shell) + continue print("\ntesting in shell: %s..." % shell) config.override("default_shell", shell) func(self, *args, **kwargs) diff --git a/src/rez/util.py b/src/rez/util.py index ded0fe1d5..ac7dae2c1 100644 --- a/src/rez/util.py +++ b/src/rez/util.py @@ -45,10 +45,16 @@ def create_executable_script(filepath, body, program=None): if not body.endswith('\n'): body += '\n' + + if os.name == "nt": + if program == "cmd" and not filepath.endswith(".bat"): + filepath += ".bat" + if program == "powershell" and not filepath.endswith(".ps1"): + filepath += ".ps1" with open(filepath, 'w') as f: - # TODO: make cross platform - f.write("#!/usr/bin/env %s\n" % program) + if os.name == "posix": + f.write("#!/usr/bin/env %s\n" % program) f.write(body) # TODO: Although Windows supports os.chmod you can only set the readonly diff --git a/src/rez/utils/_version.py b/src/rez/utils/_version.py index 952fd1823..176d4959a 100644 --- a/src/rez/utils/_version.py +++ b/src/rez/utils/_version.py @@ -1,7 +1,7 @@ # Update this value to version up Rez. Do not place anything else in this file. -_rez_version = "2.40.3" +_rez_version = "2.40.4" # Copyright 2013-2016 Allan Johns. diff --git a/src/rezplugins/shell/powershell.py b/src/rezplugins/shell/powershell.py index 0b00230d7..f6a91fa89 100644 --- a/src/rezplugins/shell/powershell.py +++ b/src/rezplugins/shell/powershell.py @@ -18,6 +18,7 @@ class PowerShell(CMD): syspaths = None _executable = None + stdin_arg_name = "in_stdin" @property def executable(cls): @@ -36,28 +37,64 @@ def file_extension(cls): @classmethod def startup_capabilities(cls, rcfile=False, norc=False, stdin=False, command=False): - cls._unsupported_option('rcfile', rcfile) cls._unsupported_option('norc', norc) - cls._unsupported_option('stdin', stdin) - rcfile = False norc = False - stdin = False + + if command not in (None, False): + cls._overruled_option('stdin', 'command', stdin) + cls._overruled_option('rcfile', 'command', rcfile) + stdin = False + rcfile = False + if stdin: + cls._overruled_option('rcfile', 'stdin', rcfile) + rcfile = False + return (rcfile, norc, stdin, command) @classmethod def get_startup_sequence(cls, rcfile, norc, stdin, command): rcfile, norc, stdin, command = \ cls.startup_capabilities(rcfile, norc, stdin, command) + + files = [] + do_rcfile = False + + if rcfile: + do_rcfile = True + if rcfile and os.path.exists(os.path.expanduser(rcfile)) and rcfile.endswith(".ps1"): + files.append(rcfile) return dict( stdin=stdin, command=command, - do_rcfile=False, + do_rcfile=do_rcfile, envvar=None, - files=[], + files=files, bind_files=[], source_bind_files=(not norc) ) + + def add_argument_parser(self, arg_dict={}): + """ + dict is of form: + arg_dict= { argument1: {"type": type, + "default_value": default_value + }, + argument2: {"type": type, + "default_value": default_value + } + } + """ + self._addline('param (') + for arg in arg_dict: + self._addline('\t[{}]${}={}'.format( arg_dict[arg]["type"], + arg, + arg_dict[arg]["default_value"]) + ) + self._addline(')') + + def invoke(self, expr): + self._addline('Invoke-Expression {}'.format(expr)) def _bind_interactive_rez(self): if config.set_prompt and self.settings.prompt: @@ -71,11 +108,18 @@ def spawn_shell(self, context_file, tmpdir, rcfile=None, norc=False, shell_command = None def _record_shell(ex, files, bind_rez=True, print_msg=False): + if startup_sequence["stdin"]: + # prepare the script for $input argument + arg_data = {self.stdin_arg_name:{"type":"string", "default_value":'""'}} + ex.interpreter.add_argument_parser(arg_data) ex.source(context_file) if startup_sequence["envvar"]: ex.unsetenv(startup_sequence["envvar"]) if bind_rez: ex.interpreter._bind_interactive_rez() + # The "files" item can only be fed by the rcfile argument, it should contain only one element + for f in startup_sequence["files"]: + ex.source(f) if print_msg and not quiet: ex.info('You are now in a rez-configured environment.') @@ -84,6 +128,9 @@ def _record_shell(ex, files, bind_rez=True, print_msg=False): # available, in which case the command will succeed # but output to stderr, muted with 2>$null ex.command("Try { rez context 2>$null } Catch { }") + if startup_sequence["stdin"]: + # Invoke what was passed to stdin + ex.interpreter.invoke("${}".format(self.stdin_arg_name)) executor = RexExecutor(interpreter=self.new_shell(), parent_environ={}, @@ -92,6 +139,9 @@ def _record_shell(ex, files, bind_rez=True, print_msg=False): if startup_sequence["command"] is not None: _record_shell(executor, files=startup_sequence["files"]) shell_command = startup_sequence["command"] + elif startup_sequence["stdin"]: + _record_shell(executor, + files=startup_sequence["files"]) else: _record_shell(executor, files=startup_sequence["files"], @@ -117,6 +167,17 @@ def _record_shell(ex, files, bind_rez=True, print_msg=False): if not isinstance(cmd, (tuple, list)): cmd = pre_command.rstrip().split() + + + target_file_execute_string = '. "{}"'.format(target_file) + if startup_sequence["stdin"]: + target_file_execute_string += " -in_stdin $input" + if shell_command or startup_sequence["stdin"]: + # We force powershell to return the exit code of the target file + # Without this, the return code is the one from powershell.exe + # For "stdin" if we don't force to quit, the shell will never end. + # That also means that stdin will work only once + target_file_execute_string += ";exit $LASTEXITCODE" cmd += [ self.executable, @@ -131,11 +192,14 @@ def _record_shell(ex, files, bind_rez=True, print_msg=False): "-ExecutionPolicy", "Unrestricted", # Start from this script - '. "{}"'.format(target_file) + target_file_execute_string ] - if shell_command is None: + if shell_command is None or startup_sequence["stdin"] is None: cmd.insert(1, "-NoExit") + + if startup_sequence["stdin"] and stdin and (stdin is not True): + Popen_args["stdin"] = stdin # No environment was explicity passed if not env and not config.inherit_parent_environment: