diff options
author | Bob Haarman <inglorion@chromium.org> | 2020-12-15 19:28:45 +0000 |
---|---|---|
committer | Commit Bot <commit-bot@chromium.org> | 2021-01-06 23:40:08 +0000 |
commit | 9f99f6cc358c315eb5cffbf4614852bcd7ff7ced (patch) | |
tree | aafe904ad9707dded07f59315de5ebbd72690f0e | |
parent | d82842027768f07690a2ace2b6b387e929a61bfd (diff) | |
download | toolchain-utils-9f99f6cc358c315eb5cffbf4614852bcd7ff7ced.tar.gz |
rust_uprev: also uprev rust-bootstrap
Since crrev.com/c/2436432, dev-lang/rust requires
dev-lang/rust-bootstrap to build. Because of this,
rust uprevs now require that rust-bootstrap be uprevved,
too. This CL changes the rust_uprev script to do so.
BUG=chromium:1159066
TEST=python3 ./rust_tools/rust_uprev_test.py
Change-Id: I48482e780d9ebbc012142687f2edfcc8f83e9d56
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/third_party/toolchain-utils/+/2597493
Commit-Queue: Bob Haarman <inglorion@chromium.org>
Tested-by: Bob Haarman <inglorion@chromium.org>
Reviewed-by: George Burgess <gbiv@chromium.org>
-rwxr-xr-x | rust_tools/rust_uprev.py | 320 | ||||
-rwxr-xr-x | rust_tools/rust_uprev_test.py | 321 |
2 files changed, 283 insertions, 358 deletions
diff --git a/rust_tools/rust_uprev.py b/rust_tools/rust_uprev.py index b4020ea1..6d112420 100755 --- a/rust_tools/rust_uprev.py +++ b/rust_tools/rust_uprev.py @@ -36,7 +36,6 @@ See `--help` for all available options. # pylint: disable=cros-logging-import import argparse -import glob import pathlib import json import logging @@ -45,11 +44,12 @@ import re import shutil import subprocess import sys -import tempfile +from pathlib import Path from typing import Any, Callable, Dict, List, NamedTuple, Optional, T, Tuple from llvm_tools import chroot, git -RUST_PATH = '/mnt/host/source/src/third_party/chromiumos-overlay/dev-lang/rust' +RUST_PATH = Path( + '/mnt/host/source/src/third_party/chromiumos-overlay/dev-lang/rust') def get_command_output(command: List[str], *args, **kwargs) -> str: @@ -92,20 +92,33 @@ class RustVersion(NamedTuple): int(m.group('major')), int(m.group('minor')), int(m.group('patch'))) -def find_virtual_rust_ebuild(version: RustVersion) -> str: - """Finds the virtual/rust ebuild for a given RustVersion. +def find_ebuild_path(directory: Path, + name: str, + version: Optional[RustVersion] = None) -> Path: + """Finds an ebuild in a directory. - This finds 1.2.3 and also 1.2.3-r4. It expects that there will - be exactly one match, and will assert if that is not the case. + 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. """ - virtual_rust_dir = os.path.join(RUST_PATH, '../../virtual/rust') - pattern = os.path.join(virtual_rust_dir, f'rust-{version}*.ebuild') - matches = glob.glob(pattern) - # We expect exactly one match. + if version: + pattern = '%s-%s*.ebuild' % (name, version) + else: + pattern = '%s-*.ebuild' % (name,) + matches = list(Path(directory).glob(pattern)) assert len(matches) == 1, matches return matches[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))) + + def parse_commandline_args() -> argparse.Namespace: parser = argparse.ArgumentParser( description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) @@ -173,6 +186,18 @@ def parse_commandline_args() -> argparse.Namespace: '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', @@ -225,25 +250,8 @@ def parse_commandline_args() -> argparse.Namespace: return args -def parse_stage0_file(new_version: RustVersion) -> Tuple[str, str, str]: - # Find stage0 date, rustc and cargo - stage0_file = get_command_output([ - 'curl', '-f', 'https://raw.githubusercontent.com/rust-lang/rust/' - f'{new_version}/src/stage0.txt' - ]) - regexp = re.compile(r'date:\s*(?P<date>\d+-\d+-\d+)\s+' - r'rustc:\s*(?P<rustc>\d+\.\d+\.\d+)\s+' - r'cargo:\s*(?P<cargo>\d+\.\d+\.\d+)') - m = regexp.search(stage0_file) - assert m, 'failed to parse stage0.txt file' - stage0_date, stage0_rustc, stage0_cargo = m.groups() - logging.info('Found stage0 file has date: %s, rustc: %s, cargo: %s', - stage0_date, stage0_rustc, stage0_cargo) - return stage0_date, stage0_rustc, stage0_cargo - - def prepare_uprev(rust_version: RustVersion, template: Optional[RustVersion] - ) -> Optional[Tuple[RustVersion, str]]: + ) -> Optional[Tuple[RustVersion, str, RustVersion]]: if template is None: ebuild_path = get_command_output(['equery', 'w', 'rust']) ebuild_name = os.path.basename(ebuild_path) @@ -252,6 +260,8 @@ def prepare_uprev(rust_version: RustVersion, template: Optional[RustVersion] 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.', @@ -260,66 +270,69 @@ def prepare_uprev(rust_version: RustVersion, template: Optional[RustVersion] logging.info('Template Rust version is %s (ebuild: %r)', template_version, ebuild_path) - return template_version, ebuild_path + logging.info('rust-bootstrap version is %s', bootstrap_version) + return template_version, ebuild_path, bootstrap_version -def copy_patches(template_version: RustVersion, + +def copy_patches(directory: Path, template_version: RustVersion, new_version: RustVersion) -> None: - patch_path = os.path.join(RUST_PATH, 'files') + 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 f'rust-{template_version}' not in f: + if not f.startswith(prefix): continue - logging.info('Rename patch %s to new version', f) + 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'files/rust-{new_version}-*.patch'], - cwd=RUST_PATH) + 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, - os.path.join(RUST_PATH, f'rust-{new_version}.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 update_ebuild(ebuild_file: str, stage0_info: Tuple[str, str, str]) -> None: - stage0_date, stage0_rustc, stage0_cargo = stage0_info - with open(ebuild_file, encoding='utf-8') as f: - contents = f.read() - # Update STAGE0_DATE in the ebuild - stage0_date_re = re.compile(r'STAGE0_DATE="(\d+-\d+-\d+)"') - if not stage0_date_re.search(contents): - raise RuntimeError('STAGE0_DATE not found in rust ebuild') - new_contents = stage0_date_re.sub(f'STAGE0_DATE="{stage0_date}"', contents) - - # Update STAGE0_VERSION in the ebuild - stage0_rustc_re = re.compile(r'STAGE0_VERSION="[^"]*"') - if not stage0_rustc_re.search(new_contents): - raise RuntimeError('STAGE0_VERSION not found in rust ebuild') - new_contents = stage0_rustc_re.sub(f'STAGE0_VERSION="{stage0_rustc}"', - new_contents) - - # Update STAGE0_VERSION_CARGO in the ebuild - stage0_cargo_re = re.compile(r'STAGE0_VERSION_CARGO="[^"]*"') - if not stage0_cargo_re.search(new_contents): - raise RuntimeError('STAGE0_VERSION_CARGO not found in rust ebuild') - new_contents = stage0_cargo_re.sub(f'STAGE0_VERSION_CARGO="{stage0_cargo}"', - new_contents) - with open(ebuild_file, 'w', encoding='utf-8') as f: - f.write(new_contents) - logging.info( - 'Rust ebuild file has STAGE0_DATE, STAGE0_VERSION, STAGE0_VERSION_CARGO ' - 'updated to %s, %s, %s respectively', stage0_date, stage0_rustc, - stage0_cargo) - - -def flip_mirror_in_ebuild(ebuild_file: str, add: bool) -> None: +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_FULL_BOOTSTRAP_SEQUENCE=\([^)]*)', + f'\\1\t{old_version}\n', + old_text, + flags=re.MULTILINE) + assert changes == 1, 'Failed to update RUSTC_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: @@ -340,25 +353,27 @@ def flip_mirror_in_ebuild(ebuild_file: str, add: bool) -> None: f.write(new_contents) -def rust_ebuild_actions(actions: List[str], sudo: bool = False) -> None: - ebuild_path_inchroot = get_command_output(['equery', 'w', 'rust']) +def ebuild_actions(package: str, actions: List[str], + sudo: bool = False) -> None: + ebuild_path_inchroot = get_command_output(['equery', 'w', package]) cmd = ['ebuild', ebuild_path_inchroot] + actions if sudo: cmd = ['sudo'] + cmd subprocess.check_call(cmd) -def update_manifest(ebuild_file: str) -> None: - logging.info('Added "mirror" to RESTRICT to Rust ebuild') - flip_mirror_in_ebuild(ebuild_file, add=True) - rust_ebuild_actions(['manifest']) - logging.info('Removed "mirror" to RESTRICT from Rust ebuild') - flip_mirror_in_ebuild(ebuild_file, add=False) +def update_manifest(ebuild_file: os.PathLike) -> None: + 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 = os.path.join( - RUST_PATH, '../../profiles/targets/chromeos/package.provided') + package_file = RUST_PATH.joinpath( + '../../profiles/targets/chromeos/package.provided') with open(package_file, encoding='utf-8') as f: contents = f.read() if add: @@ -382,91 +397,15 @@ def update_rust_packages(rust_version: RustVersion, add: bool) -> None: def update_virtual_rust(template_version: RustVersion, new_version: RustVersion) -> None: - template_ebuild = find_virtual_rust_ebuild(template_version) - virtual_rust_dir = os.path.dirname(template_ebuild) + 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 = os.path.join(virtual_rust_dir, new_name) + 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 upload_single_tarball(rust_url: str, tarfile_name: str, - tempdir: str) -> None: - rust_src = f'{rust_url}/{tarfile_name}' - gsutil_location = f'gs://chromeos-localmirror/distfiles/{tarfile_name}' - - missing_file = subprocess.call( - ['gsutil', 'ls', gsutil_location], - stdout=subprocess.DEVNULL, - stderr=subprocess.DEVNULL, - ) - if not missing_file: - logging.info('Rust artifact at %s already exists; skipping download', - gsutil_location) - return - - logging.info('Downloading Rust artifact from %s', rust_src) - - # Download Rust's source - rust_file = os.path.join(tempdir, tarfile_name) - subprocess.check_call(['curl', '-f', '-o', rust_file, rust_src]) - - # Verify the signature of the source - sig_file = os.path.join(tempdir, 'rustc_sig.asc') - subprocess.check_call(['curl', '-f', '-o', sig_file, f'{rust_src}.asc']) - try: - subprocess.check_output(['gpg', '--verify', sig_file, rust_file], - encoding='utf-8', - stderr=subprocess.STDOUT) - except subprocess.CalledProcessError as e: - if "gpg: Can't check signature" not in e.output: - raise RuntimeError(f'Failed to execute `gpg --verify`, {e.output}') - - # If it fails to verify the signature, try import rustc key, and retry. - keys = get_command_output( - ['curl', '-f', 'https://keybase.io/rust/pgp_keys.asc']) - subprocess.run(['gpg', '--import'], - input=keys, - encoding='utf-8', - check=True) - subprocess.check_call(['gpg', '--verify', sig_file, rust_file]) - - # Since we are using `-n` to skip an item if it already exists, there's no - # need to check if the file exists on GS bucket or not. - subprocess.check_call( - ['gsutil', 'cp', '-n', '-a', 'public-read', rust_file, gsutil_location]) - - -def upload_to_localmirror(tempdir: str, rust_version: RustVersion, - stage0_info: Tuple[str, str, str]) -> None: - stage0_date, stage0_rustc, stage0_cargo = stage0_info - rust_url = 'https://static.rust-lang.org/dist' - # Upload rustc source - upload_single_tarball( - rust_url, - f'rustc-{rust_version}-src.tar.gz', - tempdir, - ) - # Upload stage0 toolchain - upload_single_tarball( - f'{rust_url}/{stage0_date}', - f'rust-std-{stage0_rustc}-x86_64-unknown-linux-gnu.tar.gz', - tempdir, - ) - # Upload stage0 source - upload_single_tarball( - rust_url, - f'rustc-{stage0_rustc}-x86_64-unknown-linux-gnu.tar.gz', - tempdir, - ) - # Upload stage0 cargo - upload_single_tarball( - rust_url, - f'cargo-{stage0_cargo}-x86_64-unknown-linux-gnu.tar.gz', - tempdir, - ) - - def perform_step(state_file: pathlib.Path, tmp_state_file: pathlib.Path, completed_steps: Dict[str, Any], @@ -494,19 +433,18 @@ def perform_step(state_file: pathlib.Path, return val -def prepare_uprev_from_json(obj: Any) -> Optional[Tuple[RustVersion, str]]: +def prepare_uprev_from_json(obj: Any + ) -> Optional[Tuple[RustVersion, str, RustVersion]]: if not obj: return None - version, ebuild_path = obj - return RustVersion(*version), ebuild_path + 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: - stage0_info = run_step( - 'parse stage0 file', lambda: parse_stage0_file(rust_version)) - template_version, template_ebuild = run_step( + 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, @@ -514,15 +452,22 @@ def create_rust_uprev(rust_version: RustVersion, if template_ebuild is None: return - run_step('copy patches', lambda: copy_patches(template_version, rust_version)) + run_step( + 'copy bootstrap patches', lambda: copy_patches(rust_bootstrap_path( + ), old_bootstrap_version, template_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, stage0_info)) - with tempfile.TemporaryDirectory(dir='/tmp') as tempdir: - run_step('upload_to_localmirror', lambda: upload_to_localmirror( - tempdir, rust_version, stage0_info)) + run_step( + 'update ebuild', lambda: update_ebuild(ebuild_file, template_version)) run_step('update manifest to add new version', lambda: update_manifest( - ebuild_file)) + Path(ebuild_file))) if not skip_compile: run_step('emerge rust', lambda: subprocess.check_call( ['sudo', 'emerge', 'dev-lang/rust'])) @@ -561,6 +506,19 @@ 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 patches', lambda: remove_files( + f'files/{prefix}-*.patch', rust_bootstrap_path())) + run_step('remove old bootstrap ebuild', lambda: remove_files( + f'{prefix}*.ebuild', rust_bootstrap_path())) + ebuild_file = get_command_output(['equery', 'w', '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: @@ -569,10 +527,14 @@ def remove_rust_uprev(rust_version: Optional[RustVersion], 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=prepare_uprev_from_json, + result_from_json=find_desired_rust_version_from_json, ) run_step( 'remove patches', lambda: remove_files( @@ -587,8 +549,13 @@ def remove_rust_uprev(rust_version: Optional[RustVersion], def remove_virtual_rust(delete_version: RustVersion) -> None: - dirname, basename = os.path.split(find_virtual_rust_ebuild(delete_version)) - subprocess.check_call(['git', 'rm', basename], cwd=dirname) + ebuild = find_ebuild_path( + RUST_PATH.joinpath('../../virtual/rust'), 'rust', delete_version) + subprocess.check_call(['git', 'rm', str(ebuild.name)], cwd=ebuild.parent) + + +def rust_bootstrap_path() -> Path: + return RUST_PATH.joinpath('../rust-bootstrap') def create_new_repo(rust_version: RustVersion) -> None: @@ -673,6 +640,8 @@ def main() -> None: 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' @@ -681,6 +650,9 @@ def main() -> None: 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)) diff --git a/rust_tools/rust_uprev_test.py b/rust_tools/rust_uprev_test.py index acf700b8..b35f1db7 100755 --- a/rust_tools/rust_uprev_test.py +++ b/rust_tools/rust_uprev_test.py @@ -7,11 +7,12 @@ """Tests for rust_uprev.py""" # pylint: disable=cros-logging-import -import glob import os import shutil import subprocess +import tempfile import unittest +from pathlib import Path from unittest import mock from llvm_tools import git @@ -19,6 +20,35 @@ from llvm_tools import git import rust_uprev +class FindEbuildPathTest(unittest.TestCase): + """Tests for rust_uprev.find_ebuild_path()""" + + def test_exact_version(self): + with tempfile.TemporaryDirectory() as tmpdir: + ebuild = Path(tmpdir, 'test-1.3.4.ebuild') + ebuild.touch() + Path(tmpdir, 'test-1.2.3.ebuild').touch() + result = rust_uprev.find_ebuild_path(tmpdir, 'test', + rust_uprev.RustVersion(1, 3, 4)) + self.assertEqual(result, ebuild) + + def test_no_version(self): + with tempfile.TemporaryDirectory() as tmpdir: + ebuild = Path(tmpdir, 'test-1.2.3.ebuild') + ebuild.touch() + result = rust_uprev.find_ebuild_path(tmpdir, 'test') + self.assertEqual(result, ebuild) + + def test_patch_version(self): + with tempfile.TemporaryDirectory() as tmpdir: + ebuild = Path(tmpdir, 'test-1.3.4-r3.ebuild') + ebuild.touch() + Path(tmpdir, 'test-1.2.3.ebuild').touch() + result = rust_uprev.find_ebuild_path(tmpdir, 'test', + rust_uprev.RustVersion(1, 3, 4)) + self.assertEqual(result, ebuild) + + class RustVersionTest(unittest.TestCase): """Tests for RustVersion class""" @@ -50,6 +80,7 @@ class PrepareUprevTest(unittest.TestCase): """Tests for prepare_uprev step in rust_uprev""" def setUp(self): + self.bootstrap_version = rust_uprev.RustVersion(1, 1, 0) self.version_old = rust_uprev.RustVersion(1, 2, 3) self.version_new = rust_uprev.RustVersion(1, 3, 5) @@ -57,9 +88,15 @@ class PrepareUprevTest(unittest.TestCase): rust_uprev, 'find_ebuild_for_rust_version', return_value='/path/to/ebuild') + @mock.patch.object(rust_uprev, 'find_ebuild_path') @mock.patch.object(rust_uprev, 'get_command_output') - def test_success_with_template(self, mock_command, _mock_find_ebuild): - expected = (self.version_old, '/path/to/ebuild') + def test_success_with_template(self, mock_command, mock_find_ebuild, + _ebuild_for_version): + bootstrap_ebuild_path = Path( + '/path/to/rust-bootstrap/', + f'rust-bootstrap-{self.bootstrap_version}.ebuild') + mock_find_ebuild.return_value = bootstrap_ebuild_path + expected = (self.version_old, '/path/to/ebuild', self.bootstrap_version) actual = rust_uprev.prepare_uprev( rust_version=self.version_new, template=self.version_old) self.assertEqual(expected, actual) @@ -77,12 +114,18 @@ class PrepareUprevTest(unittest.TestCase): self.assertIsNone(ret) mock_command.assert_not_called() + @mock.patch.object(rust_uprev, 'find_ebuild_path') @mock.patch.object(os.path, 'exists') @mock.patch.object(rust_uprev, 'get_command_output') - def test_success_without_template(self, mock_command, mock_exists): + def test_success_without_template(self, mock_command, mock_exists, + mock_find_ebuild): rust_ebuild_path = f'/path/to/rust/rust-{self.version_old}-r3.ebuild' mock_command.return_value = rust_ebuild_path - expected = (self.version_old, rust_ebuild_path) + bootstrap_ebuild_path = Path( + '/path/to/rust-bootstrap', + f'rust-bootstrap-{self.bootstrap_version}.ebuild') + mock_find_ebuild.return_value = bootstrap_ebuild_path + expected = (self.version_old, rust_ebuild_path, self.bootstrap_version) actual = rust_uprev.prepare_uprev( rust_version=self.version_new, template=None) self.assertEqual(expected, actual) @@ -100,8 +143,9 @@ class PrepareUprevTest(unittest.TestCase): def test_prepare_uprev_from_json(self): ebuild_path = '/path/to/the/ebuild' - json_result = (list(self.version_new), ebuild_path) - expected = (self.version_new, ebuild_path) + json_result = (list(self.version_new), ebuild_path, + list(self.bootstrap_version)) + expected = (self.version_new, ebuild_path, self.bootstrap_version) actual = rust_uprev.prepare_uprev_from_json(json_result) self.assertEqual(expected, actual) @@ -109,32 +153,30 @@ class PrepareUprevTest(unittest.TestCase): class UpdateEbuildTest(unittest.TestCase): """Tests for update_ebuild step in rust_uprev""" ebuild_file_before = """ - STAGE0_DATE="2019-01-01" - STAGE0_VERSION="any.random.(number)" - STAGE0_VERSION_CARGO="0.0.0" +BOOTSTRAP_VERSION="1.2.0" """ ebuild_file_after = """ - STAGE0_DATE="2020-01-01" - STAGE0_VERSION="1.1.1" - STAGE0_VERSION_CARGO="0.1.0" +BOOTSTRAP_VERSION="1.3.6" """ def test_success(self): mock_open = mock.mock_open(read_data=self.ebuild_file_before) + # ebuild_file and new bootstrap version are deliberately different ebuild_file = '/path/to/rust/rust-1.3.5.ebuild' with mock.patch('builtins.open', mock_open): - rust_uprev.update_ebuild(ebuild_file, ('2020-01-01', '1.1.1', '0.1.0')) + rust_uprev.update_ebuild(ebuild_file, + rust_uprev.RustVersion.parse('1.3.6')) mock_open.return_value.__enter__().write.assert_called_once_with( self.ebuild_file_after) def test_fail_when_ebuild_misses_a_variable(self): - ebuild_file = 'STAGE0_DATE="2019-01-01"' - mock_open = mock.mock_open(read_data=ebuild_file) + mock_open = mock.mock_open(read_data='') ebuild_file = '/path/to/rust/rust-1.3.5.ebuild' with mock.patch('builtins.open', mock_open): with self.assertRaises(RuntimeError) as context: - rust_uprev.update_ebuild(ebuild_file, ('2020-01-01', '1.1.1', '0.1.0')) - self.assertEqual('STAGE0_VERSION not found in rust ebuild', + rust_uprev.update_ebuild(ebuild_file, + rust_uprev.RustVersion.parse('1.2.0')) + self.assertEqual('BOOTSTRAP_VERSION not found in rust ebuild', str(context.exception)) @@ -179,16 +221,56 @@ class UpdateManifestTest(unittest.TestCase): expect_write=False) @mock.patch.object(rust_uprev, 'flip_mirror_in_ebuild') - @mock.patch.object(rust_uprev, 'rust_ebuild_actions') + @mock.patch.object(rust_uprev, 'ebuild_actions') def test_update_manifest(self, mock_run, mock_flip): - ebuild_file = '/path/to/rust/rust-1.1.1.ebuild' + ebuild_file = Path('/path/to/rust/rust-1.1.1.ebuild') rust_uprev.update_manifest(ebuild_file) - mock_run.assert_called_once_with(['manifest']) + mock_run.assert_called_once_with('rust', ['manifest']) mock_flip.assert_has_calls( [mock.call(ebuild_file, add=True), mock.call(ebuild_file, add=False)]) +class UpdateBootstrapEbuildTest(unittest.TestCase): + """Tests for rust_uprev.update_bootstrap_ebuild()""" + + def test_update_bootstrap_ebuild(self): + # The update should do two things: + # 1. Create a copy of rust-bootstrap's ebuild with the new version number. + # 2. Add the old PV to RUSTC_FULL_BOOTSTRAP_SEQUENCE. + with tempfile.TemporaryDirectory() as tmpdir_str, \ + mock.patch.object(rust_uprev, 'find_ebuild_path') as mock_find_ebuild: + tmpdir = Path(tmpdir_str) + bootstrapdir = Path.joinpath(tmpdir, 'rust-bootstrap') + bootstrapdir.mkdir() + old_ebuild = bootstrapdir.joinpath('rust-bootstrap-1.45.2.ebuild') + old_ebuild.write_text( + encoding='utf-8', + data=""" +some text +RUSTC_FULL_BOOTSTRAP_SEQUENCE=( +\t1.43.1 +\t1.44.1 +) +some more text +""") + mock_find_ebuild.return_value = old_ebuild + rust_uprev.update_bootstrap_ebuild(rust_uprev.RustVersion(1, 46, 0)) + new_ebuild = bootstrapdir.joinpath('rust-bootstrap-1.46.0.ebuild') + self.assertTrue(new_ebuild.exists()) + text = new_ebuild.read_text() + self.assertEqual( + text, """ +some text +RUSTC_FULL_BOOTSTRAP_SEQUENCE=( +\t1.43.1 +\t1.44.1 +\t1.45.2 +) +some more text +""") + + class UpdateRustPackagesTests(unittest.TestCase): """Tests for update_rust_packages step.""" @@ -224,103 +306,6 @@ class UpdateRustPackagesTests(unittest.TestCase): package_after) -class UploadToLocalmirrorTests(unittest.TestCase): - """Tests for upload_to_localmirror""" - - def setUp(self): - self.tempdir = '/tmp/any/dir' - self.new_version = rust_uprev.RustVersion(1, 3, 5) - self.rust_url = 'https://static.rust-lang.org/dist' - self.tarfile_name = f'rustc-{self.new_version}-src.tar.gz' - self.rust_src = f'https://static.rust-lang.org/dist/{self.tarfile_name}' - self.gsurl = f'gs://chromeos-localmirror/distfiles/{self.tarfile_name}' - self.rust_file = os.path.join(self.tempdir, self.tarfile_name) - self.sig_file = os.path.join(self.tempdir, 'rustc_sig.asc') - - @mock.patch.object(subprocess, 'call', return_value=1) - @mock.patch.object(subprocess, 'check_call') - @mock.patch.object(subprocess, 'check_output') - @mock.patch.object(subprocess, 'run') - def test_pass_without_retry(self, mock_run, mock_output, mock_call, - mock_raw_call): - rust_uprev.upload_single_tarball(self.rust_url, self.tarfile_name, - self.tempdir) - mock_output.assert_called_once_with( - ['gpg', '--verify', self.sig_file, self.rust_file], - encoding='utf-8', - stderr=subprocess.STDOUT) - mock_raw_call.assert_has_calls([ - mock.call(['gsutil', 'ls', self.gsurl], - stdout=subprocess.DEVNULL, - stderr=subprocess.DEVNULL) - ]) - mock_call.assert_has_calls([ - mock.call(['curl', '-f', '-o', self.rust_file, self.rust_src]), - mock.call(['curl', '-f', '-o', self.sig_file, f'{self.rust_src}.asc']), - mock.call([ - 'gsutil', 'cp', '-n', '-a', 'public-read', self.rust_file, - self.gsurl - ]) - ]) - mock_run.assert_not_called() - - @mock.patch.object(subprocess, 'call') - @mock.patch.object(subprocess, 'check_call') - @mock.patch.object(subprocess, 'check_output') - @mock.patch.object(subprocess, 'run') - @mock.patch.object(rust_uprev, 'get_command_output') - def test_pass_with_retry(self, mock_output, mock_run, mock_check, mock_call, - mock_raw_call): - mock_check.side_effect = subprocess.CalledProcessError( - returncode=2, cmd=None, output="gpg: Can't check signature") - mock_output.return_value = 'some_gpg_keys' - rust_uprev.upload_single_tarball(self.rust_url, self.tarfile_name, - self.tempdir) - mock_check.assert_called_once_with( - ['gpg', '--verify', self.sig_file, self.rust_file], - encoding='utf-8', - stderr=subprocess.STDOUT) - mock_output.assert_called_once_with( - ['curl', '-f', 'https://keybase.io/rust/pgp_keys.asc']) - mock_run.assert_called_once_with(['gpg', '--import'], - input='some_gpg_keys', - encoding='utf-8', - check=True) - mock_raw_call.assert_has_calls([ - mock.call(['gsutil', 'ls', self.gsurl], - stdout=subprocess.DEVNULL, - stderr=subprocess.DEVNULL) - ]) - mock_call.assert_has_calls([ - mock.call(['curl', '-f', '-o', self.rust_file, self.rust_src]), - mock.call(['curl', '-f', '-o', self.sig_file, f'{self.rust_src}.asc']), - mock.call(['gpg', '--verify', self.sig_file, self.rust_file]), - mock.call([ - 'gsutil', 'cp', '-n', '-a', 'public-read', self.rust_file, - self.gsurl - ]) - ]) - - @mock.patch.object(rust_uprev, 'upload_single_tarball') - def test_upload_to_mirror(self, mock_upload): - stage0_info = '2020-01-01', '1.1.1', '0.1.0' - rust_uprev.upload_to_localmirror(self.tempdir, self.new_version, - stage0_info) - mock_upload.assert_has_calls([ - mock.call(self.rust_url, f'rustc-{self.new_version}-src.tar.gz', - self.tempdir), - mock.call(f'{self.rust_url}/{stage0_info[0]}', - f'rust-std-{stage0_info[1]}-x86_64-unknown-linux-gnu.tar.gz', - self.tempdir), - mock.call(self.rust_url, - f'rustc-{stage0_info[1]}-x86_64-unknown-linux-gnu.tar.gz', - self.tempdir), - mock.call(self.rust_url, - f'cargo-{stage0_info[2]}-x86_64-unknown-linux-gnu.tar.gz', - self.tempdir), - ]) - - class RustUprevOtherStagesTests(unittest.TestCase): """Tests for other steps in rust_uprev""" @@ -331,25 +316,6 @@ class RustUprevOtherStagesTests(unittest.TestCase): self.ebuild_file = os.path.join(rust_uprev.RUST_PATH, 'rust-{self.new_version}.ebuild') - @mock.patch.object(rust_uprev, 'get_command_output') - def test_parse_stage0_file(self, mock_get): - stage0_file = """ - unrelated stuff before - date: 2020-01-01 - rustc: 1.1.1 - cargo: 0.1.0 - unrelated stuff after - """ - mock_get.return_value = stage0_file - expected = '2020-01-01', '1.1.1', '0.1.0' - rust_version = rust_uprev.RustVersion(1, 2, 3) - actual = rust_uprev.parse_stage0_file(rust_version) - self.assertEqual(expected, actual) - mock_get.assert_called_once_with([ - 'curl', '-f', 'https://raw.githubusercontent.com/rust-lang/rust/' - f'{rust_version}/src/stage0.txt' - ]) - @mock.patch.object(shutil, 'copyfile') @mock.patch.object(os, 'listdir') @mock.patch.object(subprocess, 'check_call') @@ -360,7 +326,8 @@ class RustUprevOtherStagesTests(unittest.TestCase): f'rust-{self.current_version}-patch-1.patch', f'rust-{self.current_version}-patch-2-new.patch' ] - rust_uprev.copy_patches(self.current_version, self.new_version) + rust_uprev.copy_patches(rust_uprev.RUST_PATH, self.current_version, + self.new_version) mock_copy.assert_has_calls([ mock.call( os.path.join(rust_uprev.RUST_PATH, 'files', @@ -375,8 +342,8 @@ class RustUprevOtherStagesTests(unittest.TestCase): f'rust-{self.new_version}-patch-2-new.patch')) ]) mock_call.assert_called_once_with( - ['git', 'add', f'files/rust-{self.new_version}-*.patch'], - cwd=rust_uprev.RUST_PATH) + ['git', 'add', f'rust-{self.new_version}-*.patch'], + cwd=rust_uprev.RUST_PATH.joinpath('files')) @mock.patch.object(shutil, 'copyfile') @mock.patch.object(subprocess, 'check_call') @@ -385,65 +352,51 @@ class RustUprevOtherStagesTests(unittest.TestCase): rust_uprev.create_ebuild(template_ebuild, self.new_version) mock_copy.assert_called_once_with( template_ebuild, - os.path.join(rust_uprev.RUST_PATH, f'rust-{self.new_version}.ebuild')) + rust_uprev.RUST_PATH.joinpath(f'rust-{self.new_version}.ebuild')) mock_call.assert_called_once_with( ['git', 'add', f'rust-{self.new_version}.ebuild'], cwd=rust_uprev.RUST_PATH) - @mock.patch.object(glob, 'glob') @mock.patch.object(subprocess, 'check_call') - def test_remove_virtual_rust(self, mock_call, mock_glob): - virtual_rust_dir = os.path.join(rust_uprev.RUST_PATH, '../../virtual/rust') - mock_glob.return_value = [ - os.path.join(virtual_rust_dir, f'rust-{self.old_version}.ebuild'), - ] - rust_uprev.remove_virtual_rust(self.old_version) - mock_call.assert_called_once_with( - ['git', 'rm', f'rust-{self.old_version}.ebuild'], cwd=virtual_rust_dir) + def test_remove_rust_bootstrap_version(self, mock_call): + bootstrap_path = os.path.join(rust_uprev.RUST_PATH, '..', 'rust-bootstrap') + rust_uprev.remove_rust_bootstrap_version(self.old_version, lambda *x: ()) + mock_call.has_calls([ + [ + 'git', 'rm', + os.path.join(bootstrap_path, 'files', + f'rust-bootstrap-{self.old_version}-*.patch') + ], + [ + 'git', 'rm', + os.path.join(bootstrap_path, + f'rust-bootstrap-{self.old_version}.ebuild') + ], + ]) - @mock.patch.object(glob, 'glob') + @mock.patch.object(rust_uprev, 'find_ebuild_path') @mock.patch.object(subprocess, 'check_call') - def test_remove_virtual_rust_patch(self, mock_call, mock_glob): - virtual_rust_dir = os.path.join(rust_uprev.RUST_PATH, '../../virtual/rust') - mock_glob.return_value = [ - os.path.join(virtual_rust_dir, f'rust-{self.old_version}-r3.ebuild'), - ] + def test_remove_virtual_rust(self, mock_call, mock_find_ebuild): + ebuild_path = Path(f'/some/dir/virtual/rust/rust-{self.old_version}.ebuild') + mock_find_ebuild.return_value = Path(ebuild_path) rust_uprev.remove_virtual_rust(self.old_version) mock_call.assert_called_once_with( - ['git', 'rm', f'rust-{self.old_version}-r3.ebuild'], - cwd=virtual_rust_dir) - - @mock.patch.object(glob, 'glob') - @mock.patch.object(shutil, 'copyfile') - @mock.patch.object(subprocess, 'check_call') - def test_update_virtual_rust(self, mock_call, mock_copy, mock_glob): - virtual_rust_dir = os.path.join(rust_uprev.RUST_PATH, '../../virtual/rust') - mock_glob.return_value = [ - os.path.join(virtual_rust_dir, f'rust-{self.current_version}.ebuild'), - ] - rust_uprev.update_virtual_rust(self.current_version, self.new_version) - mock_call.assert_called_once_with( - ['git', 'add', f'rust-{self.new_version}.ebuild'], cwd=virtual_rust_dir) - mock_copy.assert_called_once_with( - os.path.join(virtual_rust_dir, f'rust-{self.current_version}.ebuild'), - os.path.join(virtual_rust_dir, f'rust-{self.new_version}.ebuild')) + ['git', 'rm', str(ebuild_path.name)], cwd=ebuild_path.parent) - @mock.patch.object(glob, 'glob') + @mock.patch.object(rust_uprev, 'find_ebuild_path') @mock.patch.object(shutil, 'copyfile') @mock.patch.object(subprocess, 'check_call') - def test_update_virtual_rust_patched(self, mock_call, mock_copy, mock_glob): - virtual_rust_dir = os.path.join(rust_uprev.RUST_PATH, '../../virtual/rust') - mock_glob.return_value = [ - os.path.join(virtual_rust_dir, - f'rust-{self.current_version}-r3.ebuild'), - ] + def test_update_virtual_rust(self, mock_call, mock_copy, mock_find_ebuild): + ebuild_path = Path( + f'/some/dir/virtual/rust/rust-{self.current_version}.ebuild') + mock_find_ebuild.return_value = Path(ebuild_path) rust_uprev.update_virtual_rust(self.current_version, self.new_version) mock_call.assert_called_once_with( - ['git', 'add', f'rust-{self.new_version}.ebuild'], cwd=virtual_rust_dir) + ['git', 'add', f'rust-{self.new_version}.ebuild'], + cwd=ebuild_path.parent) mock_copy.assert_called_once_with( - os.path.join(virtual_rust_dir, - f'rust-{self.current_version}-r3.ebuild'), - os.path.join(virtual_rust_dir, f'rust-{self.new_version}.ebuild')) + ebuild_path.parent.joinpath(f'rust-{self.current_version}.ebuild'), + ebuild_path.parent.joinpath(f'rust-{self.new_version}.ebuild')) @mock.patch.object(os, 'listdir') def test_find_oldest_rust_version_in_chroot_pass(self, mock_ls): |