#!/usr/bin/env python3 # Copyright (C) 2023 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 argparse import collections import json import os import subprocess import sys import tempfile def get_top() -> str: path = '.' while not os.path.isfile(os.path.join(path, 'build/soong/tests/genrule_sandbox_test.py')): if os.path.abspath(path) == '/': sys.exit('Could not find android source tree root.') path = os.path.join(path, '..') return os.path.abspath(path) def _build_with_soong(targets, target_product, *, keep_going = False, extra_env={}): env = { **os.environ, "TARGET_PRODUCT": target_product, "TARGET_BUILD_VARIANT": "userdebug", } env.update(extra_env) args = [ "build/soong/soong_ui.bash", "--make-mode", "--skip-soong-tests", ] if keep_going: args.append("-k") args.extend(targets) try: subprocess.check_output( args, env=env, ) except subprocess.CalledProcessError as e: print(e) print(e.stdout) print(e.stderr) exit(1) def _find_outputs_for_modules(modules, out_dir, target_product): module_path = os.path.join(out_dir, "soong", "module-actions.json") if not os.path.exists(module_path): _build_with_soong(["json-module-graph"], target_product) with open(module_path) as f: action_graph = json.load(f) module_to_outs = collections.defaultdict(set) for mod in action_graph: name = mod["Name"] if name in modules: for act in mod["Module"]["Actions"]: if "}generate" in act["Desc"]: module_to_outs[name].update(act["Outputs"]) return module_to_outs def _compare_outputs(module_to_outs, tempdir) -> dict[str, list[str]]: different_modules = collections.defaultdict(list) for module, outs in module_to_outs.items(): for out in outs: try: subprocess.check_output(["diff", os.path.join(tempdir, out), out]) except subprocess.CalledProcessError as e: different_modules[module].append(e.stdout) return different_modules def main(): parser = argparse.ArgumentParser() parser.add_argument( "--target_product", "-t", default="aosp_cf_arm64_phone", help="optional, target product, always runs as eng", ) parser.add_argument( "modules", nargs="+", help="modules to compare builds with genrule sandboxing enabled/not", ) parser.add_argument( "--show-diff", "-d", action="store_true", help="whether to display differing files", ) parser.add_argument( "--output-paths-only", "-o", action="store_true", help="Whether to only return the output paths per module", ) args = parser.parse_args() os.chdir(get_top()) out_dir = os.environ.get("OUT_DIR", "out") print("finding output files for the modules...") module_to_outs = _find_outputs_for_modules(set(args.modules), out_dir, args.target_product) if not module_to_outs: sys.exit("No outputs found") if args.output_paths_only: for m, o in module_to_outs.items(): print(f"{m} outputs: {o}") sys.exit(0) all_outs = list(set.union(*module_to_outs.values())) print("building without sandboxing...") _build_with_soong(all_outs, args.target_product) with tempfile.TemporaryDirectory() as tempdir: for f in all_outs: subprocess.check_call(["cp", "--parents", f, tempdir]) print("building with sandboxing...") _build_with_soong( all_outs, args.target_product, # We've verified these build without sandboxing already, so do the sandboxing build # with keep_going = True so that we can find all the genrules that fail to build with # sandboxing. keep_going = True, extra_env={"GENRULE_SANDBOXING": "true"}, ) diffs = _compare_outputs(module_to_outs, tempdir) if len(diffs) == 0: print("All modules are correct") elif args.show_diff: for m, d in diffs.items(): print(f"Module {m} has diffs {d}") else: print(f"Modules {list(diffs.keys())} have diffs") if __name__ == "__main__": main()