Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 # Copyright 2016 The LUCI Authors. All rights reserved. | 1 # Copyright 2016 The LUCI Authors. All rights reserved. |
| 2 # Use of this source code is governed under the Apache License, Version 2.0 | 2 # Use of this source code is governed under the Apache License, Version 2.0 |
| 3 # that can be found in the LICENSE file. | 3 # that can be found in the LICENSE file. |
| 4 | 4 |
| 5 import calendar | 5 import calendar |
| 6 import collections | 6 import collections |
| 7 import contextlib | 7 import contextlib |
| 8 import datetime | 8 import datetime |
| 9 import itertools | |
| 9 import json | 10 import json |
| 10 import os | 11 import os |
| 11 import re | 12 import re |
| 12 import StringIO | 13 import StringIO |
| 13 import sys | 14 import sys |
| 14 import tempfile | 15 import tempfile |
| 15 import time | 16 import time |
| 16 import traceback | 17 import traceback |
| 17 | 18 |
| 18 from . import recipe_api | 19 from . import recipe_api |
| (...skipping 150 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 169 def stream(inner): | 170 def stream(inner): |
| 170 return step_stream | 171 return step_stream |
| 171 | 172 |
| 172 return EmptyOpenStep() | 173 return EmptyOpenStep() |
| 173 | 174 |
| 174 rendered_step = render_step( | 175 rendered_step = render_step( |
| 175 step_config, recipe_test_api.DisabledTestData() | 176 step_config, recipe_test_api.DisabledTestData() |
| 176 ) | 177 ) |
| 177 step_config = None # Make sure we use rendered step config. | 178 step_config = None # Make sure we use rendered step config. |
| 178 | 179 |
| 179 rendered_step = rendered_step._replace( | |
| 180 config=rendered_step.config._replace( | |
| 181 cmd=map(str, rendered_step.config.cmd), | |
| 182 ), | |
| 183 ) | |
| 184 | |
| 185 step_env = _merge_envs(os.environ, (rendered_step.config.env or {})) | 180 step_env = _merge_envs(os.environ, (rendered_step.config.env or {})) |
| 181 # Now that the step's environment is all sorted, evaluate PATH and PATHEXT | |
| 182 # on windows to find the actual intended executable. | |
| 183 rendered_step = _hunt_path(rendered_step, step_env) | |
| 186 self._print_step(step_stream, rendered_step, step_env) | 184 self._print_step(step_stream, rendered_step, step_env) |
| 187 | 185 |
| 188 class ReturnOpenStep(OpenStep): | 186 class ReturnOpenStep(OpenStep): |
| 189 def run(inner): | 187 def run(inner): |
| 190 step_config = rendered_step.config | 188 step_config = rendered_step.config |
| 191 try: | 189 try: |
| 192 # Open file handles for IO redirection based on file names in | 190 # Open file handles for IO redirection based on file names in |
| 193 # step_config. | 191 # step_config. |
| 194 handles = { | 192 handles = { |
| 195 'stdout': step_stream, | 193 'stdout': step_stream, |
| (...skipping 336 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 532 # This assert ensures that: | 530 # This assert ensures that: |
| 533 # no two placeholders have the same name | 531 # no two placeholders have the same name |
| 534 # at most one placeholder has the default name | 532 # at most one placeholder has the default name |
| 535 assert item.name not in output_phs[module_name][placeholder_name], ( | 533 assert item.name not in output_phs[module_name][placeholder_name], ( |
| 536 'Step "%s" has multiple output placeholders of %s.%s. Please ' | 534 'Step "%s" has multiple output placeholders of %s.%s. Please ' |
| 537 'specify explicit and different names for them.' % ( | 535 'specify explicit and different names for them.' % ( |
| 538 step_config.name, module_name, placeholder_name)) | 536 step_config.name, module_name, placeholder_name)) |
| 539 output_phs[module_name][placeholder_name][item.name] = (item, tdata) | 537 output_phs[module_name][placeholder_name][item.name] = (item, tdata) |
| 540 else: | 538 else: |
| 541 new_cmd.append(item) | 539 new_cmd.append(item) |
| 542 step_config = step_config._replace(cmd=new_cmd) | 540 step_config = step_config._replace(cmd=map(str, new_cmd)) |
| 543 | 541 |
| 544 # Process 'stdout', 'stderr' and 'stdin' placeholders, if given. | 542 # Process 'stdout', 'stderr' and 'stdin' placeholders, if given. |
| 545 stdio_placeholders = {} | 543 stdio_placeholders = {} |
| 546 for key in ('stdout', 'stderr', 'stdin'): | 544 for key in ('stdout', 'stderr', 'stdin'): |
| 547 placeholder = getattr(step_config, key) | 545 placeholder = getattr(step_config, key) |
| 548 tdata = None | 546 tdata = None |
| 549 if placeholder: | 547 if placeholder: |
| 550 if key == 'stdin': | 548 if key == 'stdin': |
| 551 assert isinstance(placeholder, util.InputPlaceholder), ( | 549 assert isinstance(placeholder, util.InputPlaceholder), ( |
| 552 '%s(%r) should be an InputPlaceholder.' % (key, placeholder)) | 550 '%s(%r) should be an InputPlaceholder.' % (key, placeholder)) |
| (...skipping 90 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 643 return result | 641 return result |
| 644 for k, v in override.items(): | 642 for k, v in override.items(): |
| 645 if v is None: | 643 if v is None: |
| 646 if k in result: | 644 if k in result: |
| 647 del result[k] | 645 del result[k] |
| 648 else: | 646 else: |
| 649 result[str(k)] = str(v) % original | 647 result[str(k)] = str(v) % original |
| 650 return result | 648 return result |
| 651 | 649 |
| 652 | 650 |
| 651 if sys.platform == "win32": | |
|
Vadim Sh.
2017/03/01 19:40:45
nit: the rest of the file seems to be using ' for
| |
| 652 def _hunt_path(rendered_step, step_env): | |
| 653 """This takes the lazy cross-product of PATH and PATHEXT to find what | |
| 654 cmd.exe would have found for the command if we used shell=True. | |
| 655 | |
| 656 This must be called on the render_step AFTER _merge_envs has produced | |
| 657 step_env to pick up any changes to PATH or PATHEXT. | |
| 658 | |
| 659 If it succeeds, it returns a new rendered_step. If it fails, it returns the | |
| 660 same rendered_step, and subprocess will run as normal (and likely fail). | |
| 661 | |
| 662 This will not attempt to do any evaluations for commands where the program | |
| 663 already has an explicit extension (.exe, .bat, etc.), and it will not | |
| 664 attempt to do any evaluations for commands where the program is an absolute | |
| 665 path. | |
| 666 """ | |
| 667 cmd = rendered_step.cmd | |
| 668 cmd0 = cmd[0] | |
| 669 if '.' in cmd0: # something.ext will work fine with subprocess. | |
| 670 return rendered_step | |
| 671 if os.path.abspath(cmd0): # PATH isn't even used | |
| 672 return rendered_step | |
| 673 | |
| 674 # begin the hunt | |
| 675 full_path = step_env.get("PATH", "").split(os.path.pathsep) | |
| 676 full_pathext = step_env.get("PATHEXT", ".EXE;.BAT").split(os.path.pathsep) | |
|
Vadim Sh.
2017/03/01 19:40:45
nit: let's lower them, or we'll have ugly "cipd.EX
| |
| 677 if not (full_path and full_pathext): | |
| 678 return rendered_step | |
| 679 | |
| 680 # try every extension for each path | |
| 681 for path, ext in itertools.product(path, ext): | |
|
Vadim Sh.
2017/03/01 19:40:45
er.. what?.. Should it be itertools.product(full_p
| |
| 682 candidate = os.path.join(path, cmd0+ext) | |
| 683 if os.path.exists(candidate): | |
| 684 return rendered_step._replace( | |
| 685 config=rendered_step.config._replace( | |
| 686 cmd=[candidate]+cmd[1:], | |
| 687 ), | |
| 688 ) | |
| 689 return rendered_step | |
| 690 else: | |
| 691 def _hunt_path(rendered_step, _step_env): | |
| 692 return rendered_step | |
| 693 | |
| 694 | |
| 653 def _shell_quote(arg): | 695 def _shell_quote(arg): |
| 654 """Shell-quotes a string with minimal noise. | 696 """Shell-quotes a string with minimal noise. |
| 655 | 697 |
| 656 Such that it is still reproduced exactly in a bash/zsh shell. | 698 Such that it is still reproduced exactly in a bash/zsh shell. |
| 657 """ | 699 """ |
| 658 | 700 |
| 659 arg = arg.encode('utf-8') | 701 arg = arg.encode('utf-8') |
| 660 | 702 |
| 661 if arg == '': | 703 if arg == '': |
| 662 return "''" | 704 return "''" |
| (...skipping 15 matching lines...) Expand all Loading... | |
| 678 supplied command, and only uses the |env| kwarg for modifying the environment | 720 supplied command, and only uses the |env| kwarg for modifying the environment |
| 679 of the child process. | 721 of the child process. |
| 680 """ | 722 """ |
| 681 saved_path = os.environ['PATH'] | 723 saved_path = os.environ['PATH'] |
| 682 try: | 724 try: |
| 683 if path is not None: | 725 if path is not None: |
| 684 os.environ['PATH'] = path | 726 os.environ['PATH'] = path |
| 685 yield | 727 yield |
| 686 finally: | 728 finally: |
| 687 os.environ['PATH'] = saved_path | 729 os.environ['PATH'] = saved_path |
| OLD | NEW |