1*333d2b36SAndroid Build Coastguard Worker#!/usr/bin/env python3 2*333d2b36SAndroid Build Coastguard Worker 3*333d2b36SAndroid Build Coastguard Workerimport asyncio 4*333d2b36SAndroid Build Coastguard Workerimport argparse 5*333d2b36SAndroid Build Coastguard Workerimport dataclasses 6*333d2b36SAndroid Build Coastguard Workerimport hashlib 7*333d2b36SAndroid Build Coastguard Workerimport os 8*333d2b36SAndroid Build Coastguard Workerimport re 9*333d2b36SAndroid Build Coastguard Workerimport socket 10*333d2b36SAndroid Build Coastguard Workerimport subprocess 11*333d2b36SAndroid Build Coastguard Workerimport sys 12*333d2b36SAndroid Build Coastguard Workerimport zipfile 13*333d2b36SAndroid Build Coastguard Worker 14*333d2b36SAndroid Build Coastguard Workerfrom typing import List 15*333d2b36SAndroid Build Coastguard Worker 16*333d2b36SAndroid Build Coastguard Workerdef get_top() -> str: 17*333d2b36SAndroid Build Coastguard Worker path = '.' 18*333d2b36SAndroid Build Coastguard Worker while not os.path.isfile(os.path.join(path, 'build/soong/soong_ui.bash')): 19*333d2b36SAndroid Build Coastguard Worker if os.path.abspath(path) == '/': 20*333d2b36SAndroid Build Coastguard Worker sys.exit('Could not find android source tree root.') 21*333d2b36SAndroid Build Coastguard Worker path = os.path.join(path, '..') 22*333d2b36SAndroid Build Coastguard Worker return os.path.abspath(path) 23*333d2b36SAndroid Build Coastguard Worker 24*333d2b36SAndroid Build Coastguard Worker 25*333d2b36SAndroid Build Coastguard Worker_PRODUCT_REGEX = re.compile(r'([a-zA-Z_][a-zA-Z0-9_]*)(?:(?:-([a-zA-Z_][a-zA-Z0-9_]*))?-(user|userdebug|eng))?') 26*333d2b36SAndroid Build Coastguard Worker 27*333d2b36SAndroid Build Coastguard Worker 28*333d2b36SAndroid Build Coastguard Worker@dataclasses.dataclass(frozen=True) 29*333d2b36SAndroid Build Coastguard Workerclass Product: 30*333d2b36SAndroid Build Coastguard Worker """Represents a TARGET_PRODUCT and TARGET_BUILD_VARIANT.""" 31*333d2b36SAndroid Build Coastguard Worker product: str 32*333d2b36SAndroid Build Coastguard Worker release: str 33*333d2b36SAndroid Build Coastguard Worker variant: str 34*333d2b36SAndroid Build Coastguard Worker 35*333d2b36SAndroid Build Coastguard Worker def __post_init__(self): 36*333d2b36SAndroid Build Coastguard Worker if not _PRODUCT_REGEX.match(str(self)): 37*333d2b36SAndroid Build Coastguard Worker raise ValueError(f'Invalid product name: {self}') 38*333d2b36SAndroid Build Coastguard Worker 39*333d2b36SAndroid Build Coastguard Worker def __str__(self): 40*333d2b36SAndroid Build Coastguard Worker return self.product + '-' + self.release + '-' + self.variant 41*333d2b36SAndroid Build Coastguard Worker 42*333d2b36SAndroid Build Coastguard Worker 43*333d2b36SAndroid Build Coastguard Workerasync def run_make_nothing(product: Product, out_dir: str) -> bool: 44*333d2b36SAndroid Build Coastguard Worker """Runs a build and returns if it succeeded or not.""" 45*333d2b36SAndroid Build Coastguard Worker with open(os.path.join(out_dir, 'build.log'), 'wb') as f: 46*333d2b36SAndroid Build Coastguard Worker result = await asyncio.create_subprocess_exec( 47*333d2b36SAndroid Build Coastguard Worker 'prebuilts/build-tools/linux-x86/bin/nsjail', 48*333d2b36SAndroid Build Coastguard Worker '-q', 49*333d2b36SAndroid Build Coastguard Worker '--cwd', 50*333d2b36SAndroid Build Coastguard Worker os.getcwd(), 51*333d2b36SAndroid Build Coastguard Worker '-e', 52*333d2b36SAndroid Build Coastguard Worker '-B', 53*333d2b36SAndroid Build Coastguard Worker '/', 54*333d2b36SAndroid Build Coastguard Worker '-B', 55*333d2b36SAndroid Build Coastguard Worker f'{os.path.abspath(out_dir)}:{os.path.join(os.getcwd(), "out")}', 56*333d2b36SAndroid Build Coastguard Worker '--time_limit', 57*333d2b36SAndroid Build Coastguard Worker '0', 58*333d2b36SAndroid Build Coastguard Worker '--skip_setsid', 59*333d2b36SAndroid Build Coastguard Worker '--keep_caps', 60*333d2b36SAndroid Build Coastguard Worker '--disable_clone_newcgroup', 61*333d2b36SAndroid Build Coastguard Worker '--disable_clone_newnet', 62*333d2b36SAndroid Build Coastguard Worker '--rlimit_as', 63*333d2b36SAndroid Build Coastguard Worker 'soft', 64*333d2b36SAndroid Build Coastguard Worker '--rlimit_core', 65*333d2b36SAndroid Build Coastguard Worker 'soft', 66*333d2b36SAndroid Build Coastguard Worker '--rlimit_cpu', 67*333d2b36SAndroid Build Coastguard Worker 'soft', 68*333d2b36SAndroid Build Coastguard Worker '--rlimit_fsize', 69*333d2b36SAndroid Build Coastguard Worker 'soft', 70*333d2b36SAndroid Build Coastguard Worker '--rlimit_nofile', 71*333d2b36SAndroid Build Coastguard Worker 'soft', 72*333d2b36SAndroid Build Coastguard Worker '--proc_rw', 73*333d2b36SAndroid Build Coastguard Worker '--hostname', 74*333d2b36SAndroid Build Coastguard Worker socket.gethostname(), 75*333d2b36SAndroid Build Coastguard Worker '--', 76*333d2b36SAndroid Build Coastguard Worker 'build/soong/soong_ui.bash', 77*333d2b36SAndroid Build Coastguard Worker '--make-mode', 78*333d2b36SAndroid Build Coastguard Worker f'TARGET_PRODUCT={product.product}', 79*333d2b36SAndroid Build Coastguard Worker f'TARGET_RELEASE={product.release}', 80*333d2b36SAndroid Build Coastguard Worker f'TARGET_BUILD_VARIANT={product.variant}', 81*333d2b36SAndroid Build Coastguard Worker '--skip-ninja', 82*333d2b36SAndroid Build Coastguard Worker 'nothing', stdout=f, stderr=subprocess.STDOUT) 83*333d2b36SAndroid Build Coastguard Worker return await result.wait() == 0 84*333d2b36SAndroid Build Coastguard Worker 85*333d2b36SAndroid Build Coastguard WorkerSUBNINJA_OR_INCLUDE_REGEX = re.compile(rb'\n(?:include|subninja) ') 86*333d2b36SAndroid Build Coastguard Worker 87*333d2b36SAndroid Build Coastguard Workerdef find_subninjas_and_includes(contents) -> List[str]: 88*333d2b36SAndroid Build Coastguard Worker results = [] 89*333d2b36SAndroid Build Coastguard Worker def get_path_from_directive(i): 90*333d2b36SAndroid Build Coastguard Worker j = contents.find(b'\n', i) 91*333d2b36SAndroid Build Coastguard Worker if j < 0: 92*333d2b36SAndroid Build Coastguard Worker path_bytes = contents[i:] 93*333d2b36SAndroid Build Coastguard Worker else: 94*333d2b36SAndroid Build Coastguard Worker path_bytes = contents[i:j] 95*333d2b36SAndroid Build Coastguard Worker path_bytes = path_bytes.removesuffix(b'\r') 96*333d2b36SAndroid Build Coastguard Worker path = path_bytes.decode() 97*333d2b36SAndroid Build Coastguard Worker if '$' in path: 98*333d2b36SAndroid Build Coastguard Worker sys.exit('includes/subninjas with variables are unsupported: '+path) 99*333d2b36SAndroid Build Coastguard Worker return path 100*333d2b36SAndroid Build Coastguard Worker 101*333d2b36SAndroid Build Coastguard Worker if contents.startswith(b"include "): 102*333d2b36SAndroid Build Coastguard Worker results.append(get_path_from_directive(len(b"include "))) 103*333d2b36SAndroid Build Coastguard Worker elif contents.startswith(b"subninja "): 104*333d2b36SAndroid Build Coastguard Worker results.append(get_path_from_directive(len(b"subninja "))) 105*333d2b36SAndroid Build Coastguard Worker 106*333d2b36SAndroid Build Coastguard Worker for match in SUBNINJA_OR_INCLUDE_REGEX.finditer(contents): 107*333d2b36SAndroid Build Coastguard Worker results.append(get_path_from_directive(match.end())) 108*333d2b36SAndroid Build Coastguard Worker 109*333d2b36SAndroid Build Coastguard Worker return results 110*333d2b36SAndroid Build Coastguard Worker 111*333d2b36SAndroid Build Coastguard Worker 112*333d2b36SAndroid Build Coastguard Workerdef transitively_included_ninja_files(out_dir: str, ninja_file: str, seen): 113*333d2b36SAndroid Build Coastguard Worker with open(ninja_file, 'rb') as f: 114*333d2b36SAndroid Build Coastguard Worker contents = f.read() 115*333d2b36SAndroid Build Coastguard Worker 116*333d2b36SAndroid Build Coastguard Worker results = [ninja_file] 117*333d2b36SAndroid Build Coastguard Worker seen[ninja_file] = True 118*333d2b36SAndroid Build Coastguard Worker sub_files = find_subninjas_and_includes(contents) 119*333d2b36SAndroid Build Coastguard Worker for sub_file in sub_files: 120*333d2b36SAndroid Build Coastguard Worker sub_file = os.path.join(out_dir, sub_file.removeprefix('out/')) 121*333d2b36SAndroid Build Coastguard Worker if sub_file not in seen: 122*333d2b36SAndroid Build Coastguard Worker results.extend(transitively_included_ninja_files(out_dir, sub_file, seen)) 123*333d2b36SAndroid Build Coastguard Worker 124*333d2b36SAndroid Build Coastguard Worker return results 125*333d2b36SAndroid Build Coastguard Worker 126*333d2b36SAndroid Build Coastguard Worker 127*333d2b36SAndroid Build Coastguard Workerdef hash_ninja_file(out_dir: str, ninja_file: str, hasher): 128*333d2b36SAndroid Build Coastguard Worker with open(ninja_file, 'rb') as f: 129*333d2b36SAndroid Build Coastguard Worker contents = f.read() 130*333d2b36SAndroid Build Coastguard Worker 131*333d2b36SAndroid Build Coastguard Worker sub_files = find_subninjas_and_includes(contents) 132*333d2b36SAndroid Build Coastguard Worker 133*333d2b36SAndroid Build Coastguard Worker hasher.update(contents) 134*333d2b36SAndroid Build Coastguard Worker 135*333d2b36SAndroid Build Coastguard Worker for sub_file in sub_files: 136*333d2b36SAndroid Build Coastguard Worker hash_ninja_file(out_dir, os.path.join(out_dir, sub_file.removeprefix('out/')), hasher) 137*333d2b36SAndroid Build Coastguard Worker 138*333d2b36SAndroid Build Coastguard Worker 139*333d2b36SAndroid Build Coastguard Workerdef hash_files(files: List[str]) -> str: 140*333d2b36SAndroid Build Coastguard Worker hasher = hashlib.md5() 141*333d2b36SAndroid Build Coastguard Worker for file in files: 142*333d2b36SAndroid Build Coastguard Worker with open(file, 'rb') as f: 143*333d2b36SAndroid Build Coastguard Worker hasher.update(f.read()) 144*333d2b36SAndroid Build Coastguard Worker return hasher.hexdigest() 145*333d2b36SAndroid Build Coastguard Worker 146*333d2b36SAndroid Build Coastguard Worker 147*333d2b36SAndroid Build Coastguard Workerdef dist_ninja_files(out_dir: str, zip_name: str, ninja_files: List[str]): 148*333d2b36SAndroid Build Coastguard Worker dist_dir = os.getenv('DIST_DIR', os.path.join(os.getenv('OUT_DIR', 'out'), 'dist')) 149*333d2b36SAndroid Build Coastguard Worker os.makedirs(dist_dir, exist_ok=True) 150*333d2b36SAndroid Build Coastguard Worker 151*333d2b36SAndroid Build Coastguard Worker with open(os.path.join(dist_dir, zip_name), 'wb') as f: 152*333d2b36SAndroid Build Coastguard Worker with zipfile.ZipFile(f, mode='w') as zf: 153*333d2b36SAndroid Build Coastguard Worker for ninja_file in ninja_files: 154*333d2b36SAndroid Build Coastguard Worker zf.write(ninja_file, arcname=os.path.basename(out_dir)+'/out/' + os.path.relpath(ninja_file, out_dir)) 155*333d2b36SAndroid Build Coastguard Worker 156*333d2b36SAndroid Build Coastguard Worker 157*333d2b36SAndroid Build Coastguard Workerasync def main(): 158*333d2b36SAndroid Build Coastguard Worker parser = argparse.ArgumentParser() 159*333d2b36SAndroid Build Coastguard Worker args = parser.parse_args() 160*333d2b36SAndroid Build Coastguard Worker 161*333d2b36SAndroid Build Coastguard Worker os.chdir(get_top()) 162*333d2b36SAndroid Build Coastguard Worker subprocess.check_call(['touch', 'build/soong/Android.bp']) 163*333d2b36SAndroid Build Coastguard Worker 164*333d2b36SAndroid Build Coastguard Worker product = Product( 165*333d2b36SAndroid Build Coastguard Worker 'aosp_cf_x86_64_phone', 166*333d2b36SAndroid Build Coastguard Worker 'trunk_staging', 167*333d2b36SAndroid Build Coastguard Worker 'userdebug', 168*333d2b36SAndroid Build Coastguard Worker ) 169*333d2b36SAndroid Build Coastguard Worker os.environ['TARGET_PRODUCT'] = 'aosp_cf_x86_64_phone' 170*333d2b36SAndroid Build Coastguard Worker os.environ['TARGET_RELEASE'] = 'trunk_staging' 171*333d2b36SAndroid Build Coastguard Worker os.environ['TARGET_BUILD_VARIANT'] = 'userdebug' 172*333d2b36SAndroid Build Coastguard Worker 173*333d2b36SAndroid Build Coastguard Worker out_dir1 = os.path.join(os.getenv('OUT_DIR', 'out'), 'determinism_test_out1') 174*333d2b36SAndroid Build Coastguard Worker out_dir2 = os.path.join(os.getenv('OUT_DIR', 'out'), 'determinism_test_out2') 175*333d2b36SAndroid Build Coastguard Worker 176*333d2b36SAndroid Build Coastguard Worker os.makedirs(out_dir1, exist_ok=True) 177*333d2b36SAndroid Build Coastguard Worker os.makedirs(out_dir2, exist_ok=True) 178*333d2b36SAndroid Build Coastguard Worker 179*333d2b36SAndroid Build Coastguard Worker success1, success2 = await asyncio.gather( 180*333d2b36SAndroid Build Coastguard Worker run_make_nothing(product, out_dir1), 181*333d2b36SAndroid Build Coastguard Worker run_make_nothing(product, out_dir2)) 182*333d2b36SAndroid Build Coastguard Worker 183*333d2b36SAndroid Build Coastguard Worker if not success1: 184*333d2b36SAndroid Build Coastguard Worker with open(os.path.join(out_dir1, 'build.log'), 'r') as f: 185*333d2b36SAndroid Build Coastguard Worker print(f.read(), file=sys.stderr) 186*333d2b36SAndroid Build Coastguard Worker sys.exit('build failed') 187*333d2b36SAndroid Build Coastguard Worker if not success2: 188*333d2b36SAndroid Build Coastguard Worker with open(os.path.join(out_dir2, 'build.log'), 'r') as f: 189*333d2b36SAndroid Build Coastguard Worker print(f.read(), file=sys.stderr) 190*333d2b36SAndroid Build Coastguard Worker sys.exit('build failed') 191*333d2b36SAndroid Build Coastguard Worker 192*333d2b36SAndroid Build Coastguard Worker ninja_files1 = transitively_included_ninja_files(out_dir1, os.path.join(out_dir1, f'combined-{product.product}.ninja'), {}) 193*333d2b36SAndroid Build Coastguard Worker ninja_files2 = transitively_included_ninja_files(out_dir2, os.path.join(out_dir2, f'combined-{product.product}.ninja'), {}) 194*333d2b36SAndroid Build Coastguard Worker 195*333d2b36SAndroid Build Coastguard Worker dist_ninja_files(out_dir1, 'determinism_test_files_1.zip', ninja_files1) 196*333d2b36SAndroid Build Coastguard Worker dist_ninja_files(out_dir2, 'determinism_test_files_2.zip', ninja_files2) 197*333d2b36SAndroid Build Coastguard Worker 198*333d2b36SAndroid Build Coastguard Worker hash1 = hash_files(ninja_files1) 199*333d2b36SAndroid Build Coastguard Worker hash2 = hash_files(ninja_files2) 200*333d2b36SAndroid Build Coastguard Worker 201*333d2b36SAndroid Build Coastguard Worker if hash1 != hash2: 202*333d2b36SAndroid Build Coastguard Worker sys.exit("ninja files were not deterministic! See disted determinism_test_files_1/2.zip") 203*333d2b36SAndroid Build Coastguard Worker 204*333d2b36SAndroid Build Coastguard Worker print("Success, ninja files were deterministic") 205*333d2b36SAndroid Build Coastguard Worker 206*333d2b36SAndroid Build Coastguard Worker 207*333d2b36SAndroid Build Coastguard Workerif __name__ == "__main__": 208*333d2b36SAndroid Build Coastguard Worker asyncio.run(main()) 209*333d2b36SAndroid Build Coastguard Worker 210*333d2b36SAndroid Build Coastguard Worker 211