#!/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.path import subprocess import tempfile SRC_ROOT_DIR = os.path.abspath(__file__ + "/../../../..") def _module_graph_path(out_dir): return os.path.join(SRC_ROOT_DIR, out_dir, "soong", "module-actions.json") def _build_with_soong(targets, target_product, out_dir, extra_env={}): env = { "TARGET_PRODUCT": target_product, "TARGET_BUILD_VARIANT": "userdebug", } env.update(os.environ) env.update(extra_env) args = [ "build/soong/soong_ui.bash", "--make-mode", "--skip-soong-tests", ] args.extend(targets) try: out = subprocess.check_output( args, cwd=SRC_ROOT_DIR, 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( SRC_ROOT_DIR, out_dir, "soong", "module-actions.json" ) if not os.path.exists(module_path): _build_with_soong(["json-module-graph"], target_product, out_dir) action_graph = json.load(open(_module_graph_path(out_dir))) 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 _store_outputs_to_tmp(output_files): try: tempdir = tempfile.TemporaryDirectory() for f in output_files: out = subprocess.check_output( ["cp", "--parents", f, tempdir.name], cwd=SRC_ROOT_DIR, ) return tempdir except subprocess.CalledProcessError as e: print(e) print(e.stdout) print(e.stderr) def _diff_outs(file1, file2, show_diff): base_args = ["diff"] if not show_diff: base_args.append("--brief") try: args = base_args + [file1, file2] output = subprocess.check_output( args, cwd=SRC_ROOT_DIR, ) except subprocess.CalledProcessError as e: if e.returncode == 1: if show_diff: return output return True return None def _compare_outputs(module_to_outs, tempdir, show_diff): different_modules = collections.defaultdict(list) for module, outs in module_to_outs.items(): for out in outs: output = None diff = _diff_outs(os.path.join(tempdir.name, out), out, show_diff) if diff: different_modules[module].append(diff) tempdir.cleanup() 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", required=False, help="whether to display differing files", ) parser.add_argument( "--output-paths-only", "-o", action="store_true", required=False, help="Whether to only return the output paths per module", ) args = parser.parse_args() out_dir = os.environ.get("OUT_DIR", "out") target_product = args.target_product modules = set(args.modules) module_to_outs = _find_outputs_for_modules(modules, out_dir, target_product) if args.output_paths_only: for m, o in module_to_outs.items(): print(f"{m} outputs: {o}") exit(0) all_outs = set() for outs in module_to_outs.values(): all_outs.update(outs) print("build without sandboxing") _build_with_soong(list(all_outs), target_product, out_dir) tempdir = _store_outputs_to_tmp(all_outs) print("build with sandboxing") _build_with_soong( list(all_outs), target_product, out_dir, extra_env={"GENRULE_SANDBOXING": "true"}, ) diffs = _compare_outputs(module_to_outs, tempdir, args.show_diff) 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()