chromium/ash/webui/recorder_app_ui/resources/scripts/cra/util.py

# Copyright 2024 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

import functools
import logging
import pathlib
import shlex
import subprocess
from typing import Optional

from cra import cli


@functools.cache
def get_cra_root() -> pathlib.Path:
    """Gets the `resources` folder of recorder app."""
    file_path = pathlib.Path(__file__).resolve()
    cra_root = (file_path / "../../..").resolve()
    assert cra_root.name == "resources"
    return cra_root


@functools.cache
def get_chromium_root() -> pathlib.Path:
    """Gets the root of chromium checkout, typically at ~/chromium/src."""
    path = (get_cra_root() / "../../../../").resolve()
    assert path.name == "src"
    return path


@functools.cache
def get_strings_dir() -> pathlib.Path:
    """Gets the folder of chromeos strings where recorder_strings.grd is at."""
    return get_chromium_root() / "chromeos"


@functools.cache
def _resolve_build_dir(build_dir: str) -> pathlib.Path:
    if "/" in build_dir:
        # This is either a relative or absolute path.
        resolved_build_dir = get_chromium_root() / pathlib.Path(build_dir)
    else:
        # Argument is a board name
        resolved_build_dir = get_chromium_root() / f"out_{build_dir}/Release"

    assert resolved_build_dir.is_dir(), (
        f"Failed to find the build output dir {build_dir}."
        " Please check and build Chrome at least once.")

    return resolved_build_dir


def build_dir_option(optional: bool = False):
    # TODO(pihsun): Make this always optional by guessing the build_dir from
    # mtime of the out_{board} directory when it's not explicitly given.
    return cli.option(
        "--build_dir" if optional else "build_dir",
        help="""
            Board name or chrome build directory.
            Can either be a board name (e.g. betty), a relative path to chrome
            source directory (e.g. out_betty/Release), or an absolute path to
            the build directory.
            The provided folder is used for finding MWC and lit, which is board
            independent. All other board dependent references will be stubbed.
        """,
        type=_resolve_build_dir,
        metavar="<board name|build directory>",
    )


def shell_join(cmd: list[str]) -> str:
    return " ".join(shlex.quote(c) for c in cmd)


def run(args: list[str], *, cwd: Optional[pathlib.Path] = None):
    logging.debug(f"$ {shell_join(args)}")
    subprocess.check_call(args, cwd=cwd)


def check_output(args: list[str]) -> str:
    logging.debug(f"$ {shell_join(args)}")
    return subprocess.check_output(args, text=True)


def run_node(args: list[str], *, cwd: Optional[pathlib.Path] = None):
    root = get_chromium_root()
    node = root / "third_party/node/linux/node-linux-x64/bin/node"
    binary = root / "third_party/node/node_modules" / args[0]
    run([str(node), str(binary)] + args[1:], cwd=cwd)


def to_camel_case(s: str) -> str:
    """Converts CAPITAL_CASE to camelCase."""
    start, *rest = s.lower().split('_')
    return start + ''.join(part.capitalize() for part in rest)