aboutsummaryrefslogtreecommitdiff
path: root/rust_tools
diff options
context:
space:
mode:
Diffstat (limited to 'rust_tools')
-rwxr-xr-xrust_tools/rust_uprev.py479
-rwxr-xr-xrust_tools/rust_uprev_test.py415
-rwxr-xr-xrust_tools/rust_watch.py127
-rwxr-xr-xrust_tools/rust_watch_test.py109
4 files changed, 493 insertions, 637 deletions
diff --git a/rust_tools/rust_uprev.py b/rust_tools/rust_uprev.py
index 011639df..3c0ad012 100755
--- a/rust_tools/rust_uprev.py
+++ b/rust_tools/rust_uprev.py
@@ -33,6 +33,8 @@ the CL to chromium code review.
See `--help` for all available options.
"""
+# pylint: disable=cros-logging-import
+
import argparse
import pathlib
import json
@@ -42,30 +44,16 @@ import re
import shutil
import subprocess
import sys
-from pathlib import Path
+import tempfile
from typing import Any, Callable, Dict, List, NamedTuple, Optional, T, Tuple
from llvm_tools import chroot, 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')
+RUST_PATH = '/mnt/host/source/src/third_party/chromiumos-overlay/dev-lang/rust'
def get_command_output(command: List[str], *args, **kwargs) -> str:
- 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.check_output(
+ command, encoding='utf-8', *args, **kwargs).strip()
class RustVersion(NamedTuple):
@@ -87,8 +75,8 @@ class RustVersion(NamedTuple):
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')))
+ return RustVersion(
+ int(m.group('major')), int(m.group('minor')), int(m.group('patch')))
@staticmethod
def parse(x: str) -> 'RustVersion':
@@ -99,54 +87,13 @@ class RustVersion(NamedTuple):
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'
-
-
-def compute_rust_bootstrap_prebuilt_name(version: RustVersion) -> str:
- 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]
-
-
-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)))
+ return RustVersion(
+ int(m.group('major')), int(m.group('minor')), int(m.group('patch')))
def parse_commandline_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(
- description=__doc__,
- formatter_class=argparse.RawDescriptionHelpFormatter)
+ description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
parser.add_argument(
'--state_file',
required=True,
@@ -211,18 +158,6 @@ 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',
@@ -275,18 +210,33 @@ 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, RustVersion]]:
+ ) -> Optional[Tuple[RustVersion, str]]:
if template is None:
- ebuild_path = find_ebuild_for_package('rust')
+ ebuild_path = get_command_output(['equery', 'w', '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.',
@@ -295,69 +245,66 @@ def prepare_uprev(rust_version: RustVersion, template: Optional[RustVersion]
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
+ return template_version, ebuild_path
-def copy_patches(directory: Path, template_version: RustVersion,
+def copy_patches(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)
+ patch_path = os.path.join(RUST_PATH, 'files')
for f in os.listdir(patch_path):
- if not f.startswith(prefix):
+ if f'rust-{template_version}' not in f:
continue
- logging.info('Copy patch %s to new version', f)
+ logging.info('Rename 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)
+ subprocess.check_call(['git', 'add', f'files/rust-{new_version}-*.patch'],
+ cwd=RUST_PATH)
def create_ebuild(template_ebuild: str, new_version: RustVersion) -> str:
shutil.copyfile(template_ebuild,
- RUST_PATH.joinpath(f'rust-{new_version}.ebuild'))
+ os.path.join(RUST_PATH, 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_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:
+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:
restrict_re = re.compile(
r'(?P<before>RESTRICT=")(?P<values>"[^"]*"|.*)(?P<after>")')
with open(ebuild_file, encoding='utf-8') as f:
@@ -378,116 +325,25 @@ def flip_mirror_in_ebuild(ebuild_file: Path, add: bool) -> None:
f.write(new_contents)
-def ebuild_actions(package: str, actions: List[str],
- sudo: bool = False) -> None:
- ebuild_path_inchroot = find_ebuild_for_package(package)
+def rust_ebuild_actions(actions: List[str], sudo: bool = False) -> None:
+ ebuild_path_inchroot = get_command_output(['equery', 'w', 'rust'])
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
-
-If the file does not yet exist at %s
-please download the file, verify its integrity
-with something like:
-
-curl -O https://static.rust-lang.org/dist/%s
-gpg --verify %s.asc
-
-You may need to import the signing key first, e.g.:
-
-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':
- 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 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'])
-
-
-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_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_rust_packages(rust_version: RustVersion, add: bool) -> None:
- package_file = RUST_PATH.joinpath(
- '../../profiles/targets/chromeos/package.provided')
+ package_file = os.path.join(
+ RUST_PATH, '../../profiles/targets/chromeos/package.provided')
with open(package_file, encoding='utf-8') as f:
contents = f.read()
if add:
@@ -511,13 +367,90 @@ def update_rust_packages(rust_version: RustVersion, add: bool) -> None:
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)
+ virtual_rust_dir = os.path.join(RUST_PATH, '../../virtual/rust')
+ assert os.path.exists(virtual_rust_dir)
+ shutil.copyfile(
+ os.path.join(virtual_rust_dir, f'rust-{template_version}.ebuild'),
+ os.path.join(virtual_rust_dir, f'rust-{new_version}.ebuild'))
+ subprocess.check_call(['git', 'add', f'rust-{new_version}.ebuild'],
+ 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,
@@ -547,18 +480,19 @@ def perform_step(state_file: pathlib.Path,
return val
-def prepare_uprev_from_json(
- obj: Any) -> Optional[Tuple[RustVersion, str, RustVersion]]:
+def prepare_uprev_from_json(obj: Any) -> Optional[Tuple[RustVersion, str]]:
if not obj:
return None
- version, ebuild_path, bootstrap_version = obj
- return RustVersion(*version), ebuild_path, RustVersion(*bootstrap_version)
+ version, ebuild_path = obj
+ return RustVersion(*version), ebuild_path
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(
+ stage0_info = run_step(
+ 'parse stage0 file', lambda: parse_stage0_file(rust_version))
+ template_version, template_ebuild = run_step(
'prepare uprev',
lambda: prepare_uprev(rust_version, maybe_template_version),
result_from_json=prepare_uprev_from_json,
@@ -566,31 +500,18 @@ def create_rust_uprev(rust_version: RustVersion,
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))
+ run_step('copy patches', lambda: copy_patches(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 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 manifest to add new version', lambda: update_manifest(
- Path(ebuild_file)))
+ ebuild_file))
if not skip_compile:
- run_step(
- 'emerge rust', lambda: subprocess.check_call(
- ['sudo', 'emerge', 'dev-lang/rust']))
+ 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(
@@ -599,7 +520,8 @@ def create_rust_uprev(rust_version: RustVersion,
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')]
+ for x in os.listdir(RUST_PATH)
+ if x.endswith('.ebuild')]
def find_oldest_rust_version_in_chroot() -> Tuple[RustVersion, str]:
@@ -625,52 +547,32 @@ 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,
+ result_from_json=prepare_uprev_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')
+ ebuild_file = get_command_output(['equery', 'w', '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))
-
-
-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)
-
-
-def rust_bootstrap_path() -> Path:
- return RUST_PATH.joinpath('../rust-bootstrap')
+ run_step(
+ 'remove virtual/rust', lambda: remove_files(
+ f'rust-{delete_version}.ebuild',
+ os.path.join(RUST_PATH, '../../virtual/rust')))
def create_new_repo(rust_version: RustVersion) -> None:
@@ -684,7 +586,7 @@ def create_new_repo(rust_version: RustVersion) -> None:
def build_cross_compiler() -> None:
# Get target triples in ebuild
- rust_ebuild = find_ebuild_for_package('rust')
+ rust_ebuild = get_command_output(['equery', 'w', 'rust'])
with open(rust_ebuild, encoding='utf-8') as f:
contents = f.read()
@@ -755,8 +657,6 @@ 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'
@@ -765,9 +665,6 @@ 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 00761391..fc506004 100755
--- a/rust_tools/rust_uprev_test.py
+++ b/rust_tools/rust_uprev_test.py
@@ -6,94 +6,16 @@
"""Tests for rust_uprev.py"""
+# pylint: disable=cros-logging-import
import os
import shutil
import subprocess
-import tempfile
import unittest
-from pathlib import Path
from unittest import mock
from llvm_tools import git
import rust_uprev
-from rust_uprev import RustVersion
-
-
-def _fail_command(cmd, *_args, **_kwargs):
- err = subprocess.CalledProcessError(returncode=1, cmd=cmd)
- err.stderr = b'mock failure'
- raise err
-
-
-class FetchDistfileTest(unittest.TestCase):
- """Tests rust_uprev.fetch_distfile_from_mirror()"""
-
- @mock.patch.object(rust_uprev, 'get_distdir', return_value='/fake/distfiles')
- @mock.patch.object(subprocess, 'call', side_effect=_fail_command)
- def test_fetch_difstfile_fail(self, *_args) -> None:
- with self.assertRaises(subprocess.CalledProcessError):
- rust_uprev.fetch_distfile_from_mirror('test_distfile.tar.gz')
-
- @mock.patch.object(rust_uprev,
- 'get_command_output_unchecked',
- return_value='AccessDeniedException: Access denied.')
- @mock.patch.object(rust_uprev, 'get_distdir', return_value='/fake/distfiles')
- @mock.patch.object(subprocess, 'call', return_value=0)
- def test_fetch_distfile_acl_access_denied(self, *_args) -> None:
- rust_uprev.fetch_distfile_from_mirror('test_distfile.tar.gz')
-
- @mock.patch.object(
- rust_uprev,
- 'get_command_output_unchecked',
- return_value='[ { "entity": "allUsers", "role": "READER" } ]')
- @mock.patch.object(rust_uprev, 'get_distdir', return_value='/fake/distfiles')
- @mock.patch.object(subprocess, 'call', return_value=0)
- def test_fetch_distfile_acl_ok(self, *_args) -> None:
- rust_uprev.fetch_distfile_from_mirror('test_distfile.tar.gz')
-
- @mock.patch.object(
- rust_uprev,
- 'get_command_output_unchecked',
- return_value='[ { "entity": "___fake@google.com", "role": "OWNER" } ]')
- @mock.patch.object(rust_uprev, 'get_distdir', return_value='/fake/distfiles')
- @mock.patch.object(subprocess, 'call', return_value=0)
- def test_fetch_distfile_acl_wrong(self, *_args) -> None:
- with self.assertRaisesRegex(Exception, 'allUsers.*READER'):
- with self.assertLogs(level='ERROR') as log:
- rust_uprev.fetch_distfile_from_mirror('test_distfile.tar.gz')
- self.assertIn(
- '[ { "entity": "___fake@google.com", "role": "OWNER" } ]',
- '\n'.join(log.output))
-
-
-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):
@@ -127,77 +49,58 @@ 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)
- @mock.patch.object(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,
+ 'find_ebuild_for_rust_version',
+ return_value='/path/to/ebuild')
@mock.patch.object(rust_uprev, 'get_command_output')
- 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)
+ def test_success_with_template(self, mock_command, mock_find_ebuild):
+ expected = (self.version_old, '/path/to/ebuild')
+ actual = rust_uprev.prepare_uprev(
+ rust_version=self.version_new, template=self.version_old)
self.assertEqual(expected, actual)
mock_command.assert_not_called()
- @mock.patch.object(rust_uprev,
- 'find_ebuild_for_rust_version',
- return_value='/path/to/ebuild')
- @mock.patch.object(rust_uprev,
- 'get_rust_bootstrap_version',
- return_value=RustVersion(0, 41, 12))
+ @mock.patch.object(
+ rust_uprev,
+ 'find_ebuild_for_rust_version',
+ return_value='/path/to/ebuild')
@mock.patch.object(rust_uprev, 'get_command_output')
def test_return_none_with_template_larger_than_input(self, mock_command,
- *_args):
- ret = rust_uprev.prepare_uprev(rust_version=self.version_old,
- template=self.version_new)
+ _mock_find_ebuild):
+ ret = rust_uprev.prepare_uprev(
+ rust_version=self.version_old, template=self.version_new)
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,
- mock_find_ebuild):
+ def test_success_without_template(self, mock_command, mock_exists):
rust_ebuild_path = f'/path/to/rust/rust-{self.version_old}-r3.ebuild'
mock_command.return_value = 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)
+ expected = (self.version_old, rust_ebuild_path)
+ actual = rust_uprev.prepare_uprev(
+ rust_version=self.version_new, template=None)
self.assertEqual(expected, actual)
mock_command.assert_called_once_with(['equery', 'w', 'rust'])
mock_exists.assert_not_called()
- @mock.patch.object(rust_uprev,
- 'get_rust_bootstrap_version',
- return_value=RustVersion(0, 41, 12))
@mock.patch.object(os.path, 'exists')
@mock.patch.object(rust_uprev, 'get_command_output')
def test_return_none_with_ebuild_larger_than_input(self, mock_command,
- mock_exists, *_args):
+ mock_exists):
mock_command.return_value = f'/path/to/rust/rust-{self.version_new}.ebuild'
- ret = rust_uprev.prepare_uprev(rust_version=self.version_old,
- template=None)
+ ret = rust_uprev.prepare_uprev(rust_version=self.version_old, template=None)
self.assertIsNone(ret)
mock_exists.assert_not_called()
def test_prepare_uprev_from_json(self):
ebuild_path = '/path/to/the/ebuild'
- json_result = (list(self.version_new), ebuild_path,
- list(self.bootstrap_version))
- expected = (self.version_new, ebuild_path, self.bootstrap_version)
+ json_result = (list(self.version_new), ebuild_path)
+ expected = (self.version_new, ebuild_path)
actual = rust_uprev.prepare_uprev_from_json(json_result)
self.assertEqual(expected, actual)
@@ -205,30 +108,32 @@ class PrepareUprevTest(unittest.TestCase):
class UpdateEbuildTest(unittest.TestCase):
"""Tests for update_ebuild step in rust_uprev"""
ebuild_file_before = """
-BOOTSTRAP_VERSION="1.2.0"
+ STAGE0_DATE="2019-01-01"
+ STAGE0_VERSION="any.random.(number)"
+ STAGE0_VERSION_CARGO="0.0.0"
"""
ebuild_file_after = """
-BOOTSTRAP_VERSION="1.3.6"
+ STAGE0_DATE="2020-01-01"
+ STAGE0_VERSION="1.1.1"
+ STAGE0_VERSION_CARGO="0.1.0"
"""
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,
- rust_uprev.RustVersion.parse('1.3.6'))
+ rust_uprev.update_ebuild(ebuild_file, ('2020-01-01', '1.1.1', '0.1.0'))
mock_open.return_value.__enter__().write.assert_called_once_with(
self.ebuild_file_after)
def test_fail_when_ebuild_misses_a_variable(self):
- mock_open = mock.mock_open(read_data='')
+ ebuild_file = 'STAGE0_DATE="2019-01-01"'
+ mock_open = mock.mock_open(read_data=ebuild_file)
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,
- rust_uprev.RustVersion.parse('1.2.0'))
- self.assertEqual('BOOTSTRAP_VERSION not found in rust ebuild',
+ rust_uprev.update_ebuild(ebuild_file, ('2020-01-01', '1.1.1', '0.1.0'))
+ self.assertEqual('STAGE0_VERSION not found in rust ebuild',
str(context.exception))
@@ -245,79 +150,44 @@ class UpdateManifestTest(unittest.TestCase):
f'RESTRICT="{after}"')
def test_add_mirror_in_ebuild(self):
- self._run_test_flip_mirror(before='variable1 variable2',
- after='variable1 variable2 mirror',
- add=True,
- expect_write=True)
+ self._run_test_flip_mirror(
+ before='variable1 variable2',
+ after='variable1 variable2 mirror',
+ add=True,
+ expect_write=True)
def test_remove_mirror_in_ebuild(self):
- self._run_test_flip_mirror(before='variable1 variable2 mirror',
- after='variable1 variable2',
- add=False,
- expect_write=True)
+ self._run_test_flip_mirror(
+ before='variable1 variable2 mirror',
+ after='variable1 variable2',
+ add=False,
+ expect_write=True)
def test_add_mirror_when_exists(self):
- self._run_test_flip_mirror(before='variable1 variable2 mirror',
- after='variable1 variable2 mirror',
- add=True,
- expect_write=False)
+ self._run_test_flip_mirror(
+ before='variable1 variable2 mirror',
+ after='variable1 variable2 mirror',
+ add=True,
+ expect_write=False)
def test_remove_mirror_when_not_exists(self):
- self._run_test_flip_mirror(before='variable1 variable2',
- after='variable1 variable2',
- add=False,
- expect_write=False)
+ self._run_test_flip_mirror(
+ before='variable1 variable2',
+ after='variable1 variable2',
+ add=False,
+ expect_write=False)
@mock.patch.object(rust_uprev, 'flip_mirror_in_ebuild')
- @mock.patch.object(rust_uprev, 'ebuild_actions')
+ @mock.patch.object(rust_uprev, 'rust_ebuild_actions')
def test_update_manifest(self, mock_run, mock_flip):
- ebuild_file = Path('/path/to/rust/rust-1.1.1.ebuild')
+ ebuild_file = '/path/to/rust/rust-1.1.1.ebuild'
rust_uprev.update_manifest(ebuild_file)
- mock_run.assert_called_once_with('rust', ['manifest'])
+ mock_run.assert_called_once_with(['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_RAW_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_RAW_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_RAW_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."""
@@ -353,6 +223,103 @@ 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"""
@@ -363,6 +330,25 @@ 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')
@@ -373,8 +359,7 @@ 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(rust_uprev.RUST_PATH, self.current_version,
- self.new_version)
+ rust_uprev.copy_patches(self.current_version, self.new_version)
mock_copy.assert_has_calls([
mock.call(
os.path.join(rust_uprev.RUST_PATH, 'files',
@@ -389,8 +374,8 @@ class RustUprevOtherStagesTests(unittest.TestCase):
f'rust-{self.new_version}-patch-2-new.patch'))
])
mock_call.assert_called_once_with(
- ['git', 'add', f'rust-{self.new_version}-*.patch'],
- cwd=rust_uprev.RUST_PATH.joinpath('files'))
+ ['git', 'add', f'files/rust-{self.new_version}-*.patch'],
+ cwd=rust_uprev.RUST_PATH)
@mock.patch.object(shutil, 'copyfile')
@mock.patch.object(subprocess, 'check_call')
@@ -399,53 +384,23 @@ class RustUprevOtherStagesTests(unittest.TestCase):
rust_uprev.create_ebuild(template_ebuild, self.new_version)
mock_copy.assert_called_once_with(
template_ebuild,
- rust_uprev.RUST_PATH.joinpath(f'rust-{self.new_version}.ebuild'))
+ os.path.join(rust_uprev.RUST_PATH, 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(rust_uprev, 'find_ebuild_for_package')
- @mock.patch.object(subprocess, 'check_call')
- def test_remove_rust_bootstrap_version(self, mock_call, *_args):
- 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(rust_uprev, 'find_ebuild_path')
- @mock.patch.object(subprocess, 'check_call')
- 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', str(ebuild_path.name)], cwd=ebuild_path.parent)
-
- @mock.patch.object(rust_uprev, 'find_ebuild_path')
+ @mock.patch.object(os.path, 'exists', return_value=True)
@mock.patch.object(shutil, 'copyfile')
@mock.patch.object(subprocess, 'check_call')
- 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)
+ def test_update_virtual_rust(self, mock_call, mock_copy, mock_exists):
+ virtual_rust_dir = os.path.join(rust_uprev.RUST_PATH, '../../virtual/rust')
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=ebuild_path.parent)
+ ['git', 'add', f'rust-{self.new_version}.ebuild'], cwd=virtual_rust_dir)
mock_copy.assert_called_once_with(
- ebuild_path.parent.joinpath(f'rust-{self.current_version}.ebuild'),
- ebuild_path.parent.joinpath(f'rust-{self.new_version}.ebuild'))
+ os.path.join(virtual_rust_dir, f'rust-{self.current_version}.ebuild'),
+ os.path.join(virtual_rust_dir, f'rust-{self.new_version}.ebuild'))
+ mock_exists.assert_called_once_with(virtual_rust_dir)
@mock.patch.object(os, 'listdir')
def test_find_oldest_rust_version_in_chroot_pass(self, mock_ls):
diff --git a/rust_tools/rust_watch.py b/rust_tools/rust_watch.py
index c347d2c6..b9ad7b82 100755
--- a/rust_tools/rust_watch.py
+++ b/rust_tools/rust_watch.py
@@ -9,6 +9,8 @@
Sends an email if something interesting (probably) happened.
"""
+# pylint: disable=cros-logging-import
+
import argparse
import itertools
import json
@@ -19,9 +21,10 @@ import shutil
import subprocess
import sys
import time
-from typing import Any, Dict, Iterable, List, NamedTuple, Optional, Tuple
+from typing import Any, Dict, Iterable, List, Optional, Tuple, NamedTuple
-from cros_utils import bugs, email_sender, tiny_render
+from cros_utils import email_sender
+from cros_utils import tiny_render
def gentoo_sha_to_link(sha: str) -> str:
@@ -161,7 +164,7 @@ def get_new_gentoo_commits(git_dir: pathlib.Path,
'git',
'log',
'--format=%H %s',
- f'{most_recent_sha}..origin/master', # nocheck
+ f'{most_recent_sha}..origin/master',
'--',
'dev-lang/rust',
],
@@ -222,63 +225,43 @@ def atomically_write_state(state_file: pathlib.Path, state: State) -> None:
temp_file.rename(state_file)
-def file_bug(title: str, body: str) -> None:
- """Files a bug against gbiv@ with the given title/body."""
- bugs.CreateNewBug(
- bugs.WellKnownComponents.CrOSToolchainPublic,
- title,
- body,
- # To either take or reassign depending on the rotation.
- assignee='gbiv@google.com',
- )
-
-
-def maybe_compose_bug(
- old_state: State,
- newest_release: RustReleaseVersion,
-) -> Optional[Tuple[str, str]]:
- """Creates a bug to file about the new release, if doing is desired."""
- if newest_release == old_state.last_seen_release:
- return None
-
- title = f'[Rust] Update to {newest_release}'
- body = ('A new release has been detected; we should probably roll to it. '
- "Please see go/crostc-rust-rotation for who's turn it is.")
- return title, body
-
-
-def maybe_compose_email(
- new_gentoo_commits: List[GitCommit]
-) -> Optional[Tuple[str, List[tiny_render.Piece]]]:
+def maybe_compose_email(old_state: State, newest_release: RustReleaseVersion,
+ new_gentoo_commits: List[GitCommit]
+ ) -> Optional[Tuple[str, List[tiny_render.Piece]]]:
"""Creates an email given our new state, if doing so is appropriate."""
- if not new_gentoo_commits:
- return None
-
subject_pieces = []
body_pieces = []
- # Separate the sections a bit for prettier output.
- if body_pieces:
- body_pieces += [tiny_render.line_break, tiny_render.line_break]
+ if newest_release > old_state.last_seen_release:
+ subject_pieces.append('new rustc release detected')
+ body_pieces.append(f'Rustc tag for v{newest_release} was found.')
- if len(new_gentoo_commits) == 1:
- subject_pieces.append('new rust ebuild commit detected')
- body_pieces.append('commit:')
- else:
- subject_pieces.append('new rust ebuild commits detected')
- body_pieces.append('commits (newest first):')
-
- commit_lines = []
- for commit in new_gentoo_commits:
- commit_lines.append([
- tiny_render.Link(
- gentoo_sha_to_link(commit.sha),
- commit.sha[:12],
- ),
- f': {commit.subject}',
- ])
+ if new_gentoo_commits:
+ # Separate the sections a bit for prettier output.
+ if body_pieces:
+ body_pieces += [tiny_render.line_break, tiny_render.line_break]
- body_pieces.append(tiny_render.UnorderedList(commit_lines))
+ if len(new_gentoo_commits) == 1:
+ subject_pieces.append('new rust ebuild commit detected')
+ body_pieces.append('commit:')
+ else:
+ subject_pieces.append('new rust ebuild commits detected')
+ body_pieces.append('commits (newest first):')
+
+ commit_lines = []
+ for commit in new_gentoo_commits:
+ commit_lines.append([
+ tiny_render.Link(
+ gentoo_sha_to_link(commit.sha),
+ commit.sha[:12],
+ ),
+ f': {commit.subject}',
+ ])
+
+ body_pieces.append(tiny_render.UnorderedList(commit_lines))
+
+ if not subject_pieces:
+ return None
subject = '[rust-watch] ' + '; '.join(subject_pieces)
return subject, body_pieces
@@ -288,14 +271,11 @@ def main(argv: List[str]) -> None:
logging.basicConfig(level=logging.INFO)
parser = argparse.ArgumentParser(
- description=__doc__,
- formatter_class=argparse.RawDescriptionHelpFormatter)
- parser.add_argument('--state_dir',
- required=True,
- help='Directory to store state in.')
- parser.add_argument('--skip_side_effects',
- action='store_true',
- help="Don't send an email or file a bug.")
+ description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
+ parser.add_argument(
+ '--state_dir', required=True, help='Directory to store state in.')
+ parser.add_argument(
+ '--skip_email', action='store_true', help="Don't send an email.")
parser.add_argument(
'--skip_state_update',
action='store_true',
@@ -338,25 +318,14 @@ def main(argv: List[str]) -> None:
prior_state.last_gentoo_sha)
logging.info('New commits: %r', new_commits)
- maybe_bug = maybe_compose_bug(prior_state, most_recent_release)
- maybe_email = maybe_compose_email(new_commits)
-
- if maybe_bug is None:
- logging.info('No bug to file')
- else:
- title, body = maybe_bug
- if opts.skip_side_effects:
- logging.info('Skipping sending bug with title %r and contents\n%s',
- title, body)
- else:
- logging.info('Writing new bug')
- file_bug(title, body)
+ maybe_email = maybe_compose_email(prior_state, most_recent_release,
+ new_commits)
if maybe_email is None:
- logging.info('No email to send')
+ logging.info('No updates to send')
else:
title, body = maybe_email
- if opts.skip_side_effects:
+ if opts.skip_email:
logging.info('Skipping sending email with title %r and contents\n%s',
title, tiny_render.render_html_pieces(body))
else:
@@ -367,8 +336,8 @@ def main(argv: List[str]) -> None:
logging.info('Skipping state update, as requested')
return
- newest_sha = (new_commits[-1].sha
- if new_commits else prior_state.last_gentoo_sha)
+ newest_sha = (
+ new_commits[-1].sha if new_commits else prior_state.last_gentoo_sha)
atomically_write_state(
state_file,
State(
diff --git a/rust_tools/rust_watch_test.py b/rust_tools/rust_watch_test.py
index 583a9125..97d111fc 100755
--- a/rust_tools/rust_watch_test.py
+++ b/rust_tools/rust_watch_test.py
@@ -6,6 +6,8 @@
"""Tests for rust_watch.py."""
+# pylint: disable=cros-logging-import
+
import logging
import pathlib
import subprocess
@@ -13,15 +15,16 @@ import time
import unittest
import unittest.mock
-from cros_utils import tiny_render
-
import rust_watch
+from cros_utils import tiny_render
class Test(unittest.TestCase):
"""Tests."""
+
def _silence_logs(self):
"""Silences all log output until the end of the current test."""
+
def should_log(_record):
return 0
@@ -37,8 +40,8 @@ class Test(unittest.TestCase):
def test_release_version_json_round_trips(self):
ver = rust_watch.RustReleaseVersion(1, 2, 3)
- self.assertEqual(rust_watch.RustReleaseVersion.from_json(ver.to_json()),
- ver)
+ self.assertEqual(
+ rust_watch.RustReleaseVersion.from_json(ver.to_json()), ver)
def test_state_json_round_trips(self):
state = rust_watch.State(
@@ -95,14 +98,34 @@ class Test(unittest.TestCase):
rust_watch.GitCommit('abc123', 'newer commit'),
])
+ def test_compose_email_on_a_new_release(self):
+ new_release = rust_watch.maybe_compose_email(
+ old_state=rust_watch.State(
+ last_seen_release=rust_watch.RustReleaseVersion(1, 0, 0),
+ last_gentoo_sha='',
+ ),
+ newest_release=rust_watch.RustReleaseVersion(1, 1, 0),
+ new_gentoo_commits=[],
+ )
+
+ self.assertEqual(new_release, ('[rust-watch] new rustc release detected',
+ ['Rustc tag for v1.1.0 was found.']))
+
def test_compose_email_on_a_new_gentoo_commit(self):
sha_a = 'a' * 40
- new_commit = rust_watch.maybe_compose_email(new_gentoo_commits=[
- rust_watch.GitCommit(
- sha=sha_a,
- subject='summary_a',
+ new_commit = rust_watch.maybe_compose_email(
+ old_state=rust_watch.State(
+ last_seen_release=rust_watch.RustReleaseVersion(1, 0, 0),
+ last_gentoo_sha='',
),
- ], )
+ newest_release=rust_watch.RustReleaseVersion(1, 0, 0),
+ new_gentoo_commits=[
+ rust_watch.GitCommit(
+ sha=sha_a,
+ subject='summary_a',
+ ),
+ ],
+ )
self.assertEqual(new_commit,
('[rust-watch] new rust ebuild commit detected', [
@@ -118,48 +141,60 @@ class Test(unittest.TestCase):
])
]))
- def test_compose_email_composes_nothing_when_no_new_updates_exist(self):
- self.assertIsNone(rust_watch.maybe_compose_email(new_gentoo_commits=()))
-
- def test_compose_bug_creates_bugs_on_new_versions(self):
- title, body = rust_watch.maybe_compose_bug(
- old_state=rust_watch.State(
- last_seen_release=rust_watch.RustReleaseVersion(1, 0, 0),
- last_gentoo_sha='',
- ),
- newest_release=rust_watch.RustReleaseVersion(1, 0, 1),
- )
- self.assertEqual(title, '[Rust] Update to 1.0.1')
- self.assertTrue(body.startswith('A new release has been detected;'))
-
- title, body = rust_watch.maybe_compose_bug(
+ def test_compose_email_on_multiple_events(self):
+ sha_a = 'a' * 40
+ new_commit_and_release = rust_watch.maybe_compose_email(
old_state=rust_watch.State(
last_seen_release=rust_watch.RustReleaseVersion(1, 0, 0),
last_gentoo_sha='',
),
newest_release=rust_watch.RustReleaseVersion(1, 1, 0),
+ new_gentoo_commits=[
+ rust_watch.GitCommit(
+ sha=sha_a,
+ subject='summary_a',
+ ),
+ ],
)
- self.assertEqual(title, '[Rust] Update to 1.1.0')
- self.assertTrue(body.startswith('A new release has been detected;'))
- title, body = rust_watch.maybe_compose_bug(
- old_state=rust_watch.State(
- last_seen_release=rust_watch.RustReleaseVersion(1, 0, 0),
- last_gentoo_sha='',
- ),
- newest_release=rust_watch.RustReleaseVersion(2, 0, 0),
- )
- self.assertEqual(title, '[Rust] Update to 2.0.0')
- self.assertTrue(body.startswith('A new release has been detected;'))
+ self.assertEqual(
+ new_commit_and_release,
+ ('[rust-watch] new rustc release detected; new rust ebuild commit '
+ 'detected', [
+ 'Rustc tag for v1.1.0 was found.',
+ tiny_render.line_break,
+ tiny_render.line_break,
+ 'commit:',
+ tiny_render.UnorderedList([
+ [
+ tiny_render.Link(
+ rust_watch.gentoo_sha_to_link(sha_a),
+ sha_a[:12],
+ ),
+ ': summary_a',
+ ],
+ ]),
+ ]))
- def test_compose_bug_does_nothing_when_no_new_updates_exist(self):
+ def test_compose_email_composes_nothing_when_no_new_updates_exist(self):
self.assertIsNone(
- rust_watch.maybe_compose_bug(
+ rust_watch.maybe_compose_email(
old_state=rust_watch.State(
last_seen_release=rust_watch.RustReleaseVersion(1, 0, 0),
last_gentoo_sha='',
),
newest_release=rust_watch.RustReleaseVersion(1, 0, 0),
+ new_gentoo_commits=[],
+ ))
+
+ self.assertIsNone(
+ rust_watch.maybe_compose_email(
+ old_state=rust_watch.State(
+ last_seen_release=rust_watch.RustReleaseVersion(1, 1, 0),
+ last_gentoo_sha='',
+ ),
+ newest_release=rust_watch.RustReleaseVersion(1, 0, 0),
+ new_gentoo_commits=[],
))