blob: ee3a5b79bd508dd34b64ac35c899cfcb178c15b9 [file] [log] [blame]
Josip Sokcevicb8139bd2024-02-06 14:25:23 -08001#!/usr/bin/env python3
Mike Frysingerf241f8c2020-02-20 17:08:43 -05002# Copyright (C) 2008 The Android Open Source Project
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070015
Mike Frysinger87fb5a12019-06-13 01:54:46 -040016"""Repo launcher.
17
18This is a standalone tool that people may copy to anywhere in their system.
19It is used to get an initial repo client checkout, and after that it runs the
20copy of repo in the checkout.
21"""
22
Mike Frysinger84094102020-02-11 02:10:28 -050023import datetime
Mike Frysinger3ba716f2019-06-13 01:48:12 -040024import os
25import platform
Mike Frysinger949bc342020-02-18 21:37:00 -050026import shlex
Mike Frysinger3ba716f2019-06-13 01:48:12 -040027import subprocess
28import sys
Mike Frysinger59b81c82025-03-27 16:32:16 -040029from typing import NamedTuple
Mike Frysinger3ba716f2019-06-13 01:48:12 -040030
31
Mike Frysinger47692012021-01-04 21:55:26 -050032# These should never be newer than the main.py version since this needs to be a
33# bit more flexible with older systems. See that file for more details on the
34# versions we select.
35MIN_PYTHON_VERSION_SOFT = (3, 6)
Jason R. Coombsb32ccbb2023-09-29 11:04:49 -040036MIN_PYTHON_VERSION_HARD = (3, 6)
Mike Frysinger47692012021-01-04 21:55:26 -050037
38
Mike Frysinger6fb0cb52020-02-12 09:39:23 -050039# Keep basic logic in sync with repo_trace.py.
Mike Frysingerd4aee652023-10-19 05:13:32 -040040class Trace:
Mike Frysingerb8615112023-09-01 13:58:46 -040041 """Trace helper logic."""
Mike Frysinger6fb0cb52020-02-12 09:39:23 -050042
Mike Frysingerb8615112023-09-01 13:58:46 -040043 REPO_TRACE = "REPO_TRACE"
Mike Frysinger6fb0cb52020-02-12 09:39:23 -050044
Mike Frysingerb8615112023-09-01 13:58:46 -040045 def __init__(self):
46 self.set(os.environ.get(self.REPO_TRACE) == "1")
Mike Frysinger6fb0cb52020-02-12 09:39:23 -050047
Mike Frysingerb8615112023-09-01 13:58:46 -040048 def set(self, value):
49 self.enabled = bool(value)
Mike Frysinger6fb0cb52020-02-12 09:39:23 -050050
Mike Frysingerb8615112023-09-01 13:58:46 -040051 def print(self, *args, **kwargs):
52 if self.enabled:
53 print(*args, **kwargs)
Mike Frysinger6fb0cb52020-02-12 09:39:23 -050054
55
56trace = Trace()
57
58
Mike Frysinger02147302025-04-09 19:59:05 -040059def cmdstr(cmd):
60 """Get a nicely quoted shell command."""
61 return " ".join(shlex.quote(x) for x in cmd)
62
63
Mike Frysinger3ba716f2019-06-13 01:48:12 -040064def exec_command(cmd):
Mike Frysingerb8615112023-09-01 13:58:46 -040065 """Execute |cmd| or return None on failure."""
Mike Frysinger02147302025-04-09 19:59:05 -040066 trace.print(":", cmdstr(cmd))
Mike Frysingerb8615112023-09-01 13:58:46 -040067 try:
68 if platform.system() == "Windows":
69 ret = subprocess.call(cmd)
70 sys.exit(ret)
71 else:
72 os.execvp(cmd[0], cmd)
73 except Exception:
74 pass
Mike Frysinger3ba716f2019-06-13 01:48:12 -040075
76
77def check_python_version():
Mike Frysingerb8615112023-09-01 13:58:46 -040078 """Make sure the active Python version is recent enough."""
Mike Frysinger3ba716f2019-06-13 01:48:12 -040079
Mike Frysingerb8615112023-09-01 13:58:46 -040080 def reexec(prog):
81 exec_command([prog] + sys.argv)
Mike Frysinger3ba716f2019-06-13 01:48:12 -040082
Mike Frysingerb8615112023-09-01 13:58:46 -040083 ver = sys.version_info
84 major = ver.major
85 minor = ver.minor
Mike Frysinger3ba716f2019-06-13 01:48:12 -040086
Peter Kjellerstedt172c5832023-11-03 17:22:40 +010087 # Try to re-exec the version specific Python if needed.
Mike Frysingerb8615112023-09-01 13:58:46 -040088 if (major, minor) < MIN_PYTHON_VERSION_SOFT:
89 # Python makes releases ~once a year, so try our min version +10 to help
90 # bridge the gap. This is the fallback anyways so perf isn't critical.
91 min_major, min_minor = MIN_PYTHON_VERSION_SOFT
92 for inc in range(0, 10):
Jason R. Coombsb32ccbb2023-09-29 11:04:49 -040093 reexec(f"python{min_major}.{min_minor + inc}")
Mike Frysinger47692012021-01-04 21:55:26 -050094
Mike Frysingerb8615112023-09-01 13:58:46 -040095 # Fallback to older versions if possible.
96 for inc in range(
97 MIN_PYTHON_VERSION_SOFT[1] - MIN_PYTHON_VERSION_HARD[1], 0, -1
98 ):
99 # Don't downgrade, and don't reexec ourselves (which would infinite loop).
100 if (min_major, min_minor - inc) <= (major, minor):
101 break
Jason R. Coombsb32ccbb2023-09-29 11:04:49 -0400102 reexec(f"python{min_major}.{min_minor - inc}")
Mike Frysinger3ba716f2019-06-13 01:48:12 -0400103
Mike Frysingerb8615112023-09-01 13:58:46 -0400104 # We're still here, so diagnose things for the user.
Jason R. Coombsd32b2dc2023-10-31 16:49:26 -0400105 if (major, minor) < MIN_PYTHON_VERSION_HARD:
Mike Frysingerb8615112023-09-01 13:58:46 -0400106 print(
Peter Kjellerstedt172c5832023-11-03 17:22:40 +0100107 "repo: error: Python version is too old; "
Mike Frysingerb8615112023-09-01 13:58:46 -0400108 "Please use Python {}.{} or newer.".format(
109 *MIN_PYTHON_VERSION_HARD
110 ),
111 file=sys.stderr,
112 )
113 sys.exit(1)
Mike Frysinger3ba716f2019-06-13 01:48:12 -0400114
115
Mike Frysingerb8615112023-09-01 13:58:46 -0400116if __name__ == "__main__":
117 check_python_version()
Mike Frysinger3ba716f2019-06-13 01:48:12 -0400118
119
Mark E. Hamilton4088eb42016-02-10 10:44:30 -0700120# repo default configuration
121#
Mike Frysingerb8615112023-09-01 13:58:46 -0400122REPO_URL = os.environ.get("REPO_URL", None)
Mark E. Hamilton55536282016-02-03 15:49:43 -0700123if not REPO_URL:
Mike Frysingerb8615112023-09-01 13:58:46 -0400124 REPO_URL = "https://gerrit.googlesource.com/git-repo"
125REPO_REV = os.environ.get("REPO_REV")
Mike Frysinger563f1a62020-02-05 23:52:07 -0500126if not REPO_REV:
Mike Frysingerb8615112023-09-01 13:58:46 -0400127 REPO_REV = "stable"
Mike Frysingera1cd7702021-04-20 23:38:04 -0400128# URL to file bug reports for repo tool issues.
Mike Frysingerb8615112023-09-01 13:58:46 -0400129BUG_URL = "https://issues.gerritcodereview.com/issues/new?component=1370071"
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700130
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700131# increment this whenever we make important changes to this script
Mike Frysinger243df202025-03-21 23:27:05 -0400132VERSION = (2, 54)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700133
134# increment this if the MAINTAINER_KEYS block is modified
Mike Frysinger9cc1d702020-02-13 18:28:03 -0500135KEYRING_VERSION = (2, 3)
Mike Frysingere4433652016-09-13 18:06:07 -0400136
137# Each individual key entry is created by using:
138# gpg --armor --export keyid
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700139MAINTAINER_KEYS = """
140
141 Repo Maintainer <[email protected]>
142-----BEGIN PGP PUBLIC KEY BLOCK-----
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700143
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
Mike Frysinger9cc1d702020-02-13 18:28:03 -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
209cZ7aFsJF4PtcDrfdejyAxbtsSHI=
210=82Tj
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700211-----END PGP PUBLIC KEY BLOCK-----
212"""
213
Mike Frysingerb8615112023-09-01 13:58:46 -0400214GIT = "git" # our git command
Mike Frysinger82caef62020-02-11 18:51:08 -0500215# NB: The version of git that the repo launcher requires may be much older than
216# the version of git that the main repo source tree requires. Keeping this at
217# an older version also makes it easier for users to upgrade/rollback as needed.
Mike Frysinger3762b172024-03-20 12:59:32 -0400218MIN_GIT_VERSION = (1, 7, 9) # minimum supported git version
Mike Frysingerb8615112023-09-01 13:58:46 -0400219repodir = ".repo" # name of repo's private directory
220S_repo = "repo" # special repo repository
221S_manifests = "manifests" # special manifest repository
222REPO_MAIN = S_repo + "/main.py" # main script
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700223
224
David Jamesbf79c662013-12-26 14:20:13 -0800225import errno
Mike Frysingere5670c82021-01-07 22:14:25 -0500226import json
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700227import optparse
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700228import re
Mitchel Humpheryseb5acc92014-03-12 10:48:15 -0700229import shutil
Sarah Owens60798a32012-10-25 17:53:09 -0700230import stat
Jason R. Coombsd32b2dc2023-10-31 16:49:26 -0400231import urllib.error
232import urllib.request
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700233
Conley Owens5e0ee142013-09-26 15:50:49 -0700234
Mike Frysingerb8615112023-09-01 13:58:46 -0400235repo_config_dir = os.getenv("REPO_CONFIG_DIR", os.path.expanduser("~"))
236home_dot_repo = os.path.join(repo_config_dir, ".repoconfig")
237gpg_dir = os.path.join(home_dot_repo, "gnupg")
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700238
David Pursehouse31b9b4b2020-02-13 08:20:14 +0900239
Josip Sokceviccf411b32024-12-03 21:29:01 +0000240def GetParser():
Mike Frysingerb8615112023-09-01 13:58:46 -0400241 """Setup the CLI parser."""
Josip Sokceviccf411b32024-12-03 21:29:01 +0000242 usage = "repo init [options] [-u] url"
Mike Frysingerd8fda902020-02-14 00:24:38 -0500243
Mike Frysingerb8615112023-09-01 13:58:46 -0400244 parser = optparse.OptionParser(usage=usage)
245 InitParser(parser)
246 return parser
Mike Frysingerd8fda902020-02-14 00:24:38 -0500247
Mike Frysinger9a734a32021-04-08 19:14:15 -0400248
Jason Chang8914b1f2023-05-26 12:44:50 -0700249def InitParser(parser):
Mike Frysingerb8615112023-09-01 13:58:46 -0400250 """Setup the CLI parser."""
251 # NB: Keep in sync with command.py:_CommonOptions().
Mike Frysinger9180a072021-04-13 14:57:40 -0400252
Mike Frysingerb8615112023-09-01 13:58:46 -0400253 # Logging.
254 group = parser.add_option_group("Logging options")
255 group.add_option(
256 "-v",
257 "--verbose",
258 dest="output_mode",
259 action="store_true",
260 help="show all output",
261 )
262 group.add_option(
263 "-q",
264 "--quiet",
265 dest="output_mode",
266 action="store_false",
267 help="only show errors",
268 )
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700269
Mike Frysingerb8615112023-09-01 13:58:46 -0400270 # Manifest.
271 group = parser.add_option_group("Manifest options")
272 group.add_option(
273 "-u",
274 "--manifest-url",
275 help="manifest repository location",
276 metavar="URL",
277 )
278 group.add_option(
279 "-b",
280 "--manifest-branch",
281 metavar="REVISION",
282 help="manifest branch or revision (use HEAD for default)",
283 )
284 group.add_option(
Kaushik Lingarkard7ebdf52024-09-05 15:34:48 -0700285 "--manifest-upstream-branch",
286 help="when a commit is provided to --manifest-branch, this "
287 "is the name of the git ref in which the commit can be found",
288 metavar="BRANCH",
289 )
290 group.add_option(
Mike Frysingerb8615112023-09-01 13:58:46 -0400291 "-m",
292 "--manifest-name",
293 default="default.xml",
294 help="initial manifest file",
295 metavar="NAME.xml",
296 )
297 group.add_option(
298 "-g",
299 "--groups",
300 default="default",
301 help="restrict manifest projects to ones with specified "
302 "group(s) [default|all|G1,G2,G3|G4,-G5,-G6]",
303 metavar="GROUP",
304 )
305 group.add_option(
306 "-p",
307 "--platform",
308 default="auto",
309 help="restrict manifest projects to ones with a specified "
310 "platform group [auto|all|none|linux|darwin|...]",
311 metavar="PLATFORM",
312 )
313 group.add_option(
314 "--submodules",
315 action="store_true",
316 help="sync any submodules associated with the manifest repo",
317 )
318 group.add_option(
319 "--standalone-manifest",
320 action="store_true",
321 help="download the manifest as a static file "
322 "rather then create a git checkout of "
323 "the manifest repo",
324 )
325 group.add_option(
326 "--manifest-depth",
327 type="int",
328 default=0,
329 metavar="DEPTH",
330 help="create a shallow clone of the manifest repo with "
331 "given depth (0 for full clone); see git clone "
332 "(default: %default)",
333 )
Mike Frysingera1051d82021-04-08 23:04:36 -0400334
Mike Frysingerb8615112023-09-01 13:58:46 -0400335 # Options that only affect manifest project, and not any of the projects
336 # specified in the manifest itself.
337 group = parser.add_option_group("Manifest (only) checkout options")
Jason Chang8914b1f2023-05-26 12:44:50 -0700338
Mike Frysingerb8615112023-09-01 13:58:46 -0400339 group.add_option(
340 "--current-branch",
341 "-c",
342 default=True,
343 dest="current_branch_only",
344 action="store_true",
345 help="fetch only current manifest branch from server (default)",
346 )
347 group.add_option(
348 "--no-current-branch",
349 dest="current_branch_only",
350 action="store_false",
351 help="fetch all manifest branches from server",
352 )
353 group.add_option(
354 "--tags", action="store_true", help="fetch tags in the manifest"
355 )
356 group.add_option(
357 "--no-tags",
358 dest="tags",
359 action="store_false",
360 help="don't fetch tags in the manifest",
361 )
Mike Frysingera1051d82021-04-08 23:04:36 -0400362
Mike Frysingerb8615112023-09-01 13:58:46 -0400363 # These are fundamentally different ways of structuring the checkout.
364 group = parser.add_option_group("Checkout modes")
365 group.add_option(
366 "--mirror",
367 action="store_true",
368 help="create a replica of the remote repositories "
369 "rather than a client working directory",
370 )
371 group.add_option(
372 "--archive",
373 action="store_true",
374 help="checkout an archive instead of a git repository for "
375 "each project. See git archive.",
376 )
377 group.add_option(
378 "--worktree",
379 action="store_true",
380 help="use git-worktree to manage projects",
381 )
Mike Frysingera1051d82021-04-08 23:04:36 -0400382
Mike Frysingerb8615112023-09-01 13:58:46 -0400383 # These are fundamentally different ways of structuring the checkout.
384 group = parser.add_option_group("Project checkout optimizations")
385 group.add_option(
386 "--reference", help="location of mirror directory", metavar="DIR"
387 )
388 group.add_option(
389 "--dissociate",
390 action="store_true",
391 help="dissociate from reference mirrors after clone",
392 )
393 group.add_option(
394 "--depth",
395 type="int",
396 default=None,
397 help="create a shallow clone with given depth; " "see git clone",
398 )
399 group.add_option(
400 "--partial-clone",
401 action="store_true",
402 help="perform partial clone (https://git-scm.com/"
403 "docs/gitrepository-layout#_code_partialclone_code)",
404 )
405 group.add_option(
406 "--no-partial-clone",
407 action="store_false",
408 help="disable use of partial clone (https://git-scm.com/"
409 "docs/gitrepository-layout#_code_partialclone_code)",
410 )
411 group.add_option(
412 "--partial-clone-exclude",
413 action="store",
414 help="exclude the specified projects (a comma-delimited "
415 "project names) from partial clone (https://git-scm.com"
416 "/docs/gitrepository-layout#_code_partialclone_code)",
417 )
418 group.add_option(
419 "--clone-filter",
420 action="store",
421 default="blob:none",
422 help="filter for use with --partial-clone " "[default: %default]",
423 )
424 group.add_option(
425 "--use-superproject",
426 action="store_true",
427 default=None,
428 help="use the manifest superproject to sync projects; implies -c",
429 )
430 group.add_option(
431 "--no-use-superproject",
432 action="store_false",
433 dest="use_superproject",
434 help="disable use of manifest superprojects",
435 )
436 group.add_option(
437 "--clone-bundle",
438 action="store_true",
439 help="enable use of /clone.bundle on HTTP/HTTPS "
440 "(default if not --partial-clone)",
441 )
442 group.add_option(
443 "--no-clone-bundle",
444 dest="clone_bundle",
445 action="store_false",
446 help="disable use of /clone.bundle on HTTP/HTTPS (default if --partial-clone)",
447 )
448 group.add_option(
449 "--git-lfs", action="store_true", help="enable Git LFS support"
450 )
451 group.add_option(
452 "--no-git-lfs",
453 dest="git_lfs",
454 action="store_false",
455 help="disable Git LFS support",
456 )
Doug Anderson49cd59b2011-06-13 21:42:06 -0700457
Mike Frysingerb8615112023-09-01 13:58:46 -0400458 # Tool.
459 group = parser.add_option_group("repo Version options")
460 group.add_option(
461 "--repo-url", metavar="URL", help="repo repository location ($REPO_URL)"
462 )
463 group.add_option(
464 "--repo-rev", metavar="REV", help="repo branch or revision ($REPO_REV)"
465 )
466 group.add_option(
467 "--repo-branch", dest="repo_rev", help=optparse.SUPPRESS_HELP
468 )
469 group.add_option(
470 "--no-repo-verify",
471 dest="repo_verify",
472 default=True,
473 action="store_false",
474 help="do not verify repo source code",
475 )
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700476
Mike Frysingerb8615112023-09-01 13:58:46 -0400477 # Other.
478 group = parser.add_option_group("Other options")
479 group.add_option(
480 "--config-name",
481 action="store_true",
482 default=False,
483 help="Always prompt for name/e-mail",
484 )
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700485
Mike Frysingerb8615112023-09-01 13:58:46 -0400486 return parser
Simran Basi1efc2b42015-08-05 15:04:22 -0700487
David Pursehouse31b9b4b2020-02-13 08:20:14 +0900488
Mike Frysinger62285d22020-02-12 08:01:38 -0500489def run_command(cmd, **kwargs):
Mike Frysingerb8615112023-09-01 13:58:46 -0400490 """Run |cmd| and return its output."""
491 check = kwargs.pop("check", False)
492 if kwargs.pop("capture_output", False):
493 kwargs.setdefault("stdout", subprocess.PIPE)
494 kwargs.setdefault("stderr", subprocess.PIPE)
495 cmd_input = kwargs.pop("input", None)
Mike Frysinger62285d22020-02-12 08:01:38 -0500496
Mike Frysingerb8615112023-09-01 13:58:46 -0400497 def decode(output):
498 """Decode |output| to text."""
499 if output is None:
500 return output
501 try:
502 return output.decode("utf-8")
503 except UnicodeError:
504 print(
Jason R. Coombsb32ccbb2023-09-29 11:04:49 -0400505 f"repo: warning: Invalid UTF-8 output:\ncmd: {cmd!r}\n{output}",
Mike Frysingerb8615112023-09-01 13:58:46 -0400506 file=sys.stderr,
507 )
508 return output.decode("utf-8", "backslashreplace")
Mike Frysinger6a784ff2020-02-14 23:38:28 -0500509
Mike Frysingerb8615112023-09-01 13:58:46 -0400510 # Run & package the results.
511 proc = subprocess.Popen(cmd, **kwargs)
512 (stdout, stderr) = proc.communicate(input=cmd_input)
Mike Frysinger02147302025-04-09 19:59:05 -0400513 dbg = ": " + cmdstr(cmd)
Mike Frysingerb8615112023-09-01 13:58:46 -0400514 if cmd_input is not None:
515 dbg += " 0<|"
516 if stdout == subprocess.PIPE:
517 dbg += " 1>|"
518 if stderr == subprocess.PIPE:
519 dbg += " 2>|"
520 elif stderr == subprocess.STDOUT:
521 dbg += " 2>&1"
522 trace.print(dbg)
Mike Frysinger243df202025-03-21 23:27:05 -0400523 ret = subprocess.CompletedProcess(
524 cmd, proc.returncode, decode(stdout), decode(stderr)
525 )
Mike Frysinger62285d22020-02-12 08:01:38 -0500526
Mike Frysingerb8615112023-09-01 13:58:46 -0400527 # If things failed, print useful debugging output.
528 if check and ret.returncode:
529 print(
Jason R. Coombsb32ccbb2023-09-29 11:04:49 -0400530 f'repo: error: "{cmd[0]}" failed with exit status {ret.returncode}',
Mike Frysingerb8615112023-09-01 13:58:46 -0400531 file=sys.stderr,
532 )
Jason R. Coombsb32ccbb2023-09-29 11:04:49 -0400533 cwd = kwargs.get("cwd", os.getcwd())
534 print(f" cwd: {cwd}\n cmd: {cmd!r}", file=sys.stderr)
David Pursehousec19cc5c2020-02-14 09:18:15 +0900535
Mike Frysingerb8615112023-09-01 13:58:46 -0400536 def _print_output(name, output):
537 if output:
538 print(
Jason R. Coombsb32ccbb2023-09-29 11:04:49 -0400539 f" {name}:"
540 + "".join(f"\n >> {x}" for x in output.splitlines()),
Mike Frysingerb8615112023-09-01 13:58:46 -0400541 file=sys.stderr,
542 )
David Pursehousec19cc5c2020-02-14 09:18:15 +0900543
Mike Frysingerb8615112023-09-01 13:58:46 -0400544 _print_output("stdout", ret.stdout)
545 _print_output("stderr", ret.stderr)
Mike Frysingerdc8185f2025-03-27 17:06:11 -0400546 # This will raise subprocess.CalledProcessError for us.
547 ret.check_returncode()
Mike Frysinger62285d22020-02-12 08:01:38 -0500548
Mike Frysingerb8615112023-09-01 13:58:46 -0400549 return ret
Mike Frysinger62285d22020-02-12 08:01:38 -0500550
551
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700552class CloneFailure(Exception):
Mike Frysingerb8615112023-09-01 13:58:46 -0400553 """Indicate the remote clone of repo itself failed."""
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700554
555
Mike Frysinger3599cc32020-02-29 02:53:41 -0500556def check_repo_verify(repo_verify, quiet=False):
Mike Frysingerb8615112023-09-01 13:58:46 -0400557 """Check the --repo-verify state."""
558 if not repo_verify:
559 print(
560 "repo: warning: verification of repo code has been disabled;\n"
561 "repo will not be able to verify the integrity of itself.\n",
562 file=sys.stderr,
563 )
564 return False
Mike Frysinger3599cc32020-02-29 02:53:41 -0500565
Mike Frysingerb8615112023-09-01 13:58:46 -0400566 if NeedSetupGnuPG():
567 return SetupGnuPG(quiet)
Mike Frysinger3599cc32020-02-29 02:53:41 -0500568
Mike Frysingerb8615112023-09-01 13:58:46 -0400569 return True
Mike Frysinger3599cc32020-02-29 02:53:41 -0500570
571
572def check_repo_rev(dst, rev, repo_verify=True, quiet=False):
Mike Frysingerb8615112023-09-01 13:58:46 -0400573 """Check that |rev| is valid."""
574 do_verify = check_repo_verify(repo_verify, quiet=quiet)
575 remote_ref, local_rev = resolve_repo_rev(dst, rev)
576 if not quiet and not remote_ref.startswith("refs/heads/"):
577 print(
578 "warning: repo is not tracking a remote branch, so it will not "
579 "receive updates",
580 file=sys.stderr,
581 )
582 if do_verify:
583 rev = verify_rev(dst, remote_ref, local_rev, quiet)
584 else:
585 rev = local_rev
586 return (remote_ref, rev)
Mike Frysinger3599cc32020-02-29 02:53:41 -0500587
588
Josip Sokceviccf411b32024-12-03 21:29:01 +0000589def _Init(args):
Mike Frysingerb8615112023-09-01 13:58:46 -0400590 """Installs repo by cloning it over the network."""
Josip Sokceviccf411b32024-12-03 21:29:01 +0000591 parser = GetParser()
Mike Frysingerb8615112023-09-01 13:58:46 -0400592 opt, args = parser.parse_args(args)
Mike Frysinger401c6f02021-02-18 15:20:15 -0500593 if args:
Mike Frysingerb8615112023-09-01 13:58:46 -0400594 if not opt.manifest_url:
595 opt.manifest_url = args.pop(0)
596 if args:
597 parser.print_usage()
598 sys.exit(1)
599 opt.quiet = opt.output_mode is False
600 opt.verbose = opt.output_mode is True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700601
Mike Frysingerb8615112023-09-01 13:58:46 -0400602 if opt.clone_bundle is None:
603 opt.clone_bundle = False if opt.partial_clone else True
Xin Lid79a4bc2020-05-20 16:03:45 -0700604
Mike Frysingerb8615112023-09-01 13:58:46 -0400605 url = opt.repo_url or REPO_URL
606 rev = opt.repo_rev or REPO_REV
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700607
Mike Frysingerb8615112023-09-01 13:58:46 -0400608 try:
609 os.mkdir(repodir)
610 except OSError as e:
611 if e.errno != errno.EEXIST:
612 print(
Jason R. Coombsb32ccbb2023-09-29 11:04:49 -0400613 f"fatal: cannot make {repodir} directory: {e.strerror}",
Mike Frysingerb8615112023-09-01 13:58:46 -0400614 file=sys.stderr,
615 )
616 # Don't raise CloneFailure; that would delete the
617 # name. Instead exit immediately.
618 #
619 sys.exit(1)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700620
Mike Frysingerb8615112023-09-01 13:58:46 -0400621 _CheckGitVersion()
622 try:
623 if not opt.quiet:
624 print("Downloading Repo source from", url)
625 dst_final = os.path.abspath(os.path.join(repodir, S_repo))
626 dst = dst_final + ".tmp"
627 shutil.rmtree(dst, ignore_errors=True)
628 _Clone(url, dst, opt.clone_bundle, opt.quiet, opt.verbose)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700629
Mike Frysingerb8615112023-09-01 13:58:46 -0400630 remote_ref, rev = check_repo_rev(
631 dst, rev, opt.repo_verify, quiet=opt.quiet
632 )
633 _Checkout(dst, remote_ref, rev, opt.quiet)
Sebastian Schuberth993dcac2018-07-13 10:25:52 +0200634
Mike Frysingerb8615112023-09-01 13:58:46 -0400635 if not os.path.isfile(os.path.join(dst, "repo")):
636 print(
637 "fatal: '%s' does not look like a git-repo repository, is "
638 "--repo-url set correctly?" % url,
639 file=sys.stderr,
640 )
641 raise CloneFailure()
Sebastian Schuberth993dcac2018-07-13 10:25:52 +0200642
Mike Frysingerb8615112023-09-01 13:58:46 -0400643 os.rename(dst, dst_final)
Mike Frysingera010a9f2022-08-19 05:16:46 -0400644
Mike Frysingerb8615112023-09-01 13:58:46 -0400645 except CloneFailure:
646 print("fatal: double check your --repo-rev setting.", file=sys.stderr)
647 if opt.quiet:
648 print(
649 "fatal: repo init failed; run without --quiet to see why",
650 file=sys.stderr,
651 )
652 raise
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700653
654
Mike Frysinger62285d22020-02-12 08:01:38 -0500655def run_git(*args, **kwargs):
Mike Frysingerb8615112023-09-01 13:58:46 -0400656 """Run git and return execution details."""
657 kwargs.setdefault("capture_output", True)
658 kwargs.setdefault("check", True)
659 try:
660 return run_command([GIT] + list(args), **kwargs)
661 except OSError as e:
662 print(file=sys.stderr)
663 print('repo: error: "%s" is not available' % GIT, file=sys.stderr)
664 print("repo: error: %s" % e, file=sys.stderr)
665 print(file=sys.stderr)
666 print(
667 "Please make sure %s is installed and in your path." % GIT,
668 file=sys.stderr,
669 )
670 sys.exit(1)
Mike Frysingerdc8185f2025-03-27 17:06:11 -0400671 except subprocess.CalledProcessError:
Mike Frysingerb8615112023-09-01 13:58:46 -0400672 raise CloneFailure()
Mike Frysinger62285d22020-02-12 08:01:38 -0500673
674
Mike Frysinger59b81c82025-03-27 16:32:16 -0400675class GitVersion(NamedTuple):
676 """The git version info broken down into components for easy analysis.
677
678 Similar to Python's sys.version_info.
679 """
680
681 major: int
682 minor: int
683 micro: int
684 full: int
Mike Frysinger6db1b9e2019-07-10 15:32:36 -0400685
David Pursehouse31b9b4b2020-02-13 08:20:14 +0900686
Mike Frysingerf88b2fe2019-07-10 15:37:43 -0400687def ParseGitVersion(ver_str=None):
Mike Frysingerb8615112023-09-01 13:58:46 -0400688 if ver_str is None:
689 # Load the version ourselves.
690 ver_str = run_git("--version").stdout
Mike Frysingerf88b2fe2019-07-10 15:37:43 -0400691
Mike Frysingerb8615112023-09-01 13:58:46 -0400692 if not ver_str.startswith("git version "):
693 return None
Conley Owensff0a3c82014-01-30 14:46:03 -0800694
Mike Frysingerb8615112023-09-01 13:58:46 -0400695 full_version = ver_str[len("git version ") :].strip()
696 num_ver_str = full_version.split("-")[0]
697 to_tuple = []
698 for num_str in num_ver_str.split(".")[:3]:
699 if num_str.isdigit():
700 to_tuple.append(int(num_str))
701 else:
702 to_tuple.append(0)
703 to_tuple.append(full_version)
704 return GitVersion(*to_tuple)
Conley Owensff0a3c82014-01-30 14:46:03 -0800705
706
Mike Frysingerf88b2fe2019-07-10 15:37:43 -0400707def _CheckGitVersion():
Mike Frysingerb8615112023-09-01 13:58:46 -0400708 ver_act = ParseGitVersion()
709 if ver_act is None:
710 print("fatal: unable to detect git version", file=sys.stderr)
711 raise CloneFailure()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700712
Mike Frysingerb8615112023-09-01 13:58:46 -0400713 if ver_act < MIN_GIT_VERSION:
714 need = ".".join(map(str, MIN_GIT_VERSION))
715 print(
Jason R. Coombsb32ccbb2023-09-29 11:04:49 -0400716 f"fatal: git {need} or later required; found {ver_act.full}",
Mike Frysingerb8615112023-09-01 13:58:46 -0400717 file=sys.stderr,
718 )
719 raise CloneFailure()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700720
721
Mike Frysinger84094102020-02-11 02:10:28 -0500722def SetGitTrace2ParentSid(env=None):
Mike Frysingerb8615112023-09-01 13:58:46 -0400723 """Set up GIT_TRACE2_PARENT_SID for git tracing."""
724 # We roughly follow the format git itself uses in trace2/tr2_sid.c.
725 # (1) Be unique (2) be valid filename (3) be fixed length.
726 #
727 # Since we always export this variable, we try to avoid more expensive calls.
728 # e.g. We don't attempt hostname lookups or hashing the results.
729 if env is None:
730 env = os.environ
Mike Frysinger84094102020-02-11 02:10:28 -0500731
Mike Frysingerb8615112023-09-01 13:58:46 -0400732 KEY = "GIT_TRACE2_PARENT_SID"
Mike Frysinger84094102020-02-11 02:10:28 -0500733
LuK1337aadd12c2023-09-16 09:36:49 +0200734 now = datetime.datetime.now(datetime.timezone.utc)
Jason R. Coombsb32ccbb2023-09-29 11:04:49 -0400735 timestamp = now.strftime("%Y%m%dT%H%M%SZ")
736 value = f"repo-{timestamp}-P{os.getpid():08x}"
Mike Frysinger84094102020-02-11 02:10:28 -0500737
Mike Frysingerb8615112023-09-01 13:58:46 -0400738 # If it's already set, then append ourselves.
739 if KEY in env:
740 value = env[KEY] + "/" + value
Mike Frysinger84094102020-02-11 02:10:28 -0500741
Mike Frysingerb8615112023-09-01 13:58:46 -0400742 _setenv(KEY, value, env=env)
Mike Frysinger84094102020-02-11 02:10:28 -0500743
744
745def _setenv(key, value, env=None):
Mike Frysingerb8615112023-09-01 13:58:46 -0400746 """Set |key| in the OS environment |env| to |value|."""
747 if env is None:
748 env = os.environ
749 # Environment handling across systems is messy.
750 try:
751 env[key] = value
752 except UnicodeEncodeError:
753 env[key] = value.encode()
Mike Frysinger84094102020-02-11 02:10:28 -0500754
755
Conley Owensc9129d92012-10-01 16:12:28 -0700756def NeedSetupGnuPG():
Mike Frysingerb8615112023-09-01 13:58:46 -0400757 if not os.path.isdir(home_dot_repo):
758 return True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700759
Mike Frysingerb8615112023-09-01 13:58:46 -0400760 kv = os.path.join(home_dot_repo, "keyring-version")
761 if not os.path.exists(kv):
762 return True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700763
Mike Frysingerb8615112023-09-01 13:58:46 -0400764 kv = open(kv).read()
765 if not kv:
766 return True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700767
Mike Frysingerb8615112023-09-01 13:58:46 -0400768 kv = tuple(map(int, kv.split(".")))
769 if kv < KEYRING_VERSION:
770 return True
771 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700772
773
Conley Owensc9129d92012-10-01 16:12:28 -0700774def SetupGnuPG(quiet):
Mike Frysingerb8615112023-09-01 13:58:46 -0400775 try:
776 os.mkdir(home_dot_repo)
777 except OSError as e:
778 if e.errno != errno.EEXIST:
779 print(
Jason R. Coombsb32ccbb2023-09-29 11:04:49 -0400780 f"fatal: cannot make {home_dot_repo} directory: {e.strerror}",
Mike Frysingerb8615112023-09-01 13:58:46 -0400781 file=sys.stderr,
782 )
783 sys.exit(1)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700784
Mike Frysingerb8615112023-09-01 13:58:46 -0400785 try:
786 os.mkdir(gpg_dir, stat.S_IRWXU)
787 except OSError as e:
788 if e.errno != errno.EEXIST:
789 print(
Jason R. Coombsb32ccbb2023-09-29 11:04:49 -0400790 f"fatal: cannot make {gpg_dir} directory: {e.strerror}",
Mike Frysingerb8615112023-09-01 13:58:46 -0400791 file=sys.stderr,
792 )
793 sys.exit(1)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700794
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700795 if not quiet:
Mike Frysingerb8615112023-09-01 13:58:46 -0400796 print(
Jason R. Coombsb32ccbb2023-09-29 11:04:49 -0400797 "repo: Updating release signing keys to keyset ver "
798 + ".".join(str(x) for x in KEYRING_VERSION),
Mike Frysingerb8615112023-09-01 13:58:46 -0400799 )
800 # NB: We use --homedir (and cwd below) because some environments (Windows) do
801 # not correctly handle full native paths. We avoid the issue by changing to
802 # the right dir with cwd=gpg_dir before executing gpg, and then telling gpg to
803 # use the cwd (.) as its homedir which leaves the path resolution logic to it.
804 cmd = ["gpg", "--homedir", ".", "--import"]
805 try:
806 # gpg can be pretty chatty. Always capture the output and if something goes
807 # wrong, the builtin check failure will dump stdout & stderr for debugging.
808 run_command(
809 cmd,
810 stdin=subprocess.PIPE,
811 capture_output=True,
812 cwd=gpg_dir,
813 check=True,
814 input=MAINTAINER_KEYS.encode("utf-8"),
815 )
816 except OSError:
817 if not quiet:
818 print("warning: gpg (GnuPG) is not available.", file=sys.stderr)
819 print(
820 "warning: Installing it is strongly encouraged.",
821 file=sys.stderr,
822 )
823 print(file=sys.stderr)
824 return False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700825
Mike Frysingerb8615112023-09-01 13:58:46 -0400826 with open(os.path.join(home_dot_repo, "keyring-version"), "w") as fd:
827 fd.write(".".join(map(str, KEYRING_VERSION)) + "\n")
828 return True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700829
830
Mike Frysinger62285d22020-02-12 08:01:38 -0500831def _SetConfig(cwd, name, value):
Mike Frysingerb8615112023-09-01 13:58:46 -0400832 """Set a git configuration option to the specified value."""
833 run_git("config", name, value, cwd=cwd)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700834
835
Mike Frysinger949bc342020-02-18 21:37:00 -0500836def _GetRepoConfig(name):
Mike Frysingerb8615112023-09-01 13:58:46 -0400837 """Read a repo configuration option."""
838 config = os.path.join(home_dot_repo, "config")
839 if not os.path.exists(config):
840 return None
Mike Frysinger949bc342020-02-18 21:37:00 -0500841
Mike Frysingerb8615112023-09-01 13:58:46 -0400842 cmd = ["config", "--file", config, "--get", name]
843 ret = run_git(*cmd, check=False)
844 if ret.returncode == 0:
845 return ret.stdout
846 elif ret.returncode == 1:
847 return None
848 else:
849 print(
Mike Frysinger02147302025-04-09 19:59:05 -0400850 f"repo: error: git {cmdstr(cmd)} failed:\n{ret.stderr}",
Mike Frysingerb8615112023-09-01 13:58:46 -0400851 file=sys.stderr,
852 )
Mike Frysingerdc8185f2025-03-27 17:06:11 -0400853 # This will raise subprocess.CalledProcessError for us.
854 ret.check_returncode()
Mike Frysinger949bc342020-02-18 21:37:00 -0500855
856
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -0700857def _InitHttp():
Mike Frysingerb8615112023-09-01 13:58:46 -0400858 handlers = []
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -0700859
Mike Frysingerb8615112023-09-01 13:58:46 -0400860 mgr = urllib.request.HTTPPasswordMgrWithDefaultRealm()
861 try:
862 import netrc
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -0700863
Mike Frysingerb8615112023-09-01 13:58:46 -0400864 n = netrc.netrc()
865 for host in n.hosts:
866 p = n.hosts[host]
867 mgr.add_password(p[1], "http://%s/" % host, p[0], p[2])
868 mgr.add_password(p[1], "https://%s/" % host, p[0], p[2])
869 except Exception:
870 pass
871 handlers.append(urllib.request.HTTPBasicAuthHandler(mgr))
872 handlers.append(urllib.request.HTTPDigestAuthHandler(mgr))
873
874 if "http_proxy" in os.environ:
875 url = os.environ["http_proxy"]
876 handlers.append(
877 urllib.request.ProxyHandler({"http": url, "https": url})
878 )
879 if "REPO_CURL_VERBOSE" in os.environ:
880 handlers.append(urllib.request.HTTPHandler(debuglevel=1))
881 handlers.append(urllib.request.HTTPSHandler(debuglevel=1))
882 urllib.request.install_opener(urllib.request.build_opener(*handlers))
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -0700883
Mark E. Hamilton4088eb42016-02-10 10:44:30 -0700884
Mike Frysingeredd3d452020-02-21 23:55:07 -0500885def _Fetch(url, cwd, src, quiet, verbose):
Mike Frysingerb8615112023-09-01 13:58:46 -0400886 cmd = ["fetch"]
887 if not verbose:
888 cmd.append("--quiet")
889 err = None
890 if not quiet and sys.stdout.isatty():
891 cmd.append("--progress")
892 elif not verbose:
893 err = subprocess.PIPE
894 cmd.append(src)
895 cmd.append("+refs/heads/*:refs/remotes/origin/*")
896 cmd.append("+refs/tags/*:refs/tags/*")
897 run_git(*cmd, stderr=err, capture_output=False, cwd=cwd)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700898
Mark E. Hamilton4088eb42016-02-10 10:44:30 -0700899
Mike Frysingeredd3d452020-02-21 23:55:07 -0500900def _DownloadBundle(url, cwd, quiet, verbose):
Mike Frysingerb8615112023-09-01 13:58:46 -0400901 if not url.endswith("/"):
902 url += "/"
903 url += "clone.bundle"
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -0700904
Mike Frysingerb8615112023-09-01 13:58:46 -0400905 ret = run_git(
906 "config", "--get-regexp", "url.*.insteadof", cwd=cwd, check=False
907 )
908 for line in ret.stdout.splitlines():
909 m = re.compile(r"^url\.(.*)\.insteadof (.*)$").match(line)
910 if m:
911 new_url = m.group(1)
912 old_url = m.group(2)
913 if url.startswith(old_url):
914 url = new_url + url[len(old_url) :]
915 break
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -0700916
Mike Frysingerb8615112023-09-01 13:58:46 -0400917 if not url.startswith("http:") and not url.startswith("https:"):
918 return False
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -0700919
Mike Frysingerb8615112023-09-01 13:58:46 -0400920 dest = open(os.path.join(cwd, ".git", "clone.bundle"), "w+b")
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -0700921 try:
Mike Frysingerb8615112023-09-01 13:58:46 -0400922 try:
923 r = urllib.request.urlopen(url)
924 except urllib.error.HTTPError as e:
925 if e.code not in [400, 401, 403, 404, 501]:
926 print("warning: Cannot get %s" % url, file=sys.stderr)
927 print("warning: HTTP error %s" % e.code, file=sys.stderr)
928 return False
929 except urllib.error.URLError as e:
930 print("fatal: Cannot get %s" % url, file=sys.stderr)
931 print("fatal: error %s" % e.reason, file=sys.stderr)
932 raise CloneFailure()
933 try:
934 if verbose:
935 print("Downloading clone bundle %s" % url, file=sys.stderr)
936 while True:
937 buf = r.read(8192)
938 if not buf:
939 return True
940 dest.write(buf)
941 finally:
942 r.close()
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -0700943 finally:
Mike Frysingerb8615112023-09-01 13:58:46 -0400944 dest.close()
Shawn O. Pearcef322b9a2011-09-19 14:50:58 -0700945
Mark E. Hamilton4088eb42016-02-10 10:44:30 -0700946
Mike Frysinger62285d22020-02-12 08:01:38 -0500947def _ImportBundle(cwd):
Mike Frysingerb8615112023-09-01 13:58:46 -0400948 path = os.path.join(cwd, ".git", "clone.bundle")
949 try:
950 _Fetch(cwd, cwd, path, True, False)
951 finally:
952 os.remove(path)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700953
Mark E. Hamilton4088eb42016-02-10 10:44:30 -0700954
Mike Frysingeredd3d452020-02-21 23:55:07 -0500955def _Clone(url, cwd, clone_bundle, quiet, verbose):
Mike Frysingerb8615112023-09-01 13:58:46 -0400956 """Clones a git repository to a new subdirectory of repodir"""
957 if verbose:
958 print("Cloning git repository", url)
Mike Frysingerdcbfadf2020-02-22 00:04:39 -0500959
Mike Frysingerb8615112023-09-01 13:58:46 -0400960 try:
961 os.mkdir(cwd)
962 except OSError as e:
963 print(
Jason R. Coombsb32ccbb2023-09-29 11:04:49 -0400964 f"fatal: cannot make {cwd} directory: {e.strerror}",
Mike Frysingerb8615112023-09-01 13:58:46 -0400965 file=sys.stderr,
966 )
967 raise CloneFailure()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700968
Mike Frysingerb8615112023-09-01 13:58:46 -0400969 run_git("init", "--quiet", cwd=cwd)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700970
Mike Frysingerb8615112023-09-01 13:58:46 -0400971 _InitHttp()
972 _SetConfig(cwd, "remote.origin.url", url)
973 _SetConfig(
974 cwd, "remote.origin.fetch", "+refs/heads/*:refs/remotes/origin/*"
975 )
976 if clone_bundle and _DownloadBundle(url, cwd, quiet, verbose):
977 _ImportBundle(cwd)
978 _Fetch(url, cwd, "origin", quiet, verbose)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700979
980
Mike Frysingercfc81112020-02-29 02:56:32 -0500981def resolve_repo_rev(cwd, committish):
Mike Frysingerb8615112023-09-01 13:58:46 -0400982 """Figure out what REPO_REV represents.
Mike Frysingercfc81112020-02-29 02:56:32 -0500983
Mike Frysingerb8615112023-09-01 13:58:46 -0400984 We support:
985 * refs/heads/xxx: Branch.
986 * refs/tags/xxx: Tag.
987 * xxx: Branch or tag or commit.
Mike Frysingercfc81112020-02-29 02:56:32 -0500988
Mike Frysingerb8615112023-09-01 13:58:46 -0400989 Args:
990 cwd: The git checkout to run in.
991 committish: The REPO_REV argument to resolve.
Mike Frysingercfc81112020-02-29 02:56:32 -0500992
Mike Frysingerb8615112023-09-01 13:58:46 -0400993 Returns:
994 A tuple of (remote ref, commit) as makes sense for the committish.
995 For branches, this will look like ('refs/heads/stable', <revision>).
996 For tags, this will look like ('refs/tags/v1.0', <revision>).
997 For commits, this will be (<revision>, <revision>).
998 """
Mike Frysingercfc81112020-02-29 02:56:32 -0500999
Mike Frysingerb8615112023-09-01 13:58:46 -04001000 def resolve(committish):
1001 ret = run_git(
1002 "rev-parse",
1003 "--verify",
Jason R. Coombsb32ccbb2023-09-29 11:04:49 -04001004 f"{committish}^{{commit}}",
Mike Frysingerb8615112023-09-01 13:58:46 -04001005 cwd=cwd,
1006 check=False,
1007 )
1008 return None if ret.returncode else ret.stdout.strip()
Mike Frysingercfc81112020-02-29 02:56:32 -05001009
Mike Frysingerb8615112023-09-01 13:58:46 -04001010 # An explicit branch.
1011 if committish.startswith("refs/heads/"):
1012 remote_ref = committish
1013 committish = committish[len("refs/heads/") :]
1014 rev = resolve("refs/remotes/origin/%s" % committish)
1015 if rev is None:
1016 print(
Jason R. Coombsb32ccbb2023-09-29 11:04:49 -04001017 f'repo: error: unknown branch "{committish}"',
Mike Frysingerb8615112023-09-01 13:58:46 -04001018 file=sys.stderr,
1019 )
1020 raise CloneFailure()
1021 return (remote_ref, rev)
Mike Frysingercfc81112020-02-29 02:56:32 -05001022
Mike Frysingerb8615112023-09-01 13:58:46 -04001023 # An explicit tag.
1024 if committish.startswith("refs/tags/"):
1025 remote_ref = committish
1026 committish = committish[len("refs/tags/") :]
1027 rev = resolve(remote_ref)
1028 if rev is None:
1029 print(
Jason R. Coombsb32ccbb2023-09-29 11:04:49 -04001030 f'repo: error: unknown tag "{committish}"',
1031 file=sys.stderr,
Mike Frysingerb8615112023-09-01 13:58:46 -04001032 )
1033 raise CloneFailure()
1034 return (remote_ref, rev)
Mike Frysingercfc81112020-02-29 02:56:32 -05001035
Mike Frysingerb8615112023-09-01 13:58:46 -04001036 # See if it's a short branch name.
1037 rev = resolve("refs/remotes/origin/%s" % committish)
1038 if rev:
Jason R. Coombsb32ccbb2023-09-29 11:04:49 -04001039 return (f"refs/heads/{committish}", rev)
Mike Frysingercfc81112020-02-29 02:56:32 -05001040
Mike Frysingerb8615112023-09-01 13:58:46 -04001041 # See if it's a tag.
Jason R. Coombsb32ccbb2023-09-29 11:04:49 -04001042 rev = resolve(f"refs/tags/{committish}")
Mike Frysingerb8615112023-09-01 13:58:46 -04001043 if rev:
Jason R. Coombsb32ccbb2023-09-29 11:04:49 -04001044 return (f"refs/tags/{committish}", rev)
Mike Frysingercfc81112020-02-29 02:56:32 -05001045
Mike Frysingerb8615112023-09-01 13:58:46 -04001046 # See if it's a commit.
1047 rev = resolve(committish)
1048 if rev and rev.lower().startswith(committish.lower()):
1049 return (rev, rev)
1050
1051 # Give up!
1052 print(
Jason R. Coombsb32ccbb2023-09-29 11:04:49 -04001053 f'repo: error: unable to resolve "{committish}"',
1054 file=sys.stderr,
Mike Frysingerb8615112023-09-01 13:58:46 -04001055 )
1056 raise CloneFailure()
Mike Frysingercfc81112020-02-29 02:56:32 -05001057
1058
Mike Frysinger3599cc32020-02-29 02:53:41 -05001059def verify_rev(cwd, remote_ref, rev, quiet):
Mike Frysingerb8615112023-09-01 13:58:46 -04001060 """Verify the commit has been signed by a tag."""
1061 ret = run_git("describe", rev, cwd=cwd)
1062 cur = ret.stdout.strip()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001063
Mike Frysingerb8615112023-09-01 13:58:46 -04001064 m = re.compile(r"^(.*)-[0-9]{1,}-g[0-9a-f]{1,}$").match(cur)
1065 if m:
1066 cur = m.group(1)
1067 if not quiet:
1068 print(file=sys.stderr)
1069 print(
Jason R. Coombsb32ccbb2023-09-29 11:04:49 -04001070 f"warning: '{remote_ref}' is not signed; "
1071 f"falling back to signed release '{cur}'",
Mike Frysingerb8615112023-09-01 13:58:46 -04001072 file=sys.stderr,
1073 )
1074 print(file=sys.stderr)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001075
Mike Frysingerb8615112023-09-01 13:58:46 -04001076 env = os.environ.copy()
1077 _setenv("GNUPGHOME", gpg_dir, env)
1078 run_git("tag", "-v", cur, cwd=cwd, env=env)
1079 return "%s^0" % cur
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001080
1081
Mike Frysingercfc81112020-02-29 02:56:32 -05001082def _Checkout(cwd, remote_ref, rev, quiet):
Mike Frysingerb8615112023-09-01 13:58:46 -04001083 """Checkout an upstream branch into the repository and track it."""
1084 run_git("update-ref", "refs/heads/default", rev, cwd=cwd)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001085
Mike Frysingerb8615112023-09-01 13:58:46 -04001086 _SetConfig(cwd, "branch.default.remote", "origin")
1087 _SetConfig(cwd, "branch.default.merge", remote_ref)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001088
Mike Frysingerb8615112023-09-01 13:58:46 -04001089 run_git("symbolic-ref", "HEAD", "refs/heads/default", cwd=cwd)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001090
Mike Frysingerb8615112023-09-01 13:58:46 -04001091 cmd = ["read-tree", "--reset", "-u"]
1092 if not quiet:
1093 cmd.append("-v")
1094 cmd.append("HEAD")
1095 run_git(*cmd, cwd=cwd)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001096
1097
1098def _FindRepo():
Mike Frysingerb8615112023-09-01 13:58:46 -04001099 """Look for a repo installation, starting at the current directory."""
1100 curdir = os.getcwd()
1101 repo = None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001102
Mike Frysingerb8615112023-09-01 13:58:46 -04001103 olddir = None
1104 while curdir != olddir and not repo:
1105 repo = os.path.join(curdir, repodir, REPO_MAIN)
1106 if not os.path.isfile(repo):
1107 repo = None
1108 olddir = curdir
1109 curdir = os.path.dirname(curdir)
1110 return (repo, os.path.join(curdir, repodir))
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001111
1112
Mike Frysingerd4aee652023-10-19 05:13:32 -04001113class _Options:
Mike Frysingerb8615112023-09-01 13:58:46 -04001114 help = False
1115 version = False
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001116
1117
Mike Frysinger949bc342020-02-18 21:37:00 -05001118def _ExpandAlias(name):
Mike Frysingerb8615112023-09-01 13:58:46 -04001119 """Look up user registered aliases."""
1120 # We don't resolve aliases for existing subcommands. This matches git.
Josip Sokceviccf411b32024-12-03 21:29:01 +00001121 if name in {"help", "init"}:
Mike Frysingerb8615112023-09-01 13:58:46 -04001122 return name, []
Mike Frysinger949bc342020-02-18 21:37:00 -05001123
Jason R. Coombsb32ccbb2023-09-29 11:04:49 -04001124 alias = _GetRepoConfig(f"alias.{name}")
Mike Frysingerb8615112023-09-01 13:58:46 -04001125 if alias is None:
1126 return name, []
Mike Frysinger949bc342020-02-18 21:37:00 -05001127
Mike Frysingerb8615112023-09-01 13:58:46 -04001128 args = alias.strip().split(" ", 1)
1129 name = args[0]
1130 if len(args) == 2:
1131 args = shlex.split(args[1])
1132 else:
1133 args = []
1134 return name, args
Mike Frysinger949bc342020-02-18 21:37:00 -05001135
1136
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001137def _ParseArguments(args):
Mike Frysingerb8615112023-09-01 13:58:46 -04001138 cmd = None
1139 opt = _Options()
1140 arg = []
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001141
Mike Frysingerb8615112023-09-01 13:58:46 -04001142 for i in range(len(args)):
1143 a = args[i]
1144 if a == "-h" or a == "--help":
1145 opt.help = True
1146 elif a == "--version":
1147 opt.version = True
1148 elif a == "--trace":
1149 trace.set(True)
1150 elif not a.startswith("-"):
1151 cmd = a
1152 arg = args[i + 1 :]
1153 break
1154 return cmd, opt, arg
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001155
1156
Mike Frysingerd4aee652023-10-19 05:13:32 -04001157class Requirements:
Mike Frysingerb8615112023-09-01 13:58:46 -04001158 """Helper for checking repo's system requirements."""
Mike Frysingere5670c82021-01-07 22:14:25 -05001159
Mike Frysingerb8615112023-09-01 13:58:46 -04001160 REQUIREMENTS_NAME = "requirements.json"
Mike Frysingere5670c82021-01-07 22:14:25 -05001161
Mike Frysingerb8615112023-09-01 13:58:46 -04001162 def __init__(self, requirements):
1163 """Initialize.
Mike Frysingere5670c82021-01-07 22:14:25 -05001164
Mike Frysingerb8615112023-09-01 13:58:46 -04001165 Args:
1166 requirements: A dictionary of settings.
1167 """
1168 self.requirements = requirements
Mike Frysingere5670c82021-01-07 22:14:25 -05001169
Mike Frysingerb8615112023-09-01 13:58:46 -04001170 @classmethod
1171 def from_dir(cls, path):
1172 return cls.from_file(os.path.join(path, cls.REQUIREMENTS_NAME))
Mike Frysingere5670c82021-01-07 22:14:25 -05001173
Mike Frysingerb8615112023-09-01 13:58:46 -04001174 @classmethod
1175 def from_file(cls, path):
1176 try:
1177 with open(path, "rb") as f:
1178 data = f.read()
Jason R. Coombsae824fb2023-10-20 23:32:40 +05451179 except OSError:
Mike Frysingerb8615112023-09-01 13:58:46 -04001180 # If we couldn't open the file, assume it's an old source tree.
1181 return None
Mike Frysingere5670c82021-01-07 22:14:25 -05001182
Mike Frysingerb8615112023-09-01 13:58:46 -04001183 return cls.from_data(data)
Mike Frysingere5670c82021-01-07 22:14:25 -05001184
Mike Frysingerb8615112023-09-01 13:58:46 -04001185 @classmethod
1186 def from_data(cls, data):
1187 comment_line = re.compile(rb"^ *#")
1188 strip_data = b"".join(
1189 x for x in data.splitlines() if not comment_line.match(x)
1190 )
1191 try:
1192 json_data = json.loads(strip_data)
1193 except Exception: # pylint: disable=broad-except
1194 # If we couldn't parse it, assume it's incompatible.
1195 return None
Mike Frysingere5670c82021-01-07 22:14:25 -05001196
Mike Frysingerb8615112023-09-01 13:58:46 -04001197 return cls(json_data)
Mike Frysingere5670c82021-01-07 22:14:25 -05001198
Mike Frysinger44066422024-03-21 12:58:01 -04001199 def get_soft_ver(self, pkg):
Mike Frysingerb8615112023-09-01 13:58:46 -04001200 """Return the soft version for |pkg| if it exists."""
Mike Frysinger44066422024-03-21 12:58:01 -04001201 return tuple(self.requirements.get(pkg, {}).get("soft", ()))
Mike Frysingere5670c82021-01-07 22:14:25 -05001202
Mike Frysinger44066422024-03-21 12:58:01 -04001203 def get_hard_ver(self, pkg):
Mike Frysingerb8615112023-09-01 13:58:46 -04001204 """Return the hard version for |pkg| if it exists."""
Mike Frysinger44066422024-03-21 12:58:01 -04001205 return tuple(self.requirements.get(pkg, {}).get("hard", ()))
Mike Frysingere5670c82021-01-07 22:14:25 -05001206
Mike Frysingerb8615112023-09-01 13:58:46 -04001207 @staticmethod
1208 def _format_ver(ver):
1209 """Return a dotted version from |ver|."""
1210 return ".".join(str(x) for x in ver)
Mike Frysingere5670c82021-01-07 22:14:25 -05001211
Mike Frysingerb8615112023-09-01 13:58:46 -04001212 def assert_ver(self, pkg, curr_ver):
1213 """Verify |pkg|'s |curr_ver| is new enough."""
1214 curr_ver = tuple(curr_ver)
Mike Frysinger44066422024-03-21 12:58:01 -04001215 soft_ver = tuple(self.get_soft_ver(pkg))
1216 hard_ver = tuple(self.get_hard_ver(pkg))
Mike Frysingerb8615112023-09-01 13:58:46 -04001217 if curr_ver < hard_ver:
1218 print(
Jason R. Coombsb32ccbb2023-09-29 11:04:49 -04001219 f'repo: error: Your version of "{pkg}" '
1220 f"({self._format_ver(curr_ver)}) is unsupported; "
1221 "Please upgrade to at least version "
1222 f"{self._format_ver(soft_ver)} to continue.",
Mike Frysingerb8615112023-09-01 13:58:46 -04001223 file=sys.stderr,
1224 )
1225 sys.exit(1)
Mike Frysingere5670c82021-01-07 22:14:25 -05001226
Mike Frysingerb8615112023-09-01 13:58:46 -04001227 if curr_ver < soft_ver:
1228 print(
Jason R. Coombsb32ccbb2023-09-29 11:04:49 -04001229 f'repo: error: Your version of "{pkg}" '
1230 f"({self._format_ver(curr_ver)}) is no longer supported; "
1231 "Please upgrade to at least version "
1232 f"{self._format_ver(soft_ver)} to continue.",
Mike Frysingerb8615112023-09-01 13:58:46 -04001233 file=sys.stderr,
1234 )
Mike Frysingere5670c82021-01-07 22:14:25 -05001235
Mike Frysingerb8615112023-09-01 13:58:46 -04001236 def assert_all(self):
1237 """Assert all of the requirements are satisified."""
1238 # See if we need a repo launcher upgrade first.
1239 self.assert_ver("repo", VERSION)
Mike Frysingere5670c82021-01-07 22:14:25 -05001240
Mike Frysingerb8615112023-09-01 13:58:46 -04001241 # Check python before we try to import the repo code.
1242 self.assert_ver("python", sys.version_info)
Mike Frysingere5670c82021-01-07 22:14:25 -05001243
Mike Frysingerb8615112023-09-01 13:58:46 -04001244 # Check git while we're at it.
1245 self.assert_ver("git", ParseGitVersion())
Mike Frysingere5670c82021-01-07 22:14:25 -05001246
1247
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001248def _Usage():
Mike Frysingerb8615112023-09-01 13:58:46 -04001249 print(
1250 """usage: repo COMMAND [ARGS]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001251
1252repo is not yet installed. Use "repo init" to install it here.
1253
1254The most commonly used repo commands are:
1255
1256 init Install repo in the current working directory
Josip Sokceviccf411b32024-12-03 21:29:01 +00001257 help Display detailed help on a command
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001258
1259For access to the full online help, install repo ("repo init").
Mike Frysingerb8615112023-09-01 13:58:46 -04001260"""
1261 )
1262 print("Bug reports:", BUG_URL)
1263 sys.exit(0)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001264
1265
1266def _Help(args):
Mike Frysingerb8615112023-09-01 13:58:46 -04001267 if args:
Josip Sokceviccf411b32024-12-03 21:29:01 +00001268 if args[0] in {"init"}:
1269 parser = GetParser()
Mike Frysingerb8615112023-09-01 13:58:46 -04001270 parser.print_help()
1271 sys.exit(0)
1272 else:
1273 print(
1274 "error: '%s' is not a bootstrap command.\n"
1275 ' For access to online help, install repo ("repo init").'
1276 % args[0],
1277 file=sys.stderr,
1278 )
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001279 else:
Mike Frysingerb8615112023-09-01 13:58:46 -04001280 _Usage()
1281 sys.exit(1)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001282
1283
Mike Frysinger8ddff5c2020-02-09 15:00:25 -05001284def _Version():
Mike Frysingerb8615112023-09-01 13:58:46 -04001285 """Show version information."""
Josip Sokcevicb1613d72024-11-15 22:41:30 +00001286 git_version = ParseGitVersion()
Mike Frysingerb8615112023-09-01 13:58:46 -04001287 print("<repo not installed>")
Jason R. Coombsb32ccbb2023-09-29 11:04:49 -04001288 print(f"repo launcher version {'.'.join(str(x) for x in VERSION)}")
1289 print(f" (from {__file__})")
Josip Sokcevicb1613d72024-11-15 22:41:30 +00001290 print(f"git {git_version.full}" if git_version else "git not installed")
Jason R. Coombsb32ccbb2023-09-29 11:04:49 -04001291 print(f"Python {sys.version}")
Mike Frysingerb8615112023-09-01 13:58:46 -04001292 uname = platform.uname()
Peter Kjellerstedtaa506db2023-11-03 17:16:19 +01001293 print(f"OS {uname.system} {uname.release} ({uname.version})")
1294 processor = uname.processor if uname.processor else "unknown"
1295 print(f"CPU {uname.machine} ({processor})")
Mike Frysingerb8615112023-09-01 13:58:46 -04001296 print("Bug reports:", BUG_URL)
1297 sys.exit(0)
Mike Frysinger8ddff5c2020-02-09 15:00:25 -05001298
1299
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001300def _NotInstalled():
Mike Frysingerb8615112023-09-01 13:58:46 -04001301 print(
1302 'error: repo is not installed. Use "repo init" to install it here.',
1303 file=sys.stderr,
1304 )
1305 sys.exit(1)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001306
1307
1308def _NoCommands(cmd):
Mike Frysingerb8615112023-09-01 13:58:46 -04001309 print(
1310 """error: command '%s' requires repo to be installed first.
1311 Use "repo init" to install it here."""
1312 % cmd,
1313 file=sys.stderr,
1314 )
1315 sys.exit(1)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001316
1317
1318def _RunSelf(wrapper_path):
Mike Frysingerb8615112023-09-01 13:58:46 -04001319 my_dir = os.path.dirname(wrapper_path)
1320 my_main = os.path.join(my_dir, "main.py")
1321 my_git = os.path.join(my_dir, ".git")
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001322
Josip Sokcevicb1613d72024-11-15 22:41:30 +00001323 if os.path.isfile(my_main):
Mike Frysingerb8615112023-09-01 13:58:46 -04001324 for name in ["git_config.py", "project.py", "subcmds"]:
1325 if not os.path.exists(os.path.join(my_dir, name)):
1326 return None, None
Josip Sokcevicb1613d72024-11-15 22:41:30 +00001327 return my_main, my_git if os.path.isdir(my_git) else None
Mike Frysingerb8615112023-09-01 13:58:46 -04001328 return None, None
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001329
1330
1331def _SetDefaultsTo(gitdir):
Mike Frysingerb8615112023-09-01 13:58:46 -04001332 global REPO_URL
1333 global REPO_REV
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001334
Mike Frysingerb8615112023-09-01 13:58:46 -04001335 REPO_URL = gitdir
1336 ret = run_git("--git-dir=%s" % gitdir, "symbolic-ref", "HEAD", check=False)
1337 if ret.returncode:
1338 # If we're not tracking a branch (bisect/etc...), then fall back to commit.
1339 print(
1340 "repo: warning: %s has no current branch; using HEAD" % gitdir,
1341 file=sys.stderr,
1342 )
1343 try:
1344 ret = run_git("rev-parse", "HEAD", cwd=gitdir)
1345 except CloneFailure:
1346 print("fatal: %s has invalid HEAD" % gitdir, file=sys.stderr)
1347 sys.exit(1)
Mike Frysingercdb344c2020-03-24 02:43:46 -04001348
Mike Frysingerb8615112023-09-01 13:58:46 -04001349 REPO_REV = ret.stdout.strip()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001350
1351
1352def main(orig_args):
Mike Frysingerb8615112023-09-01 13:58:46 -04001353 cmd, opt, args = _ParseArguments(orig_args)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001354
Mike Frysingerb8615112023-09-01 13:58:46 -04001355 # We run this early as we run some git commands ourselves.
1356 SetGitTrace2ParentSid()
Mike Frysinger84094102020-02-11 02:10:28 -05001357
Josip Sokceviccf411b32024-12-03 21:29:01 +00001358 repo_main, rel_repo_dir = _FindRepo()
Dan Willemsen745b4ad2015-10-06 15:23:19 -07001359
Mike Frysingerb8615112023-09-01 13:58:46 -04001360 wrapper_path = os.path.abspath(__file__)
1361 my_main, my_git = _RunSelf(wrapper_path)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001362
Mike Frysingerb8615112023-09-01 13:58:46 -04001363 if not repo_main:
1364 # Only expand aliases here since we'll be parsing the CLI ourselves.
1365 # If we had repo_main, alias expansion would happen in main.py.
1366 cmd, alias_args = _ExpandAlias(cmd)
1367 args = alias_args + args
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001368
Mike Frysingerb8615112023-09-01 13:58:46 -04001369 if opt.help:
1370 _Usage()
1371 if cmd == "help":
1372 _Help(args)
1373 if opt.version or cmd == "version":
1374 _Version()
1375 if not cmd:
1376 _NotInstalled()
Josip Sokceviccf411b32024-12-03 21:29:01 +00001377 if cmd == "init":
Mike Frysingerb8615112023-09-01 13:58:46 -04001378 if my_git:
1379 _SetDefaultsTo(my_git)
1380 try:
Josip Sokceviccf411b32024-12-03 21:29:01 +00001381 _Init(args)
Mike Frysingerb8615112023-09-01 13:58:46 -04001382 except CloneFailure:
1383 path = os.path.join(repodir, S_repo)
1384 print(
1385 "fatal: cloning the git-repo repository failed, will remove "
1386 "'%s' " % path,
1387 file=sys.stderr,
1388 )
1389 shutil.rmtree(path, ignore_errors=True)
1390 shutil.rmtree(path + ".tmp", ignore_errors=True)
1391 sys.exit(1)
1392 repo_main, rel_repo_dir = _FindRepo()
1393 else:
1394 _NoCommands(cmd)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001395
Mike Frysingerb8615112023-09-01 13:58:46 -04001396 if my_main:
1397 repo_main = my_main
Konrad Weihmann433977e2020-04-17 21:39:32 +02001398
Mike Frysingerb8615112023-09-01 13:58:46 -04001399 if not repo_main:
1400 print("fatal: unable to find repo entry point", file=sys.stderr)
1401 sys.exit(1)
Mike Frysingere5670c82021-01-07 22:14:25 -05001402
Mike Frysingerb8615112023-09-01 13:58:46 -04001403 reqs = Requirements.from_dir(os.path.dirname(repo_main))
1404 if reqs:
1405 reqs.assert_all()
1406
Duy Truong9f0ef5d2023-10-27 11:35:34 -07001407 # Python 3.11 introduces PYTHONSAFEPATH and the -P flag which, if enabled,
1408 # does not prepend the script's directory to sys.path by default.
1409 # repo relies on this import path, so add directory of REPO_MAIN to
1410 # PYTHONPATH so that this continues to work when PYTHONSAFEPATH is enabled.
1411 python_paths = os.environ.get("PYTHONPATH", "").split(os.pathsep)
1412 new_python_paths = [os.path.join(rel_repo_dir, S_repo)] + python_paths
1413 os.environ["PYTHONPATH"] = os.pathsep.join(new_python_paths)
1414
Mike Frysingerb8615112023-09-01 13:58:46 -04001415 ver_str = ".".join(map(str, VERSION))
1416 me = [
1417 sys.executable,
1418 repo_main,
1419 "--repo-dir=%s" % rel_repo_dir,
1420 "--wrapper-version=%s" % ver_str,
1421 "--wrapper-path=%s" % wrapper_path,
1422 "--",
1423 ]
1424 me.extend(orig_args)
1425 exec_command(me)
1426 print("fatal: unable to start %s" % repo_main, file=sys.stderr)
1427 sys.exit(148)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001428
1429
Mike Frysingerb8615112023-09-01 13:58:46 -04001430if __name__ == "__main__":
1431 main(sys.argv[1:])