Chromium Code Reviews| Index: recipe_engine/step_runner.py |
| diff --git a/recipe_engine/step_runner.py b/recipe_engine/step_runner.py |
| index 59a625593f9e8fc06e7b6bb471bded9061e5a463..e08eb2f43540cafc0fa31a5cc21b113e2085055f 100644 |
| --- a/recipe_engine/step_runner.py |
| +++ b/recipe_engine/step_runner.py |
| @@ -6,6 +6,7 @@ import calendar |
| import collections |
| import contextlib |
| import datetime |
| +import itertools |
| import json |
| import os |
| import re |
| @@ -176,13 +177,10 @@ class SubprocessStepRunner(StepRunner): |
| ) |
| step_config = None # Make sure we use rendered step config. |
| - rendered_step = rendered_step._replace( |
| - config=rendered_step.config._replace( |
| - cmd=map(str, rendered_step.config.cmd), |
| - ), |
| - ) |
| - |
| step_env = _merge_envs(os.environ, (rendered_step.config.env or {})) |
| + # Now that the step's environment is all sorted, evaluate PATH and PATHEXT |
| + # on windows to find the actual intended executable. |
| + rendered_step = _hunt_path(rendered_step, step_env) |
| self._print_step(step_stream, rendered_step, step_env) |
| class ReturnOpenStep(OpenStep): |
| @@ -539,7 +537,7 @@ def render_step(step_config, step_test): |
| output_phs[module_name][placeholder_name][item.name] = (item, tdata) |
| else: |
| new_cmd.append(item) |
| - step_config = step_config._replace(cmd=new_cmd) |
| + step_config = step_config._replace(cmd=map(str, new_cmd)) |
| # Process 'stdout', 'stderr' and 'stdin' placeholders, if given. |
| stdio_placeholders = {} |
| @@ -650,6 +648,50 @@ def _merge_envs(original, override): |
| return result |
| +if sys.platform == "win32": |
|
Vadim Sh.
2017/03/01 19:40:45
nit: the rest of the file seems to be using ' for
|
| + def _hunt_path(rendered_step, step_env): |
| + """This takes the lazy cross-product of PATH and PATHEXT to find what |
| + cmd.exe would have found for the command if we used shell=True. |
| + |
| + This must be called on the render_step AFTER _merge_envs has produced |
| + step_env to pick up any changes to PATH or PATHEXT. |
| + |
| + If it succeeds, it returns a new rendered_step. If it fails, it returns the |
| + same rendered_step, and subprocess will run as normal (and likely fail). |
| + |
| + This will not attempt to do any evaluations for commands where the program |
| + already has an explicit extension (.exe, .bat, etc.), and it will not |
| + attempt to do any evaluations for commands where the program is an absolute |
| + path. |
| + """ |
| + cmd = rendered_step.cmd |
| + cmd0 = cmd[0] |
| + if '.' in cmd0: # something.ext will work fine with subprocess. |
| + return rendered_step |
| + if os.path.abspath(cmd0): # PATH isn't even used |
| + return rendered_step |
| + |
| + # begin the hunt |
| + full_path = step_env.get("PATH", "").split(os.path.pathsep) |
| + 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
|
| + if not (full_path and full_pathext): |
| + return rendered_step |
| + |
| + # try every extension for each path |
| + 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
|
| + candidate = os.path.join(path, cmd0+ext) |
| + if os.path.exists(candidate): |
| + return rendered_step._replace( |
| + config=rendered_step.config._replace( |
| + cmd=[candidate]+cmd[1:], |
| + ), |
| + ) |
| + return rendered_step |
| +else: |
| + def _hunt_path(rendered_step, _step_env): |
| + return rendered_step |
| + |
| + |
| def _shell_quote(arg): |
| """Shell-quotes a string with minimal noise. |