1# Copyright 2024 - The Android Open Source Project 2# 3# Licensed under the Apache License, Version 2.0 (the', help='License'); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an', help='AS IS' BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14from collections import UserDict 15import json 16import logging 17import os 18from pathlib import Path 19import platform 20import subprocess 21from utils import rust_version 22 23 24class BaseEnvironment(UserDict): 25 """Base class for environment management, providing a common foundation for 26 27 both Posix and Windows environments. 28 """ 29 30 def __init__(self, aosp: Path): 31 paths = [ 32 str( 33 aosp 34 / "external" 35 / "qemu" 36 / "android" 37 / "third_party" 38 / "chromium" 39 / "depot_tools" 40 ) 41 ] 42 43 # Append prebuilt rust toolchain except for darwin_aarch64 44 # TODO(360874898): aarch64-apple-darwin prebuilt rust toolchain supported from 1.77.1 45 if not (platform.system() == "Darwin" and platform.machine() == "arm64"): 46 paths.append( 47 str( 48 aosp 49 / "prebuilts" 50 / "rust" 51 / f"{platform.system().lower()}-x86" 52 / f"{rust_version()}" 53 / "bin" 54 ) 55 ) 56 57 paths.append(os.environ.get("PATH", "")) 58 59 super().__init__({"PATH": os.pathsep.join(paths)}) 60 61 62class PosixEnvironment(BaseEnvironment): 63 64 def __init__(self, aosp: Path): 65 super().__init__(aosp) 66 67 68class VisualStudioNotFoundException(Exception): 69 pass 70 71 72class VisualStudioMissingVarException(Exception): 73 pass 74 75 76class VisualStudioNativeWorkloadNotFoundException(Exception): 77 pass 78 79 80class WindowsEnvironment(BaseEnvironment): 81 """Environment manager for Windows systems, specifically handling Visual Studio integration.""" 82 83 def __init__(self, aosp: Path): 84 assert platform.system() == "Windows" 85 super().__init__(aosp) 86 for key in os.environ: 87 self[key.upper()] = os.environ[key] 88 89 vs = self._visual_studio() 90 logging.info("Loading environment from %s", vs) 91 env_lines = subprocess.check_output( 92 [vs, "&&", "set"], encoding="utf-8" 93 ).splitlines() 94 for line in env_lines: 95 if "=" in line: 96 key, val = line.split("=", 1) 97 # Variables in windows are case insensitive, but not in python dict! 98 self[key.upper()] = val 99 100 # Set PYTHONUTF8 to 1 101 self["PYTHONUTF8"] = "1" 102 103 if not "VSINSTALLDIR" in self: 104 raise VisualStudioMissingVarException( 105 "Missing VSINSTALLDIR in environment" 106 ) 107 108 if not "VCTOOLSINSTALLDIR" in self: 109 raise VisualStudioMissingVarException( 110 "Missing VCTOOLSINSTALLDIR in environment" 111 ) 112 113 def _visual_studio(self) -> Path: 114 """Locates the Visual Studio installation and its Native Desktop workload. 115 116 Raises: 117 VisualStudioNotFoundException: When Visual Studio is not found. 118 VisualStudioNativeWorkloadNotFoundException: When the Native Desktop 119 workload is not found. 120 121 Returns: 122 Path: Path to the Visual Studio vcvars64.bat file. 123 """ 124 prgrfiles = Path(os.getenv("ProgramFiles(x86)", "C:\Program Files (x86)")) 125 res = subprocess.check_output([ 126 str( 127 prgrfiles / "Microsoft Visual Studio" / "Installer" / "vswhere.exe" 128 ), 129 "-requires", 130 "Microsoft.VisualStudio.Workload.NativeDesktop", 131 "-sort", 132 "-format", 133 "json", 134 "-utf8", 135 ]) 136 vsresult = json.loads(res) 137 if len(vsresult) == 0: 138 raise VisualStudioNativeWorkloadNotFoundException( 139 "No visual studio with the native desktop load available." 140 ) 141 142 for install in vsresult: 143 logging.debug("Considering %s", install["displayName"]) 144 candidates = list( 145 Path(install["installationPath"]).glob("**/vcvars64.bat") 146 ) 147 148 if len(candidates) > 0: 149 return candidates[0].absolute() 150 151 # Oh oh, no visual studio.. 152 raise VisualStudioNotFoundException( 153 "Unable to detect a visual studio installation with the native desktop" 154 " workload." 155 ) 156 157 158def get_default_environment(aosp: Path): 159 """Returns the appropriate environment manager based on the current operating system. 160 161 The environment will make sure the following things hold: 162 163 - Ninja will be on the PATH 164 - The visual studio tools environment will be loaded 165 """ 166 if platform.system() == "Windows": 167 return WindowsEnvironment(aosp) 168 return PosixEnvironment(aosp) 169