Rob Mohr | 97be992 | 2019-10-15 11:32:43 -0700 | [diff] [blame] | 1 | #!/bin/sh |
| 2 | # Copyright 2019 The LUCI Authors. All rights reserved. |
| 3 | # Use of this source code is governed under the Apache License, Version 2.0 |
| 4 | # that can be found in the LICENSE file. |
| 5 | |
| 6 | # We want to run python in unbuffered mode; however shebangs on linux grab the |
| 7 | # entire rest of the shebang line as a single argument, leading to errors like: |
| 8 | # |
recipe-roller | 8764136 | 2021-09-17 09:03:38 +0000 | [diff] [blame] | 9 | # /usr/bin/env: 'python3 -u': No such file or directory |
Rob Mohr | 97be992 | 2019-10-15 11:32:43 -0700 | [diff] [blame] | 10 | # |
| 11 | # This little shell hack is a triple-quoted noop in python, but in sh it |
| 12 | # evaluates to re-exec'ing this script in unbuffered mode. |
| 13 | # pylint: disable=pointless-string-statement |
recipe-roller | 8764136 | 2021-09-17 09:03:38 +0000 | [diff] [blame] | 14 | ''''exec python3 -u -- "$0" ${1+"$@"} # ''' |
Rob Mohr | 97be992 | 2019-10-15 11:32:43 -0700 | [diff] [blame] | 15 | """Bootstrap script to clone and forward to the recipe engine tool. |
| 16 | |
| 17 | ******************* |
| 18 | ** DO NOT MODIFY ** |
| 19 | ******************* |
| 20 | |
recipe-roller | 9da7527 | 2021-05-11 17:52:46 +0000 | [diff] [blame] | 21 | This is a copy of https://chromium.googlesource.com/infra/luci/recipes-py/+/main/recipes.py. |
Rob Mohr | 97be992 | 2019-10-15 11:32:43 -0700 | [diff] [blame] | 22 | To fix bugs, fix in the googlesource repo then run the autoroller. |
| 23 | """ |
| 24 | |
| 25 | # pylint: disable=wrong-import-position |
| 26 | import argparse |
recipe-roller | 6734633 | 2020-07-02 18:01:40 +0000 | [diff] [blame] | 27 | import errno |
Rob Mohr | 97be992 | 2019-10-15 11:32:43 -0700 | [diff] [blame] | 28 | import json |
| 29 | import logging |
| 30 | import os |
recipe-roller | df99619 | 2023-05-04 21:58:22 +0000 | [diff] [blame] | 31 | import shutil |
Rob Mohr | 97be992 | 2019-10-15 11:32:43 -0700 | [diff] [blame] | 32 | import subprocess |
| 33 | import sys |
Rob Mohr | 97be992 | 2019-10-15 11:32:43 -0700 | [diff] [blame] | 34 | |
recipe-roller | df99619 | 2023-05-04 21:58:22 +0000 | [diff] [blame] | 35 | import urllib.parse as urlparse |
Rob Mohr | 97be992 | 2019-10-15 11:32:43 -0700 | [diff] [blame] | 36 | |
recipe-roller | df99619 | 2023-05-04 21:58:22 +0000 | [diff] [blame] | 37 | from collections import namedtuple |
| 38 | |
recipe-roller | 75c7d4f | 2020-12-10 00:01:16 +0000 | [diff] [blame] | 39 | |
Rob Mohr | 97be992 | 2019-10-15 11:32:43 -0700 | [diff] [blame] | 40 | # The dependency entry for the recipe_engine in the client repo's recipes.cfg |
| 41 | # |
| 42 | # url (str) - the url to the engine repo we want to use. |
| 43 | # revision (str) - the git revision for the engine to get. |
| 44 | # branch (str) - the branch to fetch for the engine as an absolute ref (e.g. |
recipe-roller | 9da7527 | 2021-05-11 17:52:46 +0000 | [diff] [blame] | 45 | # refs/heads/main) |
Rob Mohr | 97be992 | 2019-10-15 11:32:43 -0700 | [diff] [blame] | 46 | EngineDep = namedtuple('EngineDep', 'url revision branch') |
| 47 | |
| 48 | |
| 49 | class MalformedRecipesCfg(Exception): |
recipe-roller | 4d9f0f1 | 2020-09-23 21:01:11 +0000 | [diff] [blame] | 50 | |
| 51 | def __init__(self, msg, path): |
recipe-roller | df99619 | 2023-05-04 21:58:22 +0000 | [diff] [blame] | 52 | full_message = f'malformed recipes.cfg: {msg}: {path!r}' |
| 53 | super().__init__(full_message) |
Rob Mohr | 97be992 | 2019-10-15 11:32:43 -0700 | [diff] [blame] | 54 | |
| 55 | |
| 56 | def parse(repo_root, recipes_cfg_path): |
recipe-roller | 4d9f0f1 | 2020-09-23 21:01:11 +0000 | [diff] [blame] | 57 | """Parse is a lightweight a recipes.cfg file parser. |
Rob Mohr | 97be992 | 2019-10-15 11:32:43 -0700 | [diff] [blame] | 58 | |
| 59 | Args: |
| 60 | repo_root (str) - native path to the root of the repo we're trying to run |
| 61 | recipes for. |
| 62 | recipes_cfg_path (str) - native path to the recipes.cfg file to process. |
| 63 | |
| 64 | Returns (as tuple): |
| 65 | engine_dep (EngineDep|None): The recipe_engine dependency, or None, if the |
| 66 | current repo IS the recipe_engine. |
| 67 | recipes_path (str) - native path to where the recipes live inside of the |
| 68 | current repo (i.e. the folder containing `recipes/` and/or |
| 69 | `recipe_modules`) |
| 70 | """ |
recipe-roller | df99619 | 2023-05-04 21:58:22 +0000 | [diff] [blame] | 71 | with open(recipes_cfg_path, 'r', encoding='utf-8') as file: |
| 72 | recipes_cfg = json.load(file) |
Rob Mohr | 97be992 | 2019-10-15 11:32:43 -0700 | [diff] [blame] | 73 | |
recipe-roller | 4d9f0f1 | 2020-09-23 21:01:11 +0000 | [diff] [blame] | 74 | try: |
recipe-roller | df99619 | 2023-05-04 21:58:22 +0000 | [diff] [blame] | 75 | if (version := recipes_cfg['api_version']) != 2: |
| 76 | raise MalformedRecipesCfg(f'unknown version {version}', recipes_cfg_path) |
Rob Mohr | 97be992 | 2019-10-15 11:32:43 -0700 | [diff] [blame] | 77 | |
recipe-roller | 4d9f0f1 | 2020-09-23 21:01:11 +0000 | [diff] [blame] | 78 | # If we're running ./recipes.py from the recipe_engine repo itself, then |
| 79 | # return None to signal that there's no EngineDep. |
recipe-roller | df99619 | 2023-05-04 21:58:22 +0000 | [diff] [blame] | 80 | repo_name = recipes_cfg.get('repo_name') |
recipe-roller | 4d9f0f1 | 2020-09-23 21:01:11 +0000 | [diff] [blame] | 81 | if not repo_name: |
recipe-roller | df99619 | 2023-05-04 21:58:22 +0000 | [diff] [blame] | 82 | repo_name = recipes_cfg['project_id'] |
recipe-roller | 4d9f0f1 | 2020-09-23 21:01:11 +0000 | [diff] [blame] | 83 | if repo_name == 'recipe_engine': |
recipe-roller | df99619 | 2023-05-04 21:58:22 +0000 | [diff] [blame] | 84 | return None, recipes_cfg.get('recipes_path', '') |
Rob Mohr | 97be992 | 2019-10-15 11:32:43 -0700 | [diff] [blame] | 85 | |
recipe-roller | df99619 | 2023-05-04 21:58:22 +0000 | [diff] [blame] | 86 | engine = recipes_cfg['deps']['recipe_engine'] |
Rob Mohr | 97be992 | 2019-10-15 11:32:43 -0700 | [diff] [blame] | 87 | |
recipe-roller | 4d9f0f1 | 2020-09-23 21:01:11 +0000 | [diff] [blame] | 88 | if 'url' not in engine: |
| 89 | raise MalformedRecipesCfg( |
| 90 | 'Required field "url" in dependency "recipe_engine" not found', |
| 91 | recipes_cfg_path) |
Rob Mohr | 97be992 | 2019-10-15 11:32:43 -0700 | [diff] [blame] | 92 | |
recipe-roller | 4d9f0f1 | 2020-09-23 21:01:11 +0000 | [diff] [blame] | 93 | engine.setdefault('revision', '') |
recipe-roller | 9da7527 | 2021-05-11 17:52:46 +0000 | [diff] [blame] | 94 | engine.setdefault('branch', 'refs/heads/main') |
recipe-roller | df99619 | 2023-05-04 21:58:22 +0000 | [diff] [blame] | 95 | recipes_path = recipes_cfg.get('recipes_path', '') |
Rob Mohr | 97be992 | 2019-10-15 11:32:43 -0700 | [diff] [blame] | 96 | |
recipe-roller | 4d9f0f1 | 2020-09-23 21:01:11 +0000 | [diff] [blame] | 97 | # TODO(iannucci): only support absolute refs |
| 98 | if not engine['branch'].startswith('refs/'): |
| 99 | engine['branch'] = 'refs/heads/' + engine['branch'] |
Rob Mohr | 97be992 | 2019-10-15 11:32:43 -0700 | [diff] [blame] | 100 | |
recipe-roller | 4d9f0f1 | 2020-09-23 21:01:11 +0000 | [diff] [blame] | 101 | recipes_path = os.path.join(repo_root, |
| 102 | recipes_path.replace('/', os.path.sep)) |
recipe-roller | df99619 | 2023-05-04 21:58:22 +0000 | [diff] [blame] | 103 | return EngineDep(**engine), recipes_path |
recipe-roller | 4d9f0f1 | 2020-09-23 21:01:11 +0000 | [diff] [blame] | 104 | except KeyError as ex: |
recipe-roller | df99619 | 2023-05-04 21:58:22 +0000 | [diff] [blame] | 105 | raise MalformedRecipesCfg(str(ex), recipes_cfg_path) from ex |
Rob Mohr | 97be992 | 2019-10-15 11:32:43 -0700 | [diff] [blame] | 106 | |
| 107 | |
recipe-roller | 1e8c8b1 | 2020-09-14 15:01:29 +0000 | [diff] [blame] | 108 | IS_WIN = sys.platform.startswith(('win', 'cygwin')) |
| 109 | |
| 110 | _BAT = '.bat' if IS_WIN else '' |
Rob Mohr | 97be992 | 2019-10-15 11:32:43 -0700 | [diff] [blame] | 111 | GIT = 'git' + _BAT |
Rob Mohr | 97be992 | 2019-10-15 11:32:43 -0700 | [diff] [blame] | 112 | CIPD = 'cipd' + _BAT |
recipe-roller | 0063a14 | 2022-07-25 14:49:19 +0000 | [diff] [blame] | 113 | REQUIRED_BINARIES = {GIT, CIPD} |
Rob Mohr | 97be992 | 2019-10-15 11:32:43 -0700 | [diff] [blame] | 114 | |
| 115 | |
| 116 | def _is_executable(path): |
recipe-roller | 4d9f0f1 | 2020-09-23 21:01:11 +0000 | [diff] [blame] | 117 | return os.path.isfile(path) and os.access(path, os.X_OK) |
Rob Mohr | 97be992 | 2019-10-15 11:32:43 -0700 | [diff] [blame] | 118 | |
| 119 | |
Rob Mohr | 97be992 | 2019-10-15 11:32:43 -0700 | [diff] [blame] | 120 | def _subprocess_call(argv, **kwargs): |
recipe-roller | 4d9f0f1 | 2020-09-23 21:01:11 +0000 | [diff] [blame] | 121 | logging.info('Running %r', argv) |
| 122 | return subprocess.call(argv, **kwargs) |
Rob Mohr | 97be992 | 2019-10-15 11:32:43 -0700 | [diff] [blame] | 123 | |
| 124 | |
| 125 | def _git_check_call(argv, **kwargs): |
recipe-roller | 4d9f0f1 | 2020-09-23 21:01:11 +0000 | [diff] [blame] | 126 | argv = [GIT] + argv |
| 127 | logging.info('Running %r', argv) |
| 128 | subprocess.check_call(argv, **kwargs) |
Rob Mohr | 97be992 | 2019-10-15 11:32:43 -0700 | [diff] [blame] | 129 | |
| 130 | |
| 131 | def _git_output(argv, **kwargs): |
recipe-roller | 4d9f0f1 | 2020-09-23 21:01:11 +0000 | [diff] [blame] | 132 | argv = [GIT] + argv |
| 133 | logging.info('Running %r', argv) |
| 134 | return subprocess.check_output(argv, **kwargs) |
Rob Mohr | 97be992 | 2019-10-15 11:32:43 -0700 | [diff] [blame] | 135 | |
| 136 | |
| 137 | def parse_args(argv): |
recipe-roller | 4d9f0f1 | 2020-09-23 21:01:11 +0000 | [diff] [blame] | 138 | """This extracts a subset of the arguments that this bootstrap script cares |
Rob Mohr | 97be992 | 2019-10-15 11:32:43 -0700 | [diff] [blame] | 139 | about. Currently this consists of: |
| 140 | * an override for the recipe engine in the form of `-O recipe_engine=/path` |
| 141 | * the --package option. |
| 142 | """ |
recipe-roller | df99619 | 2023-05-04 21:58:22 +0000 | [diff] [blame] | 143 | override_prefix = 'recipe_engine=' |
Rob Mohr | 97be992 | 2019-10-15 11:32:43 -0700 | [diff] [blame] | 144 | |
recipe-roller | df99619 | 2023-05-04 21:58:22 +0000 | [diff] [blame] | 145 | parser = argparse.ArgumentParser(add_help=False) |
| 146 | parser.add_argument('-O', '--project-override', action='append') |
| 147 | parser.add_argument('--package', type=os.path.abspath) |
| 148 | args, _ = parser.parse_known_args(argv) |
recipe-roller | 4d9f0f1 | 2020-09-23 21:01:11 +0000 | [diff] [blame] | 149 | for override in args.project_override or (): |
recipe-roller | df99619 | 2023-05-04 21:58:22 +0000 | [diff] [blame] | 150 | if override.startswith(override_prefix): |
| 151 | return override[len(override_prefix):], args.package |
recipe-roller | 4d9f0f1 | 2020-09-23 21:01:11 +0000 | [diff] [blame] | 152 | return None, args.package |
Rob Mohr | 97be992 | 2019-10-15 11:32:43 -0700 | [diff] [blame] | 153 | |
| 154 | |
| 155 | def checkout_engine(engine_path, repo_root, recipes_cfg_path): |
recipe-roller | 0063a14 | 2022-07-25 14:49:19 +0000 | [diff] [blame] | 156 | """Checks out the recipe_engine repo pinned in recipes.cfg. |
| 157 | |
recipe-roller | df99619 | 2023-05-04 21:58:22 +0000 | [diff] [blame] | 158 | Returns the path to the recipe engine repo. |
recipe-roller | 0063a14 | 2022-07-25 14:49:19 +0000 | [diff] [blame] | 159 | """ |
recipe-roller | df99619 | 2023-05-04 21:58:22 +0000 | [diff] [blame] | 160 | dep, recipes_path = parse(repo_root, recipes_cfg_path) |
recipe-roller | 4d9f0f1 | 2020-09-23 21:01:11 +0000 | [diff] [blame] | 161 | if dep is None: |
| 162 | # we're running from the engine repo already! |
recipe-roller | df99619 | 2023-05-04 21:58:22 +0000 | [diff] [blame] | 163 | return os.path.join(repo_root, recipes_path) |
Rob Mohr | 97be992 | 2019-10-15 11:32:43 -0700 | [diff] [blame] | 164 | |
recipe-roller | 4d9f0f1 | 2020-09-23 21:01:11 +0000 | [diff] [blame] | 165 | url = dep.url |
Rob Mohr | 97be992 | 2019-10-15 11:32:43 -0700 | [diff] [blame] | 166 | |
recipe-roller | 4d9f0f1 | 2020-09-23 21:01:11 +0000 | [diff] [blame] | 167 | if not engine_path and url.startswith('file://'): |
| 168 | engine_path = urlparse.urlparse(url).path |
Rob Mohr | 97be992 | 2019-10-15 11:32:43 -0700 | [diff] [blame] | 169 | |
recipe-roller | 4d9f0f1 | 2020-09-23 21:01:11 +0000 | [diff] [blame] | 170 | if not engine_path: |
| 171 | revision = dep.revision |
| 172 | branch = dep.branch |
Rob Mohr | 97be992 | 2019-10-15 11:32:43 -0700 | [diff] [blame] | 173 | |
recipe-roller | 4d9f0f1 | 2020-09-23 21:01:11 +0000 | [diff] [blame] | 174 | # Ensure that we have the recipe engine cloned. |
| 175 | engine_path = os.path.join(recipes_path, '.recipe_deps', 'recipe_engine') |
Rob Mohr | 97be992 | 2019-10-15 11:32:43 -0700 | [diff] [blame] | 176 | |
recipe-roller | df99619 | 2023-05-04 21:58:22 +0000 | [diff] [blame] | 177 | # Note: this logic mirrors the logic in recipe_engine/fetch.py |
| 178 | _git_check_call(['init', engine_path], stdout=subprocess.DEVNULL) |
Rob Mohr | 97be992 | 2019-10-15 11:32:43 -0700 | [diff] [blame] | 179 | |
recipe-roller | df99619 | 2023-05-04 21:58:22 +0000 | [diff] [blame] | 180 | try: |
| 181 | _git_check_call(['rev-parse', '--verify', f'{revision}^{{commit}}'], |
| 182 | cwd=engine_path, |
| 183 | stdout=subprocess.DEVNULL, |
| 184 | stderr=subprocess.DEVNULL) |
| 185 | except subprocess.CalledProcessError: |
| 186 | _git_check_call(['fetch', '--quiet', url, branch], |
| 187 | cwd=engine_path, |
| 188 | stdout=subprocess.DEVNULL) |
Rob Mohr | 97be992 | 2019-10-15 11:32:43 -0700 | [diff] [blame] | 189 | |
recipe-roller | 4d9f0f1 | 2020-09-23 21:01:11 +0000 | [diff] [blame] | 190 | try: |
| 191 | _git_check_call(['diff', '--quiet', revision], cwd=engine_path) |
| 192 | except subprocess.CalledProcessError: |
| 193 | index_lock = os.path.join(engine_path, '.git', 'index.lock') |
| 194 | try: |
| 195 | os.remove(index_lock) |
| 196 | except OSError as exc: |
recipe-roller | 767059d | 2020-10-01 15:01:55 +0000 | [diff] [blame] | 197 | if exc.errno != errno.ENOENT: |
recipe-roller | df99619 | 2023-05-04 21:58:22 +0000 | [diff] [blame] | 198 | logging.warning('failed to remove %r, reset will fail: %s', |
| 199 | index_lock, exc) |
recipe-roller | 4d9f0f1 | 2020-09-23 21:01:11 +0000 | [diff] [blame] | 200 | _git_check_call(['reset', '-q', '--hard', revision], cwd=engine_path) |
Rob Mohr | 97be992 | 2019-10-15 11:32:43 -0700 | [diff] [blame] | 201 | |
recipe-roller | 4d9f0f1 | 2020-09-23 21:01:11 +0000 | [diff] [blame] | 202 | # If the engine has refactored/moved modules we need to clean all .pyc files |
| 203 | # or things will get squirrely. |
| 204 | _git_check_call(['clean', '-qxf'], cwd=engine_path) |
Rob Mohr | 97be992 | 2019-10-15 11:32:43 -0700 | [diff] [blame] | 205 | |
recipe-roller | df99619 | 2023-05-04 21:58:22 +0000 | [diff] [blame] | 206 | return engine_path |
Rob Mohr | 97be992 | 2019-10-15 11:32:43 -0700 | [diff] [blame] | 207 | |
| 208 | |
| 209 | def main(): |
recipe-roller | 4d9f0f1 | 2020-09-23 21:01:11 +0000 | [diff] [blame] | 210 | for required_binary in REQUIRED_BINARIES: |
recipe-roller | df99619 | 2023-05-04 21:58:22 +0000 | [diff] [blame] | 211 | if not shutil.which(required_binary): |
| 212 | return f'Required binary is not found on PATH: {required_binary}' |
Rob Mohr | 97be992 | 2019-10-15 11:32:43 -0700 | [diff] [blame] | 213 | |
recipe-roller | 4d9f0f1 | 2020-09-23 21:01:11 +0000 | [diff] [blame] | 214 | if '--verbose' in sys.argv: |
| 215 | logging.getLogger().setLevel(logging.INFO) |
Rob Mohr | 97be992 | 2019-10-15 11:32:43 -0700 | [diff] [blame] | 216 | |
recipe-roller | 4d9f0f1 | 2020-09-23 21:01:11 +0000 | [diff] [blame] | 217 | args = sys.argv[1:] |
| 218 | engine_override, recipes_cfg_path = parse_args(args) |
Rob Mohr | 97be992 | 2019-10-15 11:32:43 -0700 | [diff] [blame] | 219 | |
recipe-roller | 4d9f0f1 | 2020-09-23 21:01:11 +0000 | [diff] [blame] | 220 | if recipes_cfg_path: |
| 221 | # calculate repo_root from recipes_cfg_path |
| 222 | repo_root = os.path.dirname( |
| 223 | os.path.dirname(os.path.dirname(recipes_cfg_path))) |
| 224 | else: |
| 225 | # find repo_root with git and calculate recipes_cfg_path |
| 226 | repo_root = ( |
| 227 | _git_output(['rev-parse', '--show-toplevel'], |
| 228 | cwd=os.path.abspath(os.path.dirname(__file__))).strip()) |
recipe-roller | 75c7d4f | 2020-12-10 00:01:16 +0000 | [diff] [blame] | 229 | repo_root = os.path.abspath(repo_root).decode() |
recipe-roller | 4d9f0f1 | 2020-09-23 21:01:11 +0000 | [diff] [blame] | 230 | recipes_cfg_path = os.path.join(repo_root, 'infra', 'config', 'recipes.cfg') |
| 231 | args = ['--package', recipes_cfg_path] + args |
recipe-roller | df99619 | 2023-05-04 21:58:22 +0000 | [diff] [blame] | 232 | engine_path = checkout_engine(engine_override, repo_root, recipes_cfg_path) |
Rob Mohr | 97be992 | 2019-10-15 11:32:43 -0700 | [diff] [blame] | 233 | |
recipe-roller | df99619 | 2023-05-04 21:58:22 +0000 | [diff] [blame] | 234 | vpython = 'vpython3' + _BAT |
| 235 | if not shutil.which(vpython): |
| 236 | return f'Required binary is not found on PATH: {vpython}' |
recipe-roller | 0063a14 | 2022-07-25 14:49:19 +0000 | [diff] [blame] | 237 | |
recipe-roller | 89179bd | 2023-05-31 06:46:44 +0000 | [diff] [blame] | 238 | # We unset PYTHONPATH here in case the user has conflicting environmental |
| 239 | # things we don't want them to leak through into the recipe_engine which |
| 240 | # manages its environment entirely via vpython. |
| 241 | # |
| 242 | # NOTE: os.unsetenv unhelpfully doesn't exist on all platforms until python3.9 |
| 243 | # so we have to use the cutesy `pop` formulation below until then... |
| 244 | os.environ.pop("PYTHONPATH", None) |
| 245 | |
recipe-roller | 132b20b | 2023-06-21 21:41:17 +0000 | [diff] [blame] | 246 | spec = '.vpython3' |
| 247 | debugger = os.environ.get('RECIPE_DEBUGGER', '') |
| 248 | if debugger.startswith('pycharm'): |
| 249 | spec = '.pycharm.vpython3' |
| 250 | elif debugger.startswith('vscode'): |
| 251 | spec = '.vscode.vpython3' |
| 252 | |
recipe-roller | 0063a14 | 2022-07-25 14:49:19 +0000 | [diff] [blame] | 253 | argv = ([ |
recipe-roller | 132b20b | 2023-06-21 21:41:17 +0000 | [diff] [blame] | 254 | vpython, |
| 255 | '-vpython-spec', |
| 256 | os.path.join(engine_path, spec), |
| 257 | '-u', |
| 258 | os.path.join(engine_path, 'recipe_engine', 'main.py'), |
recipe-roller | 0063a14 | 2022-07-25 14:49:19 +0000 | [diff] [blame] | 259 | ] + args) |
recipe-roller | 1e8c8b1 | 2020-09-14 15:01:29 +0000 | [diff] [blame] | 260 | |
recipe-roller | 4d9f0f1 | 2020-09-23 21:01:11 +0000 | [diff] [blame] | 261 | if IS_WIN: |
| 262 | # No real 'exec' on windows; set these signals to ignore so that they |
| 263 | # propagate to our children but we still wait for the child process to quit. |
recipe-roller | df99619 | 2023-05-04 21:58:22 +0000 | [diff] [blame] | 264 | import signal # pylint: disable=import-outside-toplevel |
| 265 | signal.signal(signal.SIGBREAK, signal.SIG_IGN) # pylint: disable=no-member |
recipe-roller | 4d9f0f1 | 2020-09-23 21:01:11 +0000 | [diff] [blame] | 266 | signal.signal(signal.SIGINT, signal.SIG_IGN) |
| 267 | signal.signal(signal.SIGTERM, signal.SIG_IGN) |
| 268 | return _subprocess_call(argv) |
recipe-roller | df99619 | 2023-05-04 21:58:22 +0000 | [diff] [blame] | 269 | |
| 270 | os.execvp(argv[0], argv) |
| 271 | return -1 # should never occur |
Rob Mohr | 97be992 | 2019-10-15 11:32:43 -0700 | [diff] [blame] | 272 | |
| 273 | |
| 274 | if __name__ == '__main__': |
recipe-roller | 4d9f0f1 | 2020-09-23 21:01:11 +0000 | [diff] [blame] | 275 | sys.exit(main()) |