#!/usr/bin/python3 # # Copyright (C) 2022 The Android Open Source Project # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import enum import os import platform _API_SURFACES = "api_surfaces" _INTERMEDIATES = "intermediates" # Phony target to assemble al the api files in out/api_surfaces ASSEMBLE_PHONY_TARGET = "multitree-sdk" class Context(object): """Mockable container for global state.""" def __init__(self, out_root, errors): self.out = OutDir(out_root) self.errors = errors self.tools = HostTools() class ContextTest(Context): """Context for testing. The real Context is manually constructed in orchestrator.py. """ def __init__(self, test_work_dir: str, test_name: str): """Initialize the object. Args: test_work_dir: The working directory to use for the test: typically created with tempfile.mkdtemp(). test_name: A name unique to the test: typically self.id() in the test. """ super().__init__(os.path.join(test_work_dir, test_name), Errors(None)) @enum.unique class OutDirBase(enum.Enum): """The basepath to use for output paths. ORIGIN: Path is relative to ${OUT_DIR}. Use this when the path will be consumed while not nsjailed. (default) OUTER: Path is relative to the outer tree root. Use this when the path will be consumed while nsjailed in the outer tree. """ DEFAULT = 0 ORIGIN = 1 OUTER = 2 class OutDir(object): """Encapsulates the logic about the out directory at the outer-tree level. See also inner_tree.OutDirLayout for inner tree out dir contents.""" # For ease of use. Base = OutDirBase def __init__(self, out_origin, out_path="out"): """Initialize with the root of the OUT_DIR for the inner tree. Args: out_origin: The OUT_DIR path to use. Usually "out". out_path: Where the outer tree out_dir will be mapped, relative to the outer tree root. Usually "out". """ self._base = {} self._base[self.Base.ORIGIN] = out_origin self._base[self.Base.OUTER] = out_path self._base[self.Base.DEFAULT] = self._base[self.Base.ORIGIN] def _generate_path(self, *args, base: OutDirBase = OutDirBase.DEFAULT, abspath=False): """Return the path to the file. Args: args: Path elements to use. base: Which base path to use. abspath: Whether to return the absolute path. """ ret = os.path.join(self._base[base], *args) if abspath: ret = os.path.abspath(ret) return ret def root(self, **kwargs): """The provided out_dir, mapped into "out/" for ninja.""" return self._generate_path(**kwargs) def inner_tree_dir(self, tree_root, product, **kwargs): """True root directory for inner tree inside the out dir.""" product = product or "unbundled" out_root = f'{tree_root}_{product}' return self._generate_path("trees", out_root, **kwargs) def api_ninja_file(self, **kwargs): """The ninja file that assembles API surfaces.""" return self._generate_path("api_surfaces.ninja", **kwargs) def api_library_dir(self, surface, version, library, **kwargs): """Directory for all the contents of a library inside an API surface. This includes the build files. Any intermediates should go in api_library_work_dir. """ return self._generate_path(_API_SURFACES, surface, str(version), library, **kwargs) def api_library_work_dir(self, surface, version, library, **kwargs): """Intermediate scratch directory for library inside an API surface.""" return self._generate_path(self.api_surfaces_work_dir(), surface, str(version), library, **kwargs) def api_surfaces_dir(self, **kwargs): """The published api_surfaces directory.""" return self._generate_path(_API_SURFACES, **kwargs) def api_surfaces_work_dir(self, *args): """Intermediates / scratch directory for API surface assembly. Useful for creating artifacts that are expected to be shared between multiple API libraries""" return self._generate_path(_INTERMEDIATES, _API_SURFACES, *args) def outer_ninja_file(self, **kwargs): return self._generate_path("multitree.ninja", **kwargs) def module_share_dir(self, module_type, module_name, **kwargs): return self._generate_path("shared", module_type, module_name, **kwargs) def staging_dir(self, **kwargs): return self._generate_path("staging", **kwargs) def dist_dir(self, **kwargs): """The DIST_DIR provided or out/dist""" # TODO: Look at DIST_DIR return self._generate_path("dist", **kwargs) def nsjail_config_file(self, **kwargs): """The nsjail config file used for the ninja run.""" return self._generate_path("nsjail.cfg", **kwargs) class Errors(object): """Class for reporting and tracking errors.""" def __init__(self, stream): """Initialize Error reporter with a file-like object.""" self._stream = stream self._all = [] def error(self, message, file=None, line=None, col=None): """Record the error message.""" s = "" if file: s += str(file) s += ":" if line: s += str(line) s += ":" if col: s += str(col) s += ":" if s: s += " " s += str(message) if s[-1] != "\n": s += "\n" self._all.append(s) if self._stream: self._stream.write(s) def had_error(self): """Return if there were any errors reported.""" return len(self._all) def get_errors(self): """Get all errors that were reported.""" return self._all # This clang_version was picked from Soong (build/soong/cc/config/global.go). # However, C stubs are ABI-stable and should not be affected by divergence in toolchain # versions of orchestrator and inner_build. CLANG_VERSION = "clang-r475365b" class HostTools(object): def __init__(self): if platform.system() == "Linux": self._arch = "linux-x86" else: raise Exception( f"Orchestrator: unknown system {platform.system()}") # Some of these are called a lot, so pre-compute the strings to save # memory. self._prebuilts = os.path.join("orchestrator", "prebuilts", "build-tools", self._arch, "bin") self._acp = os.path.join(self._prebuilts, "acp") self._ninja = os.path.join(self._prebuilts, "ninja") self._nsjail = os.path.join(self._prebuilts, "nsjail") clang_root = os.path.join( "orchestrator", "prebuilts", "clang", "host", self._arch, CLANG_VERSION, "bin", ) self._clang = os.path.join(clang_root, "clang") self._clang_cxx = os.path.join(clang_root, "clang++") # TODO: @property def acp(self): return self._acp # TODO: @property def ninja(self): return self._ninja def clang(self): return self._clang def clang_cxx(self): return self._clang_cxx @property def nsjail(self): return self._nsjail def choose_out_dir(): """Get the root of the out dir, either $OUT_DIR or a default.""" return os.environ.get("OUT_DIR") or "out"