xref: /aosp_15_r20/build/bazel/scripts/incremental_build/ui.py (revision 7594170e27e0732bc44b93d1440d87a54b6ffe7c)
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