1#!/usr/bin/python3 2# 3# Copyright (C) 2022 The Android Open Source Project 4# 5# Licensed under the Apache License, Version 2.0 (the "License"); 6# you may not use this file except in compliance with the License. 7# You may obtain a copy of the License at 8# 9# http://www.apache.org/licenses/LICENSE-2.0 10# 11# Unless required by applicable law or agreed to in writing, software 12# distributed under the License is distributed on an "AS IS" BASIS, 13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14# See the License for the specific language governing permissions and 15# limitations under the License. 16 17import enum 18import os 19import platform 20 21_API_SURFACES = "api_surfaces" 22_INTERMEDIATES = "intermediates" 23 24# Phony target to assemble al the api files in out/api_surfaces 25ASSEMBLE_PHONY_TARGET = "multitree-sdk" 26 27class Context(object): 28 """Mockable container for global state.""" 29 30 def __init__(self, out_root, errors): 31 self.out = OutDir(out_root) 32 self.errors = errors 33 self.tools = HostTools() 34 35 36class ContextTest(Context): 37 """Context for testing. 38 39 The real Context is manually constructed in orchestrator.py. 40 """ 41 42 def __init__(self, test_work_dir: str, test_name: str): 43 """Initialize the object. 44 45 Args: 46 test_work_dir: The working directory to use for the test: typically 47 created with tempfile.mkdtemp(). 48 test_name: A name unique to the test: typically self.id() in the test. 49 """ 50 super().__init__(os.path.join(test_work_dir, test_name), Errors(None)) 51 52 53@enum.unique 54class OutDirBase(enum.Enum): 55 """The basepath to use for output paths. 56 57 ORIGIN: Path is relative to ${OUT_DIR}. Use this when the path will be 58 consumed while not nsjailed. (default) 59 OUTER: Path is relative to the outer tree root. Use this when the path 60 will be consumed while nsjailed in the outer tree. 61 """ 62 DEFAULT = 0 63 ORIGIN = 1 64 OUTER = 2 65 66 67class OutDir(object): 68 """Encapsulates the logic about the out directory at the outer-tree level. 69 See also inner_tree.OutDirLayout for inner tree out dir contents.""" 70 71 # For ease of use. 72 Base = OutDirBase 73 74 def __init__(self, out_origin, out_path="out"): 75 """Initialize with the root of the OUT_DIR for the inner tree. 76 77 Args: 78 out_origin: The OUT_DIR path to use. Usually "out". 79 out_path: Where the outer tree out_dir will be mapped, relative to the 80 outer tree root. Usually "out". 81 """ 82 self._base = {} 83 self._base[self.Base.ORIGIN] = out_origin 84 self._base[self.Base.OUTER] = out_path 85 self._base[self.Base.DEFAULT] = self._base[self.Base.ORIGIN] 86 87 def _generate_path(self, 88 *args, 89 base: OutDirBase = OutDirBase.DEFAULT, 90 abspath=False): 91 """Return the path to the file. 92 93 Args: 94 args: Path elements to use. 95 base: Which base path to use. 96 abspath: Whether to return the absolute path. 97 """ 98 ret = os.path.join(self._base[base], *args) 99 if abspath: 100 ret = os.path.abspath(ret) 101 return ret 102 103 def root(self, **kwargs): 104 """The provided out_dir, mapped into "out/" for ninja.""" 105 return self._generate_path(**kwargs) 106 107 def inner_tree_dir(self, tree_root, product, **kwargs): 108 """True root directory for inner tree inside the out dir.""" 109 product = product or "unbundled" 110 out_root = f'{tree_root}_{product}' 111 return self._generate_path("trees", out_root, **kwargs) 112 113 def api_ninja_file(self, **kwargs): 114 """The ninja file that assembles API surfaces.""" 115 return self._generate_path("api_surfaces.ninja", **kwargs) 116 117 def api_library_dir(self, surface, version, library, **kwargs): 118 """Directory for all the contents of a library inside an API surface. 119 120 This includes the build files. Any intermediates should go in 121 api_library_work_dir. 122 """ 123 return self._generate_path(_API_SURFACES, surface, str(version), 124 library, **kwargs) 125 126 def api_library_work_dir(self, surface, version, library, **kwargs): 127 """Intermediate scratch directory for library inside an API surface.""" 128 return self._generate_path(self.api_surfaces_work_dir(), 129 surface, 130 str(version), 131 library, 132 **kwargs) 133 134 def api_surfaces_dir(self, **kwargs): 135 """The published api_surfaces directory.""" 136 return self._generate_path(_API_SURFACES, **kwargs) 137 138 def api_surfaces_work_dir(self, *args): 139 """Intermediates / scratch directory for API surface assembly. 140 Useful for creating artifacts that are expected to be shared between multiple API libraries""" 141 return self._generate_path(_INTERMEDIATES, _API_SURFACES, *args) 142 143 def outer_ninja_file(self, **kwargs): 144 return self._generate_path("multitree.ninja", **kwargs) 145 146 def module_share_dir(self, module_type, module_name, **kwargs): 147 return self._generate_path("shared", module_type, module_name, 148 **kwargs) 149 150 def staging_dir(self, **kwargs): 151 return self._generate_path("staging", **kwargs) 152 153 def dist_dir(self, **kwargs): 154 """The DIST_DIR provided or out/dist""" 155 # TODO: Look at DIST_DIR 156 return self._generate_path("dist", **kwargs) 157 158 def nsjail_config_file(self, **kwargs): 159 """The nsjail config file used for the ninja run.""" 160 return self._generate_path("nsjail.cfg", **kwargs) 161 162 163class Errors(object): 164 """Class for reporting and tracking errors.""" 165 166 def __init__(self, stream): 167 """Initialize Error reporter with a file-like object.""" 168 self._stream = stream 169 self._all = [] 170 171 def error(self, message, file=None, line=None, col=None): 172 """Record the error message.""" 173 s = "" 174 if file: 175 s += str(file) 176 s += ":" 177 if line: 178 s += str(line) 179 s += ":" 180 if col: 181 s += str(col) 182 s += ":" 183 if s: 184 s += " " 185 s += str(message) 186 if s[-1] != "\n": 187 s += "\n" 188 self._all.append(s) 189 if self._stream: 190 self._stream.write(s) 191 192 def had_error(self): 193 """Return if there were any errors reported.""" 194 return len(self._all) 195 196 def get_errors(self): 197 """Get all errors that were reported.""" 198 return self._all 199 200# This clang_version was picked from Soong (build/soong/cc/config/global.go). 201# However, C stubs are ABI-stable and should not be affected by divergence in toolchain 202# versions of orchestrator and inner_build. 203CLANG_VERSION = "clang-r475365b" 204 205class HostTools(object): 206 def __init__(self): 207 if platform.system() == "Linux": 208 self._arch = "linux-x86" 209 else: 210 raise Exception( 211 f"Orchestrator: unknown system {platform.system()}") 212 213 # Some of these are called a lot, so pre-compute the strings to save 214 # memory. 215 self._prebuilts = os.path.join("orchestrator", "prebuilts", 216 "build-tools", self._arch, "bin") 217 self._acp = os.path.join(self._prebuilts, "acp") 218 self._ninja = os.path.join(self._prebuilts, "ninja") 219 self._nsjail = os.path.join(self._prebuilts, "nsjail") 220 clang_root = os.path.join( 221 "orchestrator", 222 "prebuilts", 223 "clang", 224 "host", 225 self._arch, 226 CLANG_VERSION, 227 "bin", 228 ) 229 self._clang = os.path.join(clang_root, "clang") 230 self._clang_cxx = os.path.join(clang_root, "clang++") 231 232 233 # TODO: @property 234 def acp(self): 235 return self._acp 236 237 # TODO: @property 238 def ninja(self): 239 return self._ninja 240 241 def clang(self): 242 return self._clang 243 244 def clang_cxx(self): 245 return self._clang_cxx 246 247 @property 248 def nsjail(self): 249 return self._nsjail 250 251 252def choose_out_dir(): 253 """Get the root of the out dir, either $OUT_DIR or a default.""" 254 return os.environ.get("OUT_DIR") or "out" 255