| #!/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() |