You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
193 lines
5.4 KiB
193 lines
5.4 KiB
#!/usr/bin/env python3
|
|
# Copyright 2022 The ChromiumOS Authors.
|
|
# Use of this source code is governed by a BSD-style license that can be
|
|
# found in the LICENSE file.
|
|
|
|
"""Copies rust-bootstrap artifacts from an SDK build to localmirror.
|
|
|
|
We use localmirror to host these artifacts, but they've changed a bit over
|
|
time, so simply `gsutil.py cp $FROM $TO` doesn't work. This script allows the
|
|
convenience of the old `cp` command.
|
|
"""
|
|
|
|
import argparse
|
|
import logging
|
|
import os
|
|
from pathlib import Path
|
|
import shutil
|
|
import subprocess
|
|
import sys
|
|
import tempfile
|
|
from typing import List
|
|
|
|
|
|
_LOCALMIRROR_ROOT = 'gs://chromeos-localmirror/distfiles/'
|
|
|
|
|
|
def _is_in_chroot() -> bool:
|
|
return Path('/etc/cros_chroot_version').exists()
|
|
|
|
|
|
def _ensure_pbzip2_is_installed():
|
|
if shutil.which('pbzip2'):
|
|
return
|
|
|
|
logging.info('Auto-installing pbzip2...')
|
|
subprocess.run(['sudo', 'emerge', '-G', 'pbzip2'], check=True)
|
|
|
|
|
|
def _determine_target_path(sdk_path: str) -> str:
|
|
"""Determine where `sdk_path` should sit in localmirror."""
|
|
gs_prefix = 'gs://'
|
|
if not sdk_path.startswith(gs_prefix):
|
|
raise ValueError(f'Invalid GS path: {sdk_path!r}')
|
|
|
|
file_name = Path(sdk_path[len(gs_prefix):]).name
|
|
return _LOCALMIRROR_ROOT + file_name
|
|
|
|
|
|
def _download(remote_path: str, local_file: Path):
|
|
"""Downloads the given gs:// path to the given local file."""
|
|
logging.info('Downloading %s -> %s', remote_path, local_file)
|
|
subprocess.run(
|
|
['gsutil.py', 'cp', remote_path,
|
|
str(local_file)],
|
|
check=True,
|
|
)
|
|
|
|
|
|
def _debinpkgify(binpkg_file: Path) -> Path:
|
|
"""Converts a binpkg into the files it installs.
|
|
|
|
Note that this function makes temporary files in the same directory as
|
|
`binpkg_file`. It makes no attempt to clean them up.
|
|
"""
|
|
logging.info('Converting %s from a binpkg...', binpkg_file)
|
|
|
|
# The SDK builder produces binary packages:
|
|
# https://wiki.gentoo.org/wiki/Binary_package_guide
|
|
#
|
|
# Which means that `binpkg_file` is in the XPAK format. We want to split
|
|
# that out, and recompress it from zstd (which is the compression format
|
|
# that CrOS uses) to bzip2 (which is what we've historically used, and
|
|
# which is what our ebuild expects).
|
|
tmpdir = binpkg_file.parent
|
|
|
|
def _mkstemp(suffix=None) -> str:
|
|
fd, file_path = tempfile.mkstemp(dir=tmpdir, suffix=suffix)
|
|
os.close(fd)
|
|
return Path(file_path)
|
|
|
|
# First, split the actual artifacts that land in the chroot out to
|
|
# `temp_file`.
|
|
artifacts_file = _mkstemp()
|
|
logging.info('Extracting artifacts from %s into %s...', binpkg_file,
|
|
artifacts_file)
|
|
with artifacts_file.open('wb') as f:
|
|
subprocess.run(
|
|
[
|
|
'qtbz2',
|
|
'-s',
|
|
'-t',
|
|
'-O',
|
|
str(binpkg_file),
|
|
],
|
|
check=True,
|
|
stdout=f,
|
|
)
|
|
|
|
decompressed_artifacts_file = _mkstemp()
|
|
decompressed_artifacts_file.unlink()
|
|
logging.info('Decompressing artifacts from %s to %s...', artifacts_file,
|
|
decompressed_artifacts_file)
|
|
subprocess.run(
|
|
[
|
|
'zstd',
|
|
'-d',
|
|
str(artifacts_file),
|
|
'-o',
|
|
str(decompressed_artifacts_file),
|
|
],
|
|
check=True,
|
|
)
|
|
|
|
# Finally, recompress it as a tbz2.
|
|
tbz2_file = _mkstemp('.tbz2')
|
|
logging.info(
|
|
'Recompressing artifacts from %s to %s (this may take a while)...',
|
|
decompressed_artifacts_file, tbz2_file)
|
|
with tbz2_file.open('wb') as f:
|
|
subprocess.run(
|
|
[
|
|
'pbzip2',
|
|
'-9',
|
|
'-c',
|
|
str(decompressed_artifacts_file),
|
|
],
|
|
check=True,
|
|
stdout=f,
|
|
)
|
|
return tbz2_file
|
|
|
|
|
|
def _upload(local_file: Path, remote_path: str, force: bool):
|
|
"""Uploads the local file to the given gs:// path."""
|
|
logging.info('Uploading %s -> %s', local_file, remote_path)
|
|
cmd_base = ['gsutil.py', 'cp', '-a', 'public-read']
|
|
if not force:
|
|
cmd_base.append('-n')
|
|
subprocess.run(
|
|
cmd_base + [str(local_file), remote_path],
|
|
check=True,
|
|
stdin=subprocess.DEVNULL,
|
|
)
|
|
|
|
|
|
def main(argv: List[str]):
|
|
logging.basicConfig(
|
|
format='>> %(asctime)s: %(levelname)s: %(filename)s:%(lineno)d: '
|
|
'%(message)s',
|
|
level=logging.INFO,
|
|
)
|
|
|
|
parser = argparse.ArgumentParser(
|
|
description=__doc__,
|
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
)
|
|
|
|
parser.add_argument(
|
|
'sdk_artifact',
|
|
help='Path to the SDK rust-bootstrap artifact to copy. e.g., '
|
|
'gs://chromeos-prebuilt/host/amd64/amd64-host/'
|
|
'chroot-2022.07.12.134334/packages/dev-lang/'
|
|
'rust-bootstrap-1.59.0.tbz2.')
|
|
parser.add_argument(
|
|
'-n',
|
|
'--dry-run',
|
|
action='store_true',
|
|
help='Do everything except actually uploading the artifact.')
|
|
parser.add_argument(
|
|
'--force',
|
|
action='store_true',
|
|
help='Upload the artifact even if one exists in localmirror already.')
|
|
opts = parser.parse_args(argv)
|
|
|
|
if not _is_in_chroot():
|
|
parser.error('Run me from within the chroot.')
|
|
_ensure_pbzip2_is_installed()
|
|
|
|
target_path = _determine_target_path(opts.sdk_artifact)
|
|
with tempfile.TemporaryDirectory() as tempdir:
|
|
download_path = Path(tempdir) / 'sdk_artifact'
|
|
_download(opts.sdk_artifact, download_path)
|
|
file_to_upload = _debinpkgify(download_path)
|
|
if opts.dry_run:
|
|
logging.info('--dry-run specified; skipping upload of %s to %s',
|
|
file_to_upload, target_path)
|
|
else:
|
|
_upload(file_to_upload, target_path, opts.force)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main(sys.argv[1:])
|