1*7594170eSAndroid Build Coastguard Worker# Copyright (C) 2022 The Android Open Source Project 2*7594170eSAndroid Build Coastguard Worker# 3*7594170eSAndroid Build Coastguard Worker# Licensed under the Apache License, Version 2.0 (the "License"); 4*7594170eSAndroid Build Coastguard Worker# you may not use this file except in compliance with the License. 5*7594170eSAndroid Build Coastguard Worker# You may obtain a copy of the License at 6*7594170eSAndroid Build Coastguard Worker# 7*7594170eSAndroid Build Coastguard Worker# http://www.apache.org/licenses/LICENSE-2.0 8*7594170eSAndroid Build Coastguard Worker# 9*7594170eSAndroid Build Coastguard Worker# Unless required by applicable law or agreed to in writing, software 10*7594170eSAndroid Build Coastguard Worker# distributed under the License is distributed on an "AS IS" BASIS, 11*7594170eSAndroid Build Coastguard Worker# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12*7594170eSAndroid Build Coastguard Worker# See the License for the specific language governing permissions and 13*7594170eSAndroid Build Coastguard Worker# limitations under the License. 14*7594170eSAndroid Build Coastguard Worker 15*7594170eSAndroid Build Coastguard Workerimport argparse 16*7594170eSAndroid Build Coastguard Workerimport dataclasses 17*7594170eSAndroid Build Coastguard Workerimport functools 18*7594170eSAndroid Build Coastguard Workerimport logging 19*7594170eSAndroid Build Coastguard Workerimport os 20*7594170eSAndroid Build Coastguard Workerimport re 21*7594170eSAndroid Build Coastguard Workerimport sys 22*7594170eSAndroid Build Coastguard Workerimport textwrap 23*7594170eSAndroid Build Coastguard Workerfrom pathlib import Path 24*7594170eSAndroid Build Coastguard Workerfrom typing import Optional 25*7594170eSAndroid Build Coastguard Worker 26*7594170eSAndroid Build Coastguard Workerimport cuj_catalog 27*7594170eSAndroid Build Coastguard Workerimport util 28*7594170eSAndroid Build Coastguard Workerfrom util import BuildType 29*7594170eSAndroid Build Coastguard Worker 30*7594170eSAndroid Build Coastguard Worker 31*7594170eSAndroid Build Coastguard Worker@dataclasses.dataclass(frozen=True) 32*7594170eSAndroid Build Coastguard Workerclass UserInput: 33*7594170eSAndroid Build Coastguard Worker build_types: tuple[BuildType, ...] 34*7594170eSAndroid Build Coastguard Worker chosen_cujgroups: tuple[int, ...] 35*7594170eSAndroid Build Coastguard Worker tag: Optional[str] 36*7594170eSAndroid Build Coastguard Worker log_dir: Path 37*7594170eSAndroid Build Coastguard Worker no_warmup: bool 38*7594170eSAndroid Build Coastguard Worker targets: tuple[str, ...] 39*7594170eSAndroid Build Coastguard Worker ci_mode: bool 40*7594170eSAndroid Build Coastguard Worker 41*7594170eSAndroid Build Coastguard Worker 42*7594170eSAndroid Build Coastguard Worker@functools.cache 43*7594170eSAndroid Build Coastguard Workerdef get_user_input() -> UserInput: 44*7594170eSAndroid Build Coastguard Worker cujgroups = cuj_catalog.get_cujgroups() 45*7594170eSAndroid Build Coastguard Worker 46*7594170eSAndroid Build Coastguard Worker def validate_cujgroups(input_str: str) -> list[int]: 47*7594170eSAndroid Build Coastguard Worker if input_str.isnumeric(): 48*7594170eSAndroid Build Coastguard Worker i = int(input_str) 49*7594170eSAndroid Build Coastguard Worker if 0 <= i < len(cujgroups): 50*7594170eSAndroid Build Coastguard Worker return [i] 51*7594170eSAndroid Build Coastguard Worker logging.critical( 52*7594170eSAndroid Build Coastguard Worker f"Invalid input: {input_str}. " 53*7594170eSAndroid Build Coastguard Worker f"Expected an index between 1 and {len(cujgroups)}. " 54*7594170eSAndroid Build Coastguard Worker "Try --help to view the list of available CUJs" 55*7594170eSAndroid Build Coastguard Worker ) 56*7594170eSAndroid Build Coastguard Worker raise argparse.ArgumentTypeError() 57*7594170eSAndroid Build Coastguard Worker else: 58*7594170eSAndroid Build Coastguard Worker pattern = re.compile(input_str) 59*7594170eSAndroid Build Coastguard Worker matching_cuj_groups = [ 60*7594170eSAndroid Build Coastguard Worker i 61*7594170eSAndroid Build Coastguard Worker for i, cujgroup in enumerate(cujgroups) 62*7594170eSAndroid Build Coastguard Worker if pattern.search(cujgroup.description) 63*7594170eSAndroid Build Coastguard Worker ] 64*7594170eSAndroid Build Coastguard Worker if len(matching_cuj_groups): 65*7594170eSAndroid Build Coastguard Worker return matching_cuj_groups 66*7594170eSAndroid Build Coastguard Worker logging.critical( 67*7594170eSAndroid Build Coastguard Worker f'Invalid input: "{input_str}" does not match any CUJ. ' 68*7594170eSAndroid Build Coastguard Worker "Try --help to view the list of available CUJs" 69*7594170eSAndroid Build Coastguard Worker ) 70*7594170eSAndroid Build Coastguard Worker raise argparse.ArgumentTypeError() 71*7594170eSAndroid Build Coastguard Worker 72*7594170eSAndroid Build Coastguard Worker # importing locally here to avoid chances of cyclic import 73*7594170eSAndroid Build Coastguard Worker import incremental_build 74*7594170eSAndroid Build Coastguard Worker 75*7594170eSAndroid Build Coastguard Worker p = argparse.ArgumentParser( 76*7594170eSAndroid Build Coastguard Worker formatter_class=argparse.RawTextHelpFormatter, 77*7594170eSAndroid Build Coastguard Worker description="" 78*7594170eSAndroid Build Coastguard Worker + textwrap.dedent(incremental_build.__doc__) 79*7594170eSAndroid Build Coastguard Worker + textwrap.dedent(incremental_build.main.__doc__), 80*7594170eSAndroid Build Coastguard Worker ) 81*7594170eSAndroid Build Coastguard Worker 82*7594170eSAndroid Build Coastguard Worker cuj_list = "\n".join( 83*7594170eSAndroid Build Coastguard Worker [f"{i:2}: {cujgroup.description}" for i, cujgroup in enumerate(cujgroups)] 84*7594170eSAndroid Build Coastguard Worker ) 85*7594170eSAndroid Build Coastguard Worker p.add_argument( 86*7594170eSAndroid Build Coastguard Worker "-c", 87*7594170eSAndroid Build Coastguard Worker "--cujs", 88*7594170eSAndroid Build Coastguard Worker nargs="*", 89*7594170eSAndroid Build Coastguard Worker type=validate_cujgroups, 90*7594170eSAndroid Build Coastguard Worker help="Index number(s) for the CUJ(s) from the following list. " 91*7594170eSAndroid Build Coastguard Worker "Or substring matches for the CUJ description." 92*7594170eSAndroid Build Coastguard Worker f"Note the ordering will be respected:\n{cuj_list}", 93*7594170eSAndroid Build Coastguard Worker ) 94*7594170eSAndroid Build Coastguard Worker p.add_argument( 95*7594170eSAndroid Build Coastguard Worker "--no-warmup", 96*7594170eSAndroid Build Coastguard Worker default=False, 97*7594170eSAndroid Build Coastguard Worker action="store_true", 98*7594170eSAndroid Build Coastguard Worker help="skip warmup builds; this can skew your results for the first CUJ you run.", 99*7594170eSAndroid Build Coastguard Worker ) 100*7594170eSAndroid Build Coastguard Worker p.add_argument( 101*7594170eSAndroid Build Coastguard Worker "-t", 102*7594170eSAndroid Build Coastguard Worker "--tag", 103*7594170eSAndroid Build Coastguard Worker type=str, 104*7594170eSAndroid Build Coastguard Worker default="", 105*7594170eSAndroid Build Coastguard Worker help="Any additional tag for this set of builds, this helps " 106*7594170eSAndroid Build Coastguard Worker "distinguish the new data from previously collected data, " 107*7594170eSAndroid Build Coastguard Worker "useful for comparative analysis", 108*7594170eSAndroid Build Coastguard Worker ) 109*7594170eSAndroid Build Coastguard Worker 110*7594170eSAndroid Build Coastguard Worker log_levels = dict(getattr(logging, "_levelToName")).values() 111*7594170eSAndroid Build Coastguard Worker p.add_argument( 112*7594170eSAndroid Build Coastguard Worker "-v", 113*7594170eSAndroid Build Coastguard Worker "--verbosity", 114*7594170eSAndroid Build Coastguard Worker choices=log_levels, 115*7594170eSAndroid Build Coastguard Worker default="INFO", 116*7594170eSAndroid Build Coastguard Worker help="Log level. Defaults to %(default)s", 117*7594170eSAndroid Build Coastguard Worker ) 118*7594170eSAndroid Build Coastguard Worker default_log_dir = util.get_default_log_dir() 119*7594170eSAndroid Build Coastguard Worker p.add_argument( 120*7594170eSAndroid Build Coastguard Worker "-l", 121*7594170eSAndroid Build Coastguard Worker "--log-dir", 122*7594170eSAndroid Build Coastguard Worker type=Path, 123*7594170eSAndroid Build Coastguard Worker default=default_log_dir, 124*7594170eSAndroid Build Coastguard Worker help=textwrap.dedent( 125*7594170eSAndroid Build Coastguard Worker """\ 126*7594170eSAndroid Build Coastguard Worker Directory for timing logs. Defaults to %(default)s 127*7594170eSAndroid Build Coastguard Worker TIPS: 128*7594170eSAndroid Build Coastguard Worker 1 Specify a directory outside of the source tree 129*7594170eSAndroid Build Coastguard Worker 2 To view key metrics in metrics.csv: 130*7594170eSAndroid Build Coastguard Worker {} 131*7594170eSAndroid Build Coastguard Worker 3 To view column headers: 132*7594170eSAndroid Build Coastguard Worker {} 133*7594170eSAndroid Build Coastguard Worker """ 134*7594170eSAndroid Build Coastguard Worker ).format( 135*7594170eSAndroid Build Coastguard Worker textwrap.indent( 136*7594170eSAndroid Build Coastguard Worker util.get_cmd_to_display_tabulated_metrics(default_log_dir, False), 137*7594170eSAndroid Build Coastguard Worker " " * 4, 138*7594170eSAndroid Build Coastguard Worker ), 139*7594170eSAndroid Build Coastguard Worker textwrap.indent(util.get_csv_columns_cmd(default_log_dir), " " * 4), 140*7594170eSAndroid Build Coastguard Worker ), 141*7594170eSAndroid Build Coastguard Worker ) 142*7594170eSAndroid Build Coastguard Worker def_build_types = [ 143*7594170eSAndroid Build Coastguard Worker BuildType.SOONG_ONLY, 144*7594170eSAndroid Build Coastguard Worker ] 145*7594170eSAndroid Build Coastguard Worker p.add_argument( 146*7594170eSAndroid Build Coastguard Worker "-b", 147*7594170eSAndroid Build Coastguard Worker "--build-types", 148*7594170eSAndroid Build Coastguard Worker nargs="+", 149*7594170eSAndroid Build Coastguard Worker type=BuildType.from_flag, 150*7594170eSAndroid Build Coastguard Worker default=[def_build_types], 151*7594170eSAndroid Build Coastguard Worker help=f"Defaults to {[b.to_flag() for b in def_build_types]}. " 152*7594170eSAndroid Build Coastguard Worker f"Choose from {[e.name.lower() for e in BuildType]}", 153*7594170eSAndroid Build Coastguard Worker ) 154*7594170eSAndroid Build Coastguard Worker p.add_argument( 155*7594170eSAndroid Build Coastguard Worker "--ignore-repo-diff", 156*7594170eSAndroid Build Coastguard Worker default=False, 157*7594170eSAndroid Build Coastguard Worker action="store_true", 158*7594170eSAndroid Build Coastguard Worker help='Skip "repo status" check', 159*7594170eSAndroid Build Coastguard Worker ) 160*7594170eSAndroid Build Coastguard Worker p.add_argument( 161*7594170eSAndroid Build Coastguard Worker "--append-csv", 162*7594170eSAndroid Build Coastguard Worker default=False, 163*7594170eSAndroid Build Coastguard Worker action="store_true", 164*7594170eSAndroid Build Coastguard Worker help="Add results to existing spreadsheet", 165*7594170eSAndroid Build Coastguard Worker ) 166*7594170eSAndroid Build Coastguard Worker p.add_argument( 167*7594170eSAndroid Build Coastguard Worker "targets", 168*7594170eSAndroid Build Coastguard Worker nargs="*", 169*7594170eSAndroid Build Coastguard Worker default=["nothing"], 170*7594170eSAndroid Build Coastguard Worker help='Targets to run, e.g. "libc adbd". ' "Defaults to %(default)s", 171*7594170eSAndroid Build Coastguard Worker ) 172*7594170eSAndroid Build Coastguard Worker p.add_argument( 173*7594170eSAndroid Build Coastguard Worker "--ci-mode", 174*7594170eSAndroid Build Coastguard Worker default=False, 175*7594170eSAndroid Build Coastguard Worker action="store_true", 176*7594170eSAndroid Build Coastguard Worker help="Only use it for CI runs.It will copy the " 177*7594170eSAndroid Build Coastguard Worker "first metrics after warmup to the logs directory in CI", 178*7594170eSAndroid Build Coastguard Worker ) 179*7594170eSAndroid Build Coastguard Worker 180*7594170eSAndroid Build Coastguard Worker options = p.parse_args() 181*7594170eSAndroid Build Coastguard Worker 182*7594170eSAndroid Build Coastguard Worker if options.verbosity: 183*7594170eSAndroid Build Coastguard Worker logging.root.setLevel(options.verbosity) 184*7594170eSAndroid Build Coastguard Worker 185*7594170eSAndroid Build Coastguard Worker chosen_cujgroups: tuple[int, ...] = ( 186*7594170eSAndroid Build Coastguard Worker tuple(int(i) for sublist in options.cujs for i in sublist) 187*7594170eSAndroid Build Coastguard Worker if options.cujs 188*7594170eSAndroid Build Coastguard Worker else tuple() 189*7594170eSAndroid Build Coastguard Worker ) 190*7594170eSAndroid Build Coastguard Worker 191*7594170eSAndroid Build Coastguard Worker bazel_labels: list[str] = [ 192*7594170eSAndroid Build Coastguard Worker target for target in options.targets if target.startswith("//") 193*7594170eSAndroid Build Coastguard Worker ] 194*7594170eSAndroid Build Coastguard Worker if 0 < len(bazel_labels) < len(options.targets): 195*7594170eSAndroid Build Coastguard Worker logging.critical( 196*7594170eSAndroid Build Coastguard Worker f"Don't mix bazel labels {bazel_labels} with soong targets " 197*7594170eSAndroid Build Coastguard Worker f"{[t for t in options.targets if t not in bazel_labels]}" 198*7594170eSAndroid Build Coastguard Worker ) 199*7594170eSAndroid Build Coastguard Worker sys.exit(1) 200*7594170eSAndroid Build Coastguard Worker if os.getenv("BUILD_BROKEN_DISABLE_BAZEL") is not None: 201*7594170eSAndroid Build Coastguard Worker raise RuntimeError( 202*7594170eSAndroid Build Coastguard Worker f"use -b {BuildType.SOONG_ONLY.to_flag()} " 203*7594170eSAndroid Build Coastguard Worker f"instead of BUILD_BROKEN_DISABLE_BAZEL" 204*7594170eSAndroid Build Coastguard Worker ) 205*7594170eSAndroid Build Coastguard Worker build_types: tuple[BuildType, ...] = tuple( 206*7594170eSAndroid Build Coastguard Worker BuildType(i) for sublist in options.build_types for i in sublist 207*7594170eSAndroid Build Coastguard Worker ) 208*7594170eSAndroid Build Coastguard Worker if len(bazel_labels) > 0: 209*7594170eSAndroid Build Coastguard Worker non_b = [ 210*7594170eSAndroid Build Coastguard Worker b.name for b in build_types if b != BuildType.B_BUILD and b != BuildType.B_ANDROID 211*7594170eSAndroid Build Coastguard Worker ] 212*7594170eSAndroid Build Coastguard Worker if len(non_b): 213*7594170eSAndroid Build Coastguard Worker raise RuntimeError(f"bazel labels can not be used with {non_b}") 214*7594170eSAndroid Build Coastguard Worker 215*7594170eSAndroid Build Coastguard Worker pretty_str = "\n".join( 216*7594170eSAndroid Build Coastguard Worker [f"{i:2}: {cujgroups[i].description}" for i in chosen_cujgroups] 217*7594170eSAndroid Build Coastguard Worker ) 218*7594170eSAndroid Build Coastguard Worker logging.info(f"%d CUJs chosen:\n%s", len(chosen_cujgroups), pretty_str) 219*7594170eSAndroid Build Coastguard Worker 220*7594170eSAndroid Build Coastguard Worker if not options.ignore_repo_diff and util.has_uncommitted_changes(): 221*7594170eSAndroid Build Coastguard Worker error_message = ( 222*7594170eSAndroid Build Coastguard Worker "THERE ARE UNCOMMITTED CHANGES (TIP: repo status). " 223*7594170eSAndroid Build Coastguard Worker "Use --ignore-repo-diff to skip this check." 224*7594170eSAndroid Build Coastguard Worker ) 225*7594170eSAndroid Build Coastguard Worker if not util.is_interactive_shell(): 226*7594170eSAndroid Build Coastguard Worker logging.critical(error_message) 227*7594170eSAndroid Build Coastguard Worker sys.exit(1) 228*7594170eSAndroid Build Coastguard Worker logging.error(error_message) 229*7594170eSAndroid Build Coastguard Worker response = input("Continue?[Y/n]") 230*7594170eSAndroid Build Coastguard Worker if response.upper() != "Y": 231*7594170eSAndroid Build Coastguard Worker sys.exit(1) 232*7594170eSAndroid Build Coastguard Worker 233*7594170eSAndroid Build Coastguard Worker log_dir = Path(options.log_dir).resolve() 234*7594170eSAndroid Build Coastguard Worker if not options.append_csv and log_dir.exists(): 235*7594170eSAndroid Build Coastguard Worker error_message = ( 236*7594170eSAndroid Build Coastguard Worker f"{log_dir} already exists. " 237*7594170eSAndroid Build Coastguard Worker "Use --append-csv to skip this check." 238*7594170eSAndroid Build Coastguard Worker "Consider --tag to your new runs" 239*7594170eSAndroid Build Coastguard Worker ) 240*7594170eSAndroid Build Coastguard Worker if not util.is_interactive_shell(): 241*7594170eSAndroid Build Coastguard Worker logging.critical(error_message) 242*7594170eSAndroid Build Coastguard Worker sys.exit(1) 243*7594170eSAndroid Build Coastguard Worker logging.error(error_message) 244*7594170eSAndroid Build Coastguard Worker response = input("Continue?[Y/n]") 245*7594170eSAndroid Build Coastguard Worker if response.upper() != "Y": 246*7594170eSAndroid Build Coastguard Worker sys.exit(1) 247*7594170eSAndroid Build Coastguard Worker 248*7594170eSAndroid Build Coastguard Worker if log_dir.is_relative_to(util.get_top_dir()): 249*7594170eSAndroid Build Coastguard Worker logging.critical( 250*7594170eSAndroid Build Coastguard Worker f"choose a log_dir outside the source tree; " 251*7594170eSAndroid Build Coastguard Worker f"'{options.log_dir}' resolves to {log_dir}" 252*7594170eSAndroid Build Coastguard Worker ) 253*7594170eSAndroid Build Coastguard Worker sys.exit(1) 254*7594170eSAndroid Build Coastguard Worker 255*7594170eSAndroid Build Coastguard Worker if options.ci_mode: 256*7594170eSAndroid Build Coastguard Worker if len(chosen_cujgroups) > 1: 257*7594170eSAndroid Build Coastguard Worker logging.critical( 258*7594170eSAndroid Build Coastguard Worker "CI mode can only allow one cuj group. " 259*7594170eSAndroid Build Coastguard Worker "Remove --ci-mode flag to skip this check." 260*7594170eSAndroid Build Coastguard Worker ) 261*7594170eSAndroid Build Coastguard Worker sys.exit(1) 262*7594170eSAndroid Build Coastguard Worker if len(build_types) > 1: 263*7594170eSAndroid Build Coastguard Worker logging.critical( 264*7594170eSAndroid Build Coastguard Worker "CI mode can only allow one build type. " 265*7594170eSAndroid Build Coastguard Worker "Remove --ci-mode flag to skip this check." 266*7594170eSAndroid Build Coastguard Worker ) 267*7594170eSAndroid Build Coastguard Worker sys.exit(1) 268*7594170eSAndroid Build Coastguard Worker 269*7594170eSAndroid Build Coastguard Worker if options.no_warmup: 270*7594170eSAndroid Build Coastguard Worker logging.warning( 271*7594170eSAndroid Build Coastguard Worker "WARMUP runs will be skipped. Note this is not advised " 272*7594170eSAndroid Build Coastguard Worker "as it gives unreliable results." 273*7594170eSAndroid Build Coastguard Worker ) 274*7594170eSAndroid Build Coastguard Worker return UserInput( 275*7594170eSAndroid Build Coastguard Worker build_types=build_types, 276*7594170eSAndroid Build Coastguard Worker chosen_cujgroups=chosen_cujgroups, 277*7594170eSAndroid Build Coastguard Worker tag=options.tag, 278*7594170eSAndroid Build Coastguard Worker log_dir=Path(options.log_dir).resolve(), 279*7594170eSAndroid Build Coastguard Worker no_warmup=options.no_warmup, 280*7594170eSAndroid Build Coastguard Worker targets=options.targets, 281*7594170eSAndroid Build Coastguard Worker ci_mode=options.ci_mode, 282*7594170eSAndroid Build Coastguard Worker ) 283