Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(775)

Side by Side Diff: recipe_engine/step_runner.py

Issue 2727553005: [step_runner] add logic to resolve absolute path of argv[0] on windows. (Closed)
Patch Set: fix comment Created 3 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « no previous file | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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
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 on windows
182 # 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 337 matching lines...) Expand 10 before | Expand all | Expand 10 after
533 # This assert ensures that: 531 # This assert ensures that:
534 # no two placeholders have the same name 532 # no two placeholders have the same name
535 # at most one placeholder has the default name 533 # at most one placeholder has the default name
536 assert item.name not in output_phs[module_name][placeholder_name], ( 534 assert item.name not in output_phs[module_name][placeholder_name], (
537 'Step "%s" has multiple output placeholders of %s.%s. Please ' 535 'Step "%s" has multiple output placeholders of %s.%s. Please '
538 'specify explicit and different names for them.' % ( 536 'specify explicit and different names for them.' % (
539 step_config.name, module_name, placeholder_name)) 537 step_config.name, module_name, placeholder_name))
540 output_phs[module_name][placeholder_name][item.name] = (item, tdata) 538 output_phs[module_name][placeholder_name][item.name] = (item, tdata)
541 else: 539 else:
542 new_cmd.append(item) 540 new_cmd.append(item)
543 step_config = step_config._replace(cmd=new_cmd) 541 step_config = step_config._replace(cmd=map(str, new_cmd))
544 542
545 # Process 'stdout', 'stderr' and 'stdin' placeholders, if given. 543 # Process 'stdout', 'stderr' and 'stdin' placeholders, if given.
546 stdio_placeholders = {} 544 stdio_placeholders = {}
547 for key in ('stdout', 'stderr', 'stdin'): 545 for key in ('stdout', 'stderr', 'stdin'):
548 placeholder = getattr(step_config, key) 546 placeholder = getattr(step_config, key)
549 tdata = None 547 tdata = None
550 if placeholder: 548 if placeholder:
551 if key == 'stdin': 549 if key == 'stdin':
552 assert isinstance(placeholder, util.InputPlaceholder), ( 550 assert isinstance(placeholder, util.InputPlaceholder), (
553 '%s(%r) should be an InputPlaceholder.' % (key, placeholder)) 551 '%s(%r) should be an InputPlaceholder.' % (key, placeholder))
(...skipping 90 matching lines...) Expand 10 before | Expand all | Expand 10 after
644 return result 642 return result
645 for k, v in override.items(): 643 for k, v in override.items():
646 if v is None: 644 if v is None:
647 if k in result: 645 if k in result:
648 del result[k] 646 del result[k]
649 else: 647 else:
650 result[str(k)] = str(v) % original 648 result[str(k)] = str(v) % original
651 return result 649 return result
652 650
653 651
652 if sys.platform == "win32":
653 _hunt_path_exts = ('.exe', '.bat')
654 def _hunt_path(rendered_step, step_env):
655 """This takes the lazy cross-product of PATH and ('.exe', '.bat') to find
656 what cmd.exe would have found for the command if we used shell=True.
657
658 This must be called on the render_step AFTER _merge_envs has produced
659 step_env to pick up any changes to PATH.
660
661 If it succeeds, it returns a new rendered_step. If it fails, it returns the
662 same rendered_step, and subprocess will run as normal (and likely fail).
663
664 This will not attempt to do any evaluations for commands where the program
665 already has an explicit extension (.exe, .bat, etc.), and it will not
666 attempt to do any evaluations for commands where the program is an absolute
667 path.
668
669 This DOES NOT use PATHEXT to keep the recipe engine's behavior as
670 predictable as possible. We don't currently rely on any other runnable
671 extensions besides these two, and when we could, we choose to explicitly
672 invoke the interpreter (e.g. python.exe, cscript.exe, etc.).
673 """
674 cmd = rendered_step.config.cmd
675 cmd0 = cmd[0]
676 if '.' in cmd0: # something.ext will work fine with subprocess.
677 return rendered_step
678 if os.path.isabs(cmd0): # PATH isn't even used
679 return rendered_step
680
681 # begin the hunt
682 paths = step_env.get('PATH', '').split(os.pathsep)
683 if not paths:
684 return rendered_step
685
686 # try every extension for each path
687 for path, ext in itertools.product(paths, _hunt_path_exts):
688 candidate = os.path.join(path, cmd0+ext)
689 if os.path.isfile(candidate):
690 return rendered_step._replace(
691 config=rendered_step.config._replace(
692 cmd=[candidate]+cmd[1:],
693 ),
694 )
695 return rendered_step
696 else:
697 def _hunt_path(rendered_step, _step_env):
698 return rendered_step
699
700
654 def _shell_quote(arg): 701 def _shell_quote(arg):
655 """Shell-quotes a string with minimal noise. 702 """Shell-quotes a string with minimal noise.
656 703
657 Such that it is still reproduced exactly in a bash/zsh shell. 704 Such that it is still reproduced exactly in a bash/zsh shell.
658 """ 705 """
659 706
660 arg = arg.encode('utf-8') 707 arg = arg.encode('utf-8')
661 708
662 if arg == '': 709 if arg == '':
663 return "''" 710 return "''"
(...skipping 15 matching lines...) Expand all
679 supplied command, and only uses the |env| kwarg for modifying the environment 726 supplied command, and only uses the |env| kwarg for modifying the environment
680 of the child process. 727 of the child process.
681 """ 728 """
682 saved_path = os.environ['PATH'] 729 saved_path = os.environ['PATH']
683 try: 730 try:
684 if path is not None: 731 if path is not None:
685 os.environ['PATH'] = path 732 os.environ['PATH'] = path
686 yield 733 yield
687 finally: 734 finally:
688 os.environ['PATH'] = saved_path 735 os.environ['PATH'] = saved_path
OLDNEW
« no previous file with comments | « no previous file | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698