blob: 865df0080704788d333418e3c806becd09fafb29 [file] [log] [blame]
# python3
# Copyright 2021 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Helper functions for general tasks.
Helps for things such as generating versions, paths, and crate names."""
from __future__ import annotations
import os
import sys
from typing import Any, List
from lib import consts
def _find_chromium_root(cwd: str) -> list[str]:
"""Finds and returns the path from the root of the Chromium tree."""
# This file is at tools/crates/lib/common.py, so 4 * '..' will take us up
# to the root chromium dir.
path_components_to_root = [os.path.abspath(__file__)] + [os.pardir] * 4
abs_root_path = os.path.join(*path_components_to_root)
path_from_root = os.path.relpath(cwd, abs_root_path)
def split_path(p: str) -> list[str]:
if not p: return []
head, tail = os.path.split(p)
tail_l = [] if tail == "." else [tail]
return split_path(head) + tail_l
return split_path(path_from_root)
# The path from the root of the chromium tree to the current working directory
# as a list of path components. If chromium's src.git is rooted at `/f/b/src``,
# and the this tool is run from `/f/b/src/in/there`, then the value here would
# be `["in", "there"]`. If the tool is run from the root `/f/b/src` then the
# value here is `[]`.
_PATH_FROM_CHROMIUM_ROOT = _find_chromium_root(os.getcwd())
def crate_name_normalized(crate_name: str) -> str:
"""Normalizes a crate name for GN and file paths."""
return crate_name.replace("-", "_").replace(".", "_")
def version_is_complete(version_str: str) -> bool:
"""Returns whether the `version_str` is fully resolved or not.
A full version includes MAJOR.MINOR.PATCH components."""
parts = _version_to_parts(version_str)
# This supports semmvers with pre-release and build flags.
# https://semver.org/#backusnaur-form-grammar-for-valid-semver-versions
return len(parts) >= 3
def _version_to_parts(version_str: str) -> list[str]:
"""Converts a version string into its MAJOR.MINOR.PATCH parts."""
# TODO(danakj): This does not support pre-release or build versions such as
# 1.0.0-alpha.1 or 1.0.0+1234 at this time. We only need support it if we
# want to include such a crate in our tree.
# https://semver.org/#backusnaur-form-grammar-for-valid-semver-versions
# TODO(danakj): It would be real nice to introduce a SemmVer type instead of
# using strings, which sometimes hold partial versions, and sometimes use
# dots as separators or underscores.
parts = version_str.split(".")
assert len(parts) >= 1 and len(parts) <= 3, \
"The version \"{}\" is an invalid semmver.".format(version_str)
return parts
def version_epoch_dots(version_str: str) -> str:
"""Returns a version epoch from a given version string.
Returns a string with `.` as the component separator."""
parts = _version_to_parts(version_str)
if parts[0] != "0":
return ".".join(parts[:1])
elif parts[1] != "0":
return ".".join(parts[:2])
else:
return ".".join(parts[:3])
def version_epoch_normalized(version_str: str) -> str:
"""Returns a version epoch from a given version string.
Returns a string with `_` as the component separator."""
parts = _version_to_parts(version_str)
if parts[0] != "0":
return "_".join(parts[:1])
elif parts[1] != "0":
return "_".join(parts[:2])
else:
return "_".join(parts[:3])
def gn_third_party_path(rel_path: list[str] = []) -> str:
"""Returns the full GN path to the root of all third_party crates."""
path = _PATH_FROM_CHROMIUM_ROOT + consts.THIRD_PARTY
return "//" + "/".join(path + rel_path)
def gn_crate_path(crate_name: str, version: str,
rel_path: list[str] = []) -> str:
"""Returns the full GN path to a crate that is in third_party. This is the
path to the crate's BUILD.gn file."""
name = crate_name_normalized(crate_name)
epoch = "v" + version_epoch_normalized(version)
path = _PATH_FROM_CHROMIUM_ROOT + consts.THIRD_PARTY + [name, epoch]
return "//" + "/".join(path + rel_path)
def os_third_party_dir(rel_path: list[str] = []) -> str:
"""The relative OS disk path to the top of the third party Rust directory
where all third party crates are found, along with third_party.toml."""
return os.path.join(*consts.THIRD_PARTY, *rel_path)
def os_crate_name_dir(crate_name: str, rel_path: list[str] = []) -> str:
"""The relative OS disk path to a third party crate's top-most directory
where all versions of that crate are found."""
return os_third_party_dir(rel_path=[crate_name_normalized(crate_name)] +
rel_path)
def os_crate_version_dir(crate_name: str,
version: str,
rel_path: list[str] = []) -> str:
"""The relative OS disk path to a third party crate's versioned directory
where BUILD.gn and README.chromium are found."""
epoch = "v" + version_epoch_normalized(version)
return os_crate_name_dir(crate_name, rel_path=[epoch] + rel_path)
def os_crate_cargo_dir(crate_name: str, version: str,
rel_path: list[str] = []) -> str:
"""The relative OS disk path to a third party crate's Cargo root.
This directory is where Cargo.toml and the Rust source files are found. This
is where the crate is extracted when it is downloaded."""
return os_crate_version_dir(crate_name,
version,
rel_path=consts.CRATE_INNER_DIR + rel_path)
def crate_download_url(crate: str, version: str) -> str:
"""Returns the crates.io URL to download the crate."""
return consts.CRATES_IO_DOWNLOAD.format(crate=crate, version=version)
def crate_view_url(crate: str) -> str:
"""Returns the crates.io URL to see info about the crate."""
return consts.CRATES_IO_VIEW.format(crate=crate)
def load_toml(path: str) -> dict[str, Any]:
"""Loads a file at the path and parses it as a TOML file.
This is a helper for times when you don't need the raw text content of the
TOML file.
Returns:
A dictionary of the contents of the TOML file."""
with open(path, "r") as cargo_file:
toml_string = cargo_file.read()
import toml
return dict(toml.loads(toml_string))
def print_same_line(s: str, fill_num_chars: int, done: bool = False) -> int:
"""A helper to repeatedly print to the same line.
Args:
s: The text to be printed.
fill_num_chars: This should be `0` on the first call to
print_same_line() for a series of prints to the same output line. Then
it should be the return value of the previous call to
print_same_line() repeatedly until `done` is True, at which time the
cursor will be moved to the next output line.
done: On the final call to print_same_line() for a given line of output,
pass `True` to have the cursor move to the next line.
Returns:
The number of characters that were written, which should be passed as
`fill_num_chars` on the next call. At the end of printing over the same
line, finish by calling with `done` set to true, which will move to the
next line."""
s += " " * (fill_num_chars - len(s))
if not done:
print("\r" + s, end="")
else:
print("\r" + s)
return len(s)