| # 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. |
| |
| from __future__ import annotations |
| |
| import functools |
| from typing import TYPE_CHECKING, ClassVar, Final, Iterable, Optional, \ |
| TypeAlias |
| |
| from typing_extensions import override |
| |
| from crossbench import path as pth |
| |
| if TYPE_CHECKING: |
| from crossbench.plt.base import Platform |
| BinaryLookup: TypeAlias = pth.AnyPathLike | Iterable[pth.AnyPathLike] |
| |
| |
| class BinaryNotFoundError(RuntimeError): |
| |
| def __init__(self, binary: Binary, platform: Platform) -> None: |
| self.binary: Final[Binary] = binary |
| self.platform: Final[Platform] = platform |
| super().__init__(self._create_message()) |
| |
| def _create_message(self) -> str: |
| return (f"Could not find binary '{self.binary}' on {self.platform}. " |
| f"Please install {self.binary.name} or use the " |
| f"--bin-{self.binary.name} " |
| "command line flag to manually specify a path.") |
| |
| |
| class UnsupportedPlatformError(BinaryNotFoundError): |
| |
| def __init__(self, binary: Binary, platform: Platform, expected: str) -> None: |
| self.expected_platform_name: str = expected |
| super().__init__(binary, platform) |
| |
| @override |
| def _create_message(self) -> str: |
| return (f"Could not find binary '{self.binary}' on {self.platform}. " |
| f"Only supported on {self.expected_platform_name}") |
| |
| |
| class Binary: |
| """A binary abstraction for multiple platforms. |
| Use this implementation to define binaries that exist on multiple platforms. |
| For platform-specific binaries use subclasses of Binary.""" |
| |
| def __init__(self, |
| name: str, |
| default: Optional[BinaryLookup] = None, |
| posix: Optional[BinaryLookup] = None, |
| linux: Optional[BinaryLookup] = None, |
| android: Optional[BinaryLookup] = None, |
| macos: Optional[BinaryLookup] = None, |
| win: Optional[BinaryLookup] = None, |
| chromeos: Optional[BinaryLookup] = None) -> None: |
| self._name = name |
| self._default = self._convert(default) |
| self._posix = self._convert(posix) |
| self._linux = self._convert(linux) |
| self._android = self._convert(android) |
| self._macos = self._convert(macos) |
| self._win = self._convert(win) |
| self._validate_win() |
| self._chromeos = self._convert(chromeos) |
| if not any((chromeos, default, posix, linux, android, macos, win)): |
| raise ValueError("At least one platform binary must be provided") |
| |
| def _convert(self, |
| paths: Optional[BinaryLookup] = None) -> tuple[pth.AnyPath, ...]: |
| if paths is None: |
| return () |
| if isinstance(paths, str): |
| path: str = paths |
| if not path: |
| raise ValueError("Got unexpected empty string as binary path") |
| paths = [path] |
| elif isinstance(paths, pth.AnyPath): |
| paths = [paths] |
| return tuple(pth.AnyPath(path) for path in paths) |
| |
| def _validate_win(self) -> None: |
| for path in self._win: |
| if path.suffix != ".exe": |
| raise ValueError(f"Windows binary {path} should have '.exe' suffix") |
| |
| @property |
| def name(self) -> str: |
| return self._name |
| |
| def __str__(self) -> str: |
| return self._name |
| |
| def search(self, platform: Platform) -> pth.AnyPath | None: |
| self._validate_platform(platform) |
| for binary in self.platform_path(platform): |
| binary_path = platform.path(binary) |
| if result := platform.search_binary(binary_path): |
| return result |
| return None |
| |
| @functools.cache |
| def resolve_cached(self, platform: Platform) -> pth.AnyPath: |
| return self.resolve(platform) |
| |
| def resolve(self, platform: Platform) -> pth.AnyPath: |
| if path := self.search(platform): |
| return path |
| raise BinaryNotFoundError(self, platform) |
| |
| def platform_path(self, platform: Platform) -> tuple[pth.AnyPath, ...]: |
| if self._chromeos and platform.is_chromeos: |
| return self._chromeos |
| if self._linux and platform.is_linux: |
| return self._linux |
| if self._android and platform.is_android: |
| return self._android |
| if self._macos and platform.is_macos: |
| return self._macos |
| if self._posix and platform.is_posix: |
| return self._posix |
| if platform.is_win: |
| if self._win: |
| return self._win |
| if self._default: |
| return self._win_default() |
| return self._default |
| |
| def _win_default(self) -> tuple[pth.AnyPath, ...]: |
| return tuple( |
| default if default.suffix == ".exe" else default.with_suffix(".exe") |
| for default in self._default) |
| |
| def _validate_platform(self, platform: Platform) -> None: |
| pass |
| |
| |
| class PosixBinary(Binary): |
| |
| def __init__(self, name: pth.AnyPathLike) -> None: |
| super().__init__(pth.AnyPosixPath(name).name, posix=name) |
| |
| @override |
| def _validate_platform(self, platform: Platform) -> None: |
| if not platform.is_posix: |
| raise UnsupportedPlatformError(self, platform, "posix") |
| |
| |
| class MacOsBinary(Binary): |
| |
| def __init__(self, name: pth.AnyPathLike) -> None: |
| super().__init__(pth.AnyPosixPath(name).name, macos=name) |
| |
| @override |
| def _validate_platform(self, platform: Platform) -> None: |
| if not platform.is_macos: |
| raise UnsupportedPlatformError(self, platform, "macos") |
| |
| |
| class LinuxBinary(Binary): |
| |
| def __init__(self, name: pth.AnyPathLike) -> None: |
| super().__init__(pth.AnyPosixPath(name).name, linux=name) |
| |
| @override |
| def _validate_platform(self, platform: Platform) -> None: |
| if not platform.is_posix: |
| raise UnsupportedPlatformError(self, platform, "linux") |
| |
| |
| class AndroidBinary(Binary): |
| |
| def __init__(self, name: pth.AnyPathLike) -> None: |
| super().__init__(pth.AnyPosixPath(name).name, android=name) |
| |
| @override |
| def _validate_platform(self, platform: Platform) -> None: |
| if not platform.is_android: |
| raise UnsupportedPlatformError(self, platform, "android") |
| |
| |
| class WinBinary(Binary): |
| |
| def __init__(self, name: pth.AnyPathLike) -> None: |
| super().__init__(pth.AnyWindowsPath(name).name, win=name) |
| |
| @override |
| def _validate_platform(self, platform: Platform) -> None: |
| if not platform.is_win: |
| raise UnsupportedPlatformError(self, platform, "windows") |
| |
| |
| class ChromeOSBinary(Binary): |
| |
| def __init__(self, name: pth.AnyPathLike) -> None: |
| super().__init__(pth.AnyPosixPath(name).name, chromeos=name) |
| |
| @override |
| def _validate_platform(self, platform: Platform) -> None: |
| if not platform.is_chromeos: |
| raise UnsupportedPlatformError(self, platform, "chromeos") |
| |
| |
| class Binaries: |
| ADB: ClassVar = Binary( |
| "adb", |
| macos=["adb", "~/Library/Android/sdk/platform-tools/adb"], |
| linux=["adb"], |
| win=["adb.exe", "Android/sdk/platform-tools/adb.exe"]) |
| CPIO: ClassVar = LinuxBinary("cpio") |
| FFMPEG: ClassVar = Binary("ffmpeg", posix="ffmpeg") |
| GCERTSTATUS: ClassVar = Binary("gcertstatus", posix="gcertstatus") |
| GO: ClassVar = Binary("go", posix="go") |
| GSUTIL: ClassVar = Binary("gsutil", posix="gsutil") |
| LSCPU: ClassVar = LinuxBinary("lscpu") |
| MONTAGE: ClassVar = Binary("montage", posix="montage") |
| ON_AC_POWER: ClassVar = LinuxBinary("on_ac_power") |
| PERF: ClassVar = LinuxBinary("perf") |
| PPROF: ClassVar = LinuxBinary("pprof") |
| PYTHON3: ClassVar = Binary("python3", default="python3", win="python3.exe") |
| RPM2CPIO: ClassVar = LinuxBinary("rpm2cpio") |
| SIMPLEPERF: ClassVar = AndroidBinary("simpleperf") |
| XCTRACE: ClassVar = MacOsBinary("xctrace") |
| CHROMEDRIVER: ClassVar = Binary( |
| "chromedriver", |
| chromeos="/usr/local/chromedriver/chromedriver", |
| linux="chromedriver") |
| |
| |
| class Browsers: |
| SAFARI: ClassVar = MacOsBinary("Safari.app") |
| SAFARI_TECH_PREVIEW: ClassVar = MacOsBinary("Safari Technology Preview.app") |
| FIREFOX_STABLE: ClassVar = Binary( |
| "firefox stable", |
| macos="Firefox.app", |
| linux="firefox", |
| win="Mozilla Firefox/firefox.exe") |
| FIREFOX_DEV: ClassVar = Binary( |
| "firefox developer edition", |
| macos="Firefox Developer Edition.app", |
| linux="firefox-developer-edition", |
| win="Firefox Developer Edition/firefox.exe") |
| FIREFOX_NIGHTLY: ClassVar = Binary( |
| "Firefox nightly", |
| macos="Firefox Nightly.app", |
| linux=["firefox-nightly", "firefox-trunk"], |
| win="Firefox Nightly/firefox.exe") |