1# Copyright 2024 The Chromium Authors 2# Use of this source code is governed by a BSD-style license that can be 3# found in the LICENSE file. 4 5from __future__ import annotations 6 7import functools 8from typing import TYPE_CHECKING, Iterable, Optional, Tuple, Union 9 10from crossbench import path as pth 11 12if TYPE_CHECKING: 13 from crossbench.plt.base import Platform 14 15 16class BinaryNotFoundError(RuntimeError): 17 18 def __init__(self, binary: Binary, platform: Platform): 19 self.binary = binary 20 self.platform = platform 21 super().__init__(self._create_message()) 22 23 def _create_message(self) -> str: 24 return (f"Could not find binary '{self.binary}' on {self.platform}. " 25 f"Please install {self.binary.name} or use the " 26 f"--bin-{self.binary.name} " 27 "command line flag to manually specify a path.") 28 29 30class UnsupportedPlatformError(BinaryNotFoundError): 31 32 def __init__(self, binary: Binary, platform: Platform, expected: str): 33 self.expected_platform_name: str = expected 34 super().__init__(binary, platform) 35 36 def _create_message(self) -> str: 37 return (f"Could not find binary '{self.binary}' on {self.platform}. " 38 f"Only supported on {self.expected_platform_name}") 39 40 41BinaryLookup = Union[pth.AnyPathLike, Iterable[pth.AnyPathLike]] 42 43 44class Binary: 45 """A binary abstraction for multiple platforms. 46 Use this implementation to define binaries that exist on multiple platforms. 47 For platform-specific binaries use subclasses of Binary.""" 48 49 def __init__(self, 50 name: str, 51 default: Optional[BinaryLookup] = None, 52 posix: Optional[BinaryLookup] = None, 53 linux: Optional[BinaryLookup] = None, 54 android: Optional[BinaryLookup] = None, 55 macos: Optional[BinaryLookup] = None, 56 win: Optional[BinaryLookup] = None) -> None: 57 self._name = name 58 self._default = self._convert(default) 59 self._posix = self._convert(posix) 60 self._linux = self._convert(linux) 61 self._android = self._convert(android) 62 self._macos = self._convert(macos) 63 self._win = self._convert(win) 64 self._validate_win() 65 if not any((default, posix, linux, android, macos, win)): 66 raise ValueError("At least one platform binary must be provided") 67 68 def _convert(self, 69 paths: Optional[BinaryLookup] = None) -> Tuple[pth.AnyPath, ...]: 70 if paths is None: 71 return tuple() 72 if isinstance(paths, str): 73 path: str = paths 74 if not path: 75 raise ValueError("Got unexpected empty string as binary path") 76 paths = [path] 77 elif isinstance(paths, pth.AnyPath): 78 paths = [paths] 79 return tuple(pth.AnyPath(path) for path in paths) 80 81 def _validate_win(self) -> None: 82 for path in self._win: 83 if path.suffix != ".exe": 84 raise ValueError(f"Windows binary {path} should have '.exe' suffix") 85 86 @property 87 def name(self) -> str: 88 return self._name 89 90 def __str__(self) -> str: 91 return self._name 92 93 @functools.lru_cache(maxsize=None) # pylint: disable=method-cache-max-size-none 94 def resolve_cached(self, platform: Platform) -> pth.AnyPath: 95 return self.resolve(platform) 96 97 def resolve(self, platform: Platform) -> pth.AnyPath: 98 self._validate_platform(platform) 99 for binary in self.platform_path(platform): 100 binary_path = platform.path(binary) 101 if result := platform.search_binary(binary_path): 102 return result 103 raise BinaryNotFoundError(self, platform) 104 105 def platform_path(self, platform: Platform) -> Tuple[pth.AnyPath, ...]: 106 if self._linux and platform.is_linux: 107 return self._linux 108 if self._android and platform.is_android: 109 return self._android 110 if self._macos and platform.is_macos: 111 return self._macos 112 if self._posix and platform.is_posix: 113 return self._posix 114 if platform.is_win: 115 if self._win: 116 return self._win 117 if self._default: 118 return self._win_default() 119 return self._default 120 121 def _win_default(self) -> Tuple[pth.AnyPath, ...]: 122 return tuple( 123 default if default.suffix == ".exe" else default.with_suffix(".exe") 124 for default in self._default) 125 126 def _validate_platform(self, platform: Platform) -> None: 127 pass 128 129 130class PosixBinary(Binary): 131 132 def __init__(self, name: pth.AnyPathLike): 133 super().__init__(pth.AnyPosixPath(name).name, posix=name) 134 135 def _validate_platform(self, platform: Platform) -> None: 136 if not platform.is_posix: 137 raise UnsupportedPlatformError(self, platform, "posix") 138 139 140class MacOsBinary(Binary): 141 142 def __init__(self, name: pth.AnyPathLike): 143 super().__init__(pth.AnyPosixPath(name).name, macos=name) 144 145 def _validate_platform(self, platform: Platform) -> None: 146 if not platform.is_macos: 147 raise UnsupportedPlatformError(self, platform, "macos") 148 149 150class LinuxBinary(Binary): 151 152 def __init__(self, name: pth.AnyPathLike): 153 super().__init__(pth.AnyPosixPath(name).name, linux=name) 154 155 def _validate_platform(self, platform: Platform) -> None: 156 if not platform.is_posix: 157 raise UnsupportedPlatformError(self, platform, "linux") 158 159 160class AndroidBinary(Binary): 161 162 def __init__(self, name: pth.AnyPathLike): 163 super().__init__(pth.AnyPosixPath(name).name, android=name) 164 165 def _validate_platform(self, platform: Platform) -> None: 166 if not platform.is_android: 167 raise UnsupportedPlatformError(self, platform, "android") 168 169 170class WinBinary(Binary): 171 172 def __init__(self, name: pth.AnyPathLike): 173 super().__init__(pth.AnyWindowsPath(name).name, win=name) 174 175 def _validate_platform(self, platform: Platform) -> None: 176 if not platform.is_win: 177 raise UnsupportedPlatformError(self, platform, "windows") 178 179 180class Binaries: 181 CPIO = LinuxBinary("cpio") 182 FFMPEG = Binary("ffmpeg", posix="ffmpeg") 183 GCERTSTATUS = Binary("gcertstatus", posix="gcertstatus") 184 GO = Binary("go", posix="go") 185 GSUTIL = Binary("gsutil", posix="gsutil") 186 LSCPU = LinuxBinary("lscpu") 187 MONTAGE = Binary("montage", posix="montage") 188 ON_AC_POWER = LinuxBinary("on_ac_power") 189 PERF = LinuxBinary("perf") 190 PPROF = LinuxBinary("pprof") 191 PYTHON3 = Binary("python3", default="python3", win="python3.exe") 192 RPM2CPIO = LinuxBinary("rpm2cpio") 193 SIMPLEPERF = AndroidBinary("simpleperf") 194 XCTRACE = MacOsBinary("xctrace") 195 196 197class Browsers: 198 SAFARI = MacOsBinary("Safari.app") 199 SAFARI_TECH_PREVIEW = MacOsBinary("Safari Technology Preview.app") 200 FIREFOX_STABLE = Binary( 201 "firefox stable", 202 macos="Firefox.app", 203 linux="firefox", 204 win="Mozilla Firefox/firefox.exe") 205 FIREFOX_DEV = Binary( 206 "firefox developer edition", 207 macos="Firefox Developer Edition.app", 208 linux="firefox-developer-edition", 209 win="Firefox Developer Edition/firefox.exe") 210 FIREFOX_NIGHTLY = Binary( 211 "Firefox nightly", 212 macos="Firefox Nightly.app", 213 linux=["firefox-nightly", "firefox-trunk"], 214 win="Firefox Nightly/firefox.exe") 215