1# Copyright 2023 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 8import logging 9import os 10import shutil 11from typing import Optional 12 13from crossbench import path as pth 14from crossbench.plt.base import Platform 15 16 17class WinPlatform(Platform): 18 # TODO: support remote platforms 19 SEARCH_PATHS = ( 20 pth.LocalPath("."), 21 pth.LocalPath(os.path.expandvars("%ProgramFiles%")), 22 pth.LocalPath(os.path.expandvars("%ProgramFiles(x86)%")), 23 pth.LocalPath(os.path.expandvars("%APPDATA%")), 24 pth.LocalPath(os.path.expandvars("%LOCALAPPDATA%")), 25 ) 26 27 @property 28 def is_win(self) -> bool: 29 return True 30 31 @property 32 def name(self) -> str: 33 return "win" 34 35 @property 36 def device(self) -> str: 37 # TODO: implement 38 return "" 39 40 @functools.cached_property 41 def version(self) -> str: #pylint: disable=invalid-overridden-method 42 return self.sh_stdout("cmd", "/c", "ver").strip() 43 44 @functools.cached_property 45 def cpu(self) -> str: #pylint: disable=invalid-overridden-method 46 return self.sh_stdout("wmic", "cpu", "get", 47 "name").strip().splitlines()[2].strip() 48 49 def search_binary(self, app_or_bin: pth.AnyPathLike) -> Optional[pth.AnyPath]: 50 self.assert_is_local() 51 app_or_bin_path: pth.AnyPath = self.path(app_or_bin) 52 if not app_or_bin_path.parts: 53 raise ValueError("Got empty path") 54 if app_or_bin_path.suffix.lower() not in (".exe", ".bat"): 55 raise ValueError("Expected executable path with '.exe' or '.bat' suffix, " 56 f"but got: '{app_or_bin_path.name}'") 57 if result_path := self.which(app_or_bin): 58 assert self.exists(result_path), f"{result_path} does not exist." 59 return result_path 60 for path in self.SEARCH_PATHS: 61 # Recreate Path object for easier pyfakefs testing 62 result_path = self.path(path) / app_or_bin 63 if self.exists(result_path): 64 return result_path 65 return None 66 67 def app_version(self, app_or_bin: pth.AnyPathLike) -> str: 68 app_or_bin = self.path(app_or_bin) 69 if not self.exists(app_or_bin): 70 raise ValueError(f"Binary {app_or_bin} does not exist.") 71 if version := self.sh_stdout( 72 "powershell", "-command", 73 f"(Get-Item '{app_or_bin}').VersionInfo.ProductVersion").strip(): 74 return version 75 try: 76 # Fall back to command-line tools. 77 if version := self.sh_stdout(app_or_bin, "--version").strip(): 78 return version 79 except Exception as e: # pylint: disable=broad-exception-caught 80 logging.debug("Failed to extract binary tool version: %s", e) 81 raise ValueError(f"Could not extract version for {app_or_bin}") 82 83 84 def symlink_or_copy(self, src: pth.AnyPathLike, 85 dst: pth.AnyPathLike) -> pth.AnyPath: 86 """Windows does not support symlinking without admin support. 87 Copy files on windows but symlink everywhere else (see base Platform).""" 88 self.assert_is_local() 89 dst_path = self.path(dst) 90 shutil.copy(os.fspath(self.path(src)), os.fspath(dst_path)) 91 return dst_path 92