diff options
Diffstat (limited to 'rust_tools/rust_uprev.py')
-rwxr-xr-x | rust_tools/rust_uprev.py | 1522 |
1 files changed, 866 insertions, 656 deletions
diff --git a/rust_tools/rust_uprev.py b/rust_tools/rust_uprev.py index 011639df..382d991a 100755 --- a/rust_tools/rust_uprev.py +++ b/rust_tools/rust_uprev.py @@ -1,416 +1,437 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -# Copyright 2020 The Chromium OS Authors. All rights reserved. +# Copyright 2020 The ChromiumOS Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. """Tool to automatically generate a new Rust uprev CL. -This tool is intended to automatically generate a CL to uprev Rust to a -newer version in Chrome OS, including creating a new Rust version or -removing an old version. It's based on -src/third_party/chromiumos-overlay/dev-lang/rust/UPGRADE.md. When using -the tool, the progress can be saved to a JSON file, so the user can resume -the process after a failing step is fixed. Example usage to create a new -version: - -1. (inside chroot) $ ./rust_tools/rust_uprev.py - --state_file /tmp/state-file.json - create --rust_version 1.45.0 -2. Step "compile rust" failed due to the patches can't apply to new version -3. Manually fix the patches -4. Execute the command in step 1 again. +This tool is intended to automatically generate a CL to uprev Rust to +a newer version in Chrome OS, including creating a new Rust version or +removing an old version. When using the tool, the progress can be +saved to a JSON file, so the user can resume the process after a +failing step is fixed. Example usage to create a new version: + +1. (inside chroot) $ ./rust_tools/rust_uprev.py \\ + --state_file /tmp/rust-to-1.60.0.json \\ + roll --uprev 1.60.0 +2. Step "compile rust" failed due to the patches can't apply to new version. +3. Manually fix the patches. +4. Execute the command in step 1 again, but add "--continue" before "roll". 5. Iterate 1-4 for each failed step until the tool passes. -Replace `create --rust_version 1.45.0` with `remove --rust_version 1.43.0` -if you want to remove all 1.43.0 related stuff in the same CL. Remember to -use a different state file if you choose to run different subcommands. - -If you want a hammer that can do everything for you, use the subcommand -`roll`. It can create a Rust uprev CL with `create` and `remove` and upload -the CL to chromium code review. +Besides "roll", the tool also support subcommands that perform +various parts of an uprev. See `--help` for all available options. """ import argparse -import pathlib import json import logging import os +import pathlib +from pathlib import Path import re +import shlex import shutil import subprocess import sys -from pathlib import Path from typing import Any, Callable, Dict, List, NamedTuple, Optional, T, Tuple -from llvm_tools import chroot, git +from llvm_tools import chroot +from llvm_tools import git + -EQUERY = 'equery' -GSUTIL = 'gsutil.py' -MIRROR_PATH = 'gs://chromeos-localmirror/distfiles' -RUST_PATH = Path( - '/mnt/host/source/src/third_party/chromiumos-overlay/dev-lang/rust') +EQUERY = "equery" +GSUTIL = "gsutil.py" +MIRROR_PATH = "gs://chromeos-localmirror/distfiles" +EBUILD_PREFIX = Path("/mnt/host/source/src/third_party/chromiumos-overlay") +RUST_PATH = Path(EBUILD_PREFIX, "dev-lang", "rust") def get_command_output(command: List[str], *args, **kwargs) -> str: - return subprocess.check_output(command, encoding='utf-8', *args, - **kwargs).strip() + return subprocess.check_output( + command, encoding="utf-8", *args, **kwargs + ).strip() def get_command_output_unchecked(command: List[str], *args, **kwargs) -> str: - return subprocess.run(command, - check=False, - stdout=subprocess.PIPE, - encoding='utf-8', - *args, - **kwargs).stdout.strip() + return subprocess.run( + command, + check=False, + stdout=subprocess.PIPE, + encoding="utf-8", + *args, + **kwargs, + ).stdout.strip() class RustVersion(NamedTuple): - """NamedTuple represents a Rust version""" - major: int - minor: int - patch: int - - def __str__(self): - return f'{self.major}.{self.minor}.{self.patch}' - - @staticmethod - def parse_from_ebuild(ebuild_name: str) -> 'RustVersion': - input_re = re.compile(r'^rust-' - r'(?P<major>\d+)\.' - r'(?P<minor>\d+)\.' - r'(?P<patch>\d+)' - r'(:?-r\d+)?' - r'\.ebuild$') - m = input_re.match(ebuild_name) - assert m, f'failed to parse {ebuild_name!r}' - return RustVersion(int(m.group('major')), int(m.group('minor')), - int(m.group('patch'))) - - @staticmethod - def parse(x: str) -> 'RustVersion': - input_re = re.compile(r'^(?:rust-)?' - r'(?P<major>\d+)\.' - r'(?P<minor>\d+)\.' - r'(?P<patch>\d+)' - r'(?:.ebuild)?$') - m = input_re.match(x) - assert m, f'failed to parse {x!r}' - return RustVersion(int(m.group('major')), int(m.group('minor')), - int(m.group('patch'))) + """NamedTuple represents a Rust version""" + + major: int + minor: int + patch: int + + def __str__(self): + return f"{self.major}.{self.minor}.{self.patch}" + + @staticmethod + def parse_from_ebuild(ebuild_name: str) -> "RustVersion": + input_re = re.compile( + r"^rust-" + r"(?P<major>\d+)\." + r"(?P<minor>\d+)\." + r"(?P<patch>\d+)" + r"(:?-r\d+)?" + r"\.ebuild$" + ) + m = input_re.match(ebuild_name) + assert m, f"failed to parse {ebuild_name!r}" + return RustVersion( + int(m.group("major")), int(m.group("minor")), int(m.group("patch")) + ) + + @staticmethod + def parse(x: str) -> "RustVersion": + input_re = re.compile( + r"^(?:rust-)?" + r"(?P<major>\d+)\." + r"(?P<minor>\d+)\." + r"(?P<patch>\d+)" + r"(?:.ebuild)?$" + ) + m = input_re.match(x) + assert m, f"failed to parse {x!r}" + return RustVersion( + int(m.group("major")), int(m.group("minor")), int(m.group("patch")) + ) def compute_rustc_src_name(version: RustVersion) -> str: - return f'rustc-{version}-src.tar.gz' + return f"rustc-{version}-src.tar.gz" def compute_rust_bootstrap_prebuilt_name(version: RustVersion) -> str: - return f'rust-bootstrap-{version}.tbz2' + return f"rust-bootstrap-{version}.tbz2" def find_ebuild_for_package(name: str) -> os.PathLike: - """Returns the path to the ebuild for the named package.""" - return get_command_output([EQUERY, 'w', name]) - - -def find_ebuild_path(directory: Path, - name: str, - version: Optional[RustVersion] = None) -> Path: - """Finds an ebuild in a directory. - - Returns the path to the ebuild file. Asserts if there is not - exactly one match. The match is constrained by name and optionally - by version, but can match any patch level. E.g. "rust" version - 1.3.4 can match rust-1.3.4.ebuild but also rust-1.3.4-r6.ebuild. - """ - if version: - pattern = f'{name}-{version}*.ebuild' - else: - pattern = f'{name}-*.ebuild' - matches = list(Path(directory).glob(pattern)) - assert len(matches) == 1, matches - return matches[0] + """Returns the path to the ebuild for the named package.""" + return get_command_output([EQUERY, "w", name]) + + +def find_ebuild_path( + directory: Path, name: str, version: Optional[RustVersion] = None +) -> Path: + """Finds an ebuild in a directory. + + Returns the path to the ebuild file. The match is constrained by + name and optionally by version, but can match any patch level. + E.g. "rust" version 1.3.4 can match rust-1.3.4.ebuild but also + rust-1.3.4-r6.ebuild. + + The expectation is that there is only one matching ebuild, and + an assert is raised if this is not the case. However, symlinks to + ebuilds in the same directory are ignored, so having a + rust-x.y.z-rn.ebuild symlink to rust-x.y.z.ebuild is allowed. + """ + if version: + pattern = f"{name}-{version}*.ebuild" + else: + pattern = f"{name}-*.ebuild" + matches = set(directory.glob(pattern)) + result = [] + # Only count matches that are not links to other matches. + for m in matches: + try: + target = os.readlink(directory / m) + except OSError: + # Getting here means the match is not a symlink to one of + # the matching ebuilds, so add it to the result list. + result.append(m) + continue + if directory / target not in matches: + result.append(m) + assert len(result) == 1, result + return result[0] def get_rust_bootstrap_version(): - """Get the version of the current rust-bootstrap package.""" - bootstrap_ebuild = find_ebuild_path(rust_bootstrap_path(), 'rust-bootstrap') - m = re.match(r'^rust-bootstrap-(\d+).(\d+).(\d+)', bootstrap_ebuild.name) - assert m, bootstrap_ebuild.name - return RustVersion(int(m.group(1)), int(m.group(2)), int(m.group(3))) + """Get the version of the current rust-bootstrap package.""" + bootstrap_ebuild = find_ebuild_path(rust_bootstrap_path(), "rust-bootstrap") + m = re.match(r"^rust-bootstrap-(\d+).(\d+).(\d+)", bootstrap_ebuild.name) + assert m, bootstrap_ebuild.name + return RustVersion(int(m.group(1)), int(m.group(2)), int(m.group(3))) def parse_commandline_args() -> argparse.Namespace: - parser = argparse.ArgumentParser( - description=__doc__, - formatter_class=argparse.RawDescriptionHelpFormatter) - parser.add_argument( - '--state_file', - required=True, - help='A state file to hold previous completed steps. If the file ' - 'exists, it needs to be used together with --continue or --restart. ' - 'If not exist (do not use --continue in this case), we will create a ' - 'file for you.', - ) - parser.add_argument( - '--restart', - action='store_true', - help='Restart from the first step. Ignore the completed steps in ' - 'the state file', - ) - parser.add_argument( - '--continue', - dest='cont', - action='store_true', - help='Continue the steps from the state file', - ) - - create_parser_template = argparse.ArgumentParser(add_help=False) - create_parser_template.add_argument( - '--template', - type=RustVersion.parse, - default=None, - help='A template to use for creating a Rust uprev from, in the form ' - 'a.b.c The ebuild has to exist in the chroot. If not specified, the ' - 'tool will use the current Rust version in the chroot as template.', - ) - create_parser_template.add_argument( - '--skip_compile', - action='store_true', - help='Skip compiling rust to test the tool. Only for testing', - ) - - subparsers = parser.add_subparsers(dest='subparser_name') - subparser_names = [] - subparser_names.append('create') - create_parser = subparsers.add_parser( - 'create', - parents=[create_parser_template], - help='Create changes uprevs Rust to a new version', - ) - create_parser.add_argument( - '--rust_version', - type=RustVersion.parse, - required=True, - help='Rust version to uprev to, in the form a.b.c', - ) - - subparser_names.append('remove') - remove_parser = subparsers.add_parser( - 'remove', - help='Clean up old Rust version from chroot', - ) - remove_parser.add_argument( - '--rust_version', - type=RustVersion.parse, - default=None, - help='Rust version to remove, in the form a.b.c If not ' - 'specified, the tool will remove the oldest version in the chroot', - ) - - subparser_names.append('remove-bootstrap') - remove_bootstrap_parser = subparsers.add_parser( - 'remove-bootstrap', - help='Remove an old rust-bootstrap version', - ) - remove_bootstrap_parser.add_argument( - '--version', - type=RustVersion.parse, - required=True, - help='rust-bootstrap version to remove', - ) - - subparser_names.append('roll') - roll_parser = subparsers.add_parser( - 'roll', - parents=[create_parser_template], - help='A command can create and upload a Rust uprev CL, including ' - 'preparing the repo, creating new Rust uprev, deleting old uprev, ' - 'and upload a CL to crrev.', - ) - roll_parser.add_argument( - '--uprev', - type=RustVersion.parse, - required=True, - help='Rust version to uprev to, in the form a.b.c', - ) - roll_parser.add_argument( - '--remove', - type=RustVersion.parse, - default=None, - help='Rust version to remove, in the form a.b.c If not ' - 'specified, the tool will remove the oldest version in the chroot', - ) - roll_parser.add_argument( - '--skip_cross_compiler', - action='store_true', - help='Skip updating cross-compiler in the chroot', - ) - roll_parser.add_argument( - '--no_upload', - action='store_true', - help='If specified, the tool will not upload the CL for review', - ) - - args = parser.parse_args() - if args.subparser_name not in subparser_names: - parser.error('one of %s must be specified' % subparser_names) - - if args.cont and args.restart: - parser.error('Please select either --continue or --restart') - - if os.path.exists(args.state_file): - if not args.cont and not args.restart: - parser.error('State file exists, so you should either --continue ' - 'or --restart') - if args.cont and not os.path.exists(args.state_file): - parser.error('Indicate --continue but the state file does not exist') - - if args.restart and os.path.exists(args.state_file): - os.remove(args.state_file) - - return args - - -def prepare_uprev(rust_version: RustVersion, template: Optional[RustVersion] - ) -> Optional[Tuple[RustVersion, str, RustVersion]]: - if template is None: - ebuild_path = find_ebuild_for_package('rust') - ebuild_name = os.path.basename(ebuild_path) - template_version = RustVersion.parse_from_ebuild(ebuild_name) - else: - ebuild_path = find_ebuild_for_rust_version(template) - template_version = template - - bootstrap_version = get_rust_bootstrap_version() - - if rust_version <= template_version: - logging.info( - 'Requested version %s is not newer than the template version %s.', - rust_version, template_version) - return None + parser = argparse.ArgumentParser( + description=__doc__, + formatter_class=argparse.RawDescriptionHelpFormatter, + ) + parser.add_argument( + "--state_file", + required=True, + help="A state file to hold previous completed steps. If the file " + "exists, it needs to be used together with --continue or --restart. " + "If not exist (do not use --continue in this case), we will create a " + "file for you.", + ) + parser.add_argument( + "--restart", + action="store_true", + help="Restart from the first step. Ignore the completed steps in " + "the state file", + ) + parser.add_argument( + "--continue", + dest="cont", + action="store_true", + help="Continue the steps from the state file", + ) + + create_parser_template = argparse.ArgumentParser(add_help=False) + create_parser_template.add_argument( + "--template", + type=RustVersion.parse, + default=None, + help="A template to use for creating a Rust uprev from, in the form " + "a.b.c The ebuild has to exist in the chroot. If not specified, the " + "tool will use the current Rust version in the chroot as template.", + ) + create_parser_template.add_argument( + "--skip_compile", + action="store_true", + help="Skip compiling rust to test the tool. Only for testing", + ) - logging.info('Template Rust version is %s (ebuild: %r)', template_version, - ebuild_path) - logging.info('rust-bootstrap version is %s', bootstrap_version) + subparsers = parser.add_subparsers(dest="subparser_name") + subparser_names = [] + subparser_names.append("create") + create_parser = subparsers.add_parser( + "create", + parents=[create_parser_template], + help="Create changes uprevs Rust to a new version", + ) + create_parser.add_argument( + "--rust_version", + type=RustVersion.parse, + required=True, + help="Rust version to uprev to, in the form a.b.c", + ) - return template_version, ebuild_path, bootstrap_version + subparser_names.append("remove") + remove_parser = subparsers.add_parser( + "remove", + help="Clean up old Rust version from chroot", + ) + remove_parser.add_argument( + "--rust_version", + type=RustVersion.parse, + default=None, + help="Rust version to remove, in the form a.b.c If not " + "specified, the tool will remove the oldest version in the chroot", + ) + subparser_names.append("remove-bootstrap") + remove_bootstrap_parser = subparsers.add_parser( + "remove-bootstrap", + help="Remove an old rust-bootstrap version", + ) + remove_bootstrap_parser.add_argument( + "--version", + type=RustVersion.parse, + required=True, + help="rust-bootstrap version to remove", + ) -def copy_patches(directory: Path, template_version: RustVersion, - new_version: RustVersion) -> None: - patch_path = directory.joinpath('files') - prefix = '%s-%s-' % (directory.name, template_version) - new_prefix = '%s-%s-' % (directory.name, new_version) - for f in os.listdir(patch_path): - if not f.startswith(prefix): - continue - logging.info('Copy patch %s to new version', f) - new_name = f.replace(str(template_version), str(new_version)) - shutil.copyfile( - os.path.join(patch_path, f), - os.path.join(patch_path, new_name), + subparser_names.append("roll") + roll_parser = subparsers.add_parser( + "roll", + parents=[create_parser_template], + help="A command can create and upload a Rust uprev CL, including " + "preparing the repo, creating new Rust uprev, deleting old uprev, " + "and upload a CL to crrev.", + ) + roll_parser.add_argument( + "--uprev", + type=RustVersion.parse, + required=True, + help="Rust version to uprev to, in the form a.b.c", + ) + roll_parser.add_argument( + "--remove", + type=RustVersion.parse, + default=None, + help="Rust version to remove, in the form a.b.c If not " + "specified, the tool will remove the oldest version in the chroot", + ) + roll_parser.add_argument( + "--skip_cross_compiler", + action="store_true", + help="Skip updating cross-compiler in the chroot", + ) + roll_parser.add_argument( + "--no_upload", + action="store_true", + help="If specified, the tool will not upload the CL for review", ) - subprocess.check_call(['git', 'add', f'{new_prefix}*.patch'], cwd=patch_path) + args = parser.parse_args() + if args.subparser_name not in subparser_names: + parser.error("one of %s must be specified" % subparser_names) + + if args.cont and args.restart: + parser.error("Please select either --continue or --restart") + + if os.path.exists(args.state_file): + if not args.cont and not args.restart: + parser.error( + "State file exists, so you should either --continue " + "or --restart" + ) + if args.cont and not os.path.exists(args.state_file): + parser.error("Indicate --continue but the state file does not exist") + + if args.restart and os.path.exists(args.state_file): + os.remove(args.state_file) + + return args + + +def prepare_uprev( + rust_version: RustVersion, template: Optional[RustVersion] +) -> Optional[Tuple[RustVersion, str, RustVersion]]: + if template is None: + ebuild_path = find_ebuild_for_package("rust") + ebuild_name = os.path.basename(ebuild_path) + template_version = RustVersion.parse_from_ebuild(ebuild_name) + else: + ebuild_path = find_ebuild_for_rust_version(template) + template_version = template + + bootstrap_version = get_rust_bootstrap_version() + + if rust_version <= template_version: + logging.info( + "Requested version %s is not newer than the template version %s.", + rust_version, + template_version, + ) + return None + + logging.info( + "Template Rust version is %s (ebuild: %r)", + template_version, + ebuild_path, + ) + logging.info("rust-bootstrap version is %s", bootstrap_version) + + return template_version, ebuild_path, bootstrap_version + + +def copy_patches( + directory: Path, template_version: RustVersion, new_version: RustVersion +) -> None: + patch_path = directory / "files" + prefix = "%s-%s-" % (directory.name, template_version) + new_prefix = "%s-%s-" % (directory.name, new_version) + for f in os.listdir(patch_path): + if not f.startswith(prefix): + continue + logging.info("Copy patch %s to new version", f) + new_name = f.replace(str(template_version), str(new_version)) + shutil.copyfile( + os.path.join(patch_path, f), + os.path.join(patch_path, new_name), + ) + + subprocess.check_call( + ["git", "add", f"{new_prefix}*.patch"], cwd=patch_path + ) -def create_ebuild(template_ebuild: str, new_version: RustVersion) -> str: - shutil.copyfile(template_ebuild, - RUST_PATH.joinpath(f'rust-{new_version}.ebuild')) - subprocess.check_call(['git', 'add', f'rust-{new_version}.ebuild'], - cwd=RUST_PATH) - return os.path.join(RUST_PATH, f'rust-{new_version}.ebuild') +def create_ebuild( + template_ebuild: str, pkgatom: str, new_version: RustVersion +) -> str: + filename = f"{Path(pkgatom).name}-{new_version}.ebuild" + ebuild = EBUILD_PREFIX.joinpath(f"{pkgatom}/{filename}") + shutil.copyfile(template_ebuild, ebuild) + subprocess.check_call(["git", "add", filename], cwd=ebuild.parent) + return str(ebuild) def update_bootstrap_ebuild(new_bootstrap_version: RustVersion) -> None: - old_ebuild = find_ebuild_path(rust_bootstrap_path(), 'rust-bootstrap') - m = re.match(r'^rust-bootstrap-(\d+).(\d+).(\d+)', old_ebuild.name) - assert m, old_ebuild.name - old_version = RustVersion(m.group(1), m.group(2), m.group(3)) - new_ebuild = old_ebuild.parent.joinpath( - f'rust-bootstrap-{new_bootstrap_version}.ebuild') - old_text = old_ebuild.read_text(encoding='utf-8') - new_text, changes = re.subn(r'(RUSTC_RAW_FULL_BOOTSTRAP_SEQUENCE=\([^)]*)', - f'\\1\t{old_version}\n', - old_text, - flags=re.MULTILINE) - assert changes == 1, 'Failed to update RUSTC_RAW_FULL_BOOTSTRAP_SEQUENCE' - new_ebuild.write_text(new_text, encoding='utf-8') - - -def update_ebuild(ebuild_file: str, - new_bootstrap_version: RustVersion) -> None: - contents = open(ebuild_file, encoding='utf-8').read() - contents, subs = re.subn(r'^BOOTSTRAP_VERSION=.*$', - 'BOOTSTRAP_VERSION="%s"' % - (new_bootstrap_version, ), - contents, - flags=re.MULTILINE) - if not subs: - raise RuntimeError('BOOTSTRAP_VERSION not found in rust ebuild') - open(ebuild_file, 'w', encoding='utf-8').write(contents) - logging.info('Rust ebuild file has BOOTSTRAP_VERSION updated to %s', - new_bootstrap_version) - - -def flip_mirror_in_ebuild(ebuild_file: Path, add: bool) -> None: - restrict_re = re.compile( - r'(?P<before>RESTRICT=")(?P<values>"[^"]*"|.*)(?P<after>")') - with open(ebuild_file, encoding='utf-8') as f: - contents = f.read() - m = restrict_re.search(contents) - assert m, 'failed to find RESTRICT variable in Rust ebuild' - values = m.group('values') - if add: - if 'mirror' in values: - return - values += ' mirror' - else: - if 'mirror' not in values: - return - values = values.replace(' mirror', '') - new_contents = restrict_re.sub(r'\g<before>%s\g<after>' % values, contents) - with open(ebuild_file, 'w', encoding='utf-8') as f: - f.write(new_contents) - - -def ebuild_actions(package: str, actions: List[str], - sudo: bool = False) -> None: - ebuild_path_inchroot = find_ebuild_for_package(package) - cmd = ['ebuild', ebuild_path_inchroot] + actions - if sudo: - cmd = ['sudo'] + cmd - subprocess.check_call(cmd) + old_ebuild = find_ebuild_path(rust_bootstrap_path(), "rust-bootstrap") + m = re.match(r"^rust-bootstrap-(\d+).(\d+).(\d+)", old_ebuild.name) + assert m, old_ebuild.name + old_version = RustVersion(m.group(1), m.group(2), m.group(3)) + new_ebuild = old_ebuild.parent.joinpath( + f"rust-bootstrap-{new_bootstrap_version}.ebuild" + ) + old_text = old_ebuild.read_text(encoding="utf-8") + new_text, changes = re.subn( + r"(RUSTC_RAW_FULL_BOOTSTRAP_SEQUENCE=\([^)]*)", + f"\\1\t{old_version}\n", + old_text, + flags=re.MULTILINE, + ) + assert changes == 1, "Failed to update RUSTC_RAW_FULL_BOOTSTRAP_SEQUENCE" + new_ebuild.write_text(new_text, encoding="utf-8") + + +def update_bootstrap_version( + path: str, new_bootstrap_version: RustVersion +) -> None: + contents = open(path, encoding="utf-8").read() + contents, subs = re.subn( + r"^BOOTSTRAP_VERSION=.*$", + 'BOOTSTRAP_VERSION="%s"' % (new_bootstrap_version,), + contents, + flags=re.MULTILINE, + ) + if not subs: + raise RuntimeError(f"BOOTSTRAP_VERSION not found in {path}") + open(path, "w", encoding="utf-8").write(contents) + logging.info("Rust BOOTSTRAP_VERSION updated to %s", new_bootstrap_version) + + +def ebuild_actions( + package: str, actions: List[str], sudo: bool = False +) -> None: + ebuild_path_inchroot = find_ebuild_for_package(package) + cmd = ["ebuild", ebuild_path_inchroot] + actions + if sudo: + cmd = ["sudo"] + cmd + subprocess.check_call(cmd) def fetch_distfile_from_mirror(name: str) -> None: - """Gets the named file from the local mirror. - - This ensures that the file exists on the mirror, and - that we can read it. We overwrite any existing distfile - to ensure the checksums that update_manifest() records - match the file as it exists on the mirror. - - This function also attempts to verify the ACL for - the file (which is expected to have READER permission - for allUsers). We can only see the ACL if the user - gsutil runs with is the owner of the file. If not, - we get an access denied error. We also count this - as a success, because it means we were able to fetch - the file even though we don't own it. - """ - mirror_file = MIRROR_PATH + '/' + name - local_file = Path(get_distdir(), name) - cmd = [GSUTIL, 'cp', mirror_file, local_file] - logging.info('Running %r', cmd) - rc = subprocess.call(cmd) - if rc != 0: - logging.error( - """Could not fetch %s + """Gets the named file from the local mirror. + + This ensures that the file exists on the mirror, and + that we can read it. We overwrite any existing distfile + to ensure the checksums that update_manifest() records + match the file as it exists on the mirror. + + This function also attempts to verify the ACL for + the file (which is expected to have READER permission + for allUsers). We can only see the ACL if the user + gsutil runs with is the owner of the file. If not, + we get an access denied error. We also count this + as a success, because it means we were able to fetch + the file even though we don't own it. + """ + mirror_file = MIRROR_PATH + "/" + name + local_file = Path(get_distdir(), name) + cmd = [GSUTIL, "cp", mirror_file, local_file] + logging.info("Running %r", cmd) + rc = subprocess.call(cmd) + if rc != 0: + logging.error( + """Could not fetch %s If the file does not yet exist at %s please download the file, verify its integrity @@ -425,352 +446,541 @@ gpg --recv-keys 85AB96E6FA1BE5FE Once you have verify the integrity of the file, upload it to the local mirror using gsutil cp. -""", mirror_file, MIRROR_PATH, name, name) - raise Exception(f'Could not fetch {mirror_file}') - # Check that the ACL allows allUsers READER access. - # If we get an AccessDeniedAcception here, that also - # counts as a success, because we were able to fetch - # the file as a non-owner. - cmd = [GSUTIL, 'acl', 'get', mirror_file] - logging.info('Running %r', cmd) - output = get_command_output_unchecked(cmd, stderr=subprocess.STDOUT) - acl_verified = False - if 'AccessDeniedException:' in output: - acl_verified = True - else: - acl = json.loads(output) - for x in acl: - if x['entity'] == 'allUsers' and x['role'] == 'READER': +""", + mirror_file, + MIRROR_PATH, + name, + name, + ) + raise Exception(f"Could not fetch {mirror_file}") + # Check that the ACL allows allUsers READER access. + # If we get an AccessDeniedAcception here, that also + # counts as a success, because we were able to fetch + # the file as a non-owner. + cmd = [GSUTIL, "acl", "get", mirror_file] + logging.info("Running %r", cmd) + output = get_command_output_unchecked(cmd, stderr=subprocess.STDOUT) + acl_verified = False + if "AccessDeniedException:" in output: acl_verified = True - break - if not acl_verified: - logging.error('Output from acl get:\n%s', output) - raise Exception('Could not verify that allUsers has READER permission') - - -def fetch_bootstrap_distfiles(old_version: RustVersion, - new_version: RustVersion) -> None: - """Fetches rust-bootstrap distfiles from the local mirror - - Fetches the distfiles for a rust-bootstrap ebuild to ensure they - are available on the mirror and the local copies are the same as - the ones on the mirror. - """ - fetch_distfile_from_mirror(compute_rust_bootstrap_prebuilt_name(old_version)) - fetch_distfile_from_mirror(compute_rustc_src_name(new_version)) + else: + acl = json.loads(output) + for x in acl: + if x["entity"] == "allUsers" and x["role"] == "READER": + acl_verified = True + break + if not acl_verified: + logging.error("Output from acl get:\n%s", output) + raise Exception("Could not verify that allUsers has READER permission") + + +def fetch_bootstrap_distfiles( + old_version: RustVersion, new_version: RustVersion +) -> None: + """Fetches rust-bootstrap distfiles from the local mirror + + Fetches the distfiles for a rust-bootstrap ebuild to ensure they + are available on the mirror and the local copies are the same as + the ones on the mirror. + """ + fetch_distfile_from_mirror( + compute_rust_bootstrap_prebuilt_name(old_version) + ) + fetch_distfile_from_mirror(compute_rustc_src_name(new_version)) def fetch_rust_distfiles(version: RustVersion) -> None: - """Fetches rust distfiles from the local mirror + """Fetches rust distfiles from the local mirror - Fetches the distfiles for a rust ebuild to ensure they - are available on the mirror and the local copies are - the same as the ones on the mirror. - """ - fetch_distfile_from_mirror(compute_rustc_src_name(version)) + Fetches the distfiles for a rust ebuild to ensure they + are available on the mirror and the local copies are + the same as the ones on the mirror. + """ + fetch_distfile_from_mirror(compute_rustc_src_name(version)) def get_distdir() -> os.PathLike: - """Returns portage's distdir.""" - return get_command_output(['portageq', 'distdir']) + """Returns portage's distdir.""" + return get_command_output(["portageq", "distdir"]) def update_manifest(ebuild_file: os.PathLike) -> None: - """Updates the MANIFEST for the ebuild at the given path.""" - ebuild = Path(ebuild_file) - logging.info('Added "mirror" to RESTRICT to %s', ebuild.name) - flip_mirror_in_ebuild(ebuild, add=True) - ebuild_actions(ebuild.parent.name, ['manifest']) - logging.info('Removed "mirror" to RESTRICT from %s', ebuild.name) - flip_mirror_in_ebuild(ebuild, add=False) - - -def update_rust_packages(rust_version: RustVersion, add: bool) -> None: - package_file = RUST_PATH.joinpath( - '../../profiles/targets/chromeos/package.provided') - with open(package_file, encoding='utf-8') as f: - contents = f.read() - if add: - rust_packages_re = re.compile(r'dev-lang/rust-(\d+\.\d+\.\d+)') - rust_packages = rust_packages_re.findall(contents) - # Assume all the rust packages are in alphabetical order, so insert the new - # version to the place after the last rust_packages - new_str = f'dev-lang/rust-{rust_version}' - new_contents = contents.replace(rust_packages[-1], - f'{rust_packages[-1]}\n{new_str}') - logging.info('%s has been inserted into package.provided', new_str) - else: - old_str = f'dev-lang/rust-{rust_version}\n' - assert old_str in contents, f'{old_str!r} not found in package.provided' - new_contents = contents.replace(old_str, '') - logging.info('%s has been removed from package.provided', old_str) - - with open(package_file, 'w', encoding='utf-8') as f: - f.write(new_contents) - - -def update_virtual_rust(template_version: RustVersion, - new_version: RustVersion) -> None: - template_ebuild = find_ebuild_path(RUST_PATH.joinpath('../../virtual/rust'), - 'rust', template_version) - virtual_rust_dir = template_ebuild.parent - new_name = f'rust-{new_version}.ebuild' - new_ebuild = virtual_rust_dir.joinpath(new_name) - shutil.copyfile(template_ebuild, new_ebuild) - subprocess.check_call(['git', 'add', new_name], cwd=virtual_rust_dir) - - -def perform_step(state_file: pathlib.Path, - tmp_state_file: pathlib.Path, - completed_steps: Dict[str, Any], - step_name: str, - step_fn: Callable[[], T], - result_from_json: Optional[Callable[[Any], T]] = None, - result_to_json: Optional[Callable[[T], Any]] = None) -> T: - if step_name in completed_steps: - logging.info('Skipping previously completed step %s', step_name) - if result_from_json: - return result_from_json(completed_steps[step_name]) - return completed_steps[step_name] - - logging.info('Running step %s', step_name) - val = step_fn() - logging.info('Step %s complete', step_name) - if result_to_json: - completed_steps[step_name] = result_to_json(val) - else: - completed_steps[step_name] = val - - with tmp_state_file.open('w', encoding='utf-8') as f: - json.dump(completed_steps, f, indent=4) - tmp_state_file.rename(state_file) - return val + """Updates the MANIFEST for the ebuild at the given path.""" + ebuild = Path(ebuild_file) + ebuild_actions(ebuild.parent.name, ["manifest"]) + + +def update_rust_packages( + pkgatom: str, rust_version: RustVersion, add: bool +) -> None: + package_file = EBUILD_PREFIX.joinpath( + "profiles/targets/chromeos/package.provided" + ) + with open(package_file, encoding="utf-8") as f: + contents = f.read() + if add: + rust_packages_re = re.compile( + "^" + re.escape(pkgatom) + r"-\d+\.\d+\.\d+$", re.MULTILINE + ) + rust_packages = rust_packages_re.findall(contents) + # Assume all the rust packages are in alphabetical order, so insert + # the new version to the place after the last rust_packages + new_str = f"{pkgatom}-{rust_version}" + new_contents = contents.replace( + rust_packages[-1], f"{rust_packages[-1]}\n{new_str}" + ) + logging.info("%s has been inserted into package.provided", new_str) + else: + old_str = f"{pkgatom}-{rust_version}\n" + assert old_str in contents, f"{old_str!r} not found in package.provided" + new_contents = contents.replace(old_str, "") + logging.info("%s has been removed from package.provided", old_str) + + with open(package_file, "w", encoding="utf-8") as f: + f.write(new_contents) + + +def update_virtual_rust( + template_version: RustVersion, new_version: RustVersion +) -> None: + template_ebuild = find_ebuild_path( + EBUILD_PREFIX.joinpath("virtual/rust"), "rust", template_version + ) + virtual_rust_dir = template_ebuild.parent + new_name = f"rust-{new_version}.ebuild" + new_ebuild = virtual_rust_dir.joinpath(new_name) + shutil.copyfile(template_ebuild, new_ebuild) + subprocess.check_call(["git", "add", new_name], cwd=virtual_rust_dir) + + +def unmerge_package_if_installed(pkgatom: str) -> None: + """Unmerges a package if it is installed.""" + shpkg = shlex.quote(pkgatom) + subprocess.check_call( + [ + "sudo", + "bash", + "-c", + f"! emerge --pretend --quiet --unmerge {shpkg}" + f" || emerge --rage-clean {shpkg}", + ] + ) + + +def perform_step( + state_file: pathlib.Path, + tmp_state_file: pathlib.Path, + completed_steps: Dict[str, Any], + step_name: str, + step_fn: Callable[[], T], + result_from_json: Optional[Callable[[Any], T]] = None, + result_to_json: Optional[Callable[[T], Any]] = None, +) -> T: + if step_name in completed_steps: + logging.info("Skipping previously completed step %s", step_name) + if result_from_json: + return result_from_json(completed_steps[step_name]) + return completed_steps[step_name] + + logging.info("Running step %s", step_name) + val = step_fn() + logging.info("Step %s complete", step_name) + if result_to_json: + completed_steps[step_name] = result_to_json(val) + else: + completed_steps[step_name] = val + + with tmp_state_file.open("w", encoding="utf-8") as f: + json.dump(completed_steps, f, indent=4) + tmp_state_file.rename(state_file) + return val def prepare_uprev_from_json( - obj: Any) -> Optional[Tuple[RustVersion, str, RustVersion]]: - if not obj: - return None - version, ebuild_path, bootstrap_version = obj - return RustVersion(*version), ebuild_path, RustVersion(*bootstrap_version) - - -def create_rust_uprev(rust_version: RustVersion, - maybe_template_version: Optional[RustVersion], - skip_compile: bool, run_step: Callable[[], T]) -> None: - template_version, template_ebuild, old_bootstrap_version = run_step( - 'prepare uprev', - lambda: prepare_uprev(rust_version, maybe_template_version), - result_from_json=prepare_uprev_from_json, - ) - if template_ebuild is None: - return - - # The fetch steps will fail (on purpose) if the files they check for - # are not available on the mirror. To make them pass, fetch the - # required files yourself, verify their checksums, then upload them - # to the mirror. - run_step( - 'fetch bootstrap distfiles', lambda: fetch_bootstrap_distfiles( - old_bootstrap_version, template_version)) - run_step('fetch rust distfiles', lambda: fetch_rust_distfiles(rust_version)) - run_step('update bootstrap ebuild', lambda: update_bootstrap_ebuild( - template_version)) - run_step( - 'update bootstrap manifest', lambda: update_manifest(rust_bootstrap_path( - ).joinpath(f'rust-bootstrap-{template_version}.ebuild'))) - run_step('copy patches', lambda: copy_patches(RUST_PATH, template_version, - rust_version)) - ebuild_file = run_step( - 'create ebuild', lambda: create_ebuild(template_ebuild, rust_version)) - run_step( - 'update ebuild', lambda: update_ebuild(ebuild_file, template_version)) - run_step('update manifest to add new version', lambda: update_manifest( - Path(ebuild_file))) - if not skip_compile: + obj: Any, +) -> Optional[Tuple[RustVersion, str, RustVersion]]: + if not obj: + return None + version, ebuild_path, bootstrap_version = obj + return RustVersion(*version), ebuild_path, RustVersion(*bootstrap_version) + + +def create_rust_uprev( + rust_version: RustVersion, + maybe_template_version: Optional[RustVersion], + skip_compile: bool, + run_step: Callable[[], T], +) -> None: + template_version, template_ebuild, old_bootstrap_version = run_step( + "prepare uprev", + lambda: prepare_uprev(rust_version, maybe_template_version), + result_from_json=prepare_uprev_from_json, + ) + if template_ebuild is None: + return + + # The fetch steps will fail (on purpose) if the files they check for + # are not available on the mirror. To make them pass, fetch the + # required files yourself, verify their checksums, then upload them + # to the mirror. run_step( - 'emerge rust', lambda: subprocess.check_call( - ['sudo', 'emerge', 'dev-lang/rust'])) - run_step('insert version into rust packages', lambda: update_rust_packages( - rust_version, add=True)) - run_step('upgrade virtual/rust', lambda: update_virtual_rust( - template_version, rust_version)) + "fetch bootstrap distfiles", + lambda: fetch_bootstrap_distfiles( + old_bootstrap_version, template_version + ), + ) + run_step("fetch rust distfiles", lambda: fetch_rust_distfiles(rust_version)) + run_step( + "update bootstrap ebuild", + lambda: update_bootstrap_ebuild(template_version), + ) + run_step( + "update bootstrap manifest", + lambda: update_manifest( + rust_bootstrap_path().joinpath( + f"rust-bootstrap-{template_version}.ebuild" + ) + ), + ) + run_step( + "update bootstrap version", + lambda: update_bootstrap_version( + EBUILD_PREFIX.joinpath("eclass/cros-rustc.eclass"), template_version + ), + ) + run_step( + "copy patches", + lambda: copy_patches(RUST_PATH, template_version, rust_version), + ) + template_host_ebuild = EBUILD_PREFIX.joinpath( + f"dev-lang/rust-host/rust-host-{template_version}.ebuild" + ) + host_file = run_step( + "create host ebuild", + lambda: create_ebuild( + template_host_ebuild, "dev-lang/rust-host", rust_version + ), + ) + run_step( + "update host manifest to add new version", + lambda: update_manifest(Path(host_file)), + ) + target_file = run_step( + "create target ebuild", + lambda: create_ebuild(template_ebuild, "dev-lang/rust", rust_version), + ) + run_step( + "update target manifest to add new version", + lambda: update_manifest(Path(target_file)), + ) + if not skip_compile: + run_step("build packages", lambda: rebuild_packages(rust_version)) + run_step( + "insert host version into rust packages", + lambda: update_rust_packages( + "dev-lang/rust-host", rust_version, add=True + ), + ) + run_step( + "insert target version into rust packages", + lambda: update_rust_packages("dev-lang/rust", rust_version, add=True), + ) + run_step( + "upgrade virtual/rust", + lambda: update_virtual_rust(template_version, rust_version), + ) def find_rust_versions_in_chroot() -> List[Tuple[RustVersion, str]]: - return [(RustVersion.parse_from_ebuild(x), os.path.join(RUST_PATH, x)) - for x in os.listdir(RUST_PATH) if x.endswith('.ebuild')] + return [ + (RustVersion.parse_from_ebuild(x), os.path.join(RUST_PATH, x)) + for x in os.listdir(RUST_PATH) + if x.endswith(".ebuild") + ] -def find_oldest_rust_version_in_chroot() -> Tuple[RustVersion, str]: - rust_versions = find_rust_versions_in_chroot() - if len(rust_versions) <= 1: - raise RuntimeError('Expect to find more than one Rust versions') - return min(rust_versions) +def find_oldest_rust_version_in_chroot() -> RustVersion: + rust_versions = find_rust_versions_in_chroot() + if len(rust_versions) <= 1: + raise RuntimeError("Expect to find more than one Rust versions") + return min(rust_versions)[0] def find_ebuild_for_rust_version(version: RustVersion) -> str: - rust_ebuilds = [ - ebuild for x, ebuild in find_rust_versions_in_chroot() if x == version - ] - if not rust_ebuilds: - raise ValueError(f'No Rust ebuilds found matching {version}') - if len(rust_ebuilds) > 1: - raise ValueError(f'Multiple Rust ebuilds found matching {version}: ' - f'{rust_ebuilds}') - return rust_ebuilds[0] + rust_ebuilds = [ + ebuild for x, ebuild in find_rust_versions_in_chroot() if x == version + ] + if not rust_ebuilds: + raise ValueError(f"No Rust ebuilds found matching {version}") + if len(rust_ebuilds) > 1: + raise ValueError( + f"Multiple Rust ebuilds found matching {version}: " + f"{rust_ebuilds}" + ) + return rust_ebuilds[0] + + +def rebuild_packages(version: RustVersion): + """Rebuild packages modified by this script.""" + # Remove all packages we modify to avoid depending on preinstalled + # versions. This ensures that the packages can really be built. + packages = [ + "dev-lang/rust", + "dev-lang/rust-host", + "dev-lang/rust-bootstrap", + ] + for pkg in packages: + unmerge_package_if_installed(pkg) + # Mention only dev-lang/rust explicitly, so that others are pulled + # in as dependencies (letting us detect dependency errors). + # Packages we modify are listed in --usepkg-exclude to ensure they + # are built from source. + try: + subprocess.check_call( + [ + "sudo", + "emerge", + "--quiet-build", + "--usepkg-exclude", + " ".join(packages), + f"=dev-lang/rust-{version}", + ] + ) + except: + logging.warning( + "Failed to build dev-lang/rust or one of its dependencies." + " If necessary, you can restore rust and rust-host from" + " binary packages:\n sudo emerge --getbinpkgonly dev-lang/rust" + ) + raise + + +def remove_ebuild_version(path: os.PathLike, name: str, version: RustVersion): + """Remove the specified version of an ebuild. + + Removes {path}/{name}-{version}.ebuild and {path}/{name}-{version}-*.ebuild + using git rm. + + Args: + path: The directory in which the ebuild files are. + name: The name of the package (e.g. 'rust'). + version: The version of the ebuild to remove. + """ + path = Path(path) + pattern = f"{name}-{version}-*.ebuild" + matches = list(path.glob(pattern)) + ebuild = path / f"{name}-{version}.ebuild" + if ebuild.exists(): + matches.append(ebuild) + if not matches: + logging.warning( + "No ebuilds matching %s version %s in %r", name, version, str(path) + ) + for m in matches: + remove_files(m.name, path) def remove_files(filename: str, path: str) -> None: - subprocess.check_call(['git', 'rm', filename], cwd=path) - - -def remove_rust_bootstrap_version(version: RustVersion, - run_step: Callable[[], T]) -> None: - prefix = f'rust-bootstrap-{version}' - run_step('remove old bootstrap ebuild', lambda: remove_files( - f'{prefix}*.ebuild', rust_bootstrap_path())) - ebuild_file = find_ebuild_for_package('rust-bootstrap') - run_step('update bootstrap manifest to delete old version', lambda: - update_manifest(ebuild_file)) - - -def remove_rust_uprev(rust_version: Optional[RustVersion], - run_step: Callable[[], T]) -> None: - def find_desired_rust_version(): - if rust_version: - return rust_version, find_ebuild_for_rust_version(rust_version) - return find_oldest_rust_version_in_chroot() - - def find_desired_rust_version_from_json(obj: Any) -> Tuple[RustVersion, str]: - version, ebuild_path = obj - return RustVersion(*version), ebuild_path - - delete_version, delete_ebuild = run_step( - 'find rust version to delete', - find_desired_rust_version, - result_from_json=find_desired_rust_version_from_json, - ) - run_step( - 'remove patches', lambda: remove_files( - f'files/rust-{delete_version}-*.patch', RUST_PATH)) - run_step('remove ebuild', lambda: remove_files(delete_ebuild, RUST_PATH)) - ebuild_file = find_ebuild_for_package('rust') - run_step('update manifest to delete old version', lambda: update_manifest( - ebuild_file)) - run_step('remove version from rust packages', lambda: update_rust_packages( - delete_version, add=False)) - run_step('remove virtual/rust', lambda: remove_virtual_rust(delete_version)) + subprocess.check_call(["git", "rm", filename], cwd=path) + + +def remove_rust_bootstrap_version( + version: RustVersion, run_step: Callable[[], T] +) -> None: + run_step( + "remove old bootstrap ebuild", + lambda: remove_ebuild_version( + rust_bootstrap_path(), "rust-bootstrap", version + ), + ) + ebuild_file = find_ebuild_for_package("rust-bootstrap") + run_step( + "update bootstrap manifest to delete old version", + lambda: update_manifest(ebuild_file), + ) + + +def remove_rust_uprev( + rust_version: Optional[RustVersion], run_step: Callable[[], T] +) -> None: + def find_desired_rust_version() -> RustVersion: + if rust_version: + return rust_version + return find_oldest_rust_version_in_chroot() + + def find_desired_rust_version_from_json(obj: Any) -> RustVersion: + return RustVersion(*obj) + + delete_version = run_step( + "find rust version to delete", + find_desired_rust_version, + result_from_json=find_desired_rust_version_from_json, + ) + run_step( + "remove patches", + lambda: remove_files(f"files/rust-{delete_version}-*.patch", RUST_PATH), + ) + run_step( + "remove target ebuild", + lambda: remove_ebuild_version(RUST_PATH, "rust", delete_version), + ) + run_step( + "remove host ebuild", + lambda: remove_ebuild_version( + EBUILD_PREFIX.joinpath("dev-lang/rust-host"), + "rust-host", + delete_version, + ), + ) + target_file = find_ebuild_for_package("rust") + run_step( + "update target manifest to delete old version", + lambda: update_manifest(target_file), + ) + run_step( + "remove target version from rust packages", + lambda: update_rust_packages( + "dev-lang/rust", delete_version, add=False + ), + ) + host_file = find_ebuild_for_package("rust-host") + run_step( + "update host manifest to delete old version", + lambda: update_manifest(host_file), + ) + run_step( + "remove host version from rust packages", + lambda: update_rust_packages( + "dev-lang/rust-host", delete_version, add=False + ), + ) + run_step("remove virtual/rust", lambda: remove_virtual_rust(delete_version)) def remove_virtual_rust(delete_version: RustVersion) -> None: - ebuild = find_ebuild_path(RUST_PATH.joinpath('../../virtual/rust'), 'rust', - delete_version) - subprocess.check_call(['git', 'rm', str(ebuild.name)], cwd=ebuild.parent) + remove_ebuild_version( + EBUILD_PREFIX.joinpath("virtual/rust"), "rust", delete_version + ) def rust_bootstrap_path() -> Path: - return RUST_PATH.joinpath('../rust-bootstrap') + return EBUILD_PREFIX.joinpath("dev-lang/rust-bootstrap") def create_new_repo(rust_version: RustVersion) -> None: - output = get_command_output(['git', 'status', '--porcelain'], cwd=RUST_PATH) - if output: - raise RuntimeError( - f'{RUST_PATH} has uncommitted changes, please either discard them ' - 'or commit them.') - git.CreateBranch(RUST_PATH, f'rust-to-{rust_version}') + output = get_command_output( + ["git", "status", "--porcelain"], cwd=EBUILD_PREFIX + ) + if output: + raise RuntimeError( + f"{EBUILD_PREFIX} has uncommitted changes, please either discard " + "them or commit them." + ) + git.CreateBranch(EBUILD_PREFIX, f"rust-to-{rust_version}") def build_cross_compiler() -> None: - # Get target triples in ebuild - rust_ebuild = find_ebuild_for_package('rust') - with open(rust_ebuild, encoding='utf-8') as f: - contents = f.read() - - target_triples_re = re.compile(r'RUSTC_TARGET_TRIPLES=\(([^)]+)\)') - m = target_triples_re.search(contents) - assert m, 'RUST_TARGET_TRIPLES not found in rust ebuild' - target_triples = m.group(1).strip().split('\n') - - compiler_targets_to_install = [ - target.strip() for target in target_triples if 'cros-' in target - ] - for target in target_triples: - if 'cros-' not in target: - continue - target = target.strip() - - # We also always need arm-none-eabi, though it's not mentioned in - # RUSTC_TARGET_TRIPLES. - compiler_targets_to_install.append('arm-none-eabi') - - logging.info('Emerging cross compilers %s', compiler_targets_to_install) - subprocess.check_call( - ['sudo', 'emerge', '-j', '-G'] + - [f'cross-{target}/gcc' for target in compiler_targets_to_install]) + # Get target triples in ebuild + rust_ebuild = find_ebuild_for_package("rust") + with open(rust_ebuild, encoding="utf-8") as f: + contents = f.read() + + target_triples_re = re.compile(r"RUSTC_TARGET_TRIPLES=\(([^)]+)\)") + m = target_triples_re.search(contents) + assert m, "RUST_TARGET_TRIPLES not found in rust ebuild" + target_triples = m.group(1).strip().split("\n") + + compiler_targets_to_install = [ + target.strip() for target in target_triples if "cros-" in target + ] + for target in target_triples: + if "cros-" not in target: + continue + target = target.strip() + + # We also always need arm-none-eabi, though it's not mentioned in + # RUSTC_TARGET_TRIPLES. + compiler_targets_to_install.append("arm-none-eabi") + + logging.info("Emerging cross compilers %s", compiler_targets_to_install) + subprocess.check_call( + ["sudo", "emerge", "-j", "-G"] + + [f"cross-{target}/gcc" for target in compiler_targets_to_install] + ) def create_new_commit(rust_version: RustVersion) -> None: - subprocess.check_call(['git', 'add', '-A'], cwd=RUST_PATH) - messages = [ - f'[DO NOT SUBMIT] dev-lang/rust: upgrade to Rust {rust_version}', - '', - 'This CL is created by rust_uprev tool automatically.' - '', - 'BUG=None', - 'TEST=Use CQ to test the new Rust version', - ] - git.UploadChanges(RUST_PATH, f'rust-to-{rust_version}', messages) + subprocess.check_call(["git", "add", "-A"], cwd=EBUILD_PREFIX) + messages = [ + f"[DO NOT SUBMIT] dev-lang/rust: upgrade to Rust {rust_version}", + "", + "This CL is created by rust_uprev tool automatically." "", + "BUG=None", + "TEST=Use CQ to test the new Rust version", + ] + git.UploadChanges(EBUILD_PREFIX, f"rust-to-{rust_version}", messages) def main() -> None: - if not chroot.InChroot(): - raise RuntimeError('This script must be executed inside chroot') - - logging.basicConfig(level=logging.INFO) - - args = parse_commandline_args() - - state_file = pathlib.Path(args.state_file) - tmp_state_file = state_file.with_suffix('.tmp') - - try: - with state_file.open(encoding='utf-8') as f: - completed_steps = json.load(f) - except FileNotFoundError: - completed_steps = {} - - def run_step( - step_name: str, - step_fn: Callable[[], T], - result_from_json: Optional[Callable[[Any], T]] = None, - result_to_json: Optional[Callable[[T], Any]] = None, - ) -> T: - return perform_step(state_file, tmp_state_file, completed_steps, step_name, - step_fn, result_from_json, result_to_json) - - if args.subparser_name == 'create': - create_rust_uprev(args.rust_version, args.template, args.skip_compile, - run_step) - elif args.subparser_name == 'remove': - remove_rust_uprev(args.rust_version, run_step) - elif args.subparser_name == 'remove-bootstrap': - remove_rust_bootstrap_version(args.version, run_step) - else: - # If you have added more subparser_name, please also add the handlers above - assert args.subparser_name == 'roll' - run_step('create new repo', lambda: create_new_repo(args.uprev)) - if not args.skip_cross_compiler: - run_step('build cross compiler', build_cross_compiler) - create_rust_uprev(args.uprev, args.template, args.skip_compile, run_step) - remove_rust_uprev(args.remove, run_step) - bootstrap_version = prepare_uprev_from_json( - completed_steps['prepare uprev'])[2] - remove_rust_bootstrap_version(bootstrap_version, run_step) - if not args.no_upload: - run_step('create rust uprev CL', lambda: create_new_commit(args.uprev)) - - -if __name__ == '__main__': - sys.exit(main()) + if not chroot.InChroot(): + raise RuntimeError("This script must be executed inside chroot") + + logging.basicConfig(level=logging.INFO) + + args = parse_commandline_args() + + state_file = pathlib.Path(args.state_file) + tmp_state_file = state_file.with_suffix(".tmp") + + try: + with state_file.open(encoding="utf-8") as f: + completed_steps = json.load(f) + except FileNotFoundError: + completed_steps = {} + + def run_step( + step_name: str, + step_fn: Callable[[], T], + result_from_json: Optional[Callable[[Any], T]] = None, + result_to_json: Optional[Callable[[T], Any]] = None, + ) -> T: + return perform_step( + state_file, + tmp_state_file, + completed_steps, + step_name, + step_fn, + result_from_json, + result_to_json, + ) + + if args.subparser_name == "create": + create_rust_uprev( + args.rust_version, args.template, args.skip_compile, run_step + ) + elif args.subparser_name == "remove": + remove_rust_uprev(args.rust_version, run_step) + elif args.subparser_name == "remove-bootstrap": + remove_rust_bootstrap_version(args.version, run_step) + else: + # If you have added more subparser_name, please also add the handlers above + assert args.subparser_name == "roll" + run_step("create new repo", lambda: create_new_repo(args.uprev)) + if not args.skip_cross_compiler: + run_step("build cross compiler", build_cross_compiler) + create_rust_uprev( + args.uprev, args.template, args.skip_compile, run_step + ) + remove_rust_uprev(args.remove, run_step) + bootstrap_version = prepare_uprev_from_json( + completed_steps["prepare uprev"] + )[2] + remove_rust_bootstrap_version(bootstrap_version, run_step) + if not args.no_upload: + run_step( + "create rust uprev CL", lambda: create_new_commit(args.uprev) + ) + + +if __name__ == "__main__": + sys.exit(main()) |