674 lines
24 KiB
Python
Executable File
674 lines
24 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
# -*- coding: utf-8 -*-
|
|
# Copyright 2020 The Chromium OS Authors. All rights reserved.
|
|
# Use of this source code is governed by a BSD-style license that can be
|
|
# found in the LICENSE file.
|
|
|
|
"""Tool to automatically generate a new Rust uprev CL.
|
|
|
|
This tool is intended to automatically generate a CL to uprev Rust to a
|
|
newer version in Chrome OS, including creating a new Rust version or
|
|
removing an old version. It's based on
|
|
src/third_party/chromiumos-overlay/dev-lang/rust/UPGRADE.md. When using
|
|
the tool, the progress can be saved to a JSON file, so the user can resume
|
|
the process after a failing step is fixed. Example usage to create a new
|
|
version:
|
|
|
|
1. (inside chroot) $ ./rust_tools/rust_uprev.py
|
|
--state_file /tmp/state-file.json
|
|
create --rust_version 1.45.0
|
|
2. Step "compile rust" failed due to the patches can't apply to new version
|
|
3. Manually fix the patches
|
|
4. Execute the command in step 1 again.
|
|
5. Iterate 1-4 for each failed step until the tool passes.
|
|
|
|
Replace `create --rust_version 1.45.0` with `remove --rust_version 1.43.0`
|
|
if you want to remove all 1.43.0 related stuff in the same CL. Remember to
|
|
use a different state file if you choose to run different subcommands.
|
|
|
|
If you want a hammer that can do everything for you, use the subcommand
|
|
`roll`. It can create a Rust uprev CL with `create` and `remove` and upload
|
|
the CL to chromium code review.
|
|
|
|
See `--help` for all available options.
|
|
"""
|
|
|
|
# pylint: disable=cros-logging-import
|
|
|
|
import argparse
|
|
import pathlib
|
|
import json
|
|
import logging
|
|
import os
|
|
import re
|
|
import shutil
|
|
import subprocess
|
|
import sys
|
|
import tempfile
|
|
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'
|
|
|
|
|
|
def get_command_output(command: List[str], *args, **kwargs) -> str:
|
|
return subprocess.check_output(
|
|
command, encoding='utf-8', *args, **kwargs).strip()
|
|
|
|
|
|
class RustVersion(NamedTuple):
|
|
"""NamedTuple represents a Rust version"""
|
|
major: int
|
|
minor: int
|
|
patch: int
|
|
|
|
def __str__(self):
|
|
return f'{self.major}.{self.minor}.{self.patch}'
|
|
|
|
@staticmethod
|
|
def parse_from_ebuild(ebuild_name: str) -> 'RustVersion':
|
|
input_re = re.compile(r'^rust-'
|
|
r'(?P<major>\d+)\.'
|
|
r'(?P<minor>\d+)\.'
|
|
r'(?P<patch>\d+)'
|
|
r'(:?-r\d+)?'
|
|
r'\.ebuild$')
|
|
m = input_re.match(ebuild_name)
|
|
assert m, f'failed to parse {ebuild_name!r}'
|
|
return RustVersion(
|
|
int(m.group('major')), int(m.group('minor')), int(m.group('patch')))
|
|
|
|
@staticmethod
|
|
def parse(x: str) -> 'RustVersion':
|
|
input_re = re.compile(r'^(?:rust-)?'
|
|
r'(?P<major>\d+)\.'
|
|
r'(?P<minor>\d+)\.'
|
|
r'(?P<patch>\d+)'
|
|
r'(?:.ebuild)?$')
|
|
m = input_re.match(x)
|
|
assert m, f'failed to parse {x!r}'
|
|
return RustVersion(
|
|
int(m.group('major')), int(m.group('minor')), int(m.group('patch')))
|
|
|
|
|
|
def parse_commandline_args() -> argparse.Namespace:
|
|
parser = argparse.ArgumentParser(
|
|
description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
|
|
parser.add_argument(
|
|
'--state_file',
|
|
required=True,
|
|
help='A state file to hold previous completed steps. If the file '
|
|
'exists, it needs to be used together with --continue or --restart. '
|
|
'If not exist (do not use --continue in this case), we will create a '
|
|
'file for you.',
|
|
)
|
|
parser.add_argument(
|
|
'--restart',
|
|
action='store_true',
|
|
help='Restart from the first step. Ignore the completed steps in '
|
|
'the state file',
|
|
)
|
|
parser.add_argument(
|
|
'--continue',
|
|
dest='cont',
|
|
action='store_true',
|
|
help='Continue the steps from the state file',
|
|
)
|
|
|
|
create_parser_template = argparse.ArgumentParser(add_help=False)
|
|
create_parser_template.add_argument(
|
|
'--template',
|
|
type=RustVersion.parse,
|
|
default=None,
|
|
help='A template to use for creating a Rust uprev from, in the form '
|
|
'a.b.c The ebuild has to exist in the chroot. If not specified, the '
|
|
'tool will use the current Rust version in the chroot as template.',
|
|
)
|
|
create_parser_template.add_argument(
|
|
'--skip_compile',
|
|
action='store_true',
|
|
help='Skip compiling rust to test the tool. Only for testing',
|
|
)
|
|
|
|
subparsers = parser.add_subparsers(dest='subparser_name')
|
|
subparser_names = []
|
|
subparser_names.append('create')
|
|
create_parser = subparsers.add_parser(
|
|
'create',
|
|
parents=[create_parser_template],
|
|
help='Create changes uprevs Rust to a new version',
|
|
)
|
|
create_parser.add_argument(
|
|
'--rust_version',
|
|
type=RustVersion.parse,
|
|
required=True,
|
|
help='Rust version to uprev to, in the form a.b.c',
|
|
)
|
|
|
|
subparser_names.append('remove')
|
|
remove_parser = subparsers.add_parser(
|
|
'remove',
|
|
help='Clean up old Rust version from chroot',
|
|
)
|
|
remove_parser.add_argument(
|
|
'--rust_version',
|
|
type=RustVersion.parse,
|
|
default=None,
|
|
help='Rust version to remove, in the form a.b.c If not '
|
|
'specified, the tool will remove the oldest version in the chroot',
|
|
)
|
|
|
|
subparser_names.append('roll')
|
|
roll_parser = subparsers.add_parser(
|
|
'roll',
|
|
parents=[create_parser_template],
|
|
help='A command can create and upload a Rust uprev CL, including '
|
|
'preparing the repo, creating new Rust uprev, deleting old uprev, '
|
|
'and upload a CL to crrev.',
|
|
)
|
|
roll_parser.add_argument(
|
|
'--uprev',
|
|
type=RustVersion.parse,
|
|
required=True,
|
|
help='Rust version to uprev to, in the form a.b.c',
|
|
)
|
|
roll_parser.add_argument(
|
|
'--remove',
|
|
type=RustVersion.parse,
|
|
default=None,
|
|
help='Rust version to remove, in the form a.b.c If not '
|
|
'specified, the tool will remove the oldest version in the chroot',
|
|
)
|
|
roll_parser.add_argument(
|
|
'--skip_cross_compiler',
|
|
action='store_true',
|
|
help='Skip updating cross-compiler in the chroot',
|
|
)
|
|
roll_parser.add_argument(
|
|
'--no_upload',
|
|
action='store_true',
|
|
help='If specified, the tool will not upload the CL for review',
|
|
)
|
|
|
|
args = parser.parse_args()
|
|
if args.subparser_name not in subparser_names:
|
|
parser.error('one of %s must be specified' % subparser_names)
|
|
|
|
if args.cont and args.restart:
|
|
parser.error('Please select either --continue or --restart')
|
|
|
|
if os.path.exists(args.state_file):
|
|
if not args.cont and not args.restart:
|
|
parser.error('State file exists, so you should either --continue '
|
|
'or --restart')
|
|
if args.cont and not os.path.exists(args.state_file):
|
|
parser.error('Indicate --continue but the state file does not exist')
|
|
|
|
if args.restart and os.path.exists(args.state_file):
|
|
os.remove(args.state_file)
|
|
|
|
return args
|
|
|
|
|
|
def 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]]:
|
|
if template is None:
|
|
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
|
|
|
|
if rust_version <= template_version:
|
|
logging.info(
|
|
'Requested version %s is not newer than the template version %s.',
|
|
rust_version, template_version)
|
|
return None
|
|
|
|
logging.info('Template Rust version is %s (ebuild: %r)', template_version,
|
|
ebuild_path)
|
|
return template_version, ebuild_path
|
|
|
|
|
|
def copy_patches(template_version: RustVersion,
|
|
new_version: RustVersion) -> None:
|
|
patch_path = os.path.join(RUST_PATH, 'files')
|
|
for f in os.listdir(patch_path):
|
|
if f'rust-{template_version}' not in f:
|
|
continue
|
|
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'files/rust-{new_version}-*.patch'],
|
|
cwd=RUST_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'))
|
|
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:
|
|
restrict_re = re.compile(
|
|
r'(?P<before>RESTRICT=")(?P<values>"[^"]*"|.*)(?P<after>")')
|
|
with open(ebuild_file, encoding='utf-8') as f:
|
|
contents = f.read()
|
|
m = restrict_re.search(contents)
|
|
assert m, 'failed to find RESTRICT variable in Rust ebuild'
|
|
values = m.group('values')
|
|
if add:
|
|
if 'mirror' in values:
|
|
return
|
|
values += ' mirror'
|
|
else:
|
|
if 'mirror' not in values:
|
|
return
|
|
values = values.replace(' mirror', '')
|
|
new_contents = restrict_re.sub(r'\g<before>%s\g<after>' % values, contents)
|
|
with open(ebuild_file, 'w', encoding='utf-8') as f:
|
|
f.write(new_contents)
|
|
|
|
|
|
def 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 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 = os.path.join(
|
|
RUST_PATH, '../../profiles/targets/chromeos/package.provided')
|
|
with open(package_file, encoding='utf-8') as f:
|
|
contents = f.read()
|
|
if add:
|
|
rust_packages_re = re.compile(r'dev-lang/rust-(\d+\.\d+\.\d+)')
|
|
rust_packages = rust_packages_re.findall(contents)
|
|
# Assume all the rust packages are in alphabetical order, so insert the new
|
|
# version to the place after the last rust_packages
|
|
new_str = f'dev-lang/rust-{rust_version}'
|
|
new_contents = contents.replace(rust_packages[-1],
|
|
f'{rust_packages[-1]}\n{new_str}')
|
|
logging.info('%s has been inserted into package.provided', new_str)
|
|
else:
|
|
old_str = f'dev-lang/rust-{rust_version}\n'
|
|
assert old_str in contents, f'{old_str!r} not found in package.provided'
|
|
new_contents = contents.replace(old_str, '')
|
|
logging.info('%s has been removed from package.provided', old_str)
|
|
|
|
with open(package_file, 'w', encoding='utf-8') as f:
|
|
f.write(new_contents)
|
|
|
|
|
|
def update_virtual_rust(template_version: RustVersion,
|
|
new_version: RustVersion) -> None:
|
|
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,
|
|
tmp_state_file: pathlib.Path,
|
|
completed_steps: Dict[str, Any],
|
|
step_name: str,
|
|
step_fn: Callable[[], T],
|
|
result_from_json: Optional[Callable[[Any], T]] = None,
|
|
result_to_json: Optional[Callable[[T], Any]] = None) -> T:
|
|
if step_name in completed_steps:
|
|
logging.info('Skipping previously completed step %s', step_name)
|
|
if result_from_json:
|
|
return result_from_json(completed_steps[step_name])
|
|
return completed_steps[step_name]
|
|
|
|
logging.info('Running step %s', step_name)
|
|
val = step_fn()
|
|
logging.info('Step %s complete', step_name)
|
|
if result_to_json:
|
|
completed_steps[step_name] = result_to_json(val)
|
|
else:
|
|
completed_steps[step_name] = val
|
|
|
|
with tmp_state_file.open('w', encoding='utf-8') as f:
|
|
json.dump(completed_steps, f, indent=4)
|
|
tmp_state_file.rename(state_file)
|
|
return val
|
|
|
|
|
|
def prepare_uprev_from_json(obj: Any) -> Optional[Tuple[RustVersion, str]]:
|
|
if not obj:
|
|
return None
|
|
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:
|
|
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,
|
|
)
|
|
if template_ebuild is None:
|
|
return
|
|
|
|
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, 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(
|
|
ebuild_file))
|
|
if not skip_compile:
|
|
run_step('emerge rust', lambda: subprocess.check_call(
|
|
['sudo', 'emerge', 'dev-lang/rust']))
|
|
run_step('insert version into rust packages', lambda: update_rust_packages(
|
|
rust_version, add=True))
|
|
run_step('upgrade virtual/rust', lambda: update_virtual_rust(
|
|
template_version, rust_version))
|
|
|
|
|
|
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')]
|
|
|
|
|
|
def find_oldest_rust_version_in_chroot() -> Tuple[RustVersion, str]:
|
|
rust_versions = find_rust_versions_in_chroot()
|
|
if len(rust_versions) <= 1:
|
|
raise RuntimeError('Expect to find more than one Rust versions')
|
|
return min(rust_versions)
|
|
|
|
|
|
def find_ebuild_for_rust_version(version: RustVersion) -> str:
|
|
rust_ebuilds = [
|
|
ebuild for x, ebuild in find_rust_versions_in_chroot() if x == version
|
|
]
|
|
if not rust_ebuilds:
|
|
raise ValueError(f'No Rust ebuilds found matching {version}')
|
|
if len(rust_ebuilds) > 1:
|
|
raise ValueError(f'Multiple Rust ebuilds found matching {version}: '
|
|
f'{rust_ebuilds}')
|
|
return rust_ebuilds[0]
|
|
|
|
|
|
def remove_files(filename: str, path: str) -> None:
|
|
subprocess.check_call(['git', 'rm', filename], cwd=path)
|
|
|
|
|
|
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()
|
|
|
|
delete_version, delete_ebuild = run_step(
|
|
'find rust version to delete',
|
|
find_desired_rust_version,
|
|
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 = 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_files(
|
|
f'rust-{delete_version}.ebuild',
|
|
os.path.join(RUST_PATH, '../../virtual/rust')))
|
|
|
|
|
|
def create_new_repo(rust_version: RustVersion) -> None:
|
|
output = get_command_output(['git', 'status', '--porcelain'], cwd=RUST_PATH)
|
|
if output:
|
|
raise RuntimeError(
|
|
f'{RUST_PATH} has uncommitted changes, please either discard them '
|
|
'or commit them.')
|
|
git.CreateBranch(RUST_PATH, f'rust-to-{rust_version}')
|
|
|
|
|
|
def build_cross_compiler() -> None:
|
|
# Get target triples in ebuild
|
|
rust_ebuild = get_command_output(['equery', 'w', 'rust'])
|
|
with open(rust_ebuild, encoding='utf-8') as f:
|
|
contents = f.read()
|
|
|
|
target_triples_re = re.compile(r'RUSTC_TARGET_TRIPLES=\(([^)]+)\)')
|
|
m = target_triples_re.search(contents)
|
|
assert m, 'RUST_TARGET_TRIPLES not found in rust ebuild'
|
|
target_triples = m.group(1).strip().split('\n')
|
|
|
|
compiler_targets_to_install = [
|
|
target.strip() for target in target_triples if 'cros-' in target
|
|
]
|
|
for target in target_triples:
|
|
if 'cros-' not in target:
|
|
continue
|
|
target = target.strip()
|
|
|
|
# We also always need arm-none-eabi, though it's not mentioned in
|
|
# RUSTC_TARGET_TRIPLES.
|
|
compiler_targets_to_install.append('arm-none-eabi')
|
|
|
|
logging.info('Emerging cross compilers %s', compiler_targets_to_install)
|
|
subprocess.check_call(
|
|
['sudo', 'emerge', '-j', '-G'] +
|
|
[f'cross-{target}/gcc' for target in compiler_targets_to_install])
|
|
|
|
|
|
def create_new_commit(rust_version: RustVersion) -> None:
|
|
subprocess.check_call(['git', 'add', '-A'], cwd=RUST_PATH)
|
|
messages = [
|
|
f'[DO NOT SUBMIT] dev-lang/rust: upgrade to Rust {rust_version}',
|
|
'',
|
|
'This CL is created by rust_uprev tool automatically.'
|
|
'',
|
|
'BUG=None',
|
|
'TEST=Use CQ to test the new Rust version',
|
|
]
|
|
git.UploadChanges(RUST_PATH, f'rust-to-{rust_version}', messages)
|
|
|
|
|
|
def main() -> None:
|
|
if not chroot.InChroot():
|
|
raise RuntimeError('This script must be executed inside chroot')
|
|
|
|
logging.basicConfig(level=logging.INFO)
|
|
|
|
args = parse_commandline_args()
|
|
|
|
state_file = pathlib.Path(args.state_file)
|
|
tmp_state_file = state_file.with_suffix('.tmp')
|
|
|
|
try:
|
|
with state_file.open(encoding='utf-8') as f:
|
|
completed_steps = json.load(f)
|
|
except FileNotFoundError:
|
|
completed_steps = {}
|
|
|
|
def run_step(
|
|
step_name: str,
|
|
step_fn: Callable[[], T],
|
|
result_from_json: Optional[Callable[[Any], T]] = None,
|
|
result_to_json: Optional[Callable[[T], Any]] = None,
|
|
) -> T:
|
|
return perform_step(state_file, tmp_state_file, completed_steps, step_name,
|
|
step_fn, result_from_json, result_to_json)
|
|
|
|
if args.subparser_name == 'create':
|
|
create_rust_uprev(args.rust_version, args.template, args.skip_compile,
|
|
run_step)
|
|
elif args.subparser_name == 'remove':
|
|
remove_rust_uprev(args.rust_version, run_step)
|
|
else:
|
|
# If you have added more subparser_name, please also add the handlers above
|
|
assert args.subparser_name == 'roll'
|
|
run_step('create new repo', lambda: create_new_repo(args.uprev))
|
|
if not args.skip_cross_compiler:
|
|
run_step('build cross compiler', build_cross_compiler)
|
|
create_rust_uprev(args.uprev, args.template, args.skip_compile, run_step)
|
|
remove_rust_uprev(args.remove, run_step)
|
|
if not args.no_upload:
|
|
run_step('create rust uprev CL', lambda: create_new_commit(args.uprev))
|
|
|
|
|
|
if __name__ == '__main__':
|
|
sys.exit(main())
|