blob: 54b6bef0b4360751f335f81d51f057accabe4e67 [file] [log] [blame]
# 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, Optional
from crossbench import path as pth
if TYPE_CHECKING:
from crossbench.plt.base import Platform
class BinaryNotFoundError(RuntimeError):
def __init__(self, binary: Binary, platform: Platform):
self.binary = binary
self.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):
self.expected_platform_name: str = expected
super().__init__(binary, platform)
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[pth.RemotePathLike] = None,
posix: Optional[pth.RemotePathLike] = None,
linux: Optional[pth.RemotePathLike] = None,
android: Optional[pth.RemotePathLike] = None,
macos: Optional[pth.RemotePathLike] = None,
win: Optional[pth.RemotePathLike] = 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)
if self._win and self._win.suffix != ".exe":
raise ValueError(f"Windows binary {self._win} should have '.exe' suffix")
if not any((default, posix, linux, android, macos, win)):
raise ValueError("At least one platform binary must be provided")
def _convert(self,
path: Optional[pth.RemotePathLike]) -> Optional[pth.RemotePath]:
if path is None:
return None
if not path:
raise ValueError("Got unexpected empty string as binary path")
return pth.RemotePath(path)
@property
def name(self) -> str:
return self._name
def __str__(self) -> str:
return self._name
@functools.lru_cache(maxsize=None) # pylint: disable=method-cache-max-size-none
def resolve_cached(self, platform: Platform) -> pth.RemotePath:
return self.resolve(platform)
def resolve(self, platform: Platform) -> pth.RemotePath:
self._validate_platform(platform)
if binary := self.platform_path(platform):
binary_path = platform.path(binary)
if result := platform.search_binary(binary_path):
return result
raise BinaryNotFoundError(self, platform)
def platform_path(self, platform: Platform) -> Optional[pth.RemotePath]:
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:
if self._default.suffix == ".exe":
return self._default
# Auto-append default
return self._default.with_suffix(".exe")
return self._default
def _validate_platform(self, platform: Platform) -> None:
pass
class PosixBinary(Binary):
def __init__(self, name: pth.RemotePathLike):
super().__init__(pth.RemotePath(name).name, posix=name)
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.RemotePathLike):
super().__init__(pth.RemotePath(name).name, macos=name)
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.RemotePathLike):
super().__init__(pth.RemotePath(name).name, linux=name)
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.RemotePathLike):
super().__init__(pth.RemotePath(name).name, android=name)
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.RemotePathLike):
super().__init__(pth.RemotePath(name).name, win=name)
def _validate_platform(self, platform: Platform) -> None:
if not platform.is_win:
raise UnsupportedPlatformError(self, platform, "windows")
class Binaries:
CPIO = LinuxBinary("cpio")
FFMPEG = Binary("ffmpeg", posix="ffmpeg")
GCERTSTATUS = Binary("gcertstatus", posix="gcertstatus")
GO = Binary("go", posix="go")
GSUTIL = Binary("gsutil", posix="gsutil")
LSCPU = LinuxBinary("lscpu")
MONTAGE = Binary("montage", posix="montage")
ON_AC_POWER = LinuxBinary("on_ac_power")
PERF = LinuxBinary("perf")
PPROF = LinuxBinary("pprof")
PYTHON3 = Binary("python3", default="python3", win="python3.exe")
RPM2CPIO = LinuxBinary("rpm2cpio")
SIMPLEPERF = AndroidBinary("simpleperf")
XCTRACE = MacOsBinary("xctrace")