Get updates to infra/recipes.py from upstream
Update to 7f9335a1dbbb95a859bf47d5404df49ea76e2a6b
Bug: None
Change-Id: I6a603921774c88229e7344d76df1accdb3d44d56
Reviewed-on: https://gn-review.googlesource.com/c/gn/+/17040
Reviewed-by: David Turner <digit@google.com>
Commit-Queue: David Turner <digit@google.com>
Reviewed-by: Takuto Ikuta <tikuta@google.com>
diff --git a/infra/recipes.py b/infra/recipes.py
index 7c534c2..05ca941 100755
--- a/infra/recipes.py
+++ b/infra/recipes.py
@@ -12,7 +12,6 @@
# evaluates to re-exec'ing this script in unbuffered mode.
# pylint: disable=pointless-string-statement
''''exec python3 -u -- "$0" ${1+"$@"} # '''
-# vi: syntax=python
"""Bootstrap script to clone and forward to the recipe engine tool.
*******************
@@ -29,16 +28,14 @@
import json
import logging
import os
+import shutil
import subprocess
import sys
-from collections import namedtuple
-from io import open # pylint: disable=redefined-builtin
+import urllib.parse as urlparse
-try:
- import urllib.parse as urlparse
-except ImportError:
- import urlparse
+from collections import namedtuple
+
# The dependency entry for the recipe_engine in the client repo's recipes.cfg
#
@@ -52,8 +49,8 @@
class MalformedRecipesCfg(Exception):
def __init__(self, msg, path):
- full_message = 'malformed recipes.cfg: %s: %r' % (msg, path)
- super(MalformedRecipesCfg, self).__init__(full_message)
+ full_message = f'malformed recipes.cfg: {msg}: {path!r}'
+ super().__init__(full_message)
def parse(repo_root, recipes_cfg_path):
@@ -70,27 +67,23 @@
recipes_path (str) - native path to where the recipes live inside of the
current repo (i.e. the folder containing `recipes/` and/or
`recipe_modules`)
- py3_only (bool) - True if this repo has been marked as ONLY supporting
- python3.
"""
- with open(recipes_cfg_path, 'r') as fh:
- pb = json.load(fh)
- py3_only = pb.get('py3_only', False)
+ with open(recipes_cfg_path, 'r', encoding='utf-8') as file:
+ recipes_cfg = json.load(file)
try:
- if pb['api_version'] != 2:
- raise MalformedRecipesCfg('unknown version %d' % pb['api_version'],
- recipes_cfg_path)
+ if (version := recipes_cfg['api_version']) != 2:
+ raise MalformedRecipesCfg(f'unknown version {version}', recipes_cfg_path)
# If we're running ./recipes.py from the recipe_engine repo itself, then
# return None to signal that there's no EngineDep.
- repo_name = pb.get('repo_name')
+ repo_name = recipes_cfg.get('repo_name')
if not repo_name:
- repo_name = pb['project_id']
+ repo_name = recipes_cfg['project_id']
if repo_name == 'recipe_engine':
- return None, pb.get('recipes_path', ''), py3_only
+ return None, recipes_cfg.get('recipes_path', '')
- engine = pb['deps']['recipe_engine']
+ engine = recipes_cfg['deps']['recipe_engine']
if 'url' not in engine:
raise MalformedRecipesCfg(
@@ -99,7 +92,7 @@
engine.setdefault('revision', '')
engine.setdefault('branch', 'refs/heads/main')
- recipes_path = pb.get('recipes_path', '')
+ recipes_path = recipes_cfg.get('recipes_path', '')
# TODO(iannucci): only support absolute refs
if not engine['branch'].startswith('refs/'):
@@ -107,9 +100,9 @@
recipes_path = os.path.join(repo_root,
recipes_path.replace('/', os.path.sep))
- return EngineDep(**engine), recipes_path, py3_only
+ return EngineDep(**engine), recipes_path
except KeyError as ex:
- raise MalformedRecipesCfg(str(ex), recipes_cfg_path)
+ raise MalformedRecipesCfg(str(ex), recipes_cfg_path) from ex
IS_WIN = sys.platform.startswith(('win', 'cygwin'))
@@ -124,15 +117,6 @@
return os.path.isfile(path) and os.access(path, os.X_OK)
-# TODO: Use shutil.which once we switch to Python3.
-def _is_on_path(basename):
- for path in os.environ['PATH'].split(os.pathsep):
- full_path = os.path.join(path, basename)
- if _is_executable(full_path):
- return True
- return False
-
-
def _subprocess_call(argv, **kwargs):
logging.info('Running %r', argv)
return subprocess.call(argv, **kwargs)
@@ -156,27 +140,27 @@
* an override for the recipe engine in the form of `-O recipe_engine=/path`
* the --package option.
"""
- PREFIX = 'recipe_engine='
+ override_prefix = 'recipe_engine='
- p = argparse.ArgumentParser(add_help=False)
- p.add_argument('-O', '--project-override', action='append')
- p.add_argument('--package', type=os.path.abspath)
- args, _ = p.parse_known_args(argv)
+ parser = argparse.ArgumentParser(add_help=False)
+ parser.add_argument('-O', '--project-override', action='append')
+ parser.add_argument('--package', type=os.path.abspath)
+ args, _ = parser.parse_known_args(argv)
for override in args.project_override or ():
- if override.startswith(PREFIX):
- return override[len(PREFIX):], args.package
+ if override.startswith(override_prefix):
+ return override[len(override_prefix):], args.package
return None, args.package
def checkout_engine(engine_path, repo_root, recipes_cfg_path):
"""Checks out the recipe_engine repo pinned in recipes.cfg.
- Returns the path to the recipe engine repo and the py3_only boolean.
+ Returns the path to the recipe engine repo.
"""
- dep, recipes_path, py3_only = parse(repo_root, recipes_cfg_path)
+ dep, recipes_path = parse(repo_root, recipes_cfg_path)
if dep is None:
# we're running from the engine repo already!
- return os.path.join(repo_root, recipes_path), py3_only
+ return os.path.join(repo_root, recipes_path)
url = dep.url
@@ -190,20 +174,18 @@
# Ensure that we have the recipe engine cloned.
engine_path = os.path.join(recipes_path, '.recipe_deps', 'recipe_engine')
- with open(os.devnull, 'w') as NUL:
- # Note: this logic mirrors the logic in recipe_engine/fetch.py
- _git_check_call(['init', engine_path], stdout=NUL)
+ # Note: this logic mirrors the logic in recipe_engine/fetch.py
+ _git_check_call(['init', engine_path], stdout=subprocess.DEVNULL)
- try:
- _git_check_call(['rev-parse', '--verify',
- '%s^{commit}' % revision],
- cwd=engine_path,
- stdout=NUL,
- stderr=NUL)
- except subprocess.CalledProcessError:
- _git_check_call(['fetch', '--quiet', url, branch],
- cwd=engine_path,
- stdout=NUL)
+ try:
+ _git_check_call(['rev-parse', '--verify', f'{revision}^{{commit}}'],
+ cwd=engine_path,
+ stdout=subprocess.DEVNULL,
+ stderr=subprocess.DEVNULL)
+ except subprocess.CalledProcessError:
+ _git_check_call(['fetch', '--quiet', url, branch],
+ cwd=engine_path,
+ stdout=subprocess.DEVNULL)
try:
_git_check_call(['diff', '--quiet', revision], cwd=engine_path)
@@ -213,21 +195,21 @@
os.remove(index_lock)
except OSError as exc:
if exc.errno != errno.ENOENT:
- logging.warn('failed to remove %r, reset will fail: %s', index_lock,
- exc)
+ logging.warning('failed to remove %r, reset will fail: %s',
+ index_lock, exc)
_git_check_call(['reset', '-q', '--hard', revision], cwd=engine_path)
# If the engine has refactored/moved modules we need to clean all .pyc files
# or things will get squirrely.
_git_check_call(['clean', '-qxf'], cwd=engine_path)
- return engine_path, py3_only
+ return engine_path
def main():
for required_binary in REQUIRED_BINARIES:
- if not _is_on_path(required_binary):
- return 'Required binary is not found on PATH: %s' % required_binary
+ if not shutil.which(required_binary):
+ return f'Required binary is not found on PATH: {required_binary}'
if '--verbose' in sys.argv:
logging.getLogger().setLevel(logging.INFO)
@@ -247,27 +229,46 @@
repo_root = os.path.abspath(repo_root).decode()
recipes_cfg_path = os.path.join(repo_root, 'infra', 'config', 'recipes.cfg')
args = ['--package', recipes_cfg_path] + args
- engine_path, py3_only = checkout_engine(engine_override, repo_root, recipes_cfg_path)
+ engine_path = checkout_engine(engine_override, repo_root, recipes_cfg_path)
- using_py3 = py3_only or os.getenv('RECIPES_USE_PY3') == 'true'
- vpython = ('vpython' + ('3' if using_py3 else '') + _BAT)
- if not _is_on_path(vpython):
- return 'Required binary is not found on PATH: %s' % vpython
+ vpython = 'vpython3' + _BAT
+ if not shutil.which(vpython):
+ return f'Required binary is not found on PATH: {vpython}'
+
+ # We unset PYTHONPATH here in case the user has conflicting environmental
+ # things we don't want them to leak through into the recipe_engine which
+ # manages its environment entirely via vpython.
+ #
+ # NOTE: os.unsetenv unhelpfully doesn't exist on all platforms until python3.9
+ # so we have to use the cutesy `pop` formulation below until then...
+ os.environ.pop("PYTHONPATH", None)
+
+ spec = '.vpython3'
+ debugger = os.environ.get('RECIPE_DEBUGGER', '')
+ if debugger.startswith('pycharm'):
+ spec = '.pycharm.vpython3'
+ elif debugger.startswith('vscode'):
+ spec = '.vscode.vpython3'
argv = ([
- vpython, '-u', os.path.join(engine_path, 'recipe_engine', 'main.py'),
+ vpython,
+ '-vpython-spec',
+ os.path.join(engine_path, spec),
+ '-u',
+ os.path.join(engine_path, 'recipe_engine', 'main.py'),
] + args)
if IS_WIN:
# No real 'exec' on windows; set these signals to ignore so that they
# propagate to our children but we still wait for the child process to quit.
- import signal
- signal.signal(signal.SIGBREAK, signal.SIG_IGN)
+ import signal # pylint: disable=import-outside-toplevel
+ signal.signal(signal.SIGBREAK, signal.SIG_IGN) # pylint: disable=no-member
signal.signal(signal.SIGINT, signal.SIG_IGN)
signal.signal(signal.SIGTERM, signal.SIG_IGN)
return _subprocess_call(argv)
- else:
- os.execvp(argv[0], argv)
+
+ os.execvp(argv[0], argv)
+ return -1 # should never occur
if __name__ == '__main__':