1*333d2b36SAndroid Build Coastguard Worker#!/usr/bin/env python3 2*333d2b36SAndroid Build Coastguard Worker 3*333d2b36SAndroid Build Coastguard Worker# Copyright (C) 2023 The Android Open Source Project 4*333d2b36SAndroid Build Coastguard Worker# 5*333d2b36SAndroid Build Coastguard Worker# Licensed under the Apache License, Version 2.0 (the "License"); 6*333d2b36SAndroid Build Coastguard Worker# you may not use this file except in compliance with the License. 7*333d2b36SAndroid Build Coastguard Worker# You may obtain a copy of the License at 8*333d2b36SAndroid Build Coastguard Worker# 9*333d2b36SAndroid Build Coastguard Worker# http://www.apache.org/licenses/LICENSE-2.0 10*333d2b36SAndroid Build Coastguard Worker# 11*333d2b36SAndroid Build Coastguard Worker# Unless required by applicable law or agreed to in writing, software 12*333d2b36SAndroid Build Coastguard Worker# distributed under the License is distributed on an "AS IS" BASIS, 13*333d2b36SAndroid Build Coastguard Worker# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14*333d2b36SAndroid Build Coastguard Worker# See the License for the specific language governing permissions and 15*333d2b36SAndroid Build Coastguard Worker# limitations under the License. 16*333d2b36SAndroid Build Coastguard Worker 17*333d2b36SAndroid Build Coastguard Workerimport argparse 18*333d2b36SAndroid Build Coastguard Workerimport asyncio 19*333d2b36SAndroid Build Coastguard Workerimport collections 20*333d2b36SAndroid Build Coastguard Workerimport json 21*333d2b36SAndroid Build Coastguard Workerimport os 22*333d2b36SAndroid Build Coastguard Workerimport socket 23*333d2b36SAndroid Build Coastguard Workerimport subprocess 24*333d2b36SAndroid Build Coastguard Workerimport sys 25*333d2b36SAndroid Build Coastguard Workerimport textwrap 26*333d2b36SAndroid Build Coastguard Worker 27*333d2b36SAndroid Build Coastguard Workerdef get_top() -> str: 28*333d2b36SAndroid Build Coastguard Worker path = '.' 29*333d2b36SAndroid Build Coastguard Worker while not os.path.isfile(os.path.join(path, 'build/soong/tests/genrule_sandbox_test.py')): 30*333d2b36SAndroid Build Coastguard Worker if os.path.abspath(path) == '/': 31*333d2b36SAndroid Build Coastguard Worker sys.exit('Could not find android source tree root.') 32*333d2b36SAndroid Build Coastguard Worker path = os.path.join(path, '..') 33*333d2b36SAndroid Build Coastguard Worker return os.path.abspath(path) 34*333d2b36SAndroid Build Coastguard Worker 35*333d2b36SAndroid Build Coastguard Workerasync def _build_with_soong(out_dir, targets, *, extra_env={}): 36*333d2b36SAndroid Build Coastguard Worker env = os.environ | extra_env 37*333d2b36SAndroid Build Coastguard Worker 38*333d2b36SAndroid Build Coastguard Worker # Use nsjail to remap the out_dir to out/, because some genrules write the path to the out 39*333d2b36SAndroid Build Coastguard Worker # dir into their artifacts, so if the out directories were different it would cause a diff 40*333d2b36SAndroid Build Coastguard Worker # that doesn't really matter. 41*333d2b36SAndroid Build Coastguard Worker args = [ 42*333d2b36SAndroid Build Coastguard Worker 'prebuilts/build-tools/linux-x86/bin/nsjail', 43*333d2b36SAndroid Build Coastguard Worker '-q', 44*333d2b36SAndroid Build Coastguard Worker '--cwd', 45*333d2b36SAndroid Build Coastguard Worker os.getcwd(), 46*333d2b36SAndroid Build Coastguard Worker '-e', 47*333d2b36SAndroid Build Coastguard Worker '-B', 48*333d2b36SAndroid Build Coastguard Worker '/', 49*333d2b36SAndroid Build Coastguard Worker '-B', 50*333d2b36SAndroid Build Coastguard Worker f'{os.path.abspath(out_dir)}:{os.path.abspath("out")}', 51*333d2b36SAndroid Build Coastguard Worker '--time_limit', 52*333d2b36SAndroid Build Coastguard Worker '0', 53*333d2b36SAndroid Build Coastguard Worker '--skip_setsid', 54*333d2b36SAndroid Build Coastguard Worker '--keep_caps', 55*333d2b36SAndroid Build Coastguard Worker '--disable_clone_newcgroup', 56*333d2b36SAndroid Build Coastguard Worker '--disable_clone_newnet', 57*333d2b36SAndroid Build Coastguard Worker '--rlimit_as', 58*333d2b36SAndroid Build Coastguard Worker 'soft', 59*333d2b36SAndroid Build Coastguard Worker '--rlimit_core', 60*333d2b36SAndroid Build Coastguard Worker 'soft', 61*333d2b36SAndroid Build Coastguard Worker '--rlimit_cpu', 62*333d2b36SAndroid Build Coastguard Worker 'soft', 63*333d2b36SAndroid Build Coastguard Worker '--rlimit_fsize', 64*333d2b36SAndroid Build Coastguard Worker 'soft', 65*333d2b36SAndroid Build Coastguard Worker '--rlimit_nofile', 66*333d2b36SAndroid Build Coastguard Worker 'soft', 67*333d2b36SAndroid Build Coastguard Worker '--proc_rw', 68*333d2b36SAndroid Build Coastguard Worker '--hostname', 69*333d2b36SAndroid Build Coastguard Worker socket.gethostname(), 70*333d2b36SAndroid Build Coastguard Worker '--', 71*333d2b36SAndroid Build Coastguard Worker "build/soong/soong_ui.bash", 72*333d2b36SAndroid Build Coastguard Worker "--make-mode", 73*333d2b36SAndroid Build Coastguard Worker "--skip-soong-tests", 74*333d2b36SAndroid Build Coastguard Worker ] 75*333d2b36SAndroid Build Coastguard Worker args.extend(targets) 76*333d2b36SAndroid Build Coastguard Worker process = await asyncio.create_subprocess_exec( 77*333d2b36SAndroid Build Coastguard Worker *args, 78*333d2b36SAndroid Build Coastguard Worker stdout=asyncio.subprocess.PIPE, 79*333d2b36SAndroid Build Coastguard Worker stderr=asyncio.subprocess.PIPE, 80*333d2b36SAndroid Build Coastguard Worker env=env, 81*333d2b36SAndroid Build Coastguard Worker ) 82*333d2b36SAndroid Build Coastguard Worker stdout, stderr = await process.communicate() 83*333d2b36SAndroid Build Coastguard Worker if process.returncode != 0: 84*333d2b36SAndroid Build Coastguard Worker print(stdout) 85*333d2b36SAndroid Build Coastguard Worker print(stderr) 86*333d2b36SAndroid Build Coastguard Worker sys.exit(process.returncode) 87*333d2b36SAndroid Build Coastguard Worker 88*333d2b36SAndroid Build Coastguard Worker 89*333d2b36SAndroid Build Coastguard Workerasync def _find_outputs_for_modules(modules): 90*333d2b36SAndroid Build Coastguard Worker module_path = "out/soong/module-actions.json" 91*333d2b36SAndroid Build Coastguard Worker 92*333d2b36SAndroid Build Coastguard Worker if not os.path.exists(module_path): 93*333d2b36SAndroid Build Coastguard Worker await _build_with_soong('out', ["json-module-graph"]) 94*333d2b36SAndroid Build Coastguard Worker 95*333d2b36SAndroid Build Coastguard Worker with open(module_path) as f: 96*333d2b36SAndroid Build Coastguard Worker action_graph = json.load(f) 97*333d2b36SAndroid Build Coastguard Worker 98*333d2b36SAndroid Build Coastguard Worker module_to_outs = collections.defaultdict(set) 99*333d2b36SAndroid Build Coastguard Worker for mod in action_graph: 100*333d2b36SAndroid Build Coastguard Worker name = mod["Name"] 101*333d2b36SAndroid Build Coastguard Worker if name in modules: 102*333d2b36SAndroid Build Coastguard Worker for act in (mod["Module"]["Actions"] or []): 103*333d2b36SAndroid Build Coastguard Worker if "}generate" in act["Desc"]: 104*333d2b36SAndroid Build Coastguard Worker module_to_outs[name].update(act["Outputs"]) 105*333d2b36SAndroid Build Coastguard Worker return module_to_outs 106*333d2b36SAndroid Build Coastguard Worker 107*333d2b36SAndroid Build Coastguard Worker 108*333d2b36SAndroid Build Coastguard Workerdef _compare_outputs(module_to_outs, tempdir) -> dict[str, list[str]]: 109*333d2b36SAndroid Build Coastguard Worker different_modules = collections.defaultdict(list) 110*333d2b36SAndroid Build Coastguard Worker for module, outs in module_to_outs.items(): 111*333d2b36SAndroid Build Coastguard Worker for out in outs: 112*333d2b36SAndroid Build Coastguard Worker try: 113*333d2b36SAndroid Build Coastguard Worker subprocess.check_output(["diff", os.path.join(tempdir, out), out]) 114*333d2b36SAndroid Build Coastguard Worker except subprocess.CalledProcessError as e: 115*333d2b36SAndroid Build Coastguard Worker different_modules[module].append(e.stdout) 116*333d2b36SAndroid Build Coastguard Worker 117*333d2b36SAndroid Build Coastguard Worker return different_modules 118*333d2b36SAndroid Build Coastguard Worker 119*333d2b36SAndroid Build Coastguard Worker 120*333d2b36SAndroid Build Coastguard Workerasync def main(): 121*333d2b36SAndroid Build Coastguard Worker parser = argparse.ArgumentParser() 122*333d2b36SAndroid Build Coastguard Worker parser.add_argument( 123*333d2b36SAndroid Build Coastguard Worker "modules", 124*333d2b36SAndroid Build Coastguard Worker nargs="+", 125*333d2b36SAndroid Build Coastguard Worker help="modules to compare builds with genrule sandboxing enabled/not", 126*333d2b36SAndroid Build Coastguard Worker ) 127*333d2b36SAndroid Build Coastguard Worker parser.add_argument( 128*333d2b36SAndroid Build Coastguard Worker "--check-determinism", 129*333d2b36SAndroid Build Coastguard Worker action="store_true", 130*333d2b36SAndroid Build Coastguard Worker 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*333d2b36SAndroid Build Coastguard Worker ) 132*333d2b36SAndroid Build Coastguard Worker parser.add_argument( 133*333d2b36SAndroid Build Coastguard Worker "--show-diff", 134*333d2b36SAndroid Build Coastguard Worker "-d", 135*333d2b36SAndroid Build Coastguard Worker action="store_true", 136*333d2b36SAndroid Build Coastguard Worker help="whether to display differing files", 137*333d2b36SAndroid Build Coastguard Worker ) 138*333d2b36SAndroid Build Coastguard Worker parser.add_argument( 139*333d2b36SAndroid Build Coastguard Worker "--output-paths-only", 140*333d2b36SAndroid Build Coastguard Worker "-o", 141*333d2b36SAndroid Build Coastguard Worker action="store_true", 142*333d2b36SAndroid Build Coastguard Worker help="Whether to only return the output paths per module", 143*333d2b36SAndroid Build Coastguard Worker ) 144*333d2b36SAndroid Build Coastguard Worker args = parser.parse_args() 145*333d2b36SAndroid Build Coastguard Worker os.chdir(get_top()) 146*333d2b36SAndroid Build Coastguard Worker 147*333d2b36SAndroid Build Coastguard Worker if "TARGET_PRODUCT" not in os.environ: 148*333d2b36SAndroid Build Coastguard Worker sys.exit("Please run lunch first") 149*333d2b36SAndroid Build Coastguard Worker if os.environ.get("OUT_DIR", "out") != "out": 150*333d2b36SAndroid Build Coastguard Worker sys.exit(f"This script expects OUT_DIR to be 'out', got: '{os.environ.get('OUT_DIR')}'") 151*333d2b36SAndroid Build Coastguard Worker 152*333d2b36SAndroid Build Coastguard Worker print("finding output files for the modules...") 153*333d2b36SAndroid Build Coastguard Worker module_to_outs = await _find_outputs_for_modules(set(args.modules)) 154*333d2b36SAndroid Build Coastguard Worker if not module_to_outs: 155*333d2b36SAndroid Build Coastguard Worker sys.exit("No outputs found") 156*333d2b36SAndroid Build Coastguard Worker 157*333d2b36SAndroid Build Coastguard Worker if args.output_paths_only: 158*333d2b36SAndroid Build Coastguard Worker for m, o in module_to_outs.items(): 159*333d2b36SAndroid Build Coastguard Worker print(f"{m} outputs: {o}") 160*333d2b36SAndroid Build Coastguard Worker sys.exit(0) 161*333d2b36SAndroid Build Coastguard Worker 162*333d2b36SAndroid Build Coastguard Worker all_outs = list(set.union(*module_to_outs.values())) 163*333d2b36SAndroid Build Coastguard Worker for i, out in enumerate(all_outs): 164*333d2b36SAndroid Build Coastguard Worker if not out.startswith("out/"): 165*333d2b36SAndroid Build Coastguard Worker sys.exit("Expected output file to start with out/, found: " + out) 166*333d2b36SAndroid Build Coastguard Worker 167*333d2b36SAndroid Build Coastguard Worker other_out_dir = "out_check_determinism" if args.check_determinism else "out_not_sandboxed" 168*333d2b36SAndroid Build Coastguard Worker other_env = {"GENRULE_SANDBOXING": "false"} 169*333d2b36SAndroid Build Coastguard Worker if args.check_determinism: 170*333d2b36SAndroid Build Coastguard Worker other_env = {} 171*333d2b36SAndroid Build Coastguard Worker 172*333d2b36SAndroid Build Coastguard Worker # nsjail will complain if the out dir doesn't exist 173*333d2b36SAndroid Build Coastguard Worker os.makedirs("out", exist_ok=True) 174*333d2b36SAndroid Build Coastguard Worker os.makedirs(other_out_dir, exist_ok=True) 175*333d2b36SAndroid Build Coastguard Worker 176*333d2b36SAndroid Build Coastguard Worker print("building...") 177*333d2b36SAndroid Build Coastguard Worker await asyncio.gather( 178*333d2b36SAndroid Build Coastguard Worker _build_with_soong("out", all_outs), 179*333d2b36SAndroid Build Coastguard Worker _build_with_soong(other_out_dir, all_outs, extra_env=other_env) 180*333d2b36SAndroid Build Coastguard Worker ) 181*333d2b36SAndroid Build Coastguard Worker 182*333d2b36SAndroid Build Coastguard Worker diffs = collections.defaultdict(dict) 183*333d2b36SAndroid Build Coastguard Worker for module, outs in module_to_outs.items(): 184*333d2b36SAndroid Build Coastguard Worker for out in outs: 185*333d2b36SAndroid Build Coastguard Worker try: 186*333d2b36SAndroid Build Coastguard Worker subprocess.check_output(["diff", os.path.join(other_out_dir, out.removeprefix("out/")), out]) 187*333d2b36SAndroid Build Coastguard Worker except subprocess.CalledProcessError as e: 188*333d2b36SAndroid Build Coastguard Worker diffs[module][out] = e.stdout 189*333d2b36SAndroid Build Coastguard Worker 190*333d2b36SAndroid Build Coastguard Worker if len(diffs) == 0: 191*333d2b36SAndroid Build Coastguard Worker print("All modules are correct") 192*333d2b36SAndroid Build Coastguard Worker elif args.show_diff: 193*333d2b36SAndroid Build Coastguard Worker for m, files in diffs.items(): 194*333d2b36SAndroid Build Coastguard Worker print(f"Module {m} has diffs:") 195*333d2b36SAndroid Build Coastguard Worker for f, d in files.items(): 196*333d2b36SAndroid Build Coastguard Worker print(" "+f+":") 197*333d2b36SAndroid Build Coastguard Worker print(textwrap.indent(d, " ")) 198*333d2b36SAndroid Build Coastguard Worker else: 199*333d2b36SAndroid Build Coastguard Worker print(f"Modules {list(diffs.keys())} have diffs in these files:") 200*333d2b36SAndroid Build Coastguard Worker all_diff_files = [f for m in diffs.values() for f in m] 201*333d2b36SAndroid Build Coastguard Worker for f in all_diff_files: 202*333d2b36SAndroid Build Coastguard Worker print(f) 203*333d2b36SAndroid Build Coastguard Worker 204*333d2b36SAndroid Build Coastguard Worker 205*333d2b36SAndroid Build Coastguard Worker 206*333d2b36SAndroid Build Coastguard Workerif __name__ == "__main__": 207*333d2b36SAndroid Build Coastguard Worker asyncio.run(main()) 208