Liz Kammer | 767fad4 | 2023-06-09 11:23:15 -0400 | [diff] [blame] | 1 | #!/usr/bin/env python3 |
| 2 | |
| 3 | # Copyright (C) 2023 The Android Open Source Project |
| 4 | # |
| 5 | # Licensed under the Apache License, Version 2.0 (the "License"); |
| 6 | # you may not use this file except in compliance with the License. |
| 7 | # You may obtain a copy of the License at |
| 8 | # |
| 9 | # http://www.apache.org/licenses/LICENSE-2.0 |
| 10 | # |
| 11 | # Unless required by applicable law or agreed to in writing, software |
| 12 | # distributed under the License is distributed on an "AS IS" BASIS, |
| 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 14 | # See the License for the specific language governing permissions and |
| 15 | # limitations under the License. |
| 16 | |
| 17 | import argparse |
| 18 | import collections |
| 19 | import json |
| 20 | import os.path |
| 21 | import subprocess |
| 22 | import tempfile |
| 23 | |
| 24 | SRC_ROOT_DIR = os.path.abspath(__file__ + "/../../../..") |
| 25 | |
| 26 | |
| 27 | def _module_graph_path(out_dir): |
| 28 | return os.path.join(SRC_ROOT_DIR, out_dir, "soong", "module-actions.json") |
| 29 | |
| 30 | |
| 31 | def _build_with_soong(targets, target_product, out_dir, extra_env={}): |
| 32 | env = { |
| 33 | "TARGET_PRODUCT": target_product, |
| 34 | "TARGET_BUILD_VARIANT": "userdebug", |
| 35 | } |
| 36 | env.update(os.environ) |
| 37 | env.update(extra_env) |
| 38 | args = [ |
| 39 | "build/soong/soong_ui.bash", |
| 40 | "--make-mode", |
| 41 | "--skip-soong-tests", |
| 42 | ] |
| 43 | args.extend(targets) |
| 44 | try: |
| 45 | out = subprocess.check_output( |
| 46 | args, |
| 47 | cwd=SRC_ROOT_DIR, |
| 48 | env=env, |
| 49 | ) |
| 50 | except subprocess.CalledProcessError as e: |
| 51 | print(e) |
| 52 | print(e.stdout) |
| 53 | print(e.stderr) |
| 54 | exit(1) |
| 55 | |
| 56 | |
| 57 | def _find_outputs_for_modules(modules, out_dir, target_product): |
| 58 | module_path = os.path.join( |
| 59 | SRC_ROOT_DIR, out_dir, "soong", "module-actions.json" |
| 60 | ) |
| 61 | |
| 62 | if not os.path.exists(module_path): |
| 63 | _build_with_soong(["json-module-graph"], target_product, out_dir) |
| 64 | |
| 65 | action_graph = json.load(open(_module_graph_path(out_dir))) |
| 66 | |
| 67 | module_to_outs = collections.defaultdict(set) |
| 68 | for mod in action_graph: |
| 69 | name = mod["Name"] |
| 70 | if name in modules: |
| 71 | for act in mod["Module"]["Actions"]: |
Jason Macnak | 394f1b7 | 2023-06-15 09:28:26 -0700 | [diff] [blame^] | 72 | if "}generate" in act["Desc"]: |
Liz Kammer | 767fad4 | 2023-06-09 11:23:15 -0400 | [diff] [blame] | 73 | module_to_outs[name].update(act["Outputs"]) |
| 74 | return module_to_outs |
| 75 | |
| 76 | |
| 77 | def _store_outputs_to_tmp(output_files): |
| 78 | try: |
| 79 | tempdir = tempfile.TemporaryDirectory() |
| 80 | for f in output_files: |
| 81 | out = subprocess.check_output( |
| 82 | ["cp", "--parents", f, tempdir.name], |
| 83 | cwd=SRC_ROOT_DIR, |
| 84 | ) |
| 85 | return tempdir |
| 86 | except subprocess.CalledProcessError as e: |
| 87 | print(e) |
| 88 | print(e.stdout) |
| 89 | print(e.stderr) |
| 90 | |
| 91 | |
| 92 | def _diff_outs(file1, file2, show_diff): |
Jason Macnak | 394f1b7 | 2023-06-15 09:28:26 -0700 | [diff] [blame^] | 93 | output = None |
Liz Kammer | 767fad4 | 2023-06-09 11:23:15 -0400 | [diff] [blame] | 94 | base_args = ["diff"] |
| 95 | if not show_diff: |
| 96 | base_args.append("--brief") |
| 97 | try: |
| 98 | args = base_args + [file1, file2] |
| 99 | output = subprocess.check_output( |
| 100 | args, |
| 101 | cwd=SRC_ROOT_DIR, |
| 102 | ) |
| 103 | except subprocess.CalledProcessError as e: |
| 104 | if e.returncode == 1: |
| 105 | if show_diff: |
| 106 | return output |
| 107 | return True |
| 108 | return None |
| 109 | |
| 110 | |
| 111 | def _compare_outputs(module_to_outs, tempdir, show_diff): |
| 112 | different_modules = collections.defaultdict(list) |
| 113 | for module, outs in module_to_outs.items(): |
| 114 | for out in outs: |
| 115 | output = None |
| 116 | diff = _diff_outs(os.path.join(tempdir.name, out), out, show_diff) |
| 117 | if diff: |
| 118 | different_modules[module].append(diff) |
| 119 | |
| 120 | tempdir.cleanup() |
| 121 | return different_modules |
| 122 | |
| 123 | |
| 124 | def main(): |
| 125 | parser = argparse.ArgumentParser() |
| 126 | parser.add_argument( |
| 127 | "--target_product", |
| 128 | "-t", |
| 129 | default="aosp_cf_arm64_phone", |
| 130 | help="optional, target product, always runs as eng", |
| 131 | ) |
| 132 | parser.add_argument( |
| 133 | "modules", |
| 134 | nargs="+", |
| 135 | help="modules to compare builds with genrule sandboxing enabled/not", |
| 136 | ) |
| 137 | parser.add_argument( |
| 138 | "--show-diff", |
| 139 | "-d", |
| 140 | action="store_true", |
| 141 | required=False, |
| 142 | help="whether to display differing files", |
| 143 | ) |
Liz Kammer | 2fb361c | 2023-06-09 11:29:48 -0400 | [diff] [blame] | 144 | parser.add_argument( |
| 145 | "--output-paths-only", |
| 146 | "-o", |
| 147 | action="store_true", |
| 148 | required=False, |
| 149 | help="Whether to only return the output paths per module", |
| 150 | ) |
Liz Kammer | 767fad4 | 2023-06-09 11:23:15 -0400 | [diff] [blame] | 151 | args = parser.parse_args() |
| 152 | |
| 153 | out_dir = os.environ.get("OUT_DIR", "out") |
| 154 | target_product = args.target_product |
| 155 | modules = set(args.modules) |
| 156 | |
| 157 | module_to_outs = _find_outputs_for_modules(modules, out_dir, target_product) |
Jason Macnak | 394f1b7 | 2023-06-15 09:28:26 -0700 | [diff] [blame^] | 158 | if not module_to_outs: |
| 159 | print("No outputs found") |
| 160 | exit(1) |
| 161 | |
Liz Kammer | 2fb361c | 2023-06-09 11:29:48 -0400 | [diff] [blame] | 162 | if args.output_paths_only: |
| 163 | for m, o in module_to_outs.items(): |
| 164 | print(f"{m} outputs: {o}") |
| 165 | exit(0) |
| 166 | |
Liz Kammer | 767fad4 | 2023-06-09 11:23:15 -0400 | [diff] [blame] | 167 | all_outs = set() |
| 168 | for outs in module_to_outs.values(): |
| 169 | all_outs.update(outs) |
| 170 | print("build without sandboxing") |
| 171 | _build_with_soong(list(all_outs), target_product, out_dir) |
| 172 | tempdir = _store_outputs_to_tmp(all_outs) |
| 173 | print("build with sandboxing") |
| 174 | _build_with_soong( |
| 175 | list(all_outs), |
| 176 | target_product, |
| 177 | out_dir, |
| 178 | extra_env={"GENRULE_SANDBOXING": "true"}, |
| 179 | ) |
| 180 | diffs = _compare_outputs(module_to_outs, tempdir, args.show_diff) |
| 181 | if len(diffs) == 0: |
| 182 | print("All modules are correct") |
| 183 | elif args.show_diff: |
| 184 | for m, d in diffs.items(): |
| 185 | print(f"Module {m} has diffs {d}") |
| 186 | else: |
| 187 | print(f"Modules {list(diffs.keys())} have diffs") |
| 188 | |
| 189 | |
| 190 | if __name__ == "__main__": |
| 191 | main() |