# python3 # Copyright 2021 The Chromium Authors. All rights reserved. # 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 lib import consts def _find_chromium_root(cwd: str) -> tuple[list[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 = [] if tail == "." else [tail] return split_path(head) + tail 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) -> 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: """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 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)