1*7594170eSAndroid Build Coastguard Worker#!/usr/bin/env python3 2*7594170eSAndroid Build Coastguard Worker"""Generates a dashboard for the current RBC product/board config conversion status.""" 3*7594170eSAndroid Build Coastguard Worker# pylint: disable=line-too-long 4*7594170eSAndroid Build Coastguard Worker 5*7594170eSAndroid Build Coastguard Workerimport argparse 6*7594170eSAndroid Build Coastguard Workerimport asyncio 7*7594170eSAndroid Build Coastguard Workerimport dataclasses 8*7594170eSAndroid Build Coastguard Workerimport datetime 9*7594170eSAndroid Build Coastguard Workerimport itertools 10*7594170eSAndroid Build Coastguard Workerimport os 11*7594170eSAndroid Build Coastguard Workerimport re 12*7594170eSAndroid Build Coastguard Workerimport shutil 13*7594170eSAndroid Build Coastguard Workerimport socket 14*7594170eSAndroid Build Coastguard Workerimport subprocess 15*7594170eSAndroid Build Coastguard Workerimport sys 16*7594170eSAndroid Build Coastguard Workerimport time 17*7594170eSAndroid Build Coastguard Workerfrom typing import List, Tuple 18*7594170eSAndroid Build Coastguard Workerimport xml.etree.ElementTree as ET 19*7594170eSAndroid Build Coastguard Worker 20*7594170eSAndroid Build Coastguard Worker_PRODUCT_REGEX = re.compile(r'([a-zA-Z_][a-zA-Z0-9_]*)(?:(?:-(trunk|trunk_staging|next))?-(user|userdebug|eng))?') 21*7594170eSAndroid Build Coastguard Worker 22*7594170eSAndroid Build Coastguard Worker_ALREADY_FAILING_PRODUCTS = [ 23*7594170eSAndroid Build Coastguard Worker "aosp_cf_x86_64_tv", 24*7594170eSAndroid Build Coastguard Worker "aosp_cf_x86_tv", 25*7594170eSAndroid Build Coastguard Worker "aosp_husky_61_pgagnostic", 26*7594170eSAndroid Build Coastguard Worker "aosp_shiba_61_pgagnostic", 27*7594170eSAndroid Build Coastguard Worker] 28*7594170eSAndroid Build Coastguard Worker 29*7594170eSAndroid Build Coastguard Worker@dataclasses.dataclass(frozen=True) 30*7594170eSAndroid Build Coastguard Workerclass Product: 31*7594170eSAndroid Build Coastguard Worker """Represents a TARGET_PRODUCT and TARGET_BUILD_VARIANT.""" 32*7594170eSAndroid Build Coastguard Worker product: str 33*7594170eSAndroid Build Coastguard Worker release: str 34*7594170eSAndroid Build Coastguard Worker variant: str 35*7594170eSAndroid Build Coastguard Worker 36*7594170eSAndroid Build Coastguard Worker def __post_init__(self): 37*7594170eSAndroid Build Coastguard Worker if not _PRODUCT_REGEX.match(str(self)): 38*7594170eSAndroid Build Coastguard Worker raise ValueError(f'Invalid product name: {self}') 39*7594170eSAndroid Build Coastguard Worker 40*7594170eSAndroid Build Coastguard Worker def __str__(self): 41*7594170eSAndroid Build Coastguard Worker return self.product + '-' + self.release + '-' + self.variant 42*7594170eSAndroid Build Coastguard Worker 43*7594170eSAndroid Build Coastguard Worker 44*7594170eSAndroid Build Coastguard Worker@dataclasses.dataclass(frozen=True) 45*7594170eSAndroid Build Coastguard Workerclass ProductResult: 46*7594170eSAndroid Build Coastguard Worker product: Product 47*7594170eSAndroid Build Coastguard Worker baseline_success: bool 48*7594170eSAndroid Build Coastguard Worker product_success: bool 49*7594170eSAndroid Build Coastguard Worker product_has_diffs: bool 50*7594170eSAndroid Build Coastguard Worker 51*7594170eSAndroid Build Coastguard Worker def success(self) -> bool: 52*7594170eSAndroid Build Coastguard Worker return not self.baseline_success or ( 53*7594170eSAndroid Build Coastguard Worker self.product_success 54*7594170eSAndroid Build Coastguard Worker and not self.product_has_diffs) 55*7594170eSAndroid Build Coastguard Worker 56*7594170eSAndroid Build Coastguard Worker 57*7594170eSAndroid Build Coastguard Worker@dataclasses.dataclass(frozen=True) 58*7594170eSAndroid Build Coastguard Workerclass Directories: 59*7594170eSAndroid Build Coastguard Worker out: str 60*7594170eSAndroid Build Coastguard Worker out_baseline: str 61*7594170eSAndroid Build Coastguard Worker out_product: str 62*7594170eSAndroid Build Coastguard Worker results: str 63*7594170eSAndroid Build Coastguard Worker 64*7594170eSAndroid Build Coastguard Worker 65*7594170eSAndroid Build Coastguard Workerdef get_top() -> str: 66*7594170eSAndroid Build Coastguard Worker path = '.' 67*7594170eSAndroid Build Coastguard Worker while not os.path.isfile(os.path.join(path, 'build/soong/soong_ui.bash')): 68*7594170eSAndroid Build Coastguard Worker if os.path.abspath(path) == '/': 69*7594170eSAndroid Build Coastguard Worker sys.exit('Could not find android source tree root.') 70*7594170eSAndroid Build Coastguard Worker path = os.path.join(path, '..') 71*7594170eSAndroid Build Coastguard Worker return os.path.abspath(path) 72*7594170eSAndroid Build Coastguard Worker 73*7594170eSAndroid Build Coastguard Worker 74*7594170eSAndroid Build Coastguard Workerdef get_build_var(variable, product: Product) -> str: 75*7594170eSAndroid Build Coastguard Worker """Returns the result of the shell command get_build_var.""" 76*7594170eSAndroid Build Coastguard Worker env = { 77*7594170eSAndroid Build Coastguard Worker **os.environ, 78*7594170eSAndroid Build Coastguard Worker 'TARGET_PRODUCT': product.product, 79*7594170eSAndroid Build Coastguard Worker 'TARGET_RELEASE': product.release, 80*7594170eSAndroid Build Coastguard Worker 'TARGET_BUILD_VARIANT': product.variant, 81*7594170eSAndroid Build Coastguard Worker } 82*7594170eSAndroid Build Coastguard Worker return subprocess.check_output([ 83*7594170eSAndroid Build Coastguard Worker 'build/soong/soong_ui.bash', 84*7594170eSAndroid Build Coastguard Worker '--dumpvar-mode', 85*7594170eSAndroid Build Coastguard Worker variable 86*7594170eSAndroid Build Coastguard Worker ], env=env, text=True).strip() 87*7594170eSAndroid Build Coastguard Worker 88*7594170eSAndroid Build Coastguard Worker 89*7594170eSAndroid Build Coastguard Workerasync def run_jailed_command(args: List[str], out_dir: str, env=None) -> bool: 90*7594170eSAndroid Build Coastguard Worker """Runs a command, saves its output to out_dir/build.log, and returns if it succeeded.""" 91*7594170eSAndroid Build Coastguard Worker with open(os.path.join(out_dir, 'build.log'), 'wb') as f: 92*7594170eSAndroid Build Coastguard Worker result = await asyncio.create_subprocess_exec( 93*7594170eSAndroid Build Coastguard Worker 'prebuilts/build-tools/linux-x86/bin/nsjail', 94*7594170eSAndroid Build Coastguard Worker '-q', 95*7594170eSAndroid Build Coastguard Worker '--cwd', 96*7594170eSAndroid Build Coastguard Worker os.getcwd(), 97*7594170eSAndroid Build Coastguard Worker '-e', 98*7594170eSAndroid Build Coastguard Worker '-B', 99*7594170eSAndroid Build Coastguard Worker '/', 100*7594170eSAndroid Build Coastguard Worker '-B', 101*7594170eSAndroid Build Coastguard Worker f'{os.path.abspath(out_dir)}:{os.path.abspath("out")}', 102*7594170eSAndroid Build Coastguard Worker '--time_limit', 103*7594170eSAndroid Build Coastguard Worker '0', 104*7594170eSAndroid Build Coastguard Worker '--skip_setsid', 105*7594170eSAndroid Build Coastguard Worker '--keep_caps', 106*7594170eSAndroid Build Coastguard Worker '--disable_clone_newcgroup', 107*7594170eSAndroid Build Coastguard Worker '--disable_clone_newnet', 108*7594170eSAndroid Build Coastguard Worker '--rlimit_as', 109*7594170eSAndroid Build Coastguard Worker 'soft', 110*7594170eSAndroid Build Coastguard Worker '--rlimit_core', 111*7594170eSAndroid Build Coastguard Worker 'soft', 112*7594170eSAndroid Build Coastguard Worker '--rlimit_cpu', 113*7594170eSAndroid Build Coastguard Worker 'soft', 114*7594170eSAndroid Build Coastguard Worker '--rlimit_fsize', 115*7594170eSAndroid Build Coastguard Worker 'soft', 116*7594170eSAndroid Build Coastguard Worker '--rlimit_nofile', 117*7594170eSAndroid Build Coastguard Worker 'soft', 118*7594170eSAndroid Build Coastguard Worker '--proc_rw', 119*7594170eSAndroid Build Coastguard Worker '--hostname', 120*7594170eSAndroid Build Coastguard Worker socket.gethostname(), 121*7594170eSAndroid Build Coastguard Worker '--', 122*7594170eSAndroid Build Coastguard Worker *args, stdout=f, stderr=subprocess.STDOUT, env=env) 123*7594170eSAndroid Build Coastguard Worker return await result.wait() == 0 124*7594170eSAndroid Build Coastguard Worker 125*7594170eSAndroid Build Coastguard Worker 126*7594170eSAndroid Build Coastguard Workerasync def run_build(flags: List[str], out_dir: str) -> bool: 127*7594170eSAndroid Build Coastguard Worker return await run_jailed_command([ 128*7594170eSAndroid Build Coastguard Worker 'build/soong/soong_ui.bash', 129*7594170eSAndroid Build Coastguard Worker '--make-mode', 130*7594170eSAndroid Build Coastguard Worker *flags, 131*7594170eSAndroid Build Coastguard Worker '--skip-ninja', 132*7594170eSAndroid Build Coastguard Worker 'nothing' 133*7594170eSAndroid Build Coastguard Worker ], out_dir) 134*7594170eSAndroid Build Coastguard Worker 135*7594170eSAndroid Build Coastguard Worker 136*7594170eSAndroid Build Coastguard Workerasync def run_config(product: Product, rbc_product: bool, out_dir: str) -> bool: 137*7594170eSAndroid Build Coastguard Worker """Runs config.mk and saves results to out/rbc_variable_dump.txt.""" 138*7594170eSAndroid Build Coastguard Worker env = { 139*7594170eSAndroid Build Coastguard Worker 'OUT_DIR': 'out', 140*7594170eSAndroid Build Coastguard Worker 'TMPDIR': 'tmp', 141*7594170eSAndroid Build Coastguard Worker 'BUILD_DATETIME_FILE': 'out/build_date.txt', 142*7594170eSAndroid Build Coastguard Worker 'CALLED_FROM_SETUP': 'true', 143*7594170eSAndroid Build Coastguard Worker 'TARGET_PRODUCT': product.product, 144*7594170eSAndroid Build Coastguard Worker 'TARGET_BUILD_VARIANT': product.variant, 145*7594170eSAndroid Build Coastguard Worker 'TARGET_RELEASE': product.release, 146*7594170eSAndroid Build Coastguard Worker 'RBC_PRODUCT_CONFIG': 'true' if rbc_product else '', 147*7594170eSAndroid Build Coastguard Worker 'RBC_DUMP_CONFIG_FILE': 'out/rbc_variable_dump.txt', 148*7594170eSAndroid Build Coastguard Worker } 149*7594170eSAndroid Build Coastguard Worker return await run_jailed_command([ 150*7594170eSAndroid Build Coastguard Worker 'prebuilts/build-tools/linux-x86/bin/ckati', 151*7594170eSAndroid Build Coastguard Worker '-f', 152*7594170eSAndroid Build Coastguard Worker 'build/make/core/config.mk' 153*7594170eSAndroid Build Coastguard Worker ], out_dir, env=env) 154*7594170eSAndroid Build Coastguard Worker 155*7594170eSAndroid Build Coastguard Worker 156*7594170eSAndroid Build Coastguard Workerasync def has_diffs(success: bool, file_pairs: List[Tuple[str]], results_folder: str) -> bool: 157*7594170eSAndroid Build Coastguard Worker """Returns true if the two out folders provided have differing ninja files.""" 158*7594170eSAndroid Build Coastguard Worker if not success: 159*7594170eSAndroid Build Coastguard Worker return False 160*7594170eSAndroid Build Coastguard Worker results = [] 161*7594170eSAndroid Build Coastguard Worker for pair in file_pairs: 162*7594170eSAndroid Build Coastguard Worker name = 'soong_build.ninja' if re.search('soong/build\.[^.]+\.ninja$', pair[0]) else os.path.basename(pair[0]) 163*7594170eSAndroid Build Coastguard Worker with open(os.path.join(results_folder, name)+'.diff', 'wb') as f: 164*7594170eSAndroid Build Coastguard Worker results.append((await asyncio.create_subprocess_exec( 165*7594170eSAndroid Build Coastguard Worker 'diff', 166*7594170eSAndroid Build Coastguard Worker pair[0], 167*7594170eSAndroid Build Coastguard Worker pair[1], 168*7594170eSAndroid Build Coastguard Worker stdout=f, stderr=subprocess.STDOUT)).wait()) 169*7594170eSAndroid Build Coastguard Worker 170*7594170eSAndroid Build Coastguard Worker for return_code in await asyncio.gather(*results): 171*7594170eSAndroid Build Coastguard Worker if return_code != 0: 172*7594170eSAndroid Build Coastguard Worker return True 173*7594170eSAndroid Build Coastguard Worker return False 174*7594170eSAndroid Build Coastguard Worker 175*7594170eSAndroid Build Coastguard Worker 176*7594170eSAndroid Build Coastguard Workerdef generate_html_row(num: int, results: ProductResult): 177*7594170eSAndroid Build Coastguard Worker def generate_status_cell(success: bool, diffs: bool) -> str: 178*7594170eSAndroid Build Coastguard Worker message = 'Success' 179*7594170eSAndroid Build Coastguard Worker if diffs: 180*7594170eSAndroid Build Coastguard Worker message = 'Results differed' 181*7594170eSAndroid Build Coastguard Worker if not success: 182*7594170eSAndroid Build Coastguard Worker message = 'Build failed' 183*7594170eSAndroid Build Coastguard Worker return f'<td style="background-color: {"lightgreen" if success and not diffs else "salmon"}">{message}</td>' 184*7594170eSAndroid Build Coastguard Worker 185*7594170eSAndroid Build Coastguard Worker product = results.product 186*7594170eSAndroid Build Coastguard Worker return f''' 187*7594170eSAndroid Build Coastguard Worker <tr> 188*7594170eSAndroid Build Coastguard Worker <td>{num}</td> 189*7594170eSAndroid Build Coastguard Worker <td>{product if results.success() and results.baseline_success else f'<a href="{product}/">{product}</a>'}</td> 190*7594170eSAndroid Build Coastguard Worker {generate_status_cell(results.baseline_success, False)} 191*7594170eSAndroid Build Coastguard Worker {generate_status_cell(results.product_success, results.product_has_diffs)} 192*7594170eSAndroid Build Coastguard Worker </tr> 193*7594170eSAndroid Build Coastguard Worker ''' 194*7594170eSAndroid Build Coastguard Worker 195*7594170eSAndroid Build Coastguard Worker 196*7594170eSAndroid Build Coastguard Workerdef get_branch() -> str: 197*7594170eSAndroid Build Coastguard Worker try: 198*7594170eSAndroid Build Coastguard Worker tree = ET.parse('.repo/manifests/default.xml') 199*7594170eSAndroid Build Coastguard Worker default_tag = tree.getroot().find('default') 200*7594170eSAndroid Build Coastguard Worker return default_tag.get('remote') + '/' + default_tag.get('revision') 201*7594170eSAndroid Build Coastguard Worker except Exception as e: # pylint: disable=broad-except 202*7594170eSAndroid Build Coastguard Worker # Most likely happens due to .repo not existing on CI 203*7594170eSAndroid Build Coastguard Worker return 'Unknown' 204*7594170eSAndroid Build Coastguard Worker 205*7594170eSAndroid Build Coastguard Worker 206*7594170eSAndroid Build Coastguard Workerdef cleanup_empty_files(path): 207*7594170eSAndroid Build Coastguard Worker if os.path.isfile(path): 208*7594170eSAndroid Build Coastguard Worker if os.path.getsize(path) == 0: 209*7594170eSAndroid Build Coastguard Worker os.remove(path) 210*7594170eSAndroid Build Coastguard Worker elif os.path.isdir(path): 211*7594170eSAndroid Build Coastguard Worker for subfile in os.listdir(path): 212*7594170eSAndroid Build Coastguard Worker cleanup_empty_files(os.path.join(path, subfile)) 213*7594170eSAndroid Build Coastguard Worker if not os.listdir(path): 214*7594170eSAndroid Build Coastguard Worker os.rmdir(path) 215*7594170eSAndroid Build Coastguard Worker 216*7594170eSAndroid Build Coastguard Worker 217*7594170eSAndroid Build Coastguard Workerdef dump_files_to_stderr(path): 218*7594170eSAndroid Build Coastguard Worker if os.path.isfile(path): 219*7594170eSAndroid Build Coastguard Worker with open(path, 'r') as f: 220*7594170eSAndroid Build Coastguard Worker print(f'{path}:', file=sys.stderr) 221*7594170eSAndroid Build Coastguard Worker for line in itertools.islice(f, 200): 222*7594170eSAndroid Build Coastguard Worker print(line.rstrip('\r\n'), file=sys.stderr) 223*7594170eSAndroid Build Coastguard Worker if next(f, None) != None: 224*7594170eSAndroid Build Coastguard Worker print('... Remaining lines skipped ...', file=sys.stderr) 225*7594170eSAndroid Build Coastguard Worker elif os.path.isdir(path): 226*7594170eSAndroid Build Coastguard Worker for subfile in os.listdir(path): 227*7594170eSAndroid Build Coastguard Worker dump_files_to_stderr(os.path.join(path, subfile)) 228*7594170eSAndroid Build Coastguard Worker 229*7594170eSAndroid Build Coastguard Worker 230*7594170eSAndroid Build Coastguard Workerasync def test_one_product(product: Product, dirs: Directories) -> ProductResult: 231*7594170eSAndroid Build Coastguard Worker """Runs the builds and tests for differences for a single product.""" 232*7594170eSAndroid Build Coastguard Worker baseline_success, product_success = await asyncio.gather( 233*7594170eSAndroid Build Coastguard Worker run_build([ 234*7594170eSAndroid Build Coastguard Worker f'TARGET_PRODUCT={product.product}', 235*7594170eSAndroid Build Coastguard Worker f'TARGET_RELEASE={product.release}', 236*7594170eSAndroid Build Coastguard Worker f'TARGET_BUILD_VARIANT={product.variant}', 237*7594170eSAndroid Build Coastguard Worker ], dirs.out_baseline), 238*7594170eSAndroid Build Coastguard Worker run_build([ 239*7594170eSAndroid Build Coastguard Worker f'TARGET_PRODUCT={product.product}', 240*7594170eSAndroid Build Coastguard Worker f'TARGET_RELEASE={product.release}', 241*7594170eSAndroid Build Coastguard Worker f'TARGET_BUILD_VARIANT={product.variant}', 242*7594170eSAndroid Build Coastguard Worker 'RBC_PRODUCT_CONFIG=1', 243*7594170eSAndroid Build Coastguard Worker ], dirs.out_product), 244*7594170eSAndroid Build Coastguard Worker ) 245*7594170eSAndroid Build Coastguard Worker 246*7594170eSAndroid Build Coastguard Worker product_dashboard_folder = os.path.join(dirs.results, str(product)) 247*7594170eSAndroid Build Coastguard Worker os.mkdir(product_dashboard_folder) 248*7594170eSAndroid Build Coastguard Worker os.mkdir(product_dashboard_folder+'/baseline') 249*7594170eSAndroid Build Coastguard Worker os.mkdir(product_dashboard_folder+'/product') 250*7594170eSAndroid Build Coastguard Worker 251*7594170eSAndroid Build Coastguard Worker if not baseline_success: 252*7594170eSAndroid Build Coastguard Worker shutil.copy2(os.path.join(dirs.out_baseline, 'build.log'), 253*7594170eSAndroid Build Coastguard Worker f'{product_dashboard_folder}/baseline/build.log') 254*7594170eSAndroid Build Coastguard Worker if not product_success: 255*7594170eSAndroid Build Coastguard Worker shutil.copy2(os.path.join(dirs.out_product, 'build.log'), 256*7594170eSAndroid Build Coastguard Worker f'{product_dashboard_folder}/product/build.log') 257*7594170eSAndroid Build Coastguard Worker add_message = False 258*7594170eSAndroid Build Coastguard Worker with open(f'{product_dashboard_folder}/product/build.log', 'r') as f: 259*7594170eSAndroid Build Coastguard Worker if '/out/rbc/' in f.read(): 260*7594170eSAndroid Build Coastguard Worker add_message = True 261*7594170eSAndroid Build Coastguard Worker if add_message: 262*7594170eSAndroid Build Coastguard Worker with open(f'{product_dashboard_folder}/product/build.log', 'a') as f: 263*7594170eSAndroid Build Coastguard Worker f.write(f'\nPaths involving out/rbc are actually under {dirs.out_product}\n') 264*7594170eSAndroid Build Coastguard Worker 265*7594170eSAndroid Build Coastguard Worker files = [f'build-{product.product}.ninja', f'build-{product.product}-package.ninja', f'soong/build.{product.product}.ninja'] 266*7594170eSAndroid Build Coastguard Worker product_files = [(os.path.join(dirs.out_baseline, x), os.path.join(dirs.out_product, x)) for x in files] 267*7594170eSAndroid Build Coastguard Worker product_has_diffs = await has_diffs(baseline_success and product_success, product_files, product_dashboard_folder+'/product') 268*7594170eSAndroid Build Coastguard Worker 269*7594170eSAndroid Build Coastguard Worker # delete files that contain the product name in them to save space, 270*7594170eSAndroid Build Coastguard Worker # otherwise the ninja files end up filling up the whole harddrive 271*7594170eSAndroid Build Coastguard Worker for out_folder in [dirs.out_baseline, dirs.out_product]: 272*7594170eSAndroid Build Coastguard Worker for subfolder in ['', 'soong']: 273*7594170eSAndroid Build Coastguard Worker folder = os.path.join(out_folder, subfolder) 274*7594170eSAndroid Build Coastguard Worker for file in os.listdir(folder): 275*7594170eSAndroid Build Coastguard Worker if os.path.isfile(os.path.join(folder, file)) and product.product in file: 276*7594170eSAndroid Build Coastguard Worker os.remove(os.path.join(folder, file)) 277*7594170eSAndroid Build Coastguard Worker 278*7594170eSAndroid Build Coastguard Worker cleanup_empty_files(product_dashboard_folder) 279*7594170eSAndroid Build Coastguard Worker 280*7594170eSAndroid Build Coastguard Worker return ProductResult(product, baseline_success, product_success, product_has_diffs) 281*7594170eSAndroid Build Coastguard Worker 282*7594170eSAndroid Build Coastguard Worker 283*7594170eSAndroid Build Coastguard Workerasync def test_one_product_quick(product: Product, dirs: Directories) -> ProductResult: 284*7594170eSAndroid Build Coastguard Worker """Runs the builds and tests for differences for a single product.""" 285*7594170eSAndroid Build Coastguard Worker baseline_success, product_success = await asyncio.gather( 286*7594170eSAndroid Build Coastguard Worker run_config( 287*7594170eSAndroid Build Coastguard Worker product, 288*7594170eSAndroid Build Coastguard Worker False, 289*7594170eSAndroid Build Coastguard Worker dirs.out_baseline), 290*7594170eSAndroid Build Coastguard Worker run_config( 291*7594170eSAndroid Build Coastguard Worker product, 292*7594170eSAndroid Build Coastguard Worker True, 293*7594170eSAndroid Build Coastguard Worker dirs.out_product), 294*7594170eSAndroid Build Coastguard Worker ) 295*7594170eSAndroid Build Coastguard Worker 296*7594170eSAndroid Build Coastguard Worker product_dashboard_folder = os.path.join(dirs.results, str(product)) 297*7594170eSAndroid Build Coastguard Worker os.mkdir(product_dashboard_folder) 298*7594170eSAndroid Build Coastguard Worker os.mkdir(product_dashboard_folder+'/baseline') 299*7594170eSAndroid Build Coastguard Worker os.mkdir(product_dashboard_folder+'/product') 300*7594170eSAndroid Build Coastguard Worker 301*7594170eSAndroid Build Coastguard Worker if not baseline_success: 302*7594170eSAndroid Build Coastguard Worker shutil.copy2(os.path.join(dirs.out_baseline, 'build.log'), 303*7594170eSAndroid Build Coastguard Worker f'{product_dashboard_folder}/baseline/build.log') 304*7594170eSAndroid Build Coastguard Worker if not product_success: 305*7594170eSAndroid Build Coastguard Worker shutil.copy2(os.path.join(dirs.out_product, 'build.log'), 306*7594170eSAndroid Build Coastguard Worker f'{product_dashboard_folder}/product/build.log') 307*7594170eSAndroid Build Coastguard Worker add_message = False 308*7594170eSAndroid Build Coastguard Worker with open(f'{product_dashboard_folder}/product/build.log', 'r') as f: 309*7594170eSAndroid Build Coastguard Worker if '/out/rbc/' in f.read(): 310*7594170eSAndroid Build Coastguard Worker add_message = True 311*7594170eSAndroid Build Coastguard Worker if add_message: 312*7594170eSAndroid Build Coastguard Worker with open(f'{product_dashboard_folder}/product/build.log', 'a') as f: 313*7594170eSAndroid Build Coastguard Worker f.write(f'\nPaths involving out/rbc are actually under {dirs.out_product}\n') 314*7594170eSAndroid Build Coastguard Worker 315*7594170eSAndroid Build Coastguard Worker files = ['rbc_variable_dump.txt'] 316*7594170eSAndroid Build Coastguard Worker product_files = [(os.path.join(dirs.out_baseline, x), os.path.join(dirs.out_product, x)) for x in files] 317*7594170eSAndroid Build Coastguard Worker product_has_diffs = await has_diffs(baseline_success and product_success, product_files, product_dashboard_folder+'/product') 318*7594170eSAndroid Build Coastguard Worker 319*7594170eSAndroid Build Coastguard Worker cleanup_empty_files(product_dashboard_folder) 320*7594170eSAndroid Build Coastguard Worker 321*7594170eSAndroid Build Coastguard Worker return ProductResult(product, baseline_success, product_success, product_has_diffs) 322*7594170eSAndroid Build Coastguard Worker 323*7594170eSAndroid Build Coastguard Worker 324*7594170eSAndroid Build Coastguard Workerasync def main(): 325*7594170eSAndroid Build Coastguard Worker parser = argparse.ArgumentParser( 326*7594170eSAndroid Build Coastguard Worker description='Generates a dashboard of the starlark product configuration conversion.') 327*7594170eSAndroid Build Coastguard Worker parser.add_argument('products', nargs='*', 328*7594170eSAndroid Build Coastguard Worker help='list of products to test. If not given, all ' 329*7594170eSAndroid Build Coastguard Worker + 'products will be tested. ' 330*7594170eSAndroid Build Coastguard Worker + 'Example: aosp_arm64-userdebug') 331*7594170eSAndroid Build Coastguard Worker parser.add_argument('--quick', action='store_true', 332*7594170eSAndroid Build Coastguard Worker help='Run a quick test. This will only run config.mk and ' 333*7594170eSAndroid Build Coastguard Worker + 'diff the make variables at the end of it, instead of ' 334*7594170eSAndroid Build Coastguard Worker + 'diffing the full ninja files.') 335*7594170eSAndroid Build Coastguard Worker parser.add_argument('--exclude', nargs='+', default=[], 336*7594170eSAndroid Build Coastguard Worker help='Exclude these producs from the build. Useful if not ' 337*7594170eSAndroid Build Coastguard Worker + 'supplying a list of products manually.') 338*7594170eSAndroid Build Coastguard Worker parser.add_argument('--results-directory', 339*7594170eSAndroid Build Coastguard Worker help='Directory to store results in. Defaults to $(OUT_DIR)/rbc_dashboard. ' 340*7594170eSAndroid Build Coastguard Worker + 'Warning: will be cleared!') 341*7594170eSAndroid Build Coastguard Worker parser.add_argument('--failure-message', 342*7594170eSAndroid Build Coastguard Worker help='Additional message to append to stderr on failure.') 343*7594170eSAndroid Build Coastguard Worker args = parser.parse_args() 344*7594170eSAndroid Build Coastguard Worker 345*7594170eSAndroid Build Coastguard Worker if args.results_directory: 346*7594170eSAndroid Build Coastguard Worker args.results_directory = os.path.abspath(args.results_directory) 347*7594170eSAndroid Build Coastguard Worker 348*7594170eSAndroid Build Coastguard Worker os.chdir(get_top()) 349*7594170eSAndroid Build Coastguard Worker 350*7594170eSAndroid Build Coastguard Worker def str_to_product(p: str) -> Product: 351*7594170eSAndroid Build Coastguard Worker match = _PRODUCT_REGEX.fullmatch(p) 352*7594170eSAndroid Build Coastguard Worker if not match: 353*7594170eSAndroid Build Coastguard Worker sys.exit(f'Invalid product name: {p}. Example: aosp_arm64-trunk_staging-userdebug') 354*7594170eSAndroid Build Coastguard Worker return Product( 355*7594170eSAndroid Build Coastguard Worker match.group(1), 356*7594170eSAndroid Build Coastguard Worker match.group(2) if match.group(2) else 'trunk_staging', 357*7594170eSAndroid Build Coastguard Worker match.group(3) if match.group(3) else 'userdebug', 358*7594170eSAndroid Build Coastguard Worker ) 359*7594170eSAndroid Build Coastguard Worker 360*7594170eSAndroid Build Coastguard Worker products = [str_to_product(p) for p in args.products] 361*7594170eSAndroid Build Coastguard Worker 362*7594170eSAndroid Build Coastguard Worker if not products: 363*7594170eSAndroid Build Coastguard Worker products = list(map(lambda x: Product(x, 'trunk_staging', 'userdebug'), get_build_var( 364*7594170eSAndroid Build Coastguard Worker 'all_named_products', Product('aosp_arm64', 'trunk_staging', 'userdebug')).split())) 365*7594170eSAndroid Build Coastguard Worker 366*7594170eSAndroid Build Coastguard Worker excluded = [str_to_product(p) for p in args.exclude] 367*7594170eSAndroid Build Coastguard Worker products = [p for p in products if p not in excluded] 368*7594170eSAndroid Build Coastguard Worker 369*7594170eSAndroid Build Coastguard Worker for i, product in enumerate(products): 370*7594170eSAndroid Build Coastguard Worker for j, product2 in enumerate(products): 371*7594170eSAndroid Build Coastguard Worker if i != j and product.product == product2.product: 372*7594170eSAndroid Build Coastguard Worker sys.exit(f'Product {product.product} cannot be repeated.') 373*7594170eSAndroid Build Coastguard Worker 374*7594170eSAndroid Build Coastguard Worker out_dir = get_build_var('OUT_DIR', Product('aosp_arm64', 'trunk_staging', 'userdebug')) 375*7594170eSAndroid Build Coastguard Worker 376*7594170eSAndroid Build Coastguard Worker dirs = Directories( 377*7594170eSAndroid Build Coastguard Worker out=out_dir, 378*7594170eSAndroid Build Coastguard Worker out_baseline=os.path.join(out_dir, 'rbc_out_baseline'), 379*7594170eSAndroid Build Coastguard Worker out_product=os.path.join(out_dir, 'rbc_out_product'), 380*7594170eSAndroid Build Coastguard Worker results=args.results_directory if args.results_directory else os.path.join(out_dir, 'rbc_dashboard')) 381*7594170eSAndroid Build Coastguard Worker 382*7594170eSAndroid Build Coastguard Worker for folder in [dirs.out_baseline, dirs.out_product, dirs.results]: 383*7594170eSAndroid Build Coastguard Worker # delete and recreate the out directories. You can't reuse them for 384*7594170eSAndroid Build Coastguard Worker # a particular product, because after we delete some product-specific 385*7594170eSAndroid Build Coastguard Worker # files inside the out dir to save space, the build will fail if you 386*7594170eSAndroid Build Coastguard Worker # try to build the same product again. 387*7594170eSAndroid Build Coastguard Worker shutil.rmtree(folder, ignore_errors=True) 388*7594170eSAndroid Build Coastguard Worker os.makedirs(folder) 389*7594170eSAndroid Build Coastguard Worker 390*7594170eSAndroid Build Coastguard Worker # When running in quick mode, we still need to build 391*7594170eSAndroid Build Coastguard Worker # mk2rbc/rbcrun/AndroidProducts.mk.list, so run a get_build_var command to do 392*7594170eSAndroid Build Coastguard Worker # that in each folder. 393*7594170eSAndroid Build Coastguard Worker if args.quick: 394*7594170eSAndroid Build Coastguard Worker commands = [] 395*7594170eSAndroid Build Coastguard Worker folders = [dirs.out_baseline, dirs.out_product] 396*7594170eSAndroid Build Coastguard Worker for folder in folders: 397*7594170eSAndroid Build Coastguard Worker commands.append(run_jailed_command([ 398*7594170eSAndroid Build Coastguard Worker 'build/soong/soong_ui.bash', 399*7594170eSAndroid Build Coastguard Worker '--dumpvar-mode', 400*7594170eSAndroid Build Coastguard Worker 'TARGET_PRODUCT', 401*7594170eSAndroid Build Coastguard Worker ], folder, env = { 402*7594170eSAndroid Build Coastguard Worker **os.environ, 403*7594170eSAndroid Build Coastguard Worker 'TARGET_PRODUCT': 'aosp_arm64', 404*7594170eSAndroid Build Coastguard Worker 'TARGET_RELEASE': 'trunk_staging', 405*7594170eSAndroid Build Coastguard Worker 'TARGET_BUILD_VARIANT': 'userdebug', 406*7594170eSAndroid Build Coastguard Worker })) 407*7594170eSAndroid Build Coastguard Worker for i, success in enumerate(await asyncio.gather(*commands)): 408*7594170eSAndroid Build Coastguard Worker if not success: 409*7594170eSAndroid Build Coastguard Worker dump_files_to_stderr(os.path.join(folders[i], 'build.log')) 410*7594170eSAndroid Build Coastguard Worker sys.exit('Failed to setup output directories') 411*7594170eSAndroid Build Coastguard Worker 412*7594170eSAndroid Build Coastguard Worker with open(os.path.join(dirs.results, 'index.html'), 'w') as f: 413*7594170eSAndroid Build Coastguard Worker f.write(f''' 414*7594170eSAndroid Build Coastguard Worker <body> 415*7594170eSAndroid Build Coastguard Worker <h2>RBC Product/Board conversion status</h2> 416*7594170eSAndroid Build Coastguard Worker Generated on {datetime.date.today()} for branch {get_branch()} 417*7594170eSAndroid Build Coastguard Worker <table> 418*7594170eSAndroid Build Coastguard Worker <tr> 419*7594170eSAndroid Build Coastguard Worker <th>#</th> 420*7594170eSAndroid Build Coastguard Worker <th>product</th> 421*7594170eSAndroid Build Coastguard Worker <th>baseline</th> 422*7594170eSAndroid Build Coastguard Worker <th>RBC product config</th> 423*7594170eSAndroid Build Coastguard Worker </tr>\n''') 424*7594170eSAndroid Build Coastguard Worker f.flush() 425*7594170eSAndroid Build Coastguard Worker 426*7594170eSAndroid Build Coastguard Worker all_results = [] 427*7594170eSAndroid Build Coastguard Worker start_time = time.time() 428*7594170eSAndroid Build Coastguard Worker print(f'{"Current product":31.31} | {"Time Elapsed":>16} | {"Per each":>8} | {"ETA":>16} | Status') 429*7594170eSAndroid Build Coastguard Worker print('-' * 91) 430*7594170eSAndroid Build Coastguard Worker for i, product in enumerate(products): 431*7594170eSAndroid Build Coastguard Worker if i > 0: 432*7594170eSAndroid Build Coastguard Worker elapsed_time = time.time() - start_time 433*7594170eSAndroid Build Coastguard Worker time_per_product = elapsed_time / i 434*7594170eSAndroid Build Coastguard Worker eta = time_per_product * (len(products) - i) 435*7594170eSAndroid Build Coastguard Worker elapsed_time_str = str(datetime.timedelta(seconds=int(elapsed_time))) 436*7594170eSAndroid Build Coastguard Worker time_per_product_str = str(datetime.timedelta(seconds=int(time_per_product))) 437*7594170eSAndroid Build Coastguard Worker eta_str = str(datetime.timedelta(seconds=int(eta))) 438*7594170eSAndroid Build Coastguard Worker print(f'{f"{i+1}/{len(products)} {product}":31.31} | {elapsed_time_str:>16} | {time_per_product_str:>8} | {eta_str:>16} | ', end='', flush=True) 439*7594170eSAndroid Build Coastguard Worker else: 440*7594170eSAndroid Build Coastguard Worker print(f'{f"{i+1}/{len(products)} {product}":31.31} | {"":>16} | {"":>8} | {"":>16} | ', end='', flush=True) 441*7594170eSAndroid Build Coastguard Worker 442*7594170eSAndroid Build Coastguard Worker if not args.quick: 443*7594170eSAndroid Build Coastguard Worker result = await test_one_product(product, dirs) 444*7594170eSAndroid Build Coastguard Worker else: 445*7594170eSAndroid Build Coastguard Worker result = await test_one_product_quick(product, dirs) 446*7594170eSAndroid Build Coastguard Worker 447*7594170eSAndroid Build Coastguard Worker all_results.append(result) 448*7594170eSAndroid Build Coastguard Worker 449*7594170eSAndroid Build Coastguard Worker if result.success(): 450*7594170eSAndroid Build Coastguard Worker print('Success') 451*7594170eSAndroid Build Coastguard Worker else: 452*7594170eSAndroid Build Coastguard Worker print('Failure') 453*7594170eSAndroid Build Coastguard Worker 454*7594170eSAndroid Build Coastguard Worker f.write(generate_html_row(i+1, result)) 455*7594170eSAndroid Build Coastguard Worker f.flush() 456*7594170eSAndroid Build Coastguard Worker 457*7594170eSAndroid Build Coastguard Worker baseline_successes = len([x for x in all_results if x.baseline_success]) 458*7594170eSAndroid Build Coastguard Worker product_successes = len([x for x in all_results if x.product_success and not x.product_has_diffs]) 459*7594170eSAndroid Build Coastguard Worker f.write(f''' 460*7594170eSAndroid Build Coastguard Worker <tr> 461*7594170eSAndroid Build Coastguard Worker <td></td> 462*7594170eSAndroid Build Coastguard Worker <td># Successful</td> 463*7594170eSAndroid Build Coastguard Worker <td>{baseline_successes}</td> 464*7594170eSAndroid Build Coastguard Worker <td>{product_successes}</td> 465*7594170eSAndroid Build Coastguard Worker </tr> 466*7594170eSAndroid Build Coastguard Worker <tr> 467*7594170eSAndroid Build Coastguard Worker <td></td> 468*7594170eSAndroid Build Coastguard Worker <td># Failed</td> 469*7594170eSAndroid Build Coastguard Worker <td>N/A</td> 470*7594170eSAndroid Build Coastguard Worker <td>{baseline_successes - product_successes}</td> 471*7594170eSAndroid Build Coastguard Worker </tr> 472*7594170eSAndroid Build Coastguard Worker </table> 473*7594170eSAndroid Build Coastguard Worker Finished running successfully. 474*7594170eSAndroid Build Coastguard Worker </body>\n''') 475*7594170eSAndroid Build Coastguard Worker 476*7594170eSAndroid Build Coastguard Worker print('Success!') 477*7594170eSAndroid Build Coastguard Worker print('file://'+os.path.abspath(os.path.join(dirs.results, 'index.html'))) 478*7594170eSAndroid Build Coastguard Worker 479*7594170eSAndroid Build Coastguard Worker for result in all_results: 480*7594170eSAndroid Build Coastguard Worker if not result.success(): 481*7594170eSAndroid Build Coastguard Worker print('There were one or more failing products. First failure:', file=sys.stderr) 482*7594170eSAndroid Build Coastguard Worker dump_files_to_stderr(os.path.join(dirs.results, str(result.product))) 483*7594170eSAndroid Build Coastguard Worker if args.failure_message: 484*7594170eSAndroid Build Coastguard Worker print(args.failure_message, file=sys.stderr) 485*7594170eSAndroid Build Coastguard Worker sys.exit(1) 486*7594170eSAndroid Build Coastguard Worker 487*7594170eSAndroid Build Coastguard Worker baseline_failures = [] 488*7594170eSAndroid Build Coastguard Worker for result in all_results: 489*7594170eSAndroid Build Coastguard Worker if result.product.product not in _ALREADY_FAILING_PRODUCTS and not result.baseline_success: 490*7594170eSAndroid Build Coastguard Worker baseline_failures.append(result) 491*7594170eSAndroid Build Coastguard Worker if baseline_failures: 492*7594170eSAndroid Build Coastguard Worker product_str = "\n ".join([f"{x.product}" for x in baseline_failures]) 493*7594170eSAndroid Build Coastguard Worker print(f"These products fail to run (Make-based) product config:\n {product_str}\nFirst failure:", file=sys.stderr) 494*7594170eSAndroid Build Coastguard Worker result = baseline_failures[0] 495*7594170eSAndroid Build Coastguard Worker dump_files_to_stderr(os.path.join(dirs.results, str(result.product), 'baseline')) 496*7594170eSAndroid Build Coastguard Worker if args.failure_message: 497*7594170eSAndroid Build Coastguard Worker print(args.failure_message, file=sys.stderr) 498*7594170eSAndroid Build Coastguard Worker sys.exit(1) 499*7594170eSAndroid Build Coastguard Worker 500*7594170eSAndroid Build Coastguard Worker 501*7594170eSAndroid Build Coastguard Workerif __name__ == '__main__': 502*7594170eSAndroid Build Coastguard Worker asyncio.run(main()) 503