1# 2# Copyright (C) 2021 The Android Open Source Project 3# 4# Licensed under the Apache License, Version 2.0 (the "License"); 5# you may not use this file except in compliance with the License. 6# You may obtain a copy of the License at 7# 8# http://www.apache.org/licenses/LICENSE-2.0 9# 10# Unless required by applicable law or agreed to in writing, software 11# distributed under the License is distributed on an "AS IS" BASIS, 12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13# See the License for the specific language governing permissions and 14# limitations under the License. 15# 16"""APIs for interacting with Soong.""" 17import logging 18import os 19from pathlib import Path 20import shlex 21import shutil 22import subprocess 23 24 25def logger() -> logging.Logger: 26 """Returns the module level logger.""" 27 return logging.getLogger(__name__) 28 29 30class Soong: 31 """Interface for interacting with Soong.""" 32 33 def __init__(self, build_top: Path, out_dir: Path) -> None: 34 self.out_dir = out_dir 35 self.soong_ui_path = build_top / "build/soong/soong_ui.bash" 36 37 def soong_ui( 38 self, 39 args: list[str], 40 env: dict[str, str] | None = None, 41 capture_output: bool = False, 42 ) -> str: 43 """Executes soong_ui.bash and returns the output on success. 44 45 Args: 46 args: List of string arguments to pass to soong_ui.bash. 47 env: Additional environment variables to set when running soong_ui. 48 capture_output: True if the output of the command should be captured and 49 returned. If not, the output will be printed to stdout/stderr and an 50 empty string will be returned. 51 52 Raises: 53 subprocess.CalledProcessError: The subprocess failure if the soong command 54 failed. 55 56 Returns: 57 The interleaved contents of stdout/stderr if capture_output is True, else an 58 empty string. 59 """ 60 if env is None: 61 env = {} 62 63 # Use a (mostly) clean environment to avoid the caller's lunch 64 # environment affecting the build. 65 exec_env = { 66 # Newer versions of golang require the go cache, which defaults to somewhere 67 # in HOME if not set. 68 "HOME": os.environ["HOME"], 69 "OUT_DIR": str(self.out_dir.resolve()), 70 "PATH": os.environ["PATH"], 71 } 72 exec_env.update(env) 73 env_prefix = " ".join(f"{k}={v}" for k, v in exec_env.items()) 74 cmd = [str(self.soong_ui_path)] + args 75 logger().debug(f"running in {os.getcwd()}: {env_prefix} {shlex.join(cmd)}") 76 result = subprocess.run( 77 cmd, 78 check=True, 79 capture_output=capture_output, 80 encoding="utf-8", 81 env=exec_env, 82 ) 83 return result.stdout 84 85 def get_make_var(self, name: str) -> str: 86 """Queries the build system for the value of a make variable. 87 88 Args: 89 name: The name of the build variable to query. 90 91 Returns: 92 The value of the build variable in string form. 93 """ 94 return self.soong_ui(["--dumpvar-mode", name], capture_output=True).rstrip("\n") 95 96 def clean(self) -> None: 97 """Removes the output directory, if it exists.""" 98 if self.out_dir.exists(): 99 shutil.rmtree(self.out_dir) 100 101 def build(self, targets: list[str], env: dict[str, str] | None = None) -> None: 102 """Builds the given targets. 103 104 The out directory will be created if it does not already exist. Existing 105 contents will not be removed, but affected outputs will be modified. 106 107 Args: 108 targets: A list of target names to build. 109 env: Additional environment variables to set when running the build. 110 """ 111 self.out_dir.mkdir(parents=True, exist_ok=True) 112 self.soong_ui(["--make-mode", "--soong-only"] + targets, env=env) 113