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.
178 lines
5.6 KiB
178 lines
5.6 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.
|
|
|
|
"""Help creating a Rust ebuild with CRATES.
|
|
|
|
This script is meant to help someone creating a Rust ebuild of the type
|
|
currently used by sys-apps/ripgrep and sys-apps/rust-analyzer.
|
|
|
|
In these ebuilds, the CRATES variable is used to list all dependencies, rather
|
|
than creating an ebuild for each dependency. This style of ebuild can be used
|
|
for a crate which is only intended for use in the chromiumos SDK, and which has
|
|
many dependencies which otherwise won't be used.
|
|
|
|
To create such an ebuild, there are essentially two tasks that must be done:
|
|
|
|
1. Determine all transitive dependent crates and version and list them in the
|
|
CRATES variable. Ignore crates that are already included in the main crate's
|
|
repository.
|
|
|
|
2. Find which dependent crates are not already on a chromeos mirror, retrieve
|
|
them from crates.io, and upload them to `gs://chromeos-localmirror/distfiles`.
|
|
|
|
This script parses the crate's lockfile to list transitive dependent crates,
|
|
and either lists crates to be uploaded or actually uploads them.
|
|
|
|
Of course these can be done manually instead. If you choose to do these steps
|
|
manually, I recommend *not* using the `cargo download` tool, and instead obtain
|
|
dependent crates at
|
|
`https://crates.io/api/v1/crates/{crate_name}/{crate_version}/download`.
|
|
|
|
Example usage:
|
|
|
|
# Here we instruct the script to ignore crateA and crateB, presumably
|
|
# because they are already included in the same repository as some-crate.
|
|
# This will not actually upload any crates to `gs`.
|
|
python3 crate_ebuild_help.py --lockfile some-crate/Cargo.lock \
|
|
--ignore crateA --ignore crateB --dry-run
|
|
|
|
# Similar to the above, but here we'll actually carry out the uploads.
|
|
python3 crate_ebuild_help.py --lockfile some-crate/Cargo.lock \
|
|
--ignore crateA --ignore crateB
|
|
|
|
See the ebuild files for ripgrep or rust-analyzer for other details.
|
|
"""
|
|
|
|
import argparse
|
|
import concurrent.futures
|
|
from pathlib import Path
|
|
import subprocess
|
|
import tempfile
|
|
from typing import List, Tuple
|
|
import urllib.request
|
|
|
|
# Python 3.11 has `tomllib`, so maybe eventually we can switch to that.
|
|
import toml
|
|
|
|
|
|
def run(args: List[str]) -> bool:
|
|
result = subprocess.run(
|
|
args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=False
|
|
)
|
|
return result.returncode == 0
|
|
|
|
|
|
def run_check(args: List[str]):
|
|
subprocess.run(
|
|
args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=True
|
|
)
|
|
|
|
|
|
def gs_address_exists(address: str) -> bool:
|
|
# returns False if the file isn't there
|
|
return run(["gsutil.py", "ls", address])
|
|
|
|
|
|
def crate_already_uploaded(crate_name: str, crate_version: str) -> bool:
|
|
filename = f"{crate_name}-{crate_version}.crate"
|
|
return gs_address_exists(
|
|
f"gs://chromeos-localmirror/distfiles/{filename}"
|
|
) or gs_address_exists(f"gs://chromeos-mirror/gentoo/distfiles/{filename}")
|
|
|
|
|
|
def download_crate(crate_name: str, crate_version: str, localpath: Path):
|
|
urllib.request.urlretrieve(
|
|
f"https://crates.io/api/v1/crates/{crate_name}/{crate_version}/download",
|
|
localpath,
|
|
)
|
|
|
|
|
|
def upload_crate(crate_name: str, crate_version: str, localpath: Path):
|
|
run_check(
|
|
[
|
|
"gsutil.py",
|
|
"cp",
|
|
"-n",
|
|
"-a",
|
|
"public-read",
|
|
str(localpath),
|
|
f"gs://chromeos-localmirror/distfiles/{crate_name}-{crate_version}.crate",
|
|
]
|
|
)
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(
|
|
description="Help prepare a Rust crate for an ebuild."
|
|
)
|
|
parser.add_argument(
|
|
"--lockfile",
|
|
type=str,
|
|
required=True,
|
|
help="Path to the lockfile of the crate in question.",
|
|
)
|
|
parser.add_argument(
|
|
"--ignore",
|
|
type=str,
|
|
action="append",
|
|
required=False,
|
|
default=[],
|
|
help="Ignore the crate by this name (may be used multiple times).",
|
|
)
|
|
parser.add_argument(
|
|
"--dry-run",
|
|
action="store_true",
|
|
help="Don't actually download/upload crates, just print their names.",
|
|
)
|
|
ns = parser.parse_args()
|
|
|
|
to_ignore = set(ns.ignore)
|
|
|
|
toml_contents = toml.load(ns.lockfile)
|
|
packages = toml_contents["package"]
|
|
|
|
crates = [
|
|
(pkg["name"], pkg["version"])
|
|
for pkg in packages
|
|
if pkg["name"] not in to_ignore
|
|
]
|
|
crates.sort()
|
|
|
|
print("Dependent crates:")
|
|
for name, version in crates:
|
|
print(f"{name}-{version}")
|
|
print()
|
|
|
|
if ns.dry_run:
|
|
print("Crates that would be uploaded (skipping ones already uploaded):")
|
|
else:
|
|
print("Uploading crates (skipping ones already uploaded):")
|
|
|
|
def maybe_upload(crate: Tuple[str, str]) -> str:
|
|
name, version = crate
|
|
if crate_already_uploaded(name, version):
|
|
return ""
|
|
if not ns.dry_run:
|
|
with tempfile.TemporaryDirectory() as temp_dir:
|
|
path = Path(temp_dir.name, f"{name}-{version}.crate")
|
|
download_crate(name, version, path)
|
|
upload_crate(name, version, path)
|
|
return f"{name}-{version}"
|
|
|
|
# Simple benchmarking on my machine with rust-analyzer's Cargo.lock, using
|
|
# the --dry-run option, gives a wall time of 277 seconds with max_workers=1
|
|
# and 70 seconds with max_workers=4.
|
|
with concurrent.futures.ThreadPoolExecutor(max_workers=4) as executor:
|
|
crates_len = len(crates)
|
|
for i, s in enumerate(executor.map(maybe_upload, crates)):
|
|
if s:
|
|
j = i + 1
|
|
print(f"[{j}/{crates_len}] {s}")
|
|
print()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|