blob: ffca1f39fb3708d4922903aca0b72fb26eea1cdf [file] [log] [blame]
#!/usr/bin/env vpython3
# Copyright 2025 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
# This module contains utilities for working with information from
# `//third_party/rust/chromium_crates_io/Cargo.lock`.
#
# Throughout the module, the following naming conventions are used (illustrated
# with a crate named `syn` and published as version `2.0.50`):
#
# * `crate_name` : "syn" string
# * `crate_version`: "2.0.50" string
# * `crate_id` : "syn@2.0.50" string (syntax used by `cargo`)
# * `crate_epoch` : "v2" string (syntax used in dir names under
# //third_party/rust/<crate name>/<crate epoch>)
#
# Note that `crate_name` may not be unique (e.g. if there is both `syn@1.0.109`
# and `syn@2.0.50`). Also note that f`{crate_name}@{crate_epoch}` doesn't
# change during a minor version update (such as the one that this script
import os
import sys
import toml
from typing import Set
THIS_DIR = os.path.dirname(__file__)
CHROMIUM_DIR = os.path.normpath(os.path.join(THIS_DIR, '..', '..', '..'))
THIRD_PARTY_RUST_DIR = os.path.join(CHROMIUM_DIR, "third_party", "rust")
CRATES_DIR = os.path.join(THIRD_PARTY_RUST_DIR, "chromium_crates_io")
CARGO_LOCK_FILEPATH = os.path.join(CRATES_DIR, 'Cargo.lock')
VENDOR_DIR = os.path.join(CRATES_DIR, "vendor")
def GetCurrentCrateIds() -> Set[str]:
"""Parses Cargo.lock and returns a set of crate ids.
Example return value: `set(["serde@1.0.197", "syn@2.0.50", ...])`.
"""
with open(CARGO_LOCK_FILEPATH) as f:
t = toml.load(f)
result = set()
for p in t["package"]:
name = p["name"]
version = p["version"]
crate_id = f"{name}@{version}"
assert crate_id not in result
result.add(crate_id)
return result
def ConvertCrateIdToCrateEpoch(crate_id: str) -> str:
_AssertIsCrateId(crate_id)
crate_version = ConvertCrateIdToCrateVersion(crate_id)
v = crate_version.split('.')
if len(v) > 0 and v[0] != '0':
return f'v{v[0]}'
if len(v) > 1 and v[1] != '0':
return f'v0_{v[1]}'
assert len(v) > 2
return f'v0_0_{v[2]}'
def ConvertCrateIdToCrateName(crate_id: str) -> str:
""" Converts a `crate_id` into a `crate_name`."""
_AssertIsCrateId(crate_id)
return crate_id[:crate_id.find("@")]
def ConvertCrateIdToCrateVersion(crate_id: str) -> str:
""" Converts a `crate_id` into a `crate_version`."""
_AssertIsCrateId(crate_id)
crate_version = crate_id[crate_id.find("@") + 1:]
return crate_version
def ConvertCrateIdToBuildDir(crate_id: str) -> str:
""" Converts a `crate_id` (e.g. "foo@1.2.3") into a path to an epoch dir.
Example return value:
`"<path to chromium root>\\third_party\\rust\\foo\\v1"`
"""
_AssertIsCrateId(crate_id)
return os.path.join(
CHROMIUM_DIR, _ConvertCrateIdToBuildDirRelativeToChromiumRoot(crate_id))
def ConvertCrateIdToVendorDir(crate_id: str) -> str:
""" Converts a `crate_id` (e.g. "foo@1.2.3") into a path to a target dir.
Example return value:
`"<path to chromium root>\\third_party\\rust\\chromium_crates_io\\vendor\\foo-v1"`
"""
_AssertIsCrateId(crate_id)
crate_name = ConvertCrateIdToCrateName(crate_id)
crate_epoch = ConvertCrateIdToCrateEpoch(crate_id)
crate_vendor_dir = os.path.join(VENDOR_DIR, f"{crate_name}-{crate_epoch}")
return crate_vendor_dir
def ConvertCrateIdToGnLabel(crate_id: str) -> str:
""" Converts a `crate_id` (e.g. "foo@1.2.3") into a GN label.
See also
https://gn.googlesource.com/gn/+/main/docs/reference.md#labels
Example return value: `"//third_party/rust/foo/v1:lib"`
"""
_AssertIsCrateId(crate_id)
dir_name = _ConvertCrateIdToBuildDirRelativeToChromiumRoot(crate_id)
dir_name = dir_name.replace(os.sep, "/") # GN uses `/` as a path separator.
return f"//{dir_name}:lib"
def IsPlaceholderCrate(crate_id: str) -> bool:
""" Determines if `crate_id` corresponds to a placeholder package.
See also `//tools/crates/gnrt/removed_crate.md`.
"""
_AssertIsCrateId(crate_id)
vendor_dir = ConvertCrateIdToVendorDir(crate_id)
crate_cargo_toml_path = os.path.join(vendor_dir, "Cargo.toml")
KNOWN_PATTERN = "@generated from `tools/crates/gnrt/removed_Cargo.toml.hbs`"
try:
with open(crate_cargo_toml_path) as f:
return KNOWN_PATTERN in f.read()
except:
return False
def GetPlaceholderCrateIdForTesting() -> str:
""" Test helper for getting a placeholder `crate_id` like `"cc-1.2.22"`.
"""
# This test helper assumes that `cc` is listed in
# `chromium_crates_io/Cargo.lock` and that
# `chromium_crates_io/vendor/cc...` contains a placeholder crate (see
# `tools/crates/gnrt/removed_crate.md`).
#
# Unit tests that depend on external state are a bit icky... But it
# seems that this assumption should hold "forever" + this helps write
# more tests for other stuff, so let ignore the ickiness...
return list(filter(lambda crate_id: "cc@" in crate_id,
GetCurrentCrateIds()))[0]
def _ConvertCrateIdToBuildDirRelativeToChromiumRoot(crate_id: str) -> str:
""" Converts a `crate_id` (e.g. "foo@1.2.3") into an epoch dir.
The returned dir is relative to Chromium root.
Example return value: `"//third_party/rust/foo/v1"`
"""
crate_name = ConvertCrateIdToCrateName(crate_id)
crate_name = crate_name.replace("-", "_")
epoch = ConvertCrateIdToCrateEpoch(crate_id)
target = os.path.join("third_party", "rust", crate_name, epoch)
return target
def _AssertIsCrateId(crate_id: str) -> bool:
if not isinstance(crate_id, str) or "@" not in crate_id:
raise RuntimeError(f"This is not a valid crate id: {crate_id}")
assert __name__ != '__main__'