| 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 | 
| Cole Faust | a57c8c2 | 2023-10-26 11:42:24 -0700 | [diff] [blame] | 18 | import asyncio | 
| Liz Kammer | 767fad4 | 2023-06-09 11:23:15 -0400 | [diff] [blame] | 19 | import collections | 
|  | 20 | import json | 
| Cole Faust | bbe2cc6 | 2023-07-12 17:55:28 -0700 | [diff] [blame] | 21 | import os | 
| Cole Faust | a57c8c2 | 2023-10-26 11:42:24 -0700 | [diff] [blame] | 22 | import socket | 
| Liz Kammer | 767fad4 | 2023-06-09 11:23:15 -0400 | [diff] [blame] | 23 | import subprocess | 
| Cole Faust | bbe2cc6 | 2023-07-12 17:55:28 -0700 | [diff] [blame] | 24 | import sys | 
| Cole Faust | a57c8c2 | 2023-10-26 11:42:24 -0700 | [diff] [blame] | 25 | import textwrap | 
| Liz Kammer | 767fad4 | 2023-06-09 11:23:15 -0400 | [diff] [blame] | 26 |  | 
| Cole Faust | bbe2cc6 | 2023-07-12 17:55:28 -0700 | [diff] [blame] | 27 | def get_top() -> str: | 
|  | 28 | path = '.' | 
|  | 29 | while not os.path.isfile(os.path.join(path, 'build/soong/tests/genrule_sandbox_test.py')): | 
|  | 30 | if os.path.abspath(path) == '/': | 
|  | 31 | sys.exit('Could not find android source tree root.') | 
|  | 32 | path = os.path.join(path, '..') | 
|  | 33 | return os.path.abspath(path) | 
| Liz Kammer | 767fad4 | 2023-06-09 11:23:15 -0400 | [diff] [blame] | 34 |  | 
| Cole Faust | a57c8c2 | 2023-10-26 11:42:24 -0700 | [diff] [blame] | 35 | async def _build_with_soong(out_dir, targets, *, extra_env={}): | 
|  | 36 | env = os.environ | extra_env | 
|  | 37 |  | 
|  | 38 | # Use nsjail to remap the out_dir to out/, because some genrules write the path to the out | 
|  | 39 | # dir into their artifacts, so if the out directories were different it would cause a diff | 
|  | 40 | # that doesn't really matter. | 
| Liz Kammer | 767fad4 | 2023-06-09 11:23:15 -0400 | [diff] [blame] | 41 | args = [ | 
| Cole Faust | a57c8c2 | 2023-10-26 11:42:24 -0700 | [diff] [blame] | 42 | 'prebuilts/build-tools/linux-x86/bin/nsjail', | 
|  | 43 | '-q', | 
|  | 44 | '--cwd', | 
|  | 45 | os.getcwd(), | 
|  | 46 | '-e', | 
|  | 47 | '-B', | 
|  | 48 | '/', | 
|  | 49 | '-B', | 
|  | 50 | f'{os.path.abspath(out_dir)}:{os.path.abspath("out")}', | 
|  | 51 | '--time_limit', | 
|  | 52 | '0', | 
|  | 53 | '--skip_setsid', | 
|  | 54 | '--keep_caps', | 
|  | 55 | '--disable_clone_newcgroup', | 
|  | 56 | '--disable_clone_newnet', | 
|  | 57 | '--rlimit_as', | 
|  | 58 | 'soft', | 
|  | 59 | '--rlimit_core', | 
|  | 60 | 'soft', | 
|  | 61 | '--rlimit_cpu', | 
|  | 62 | 'soft', | 
|  | 63 | '--rlimit_fsize', | 
|  | 64 | 'soft', | 
|  | 65 | '--rlimit_nofile', | 
|  | 66 | 'soft', | 
|  | 67 | '--proc_rw', | 
|  | 68 | '--hostname', | 
|  | 69 | socket.gethostname(), | 
|  | 70 | '--', | 
| Liz Kammer | 767fad4 | 2023-06-09 11:23:15 -0400 | [diff] [blame] | 71 | "build/soong/soong_ui.bash", | 
|  | 72 | "--make-mode", | 
|  | 73 | "--skip-soong-tests", | 
|  | 74 | ] | 
|  | 75 | args.extend(targets) | 
| Cole Faust | a57c8c2 | 2023-10-26 11:42:24 -0700 | [diff] [blame] | 76 | process = await asyncio.create_subprocess_exec( | 
|  | 77 | *args, | 
|  | 78 | stdout=asyncio.subprocess.PIPE, | 
|  | 79 | stderr=asyncio.subprocess.PIPE, | 
|  | 80 | env=env, | 
|  | 81 | ) | 
|  | 82 | stdout, stderr = await process.communicate() | 
|  | 83 | if process.returncode != 0: | 
|  | 84 | print(stdout) | 
|  | 85 | print(stderr) | 
|  | 86 | sys.exit(process.returncode) | 
| Liz Kammer | 767fad4 | 2023-06-09 11:23:15 -0400 | [diff] [blame] | 87 |  | 
|  | 88 |  | 
| Cole Faust | a57c8c2 | 2023-10-26 11:42:24 -0700 | [diff] [blame] | 89 | async def _find_outputs_for_modules(modules): | 
|  | 90 | module_path = "out/soong/module-actions.json" | 
| Liz Kammer | 767fad4 | 2023-06-09 11:23:15 -0400 | [diff] [blame] | 91 |  | 
|  | 92 | if not os.path.exists(module_path): | 
| Cole Faust | a57c8c2 | 2023-10-26 11:42:24 -0700 | [diff] [blame] | 93 | await _build_with_soong('out', ["json-module-graph"]) | 
| Liz Kammer | 767fad4 | 2023-06-09 11:23:15 -0400 | [diff] [blame] | 94 |  | 
| Cole Faust | bbe2cc6 | 2023-07-12 17:55:28 -0700 | [diff] [blame] | 95 | with open(module_path) as f: | 
|  | 96 | action_graph = json.load(f) | 
| Liz Kammer | 767fad4 | 2023-06-09 11:23:15 -0400 | [diff] [blame] | 97 |  | 
|  | 98 | module_to_outs = collections.defaultdict(set) | 
|  | 99 | for mod in action_graph: | 
|  | 100 | name = mod["Name"] | 
|  | 101 | if name in modules: | 
| Cole Faust | a57c8c2 | 2023-10-26 11:42:24 -0700 | [diff] [blame] | 102 | for act in (mod["Module"]["Actions"] or []): | 
| Jason Macnak | 394f1b7 | 2023-06-15 09:28:26 -0700 | [diff] [blame] | 103 | if "}generate" in act["Desc"]: | 
| Liz Kammer | 767fad4 | 2023-06-09 11:23:15 -0400 | [diff] [blame] | 104 | module_to_outs[name].update(act["Outputs"]) | 
|  | 105 | return module_to_outs | 
|  | 106 |  | 
|  | 107 |  | 
| Cole Faust | bbe2cc6 | 2023-07-12 17:55:28 -0700 | [diff] [blame] | 108 | def _compare_outputs(module_to_outs, tempdir) -> dict[str, list[str]]: | 
| Liz Kammer | 767fad4 | 2023-06-09 11:23:15 -0400 | [diff] [blame] | 109 | different_modules = collections.defaultdict(list) | 
|  | 110 | for module, outs in module_to_outs.items(): | 
|  | 111 | for out in outs: | 
| Cole Faust | bbe2cc6 | 2023-07-12 17:55:28 -0700 | [diff] [blame] | 112 | try: | 
|  | 113 | subprocess.check_output(["diff", os.path.join(tempdir, out), out]) | 
|  | 114 | except subprocess.CalledProcessError as e: | 
|  | 115 | different_modules[module].append(e.stdout) | 
| Liz Kammer | 767fad4 | 2023-06-09 11:23:15 -0400 | [diff] [blame] | 116 |  | 
| Liz Kammer | 767fad4 | 2023-06-09 11:23:15 -0400 | [diff] [blame] | 117 | return different_modules | 
|  | 118 |  | 
|  | 119 |  | 
| Cole Faust | a57c8c2 | 2023-10-26 11:42:24 -0700 | [diff] [blame] | 120 | async def main(): | 
| Liz Kammer | 767fad4 | 2023-06-09 11:23:15 -0400 | [diff] [blame] | 121 | parser = argparse.ArgumentParser() | 
|  | 122 | parser.add_argument( | 
| Liz Kammer | 767fad4 | 2023-06-09 11:23:15 -0400 | [diff] [blame] | 123 | "modules", | 
|  | 124 | nargs="+", | 
|  | 125 | help="modules to compare builds with genrule sandboxing enabled/not", | 
|  | 126 | ) | 
|  | 127 | parser.add_argument( | 
| Cole Faust | a57c8c2 | 2023-10-26 11:42:24 -0700 | [diff] [blame] | 128 | "--check-determinism", | 
|  | 129 | action="store_true", | 
|  | 130 | help="Don't check for working sandboxing. Instead, run two default builds, and compare their outputs. This is used to check for nondeterminsim, which would also affect the sandboxed test.", | 
|  | 131 | ) | 
|  | 132 | parser.add_argument( | 
| Liz Kammer | 767fad4 | 2023-06-09 11:23:15 -0400 | [diff] [blame] | 133 | "--show-diff", | 
|  | 134 | "-d", | 
|  | 135 | action="store_true", | 
| Liz Kammer | 767fad4 | 2023-06-09 11:23:15 -0400 | [diff] [blame] | 136 | help="whether to display differing files", | 
|  | 137 | ) | 
| Liz Kammer | 2fb361c | 2023-06-09 11:29:48 -0400 | [diff] [blame] | 138 | parser.add_argument( | 
|  | 139 | "--output-paths-only", | 
|  | 140 | "-o", | 
|  | 141 | action="store_true", | 
| Liz Kammer | 2fb361c | 2023-06-09 11:29:48 -0400 | [diff] [blame] | 142 | help="Whether to only return the output paths per module", | 
|  | 143 | ) | 
| Liz Kammer | 767fad4 | 2023-06-09 11:23:15 -0400 | [diff] [blame] | 144 | args = parser.parse_args() | 
| Cole Faust | bbe2cc6 | 2023-07-12 17:55:28 -0700 | [diff] [blame] | 145 | os.chdir(get_top()) | 
| Liz Kammer | 767fad4 | 2023-06-09 11:23:15 -0400 | [diff] [blame] | 146 |  | 
| Cole Faust | a57c8c2 | 2023-10-26 11:42:24 -0700 | [diff] [blame] | 147 | if "TARGET_PRODUCT" not in os.environ: | 
|  | 148 | sys.exit("Please run lunch first") | 
|  | 149 | if os.environ.get("OUT_DIR", "out") != "out": | 
|  | 150 | sys.exit(f"This script expects OUT_DIR to be 'out', got: '{os.environ.get('OUT_DIR')}'") | 
| Liz Kammer | 767fad4 | 2023-06-09 11:23:15 -0400 | [diff] [blame] | 151 |  | 
| Cole Faust | bbe2cc6 | 2023-07-12 17:55:28 -0700 | [diff] [blame] | 152 | print("finding output files for the modules...") | 
| Cole Faust | a57c8c2 | 2023-10-26 11:42:24 -0700 | [diff] [blame] | 153 | module_to_outs = await _find_outputs_for_modules(set(args.modules)) | 
| Jason Macnak | 394f1b7 | 2023-06-15 09:28:26 -0700 | [diff] [blame] | 154 | if not module_to_outs: | 
| Cole Faust | bbe2cc6 | 2023-07-12 17:55:28 -0700 | [diff] [blame] | 155 | sys.exit("No outputs found") | 
| Jason Macnak | 394f1b7 | 2023-06-15 09:28:26 -0700 | [diff] [blame] | 156 |  | 
| Liz Kammer | 2fb361c | 2023-06-09 11:29:48 -0400 | [diff] [blame] | 157 | if args.output_paths_only: | 
|  | 158 | for m, o in module_to_outs.items(): | 
|  | 159 | print(f"{m} outputs: {o}") | 
| Cole Faust | bbe2cc6 | 2023-07-12 17:55:28 -0700 | [diff] [blame] | 160 | sys.exit(0) | 
| Liz Kammer | 2fb361c | 2023-06-09 11:29:48 -0400 | [diff] [blame] | 161 |  | 
| Cole Faust | bbe2cc6 | 2023-07-12 17:55:28 -0700 | [diff] [blame] | 162 | all_outs = list(set.union(*module_to_outs.values())) | 
| Cole Faust | a57c8c2 | 2023-10-26 11:42:24 -0700 | [diff] [blame] | 163 | for i, out in enumerate(all_outs): | 
|  | 164 | if not out.startswith("out/"): | 
|  | 165 | sys.exit("Expected output file to start with out/, found: " + out) | 
| Cole Faust | bbe2cc6 | 2023-07-12 17:55:28 -0700 | [diff] [blame] | 166 |  | 
| Cole Faust | a57c8c2 | 2023-10-26 11:42:24 -0700 | [diff] [blame] | 167 | other_out_dir = "out_check_determinism" if args.check_determinism else "out_not_sandboxed" | 
|  | 168 | other_env = {"GENRULE_SANDBOXING": "false"} | 
|  | 169 | if args.check_determinism: | 
|  | 170 | other_env = {} | 
| Cole Faust | bbe2cc6 | 2023-07-12 17:55:28 -0700 | [diff] [blame] | 171 |  | 
| Cole Faust | a57c8c2 | 2023-10-26 11:42:24 -0700 | [diff] [blame] | 172 | # nsjail will complain if the out dir doesn't exist | 
|  | 173 | os.makedirs("out", exist_ok=True) | 
|  | 174 | os.makedirs(other_out_dir, exist_ok=True) | 
| Cole Faust | bbe2cc6 | 2023-07-12 17:55:28 -0700 | [diff] [blame] | 175 |  | 
| Cole Faust | a57c8c2 | 2023-10-26 11:42:24 -0700 | [diff] [blame] | 176 | print("building...") | 
|  | 177 | await asyncio.gather( | 
|  | 178 | _build_with_soong("out", all_outs), | 
|  | 179 | _build_with_soong(other_out_dir, all_outs, extra_env=other_env) | 
|  | 180 | ) | 
|  | 181 |  | 
|  | 182 | diffs = collections.defaultdict(dict) | 
|  | 183 | for module, outs in module_to_outs.items(): | 
|  | 184 | for out in outs: | 
|  | 185 | try: | 
|  | 186 | subprocess.check_output(["diff", os.path.join(other_out_dir, out.removeprefix("out/")), out]) | 
|  | 187 | except subprocess.CalledProcessError as e: | 
|  | 188 | diffs[module][out] = e.stdout | 
|  | 189 |  | 
|  | 190 | if len(diffs) == 0: | 
|  | 191 | print("All modules are correct") | 
|  | 192 | elif args.show_diff: | 
|  | 193 | for m, files in diffs.items(): | 
|  | 194 | print(f"Module {m} has diffs:") | 
|  | 195 | for f, d in files.items(): | 
|  | 196 | print("  "+f+":") | 
|  | 197 | print(textwrap.indent(d, "    ")) | 
|  | 198 | else: | 
|  | 199 | print(f"Modules {list(diffs.keys())} have diffs in these files:") | 
|  | 200 | all_diff_files = [f for m in diffs.values() for f in m] | 
|  | 201 | for f in all_diff_files: | 
|  | 202 | print(f) | 
|  | 203 |  | 
| Liz Kammer | 767fad4 | 2023-06-09 11:23:15 -0400 | [diff] [blame] | 204 |  | 
|  | 205 |  | 
|  | 206 | if __name__ == "__main__": | 
| Cole Faust | a57c8c2 | 2023-10-26 11:42:24 -0700 | [diff] [blame] | 207 | asyncio.run(main()) |