summaryrefslogtreecommitdiffstats
path: root/chromium/tools/crates/lib/common.py
blob: 3e52da5b36df0384f9cb7ed9002a2d6f82d2e4c1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
# 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)