| # |
| # Copyright (C) 2021 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. |
| # |
| """APIs for interacting with Soong.""" |
| import logging |
| import os |
| from pathlib import Path |
| import shlex |
| import shutil |
| import subprocess |
| |
| |
| def logger() -> logging.Logger: |
| """Returns the module level logger.""" |
| return logging.getLogger(__name__) |
| |
| |
| class Soong: |
| """Interface for interacting with Soong.""" |
| |
| def __init__(self, build_top: Path, out_dir: Path) -> None: |
| self.out_dir = out_dir |
| self.soong_ui_path = build_top / "build/soong/soong_ui.bash" |
| |
| def soong_ui( |
| self, |
| args: list[str], |
| env: dict[str, str] | None = None, |
| capture_output: bool = False, |
| ) -> str: |
| """Executes soong_ui.bash and returns the output on success. |
| |
| Args: |
| args: List of string arguments to pass to soong_ui.bash. |
| env: Additional environment variables to set when running soong_ui. |
| capture_output: True if the output of the command should be captured and |
| returned. If not, the output will be printed to stdout/stderr and an |
| empty string will be returned. |
| |
| Raises: |
| subprocess.CalledProcessError: The subprocess failure if the soong command |
| failed. |
| |
| Returns: |
| The interleaved contents of stdout/stderr if capture_output is True, else an |
| empty string. |
| """ |
| if env is None: |
| env = {} |
| |
| # Use a (mostly) clean environment to avoid the caller's lunch |
| # environment affecting the build. |
| exec_env = { |
| # Newer versions of golang require the go cache, which defaults to somewhere |
| # in HOME if not set. |
| "HOME": os.environ["HOME"], |
| "OUT_DIR": str(self.out_dir.resolve()), |
| "PATH": os.environ["PATH"], |
| } |
| exec_env.update(env) |
| env_prefix = " ".join(f"{k}={v}" for k, v in exec_env.items()) |
| cmd = [str(self.soong_ui_path)] + args |
| logger().debug(f"running in {os.getcwd()}: {env_prefix} {shlex.join(cmd)}") |
| result = subprocess.run( |
| cmd, |
| check=True, |
| capture_output=capture_output, |
| encoding="utf-8", |
| env=exec_env, |
| ) |
| return result.stdout |
| |
| def get_make_var(self, name: str) -> str: |
| """Queries the build system for the value of a make variable. |
| |
| Args: |
| name: The name of the build variable to query. |
| |
| Returns: |
| The value of the build variable in string form. |
| """ |
| return self.soong_ui(["--dumpvar-mode", name], capture_output=True).rstrip("\n") |
| |
| def clean(self) -> None: |
| """Removes the output directory, if it exists.""" |
| if self.out_dir.exists(): |
| shutil.rmtree(self.out_dir) |
| |
| def build(self, targets: list[str], env: dict[str, str] | None = None) -> None: |
| """Builds the given targets. |
| |
| The out directory will be created if it does not already exist. Existing |
| contents will not be removed, but affected outputs will be modified. |
| |
| Args: |
| targets: A list of target names to build. |
| env: Additional environment variables to set when running the build. |
| """ |
| self.out_dir.mkdir(parents=True, exist_ok=True) |
| self.soong_ui(["--make-mode", "--soong-only"] + targets, env=env) |