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 21 22 23class BaseEnvironment(UserDict): 24 """Base class for environment management, providing a common foundation for 25 26 both Posix and Windows environments. 27 """ 28 29 def __init__(self, aosp: Path): 30 super().__init__({ 31 "PATH": ( 32 str( 33 aosp 34 / "external" 35 / "qemu" 36 / "android" 37 / "third_party" 38 / "chromium" 39 / "depot_tools" 40 ) 41 + os.pathsep 42 + os.environ.get("PATH", "") 43 ) 44 }) 45 46 47class PosixEnvironment(BaseEnvironment): 48 49 def __init__(self, aosp: Path): 50 super().__init__(aosp) 51 52 53class VisualStudioNotFoundException(Exception): 54 pass 55 56 57class VisualStudioMissingVarException(Exception): 58 pass 59 60 61class VisualStudioNativeWorkloadNotFoundException(Exception): 62 pass 63 64 65class WindowsEnvironment(BaseEnvironment): 66 """Environment manager for Windows systems, specifically handling Visual Studio integration.""" 67 68 def __init__(self, aosp: Path): 69 assert platform.system() == "Windows" 70 super().__init__(aosp) 71 for key in os.environ: 72 self[key.upper()] = os.environ[key] 73 74 vs = self._visual_studio() 75 logging.info("Loading environment from %s", vs) 76 env_lines = subprocess.check_output( 77 [vs, "&&", "set"], encoding="utf-8" 78 ).splitlines() 79 for line in env_lines: 80 if "=" in line: 81 key, val = line.split("=", 1) 82 # Variables in windows are case insensitive, but not in python dict! 83 self[key.upper()] = val 84 85 # Set PYTHONUTF8 to 1 86 self["PYTHONUTF8"] = "1" 87 88 if not "VSINSTALLDIR" in self: 89 raise VisualStudioMissingVarException( 90 "Missing VSINSTALLDIR in environment" 91 ) 92 93 if not "VCTOOLSINSTALLDIR" in self: 94 raise VisualStudioMissingVarException( 95 "Missing VCTOOLSINSTALLDIR in environment" 96 ) 97 98 def _visual_studio(self) -> Path: 99 """Locates the Visual Studio installation and its Native Desktop workload. 100 101 Raises: 102 VisualStudioNotFoundException: When Visual Studio is not found. 103 VisualStudioNativeWorkloadNotFoundException: When the Native Desktop 104 workload is not found. 105 106 Returns: 107 Path: Path to the Visual Studio vcvars64.bat file. 108 """ 109 prgrfiles = Path(os.getenv("ProgramFiles(x86)", "C:\Program Files (x86)")) 110 res = subprocess.check_output([ 111 str( 112 prgrfiles / "Microsoft Visual Studio" / "Installer" / "vswhere.exe" 113 ), 114 "-requires", 115 "Microsoft.VisualStudio.Workload.NativeDesktop", 116 "-sort", 117 "-format", 118 "json", 119 "-utf8", 120 ]) 121 vsresult = json.loads(res) 122 if len(vsresult) == 0: 123 raise VisualStudioNativeWorkloadNotFoundException( 124 "No visual studio with the native desktop load available." 125 ) 126 127 for install in vsresult: 128 logging.debug("Considering %s", install["displayName"]) 129 candidates = list( 130 Path(install["installationPath"]).glob("**/vcvars64.bat") 131 ) 132 133 if len(candidates) > 0: 134 return candidates[0].absolute() 135 136 # Oh oh, no visual studio.. 137 raise VisualStudioNotFoundException( 138 "Unable to detect a visual studio installation with the native desktop" 139 " workload." 140 ) 141 142 143def get_default_environment(aosp: Path): 144 """Returns the appropriate environment manager based on the current operating system. 145 146 The environment will make sure the following things hold: 147 148 - Ninja will be on the PATH 149 - The visual studio tools environment will be loaded 150 """ 151 if platform.system() == "Windows": 152 return WindowsEnvironment(aosp) 153 return PosixEnvironment(aosp) 154