xref: /aosp_15_r20/build/bazel/scripts/incremental_build/clone.py (revision 7594170e27e0732bc44b93d1440d87a54b6ffe7c)
1*7594170eSAndroid Build Coastguard Worker# Copyright (C) 2023 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 Workerimport argparse
15*7594170eSAndroid Build Coastguard Workerimport functools
16*7594170eSAndroid Build Coastguard Workerimport logging
17*7594170eSAndroid Build Coastguard Workerimport os
18*7594170eSAndroid Build Coastguard Workerimport re
19*7594170eSAndroid Build Coastguard Workerimport shutil
20*7594170eSAndroid Build Coastguard Workerimport uuid
21*7594170eSAndroid Build Coastguard Workerfrom pathlib import Path
22*7594170eSAndroid Build Coastguard Workerfrom string import Template
23*7594170eSAndroid Build Coastguard Workerfrom typing import Callable, Generator, Iterable
24*7594170eSAndroid Build Coastguard Workerfrom typing import NewType
25*7594170eSAndroid Build Coastguard Workerfrom typing import Optional
26*7594170eSAndroid Build Coastguard Workerfrom typing import TextIO
27*7594170eSAndroid Build Coastguard Worker
28*7594170eSAndroid Build Coastguard Workerimport cuj
29*7594170eSAndroid Build Coastguard Workerimport util
30*7594170eSAndroid Build Coastguard Workerfrom cuj import src
31*7594170eSAndroid Build Coastguard Workerfrom go_allowlists import GoAllowlistManipulator
32*7594170eSAndroid Build Coastguard Worker
33*7594170eSAndroid Build Coastguard Worker_ALLOWLISTS = "build/soong/android/allowlists/allowlists.go"
34*7594170eSAndroid Build Coastguard Worker
35*7594170eSAndroid Build Coastguard WorkerModuleType = NewType("ModuleType", str)
36*7594170eSAndroid Build Coastguard WorkerModuleName = NewType("ModuleName", str)
37*7594170eSAndroid Build Coastguard Worker
38*7594170eSAndroid Build Coastguard WorkerFilter = Callable[[ModuleType, ModuleName], bool]
39*7594170eSAndroid Build Coastguard Worker
40*7594170eSAndroid Build Coastguard Worker
41*7594170eSAndroid Build Coastguard Workerdef module_defs(src_lines: TextIO) -> Generator[tuple[ModuleType, str], None, None]:
42*7594170eSAndroid Build Coastguard Worker    """
43*7594170eSAndroid Build Coastguard Worker    Split `scr_lines` (an Android.bp file) into module definitions and discard
44*7594170eSAndroid Build Coastguard Worker    everything else (e.g. top level comments and assignments)
45*7594170eSAndroid Build Coastguard Worker    Assumes that the Android.bp file is properly formatted, specifically,
46*7594170eSAndroid Build Coastguard Worker    for any module definition:
47*7594170eSAndroid Build Coastguard Worker     1. the first line matches `start_pattern`, e.g. `cc_library {`
48*7594170eSAndroid Build Coastguard Worker     2. the last line matches a closing curly brace, i.e. '}'
49*7594170eSAndroid Build Coastguard Worker    """
50*7594170eSAndroid Build Coastguard Worker    start_pattern = re.compile(r"^(?P<module_type>\w+)\s*\{\s*$")
51*7594170eSAndroid Build Coastguard Worker    module_type: Optional[ModuleType] = None
52*7594170eSAndroid Build Coastguard Worker    buffer = ""
53*7594170eSAndroid Build Coastguard Worker
54*7594170eSAndroid Build Coastguard Worker    def in_module_def() -> bool:
55*7594170eSAndroid Build Coastguard Worker        return buffer != ""
56*7594170eSAndroid Build Coastguard Worker
57*7594170eSAndroid Build Coastguard Worker    for line in src_lines:
58*7594170eSAndroid Build Coastguard Worker        # NB: line includes ending newline
59*7594170eSAndroid Build Coastguard Worker        line = line.replace("$", "$$")  # escape Templating meta-char '$'
60*7594170eSAndroid Build Coastguard Worker        if not in_module_def():
61*7594170eSAndroid Build Coastguard Worker            m = start_pattern.match(line)
62*7594170eSAndroid Build Coastguard Worker            if m:
63*7594170eSAndroid Build Coastguard Worker                module_type = ModuleType(m.group("module_type"))
64*7594170eSAndroid Build Coastguard Worker                buffer = line
65*7594170eSAndroid Build Coastguard Worker        else:
66*7594170eSAndroid Build Coastguard Worker            buffer += line
67*7594170eSAndroid Build Coastguard Worker            if line.rstrip() == "}":
68*7594170eSAndroid Build Coastguard Worker                assert in_module_def()
69*7594170eSAndroid Build Coastguard Worker                # end of module definition
70*7594170eSAndroid Build Coastguard Worker                yield module_type, buffer
71*7594170eSAndroid Build Coastguard Worker                module_type = None
72*7594170eSAndroid Build Coastguard Worker                buffer = ""
73*7594170eSAndroid Build Coastguard Worker
74*7594170eSAndroid Build Coastguard Worker
75*7594170eSAndroid Build Coastguard Workerdef type_in(*module_types: str) -> Filter:
76*7594170eSAndroid Build Coastguard Worker    def f(t: ModuleType, _: ModuleName) -> bool:
77*7594170eSAndroid Build Coastguard Worker        return t in module_types
78*7594170eSAndroid Build Coastguard Worker
79*7594170eSAndroid Build Coastguard Worker    return f
80*7594170eSAndroid Build Coastguard Worker
81*7594170eSAndroid Build Coastguard Worker
82*7594170eSAndroid Build Coastguard Workerdef name_in(*module_names: str) -> Filter:
83*7594170eSAndroid Build Coastguard Worker    def f(_: ModuleType, n: ModuleName) -> bool:
84*7594170eSAndroid Build Coastguard Worker        return n in module_names
85*7594170eSAndroid Build Coastguard Worker
86*7594170eSAndroid Build Coastguard Worker    return f
87*7594170eSAndroid Build Coastguard Worker
88*7594170eSAndroid Build Coastguard Worker
89*7594170eSAndroid Build Coastguard Workerdef _modify_genrule_template(module_name: ModuleName, module_def: str) -> Optional[str]:
90*7594170eSAndroid Build Coastguard Worker    # assume `out` only occurs as top-level property of a module
91*7594170eSAndroid Build Coastguard Worker    # assume `out` is always a singleton array
92*7594170eSAndroid Build Coastguard Worker    p = re.compile(r'[\n\r]\s+out\s*:\s*\[[\n\r]*\s*"[^"]+(?=")', re.MULTILINE)
93*7594170eSAndroid Build Coastguard Worker    g = p.search(module_def)
94*7594170eSAndroid Build Coastguard Worker    if g is None:
95*7594170eSAndroid Build Coastguard Worker        logging.debug('Could not find "out" for "%s"', module_name)
96*7594170eSAndroid Build Coastguard Worker        return None
97*7594170eSAndroid Build Coastguard Worker    index = g.end()
98*7594170eSAndroid Build Coastguard Worker    return f"{module_def[: index]}-${{suffix}}{module_def[index:]}"
99*7594170eSAndroid Build Coastguard Worker
100*7594170eSAndroid Build Coastguard Worker
101*7594170eSAndroid Build Coastguard Workerdef _extract_templates_helper(
102*7594170eSAndroid Build Coastguard Worker    src_lines: TextIO, f: Filter
103*7594170eSAndroid Build Coastguard Worker) -> dict[ModuleName, Template]:
104*7594170eSAndroid Build Coastguard Worker    """
105*7594170eSAndroid Build Coastguard Worker    For `src_lines` from an Android.bp file, find modules that satisfy the
106*7594170eSAndroid Build Coastguard Worker    Filter `f` and for each such mach return a "template" text that facilitates
107*7594170eSAndroid Build Coastguard Worker    changing the module's name.
108*7594170eSAndroid Build Coastguard Worker    """
109*7594170eSAndroid Build Coastguard Worker    # assume `name` only occurs as top-level property of a module
110*7594170eSAndroid Build Coastguard Worker    name_pattern = re.compile(r'[\n\r]\s+name:\s*"(?P<name>[^"]+)(?=")', re.MULTILINE)
111*7594170eSAndroid Build Coastguard Worker    result = dict[ModuleName, Template]()
112*7594170eSAndroid Build Coastguard Worker    for module_type, module_def in module_defs(src_lines):
113*7594170eSAndroid Build Coastguard Worker        m = name_pattern.search(module_def)
114*7594170eSAndroid Build Coastguard Worker        if not m:
115*7594170eSAndroid Build Coastguard Worker            continue
116*7594170eSAndroid Build Coastguard Worker        module_name = ModuleName(m.group("name"))
117*7594170eSAndroid Build Coastguard Worker        if module_name in result:
118*7594170eSAndroid Build Coastguard Worker            logging.debug(
119*7594170eSAndroid Build Coastguard Worker                f"{module_name} already exists thus " f"ignoring {module_type}"
120*7594170eSAndroid Build Coastguard Worker            )
121*7594170eSAndroid Build Coastguard Worker            continue
122*7594170eSAndroid Build Coastguard Worker        if not f(module_type, module_name):
123*7594170eSAndroid Build Coastguard Worker            continue
124*7594170eSAndroid Build Coastguard Worker        i = m.end()
125*7594170eSAndroid Build Coastguard Worker        module_def = f"{module_def[: i]}-${{suffix}}{module_def[i:]}"
126*7594170eSAndroid Build Coastguard Worker        if module_type == ModuleType("genrule"):
127*7594170eSAndroid Build Coastguard Worker            module_def = _modify_genrule_template(module_name, module_def)
128*7594170eSAndroid Build Coastguard Worker            if module_def is None:
129*7594170eSAndroid Build Coastguard Worker                continue
130*7594170eSAndroid Build Coastguard Worker        result[module_name] = Template(module_def)
131*7594170eSAndroid Build Coastguard Worker    return result
132*7594170eSAndroid Build Coastguard Worker
133*7594170eSAndroid Build Coastguard Worker
134*7594170eSAndroid Build Coastguard Workerdef _extract_templates(
135*7594170eSAndroid Build Coastguard Worker    bps: dict[Path, Filter]
136*7594170eSAndroid Build Coastguard Worker) -> dict[Path, dict[ModuleName, Template]]:
137*7594170eSAndroid Build Coastguard Worker    """
138*7594170eSAndroid Build Coastguard Worker    If any key is a directory instead of an Android.bp file, expand it is as if it
139*7594170eSAndroid Build Coastguard Worker    were the glob pattern $key/**/Android.bp, i.e. replace it with all Android.bp
140*7594170eSAndroid Build Coastguard Worker    files under its tree.
141*7594170eSAndroid Build Coastguard Worker    """
142*7594170eSAndroid Build Coastguard Worker    bp2templates = dict[Path, dict[ModuleName, Template]]()
143*7594170eSAndroid Build Coastguard Worker    with open(src(_ALLOWLISTS), "rt") as af:
144*7594170eSAndroid Build Coastguard Worker        go_allowlists = GoAllowlistManipulator(af.readlines())
145*7594170eSAndroid Build Coastguard Worker        alwaysconvert = go_allowlists.locate("Bp2buildModuleAlwaysConvertList")
146*7594170eSAndroid Build Coastguard Worker
147*7594170eSAndroid Build Coastguard Worker    def maybe_register(bp: Path):
148*7594170eSAndroid Build Coastguard Worker        with open(bp, "rt") as src_lines:
149*7594170eSAndroid Build Coastguard Worker            templates = _extract_templates_helper(src_lines, fltr)
150*7594170eSAndroid Build Coastguard Worker            if not go_allowlists.is_dir_allowed(bp.parent):
151*7594170eSAndroid Build Coastguard Worker                templates = {n: v for n, v in templates.items() if n in alwaysconvert}
152*7594170eSAndroid Build Coastguard Worker            if len(templates) == 0:
153*7594170eSAndroid Build Coastguard Worker                logging.debug("No matches in %s", k)
154*7594170eSAndroid Build Coastguard Worker            else:
155*7594170eSAndroid Build Coastguard Worker                bp2templates[bp] = bp2templates.get(bp, {}) | templates
156*7594170eSAndroid Build Coastguard Worker
157*7594170eSAndroid Build Coastguard Worker    for k, fltr in bps.items():
158*7594170eSAndroid Build Coastguard Worker        if k.name == "Android.bp":
159*7594170eSAndroid Build Coastguard Worker            maybe_register(k)
160*7594170eSAndroid Build Coastguard Worker        for root, _, _ in os.walk(k):
161*7594170eSAndroid Build Coastguard Worker            if Path(root).is_relative_to(util.get_out_dir()):
162*7594170eSAndroid Build Coastguard Worker                continue
163*7594170eSAndroid Build Coastguard Worker            file = Path(root).joinpath("Android.bp")
164*7594170eSAndroid Build Coastguard Worker            if file.exists():
165*7594170eSAndroid Build Coastguard Worker                maybe_register(file)
166*7594170eSAndroid Build Coastguard Worker
167*7594170eSAndroid Build Coastguard Worker    return bp2templates
168*7594170eSAndroid Build Coastguard Worker
169*7594170eSAndroid Build Coastguard Worker
170*7594170eSAndroid Build Coastguard Worker@functools.cache
171*7594170eSAndroid Build Coastguard Workerdef _back_up_path() -> Path:
172*7594170eSAndroid Build Coastguard Worker    #  a directory to back up files that these CUJs change
173*7594170eSAndroid Build Coastguard Worker    return util.get_out_dir().joinpath("clone-cuj-backup")
174*7594170eSAndroid Build Coastguard Worker
175*7594170eSAndroid Build Coastguard Worker
176*7594170eSAndroid Build Coastguard Workerdef _backup(bps: Iterable[Path]):
177*7594170eSAndroid Build Coastguard Worker    # if first cuj_step then back up files to restore later
178*7594170eSAndroid Build Coastguard Worker    if _back_up_path().exists():
179*7594170eSAndroid Build Coastguard Worker        raise RuntimeError(
180*7594170eSAndroid Build Coastguard Worker            f"{_back_up_path()} already exists. "
181*7594170eSAndroid Build Coastguard Worker            f"Did you kill a previous cuj run? "
182*7594170eSAndroid Build Coastguard Worker            f"Delete {_back_up_path()} and revert changes to "
183*7594170eSAndroid Build Coastguard Worker            f"allowlists.go and Android.bp files"
184*7594170eSAndroid Build Coastguard Worker        )
185*7594170eSAndroid Build Coastguard Worker    for bp in bps:
186*7594170eSAndroid Build Coastguard Worker        src_path = bp.relative_to(util.get_top_dir())
187*7594170eSAndroid Build Coastguard Worker        bak_file = _back_up_path().joinpath(src_path)
188*7594170eSAndroid Build Coastguard Worker        os.makedirs(os.path.dirname(bak_file))
189*7594170eSAndroid Build Coastguard Worker        shutil.copy(bp, bak_file)
190*7594170eSAndroid Build Coastguard Worker    src_allowlists = src(_ALLOWLISTS)
191*7594170eSAndroid Build Coastguard Worker    bak_allowlists = _back_up_path().joinpath(_ALLOWLISTS)
192*7594170eSAndroid Build Coastguard Worker    os.makedirs(os.path.dirname(bak_allowlists))
193*7594170eSAndroid Build Coastguard Worker    shutil.copy(src_allowlists, bak_allowlists)
194*7594170eSAndroid Build Coastguard Worker
195*7594170eSAndroid Build Coastguard Worker
196*7594170eSAndroid Build Coastguard Workerdef _restore():
197*7594170eSAndroid Build Coastguard Worker    src(_ALLOWLISTS).touch(exist_ok=True)
198*7594170eSAndroid Build Coastguard Worker    for root, _, files in os.walk(_back_up_path()):
199*7594170eSAndroid Build Coastguard Worker        for file in files:
200*7594170eSAndroid Build Coastguard Worker            bak_file = Path(root).joinpath(file)
201*7594170eSAndroid Build Coastguard Worker            bak_path = bak_file.relative_to(_back_up_path())
202*7594170eSAndroid Build Coastguard Worker            src_file = util.get_top_dir().joinpath(bak_path)
203*7594170eSAndroid Build Coastguard Worker            shutil.copy(bak_file, src_file)
204*7594170eSAndroid Build Coastguard Worker            # touch to update mtime; ctime is ignored by ninja
205*7594170eSAndroid Build Coastguard Worker            src_file.touch(exist_ok=True)
206*7594170eSAndroid Build Coastguard Worker
207*7594170eSAndroid Build Coastguard Worker
208*7594170eSAndroid Build Coastguard Workerdef _bz_counterpart(bp: Path) -> Path:
209*7594170eSAndroid Build Coastguard Worker    return (
210*7594170eSAndroid Build Coastguard Worker        util.get_out_dir()
211*7594170eSAndroid Build Coastguard Worker        .joinpath("soong", "bp2build", bp.relative_to(util.get_top_dir()))
212*7594170eSAndroid Build Coastguard Worker        .with_name("BUILD.bazel")
213*7594170eSAndroid Build Coastguard Worker    )
214*7594170eSAndroid Build Coastguard Worker
215*7594170eSAndroid Build Coastguard Worker
216*7594170eSAndroid Build Coastguard Workerdef _make_clones(bp2templates: dict[Path, dict[ModuleName, Template]], n: int):
217*7594170eSAndroid Build Coastguard Worker    r = f"{str(uuid.uuid4()):.6}"  # cache-busting
218*7594170eSAndroid Build Coastguard Worker    source_count = 0
219*7594170eSAndroid Build Coastguard Worker    output = ["\n"]
220*7594170eSAndroid Build Coastguard Worker
221*7594170eSAndroid Build Coastguard Worker    with open(src(_ALLOWLISTS), "rt") as f:
222*7594170eSAndroid Build Coastguard Worker        go_allowlists = GoAllowlistManipulator(f.readlines())
223*7594170eSAndroid Build Coastguard Worker        mixed_build_enabled_list = go_allowlists.locate("ProdMixedBuildsEnabledList")
224*7594170eSAndroid Build Coastguard Worker        alwaysconvert = go_allowlists.locate("Bp2buildModuleAlwaysConvertList")
225*7594170eSAndroid Build Coastguard Worker
226*7594170eSAndroid Build Coastguard Worker    def _allow():
227*7594170eSAndroid Build Coastguard Worker        if name not in mixed_build_enabled_list:
228*7594170eSAndroid Build Coastguard Worker            mixed_build_enabled_list.prepend([name])
229*7594170eSAndroid Build Coastguard Worker        mixed_build_enabled_list.prepend(clones)
230*7594170eSAndroid Build Coastguard Worker
231*7594170eSAndroid Build Coastguard Worker        if name in alwaysconvert:
232*7594170eSAndroid Build Coastguard Worker            alwaysconvert.prepend(clones)
233*7594170eSAndroid Build Coastguard Worker
234*7594170eSAndroid Build Coastguard Worker    for bp, n2t in bp2templates.items():
235*7594170eSAndroid Build Coastguard Worker        source_count += len(n2t)
236*7594170eSAndroid Build Coastguard Worker        output.append(
237*7594170eSAndroid Build Coastguard Worker            f"{n:5,}X{len(n2t):2,} modules = {n * len(n2t):+5,} "
238*7594170eSAndroid Build Coastguard Worker            f"in {bp.relative_to(util.get_top_dir())}"
239*7594170eSAndroid Build Coastguard Worker        )
240*7594170eSAndroid Build Coastguard Worker        with open(bp, "a") as f:
241*7594170eSAndroid Build Coastguard Worker            for name, t in n2t.items():
242*7594170eSAndroid Build Coastguard Worker                clones = []
243*7594170eSAndroid Build Coastguard Worker                for n in range(1, n + 1):
244*7594170eSAndroid Build Coastguard Worker                    suffix = f"{r}-{n:05d}"
245*7594170eSAndroid Build Coastguard Worker                    f.write(t.substitute(suffix=suffix))
246*7594170eSAndroid Build Coastguard Worker                    clones.append(ModuleName(f"{name}-{suffix}"))
247*7594170eSAndroid Build Coastguard Worker                _allow()
248*7594170eSAndroid Build Coastguard Worker
249*7594170eSAndroid Build Coastguard Worker    with open(src(_ALLOWLISTS), "wt") as f:
250*7594170eSAndroid Build Coastguard Worker        f.writelines(go_allowlists.lines)
251*7594170eSAndroid Build Coastguard Worker
252*7594170eSAndroid Build Coastguard Worker    logging.info(
253*7594170eSAndroid Build Coastguard Worker        f"Cloned {n:,}X{source_count:,} modules = {n * source_count:+,} "
254*7594170eSAndroid Build Coastguard Worker        f"in {len(bp2templates)} Android.bp files"
255*7594170eSAndroid Build Coastguard Worker    )
256*7594170eSAndroid Build Coastguard Worker    logging.debug("\n".join(output))
257*7594170eSAndroid Build Coastguard Worker
258*7594170eSAndroid Build Coastguard Worker
259*7594170eSAndroid Build Coastguard Workerdef _display_sizes():
260*7594170eSAndroid Build Coastguard Worker    file_count = 0
261*7594170eSAndroid Build Coastguard Worker    orig_tot = 0
262*7594170eSAndroid Build Coastguard Worker    curr_tot = 0
263*7594170eSAndroid Build Coastguard Worker    output = ["\n"]
264*7594170eSAndroid Build Coastguard Worker    for root, _, files in os.walk(_back_up_path()):
265*7594170eSAndroid Build Coastguard Worker        file_count += len(files)
266*7594170eSAndroid Build Coastguard Worker        for file in files:
267*7594170eSAndroid Build Coastguard Worker            backup_file = Path(root).joinpath(file)
268*7594170eSAndroid Build Coastguard Worker            common_path = backup_file.relative_to(_back_up_path())
269*7594170eSAndroid Build Coastguard Worker            source_file = util.get_top_dir().joinpath(common_path)
270*7594170eSAndroid Build Coastguard Worker            curr_size = os.stat(source_file).st_size
271*7594170eSAndroid Build Coastguard Worker            curr_tot += curr_size
272*7594170eSAndroid Build Coastguard Worker            orig_size = os.stat(backup_file).st_size
273*7594170eSAndroid Build Coastguard Worker            orig_tot += orig_size
274*7594170eSAndroid Build Coastguard Worker            output.append(
275*7594170eSAndroid Build Coastguard Worker                f"{orig_size:7,} {curr_size - orig_size :+5,} => {curr_size:9,} "
276*7594170eSAndroid Build Coastguard Worker                f"bytes {source_file.relative_to(util.get_top_dir())}"
277*7594170eSAndroid Build Coastguard Worker            )
278*7594170eSAndroid Build Coastguard Worker            if file == "Android.bp":
279*7594170eSAndroid Build Coastguard Worker                bz = _bz_counterpart(source_file)
280*7594170eSAndroid Build Coastguard Worker                output.append(
281*7594170eSAndroid Build Coastguard Worker                    f"{os.stat(bz).st_size:8,} bytes "
282*7594170eSAndroid Build Coastguard Worker                    f"$OUTDIR/{bz.relative_to(util.get_out_dir())}"
283*7594170eSAndroid Build Coastguard Worker                )
284*7594170eSAndroid Build Coastguard Worker    logging.info(
285*7594170eSAndroid Build Coastguard Worker        f"Affected {file_count} files {orig_tot:,} "
286*7594170eSAndroid Build Coastguard Worker        f"{curr_tot - orig_tot:+,} => {curr_tot:,} bytes"
287*7594170eSAndroid Build Coastguard Worker    )
288*7594170eSAndroid Build Coastguard Worker    logging.debug("\n".join(output))
289*7594170eSAndroid Build Coastguard Worker
290*7594170eSAndroid Build Coastguard Worker
291*7594170eSAndroid Build Coastguard Workerdef _name_cuj(count: int, module_count: int, bp_count: int) -> str:
292*7594170eSAndroid Build Coastguard Worker    match module_count:
293*7594170eSAndroid Build Coastguard Worker        case 1:
294*7594170eSAndroid Build Coastguard Worker            name = f"{count}"
295*7594170eSAndroid Build Coastguard Worker        case _:
296*7594170eSAndroid Build Coastguard Worker            name = f"{count}x{module_count}"
297*7594170eSAndroid Build Coastguard Worker    if bp_count > 1:
298*7594170eSAndroid Build Coastguard Worker        name = f"{name}({bp_count} files)"
299*7594170eSAndroid Build Coastguard Worker    return name
300*7594170eSAndroid Build Coastguard Worker
301*7594170eSAndroid Build Coastguard Worker
302*7594170eSAndroid Build Coastguard Workerclass Clone(cuj.CujGroup):
303*7594170eSAndroid Build Coastguard Worker    def __init__(self, group_name: str, bps: dict[Path, Filter]):
304*7594170eSAndroid Build Coastguard Worker        super().__init__(group_name)
305*7594170eSAndroid Build Coastguard Worker        self.bps = bps
306*7594170eSAndroid Build Coastguard Worker
307*7594170eSAndroid Build Coastguard Worker    def get_steps(self) -> Iterable[cuj.CujStep]:
308*7594170eSAndroid Build Coastguard Worker        bp2templates = _extract_templates(self.bps)
309*7594170eSAndroid Build Coastguard Worker        bp_count = len(bp2templates)
310*7594170eSAndroid Build Coastguard Worker        if bp_count == 0:
311*7594170eSAndroid Build Coastguard Worker            raise RuntimeError(f"No eligible module to clone in {self.bps.keys()}")
312*7594170eSAndroid Build Coastguard Worker        module_count = sum(len(templates) for templates in bp2templates.values())
313*7594170eSAndroid Build Coastguard Worker
314*7594170eSAndroid Build Coastguard Worker        if "CLONE" in os.environ:
315*7594170eSAndroid Build Coastguard Worker            counts = [int(s) for s in os.environ["CLONE"].split(",")]
316*7594170eSAndroid Build Coastguard Worker        else:
317*7594170eSAndroid Build Coastguard Worker            counts = [1, 100, 200, 300, 400]
318*7594170eSAndroid Build Coastguard Worker            logging.info(
319*7594170eSAndroid Build Coastguard Worker                f'Will clone {",".join(str(i) for i in counts)} in cujs. '
320*7594170eSAndroid Build Coastguard Worker                f"You may specify alternative counts with CLONE env var, "
321*7594170eSAndroid Build Coastguard Worker                f"e.g. CLONE = 1,10,100,1000"
322*7594170eSAndroid Build Coastguard Worker            )
323*7594170eSAndroid Build Coastguard Worker        first_bp = next(iter(bp2templates.keys()))
324*7594170eSAndroid Build Coastguard Worker
325*7594170eSAndroid Build Coastguard Worker        def modify_bp():
326*7594170eSAndroid Build Coastguard Worker            with open(first_bp, mode="a") as f:
327*7594170eSAndroid Build Coastguard Worker                f.write(f"//post clone modification {uuid.uuid4()}\n")
328*7594170eSAndroid Build Coastguard Worker
329*7594170eSAndroid Build Coastguard Worker        steps: list[cuj.CujStep] = []
330*7594170eSAndroid Build Coastguard Worker        for i, count in enumerate(counts):
331*7594170eSAndroid Build Coastguard Worker            base_name = _name_cuj(count, module_count, bp_count)
332*7594170eSAndroid Build Coastguard Worker            steps.append(
333*7594170eSAndroid Build Coastguard Worker                cuj.CujStep(
334*7594170eSAndroid Build Coastguard Worker                    verb=base_name,
335*7594170eSAndroid Build Coastguard Worker                    apply_change=cuj.sequence(
336*7594170eSAndroid Build Coastguard Worker                        functools.partial(_backup, bp2templates.keys())
337*7594170eSAndroid Build Coastguard Worker                        if i == 0
338*7594170eSAndroid Build Coastguard Worker                        else _restore,
339*7594170eSAndroid Build Coastguard Worker                        functools.partial(_make_clones, bp2templates, count),
340*7594170eSAndroid Build Coastguard Worker                    ),
341*7594170eSAndroid Build Coastguard Worker                    verify=_display_sizes,
342*7594170eSAndroid Build Coastguard Worker                )
343*7594170eSAndroid Build Coastguard Worker            )
344*7594170eSAndroid Build Coastguard Worker            steps.append(cuj.CujStep(verb=f"bp aft {base_name}", apply_change=modify_bp))
345*7594170eSAndroid Build Coastguard Worker            if i == len(counts) - 1:
346*7594170eSAndroid Build Coastguard Worker                steps.append(
347*7594170eSAndroid Build Coastguard Worker                    cuj.CujStep(
348*7594170eSAndroid Build Coastguard Worker                        verb="revert",
349*7594170eSAndroid Build Coastguard Worker                        apply_change=cuj.sequence(
350*7594170eSAndroid Build Coastguard Worker                            _restore, lambda: shutil.rmtree(_back_up_path())
351*7594170eSAndroid Build Coastguard Worker                        ),
352*7594170eSAndroid Build Coastguard Worker                        verify=_display_sizes,
353*7594170eSAndroid Build Coastguard Worker                    )
354*7594170eSAndroid Build Coastguard Worker                )
355*7594170eSAndroid Build Coastguard Worker        return steps
356*7594170eSAndroid Build Coastguard Worker
357*7594170eSAndroid Build Coastguard Worker
358*7594170eSAndroid Build Coastguard Workerdef main():
359*7594170eSAndroid Build Coastguard Worker    """
360*7594170eSAndroid Build Coastguard Worker    provided only for manual run;
361*7594170eSAndroid Build Coastguard Worker    use incremental_build.sh to invoke the cuj instead
362*7594170eSAndroid Build Coastguard Worker    """
363*7594170eSAndroid Build Coastguard Worker    p = argparse.ArgumentParser()
364*7594170eSAndroid Build Coastguard Worker    p.add_argument(
365*7594170eSAndroid Build Coastguard Worker        "--module",
366*7594170eSAndroid Build Coastguard Worker        "-m",
367*7594170eSAndroid Build Coastguard Worker        default="adbd",
368*7594170eSAndroid Build Coastguard Worker        help="name of the module to clone; default=%(default)s",
369*7594170eSAndroid Build Coastguard Worker    )
370*7594170eSAndroid Build Coastguard Worker    p.add_argument(
371*7594170eSAndroid Build Coastguard Worker        "--count",
372*7594170eSAndroid Build Coastguard Worker        "-n",
373*7594170eSAndroid Build Coastguard Worker        default=1,
374*7594170eSAndroid Build Coastguard Worker        type=int,
375*7594170eSAndroid Build Coastguard Worker        help="number of times to clone; default: %(default)s",
376*7594170eSAndroid Build Coastguard Worker    )
377*7594170eSAndroid Build Coastguard Worker    adb_bp = util.get_top_dir().joinpath("packages/modules/adb/Android.bp")
378*7594170eSAndroid Build Coastguard Worker    p.add_argument(
379*7594170eSAndroid Build Coastguard Worker        "androidbp",
380*7594170eSAndroid Build Coastguard Worker        nargs="?",
381*7594170eSAndroid Build Coastguard Worker        default=adb_bp,
382*7594170eSAndroid Build Coastguard Worker        type=Path,
383*7594170eSAndroid Build Coastguard Worker        help="absolute path to Android.bp file; default=%(default)s",
384*7594170eSAndroid Build Coastguard Worker    )
385*7594170eSAndroid Build Coastguard Worker    options = p.parse_args()
386*7594170eSAndroid Build Coastguard Worker    _make_clones(
387*7594170eSAndroid Build Coastguard Worker        _extract_templates({options.androidbp: name_in(options.module)}), options.count
388*7594170eSAndroid Build Coastguard Worker    )
389*7594170eSAndroid Build Coastguard Worker    logging.warning("Changes made to your source tree; TIP: `repo status`")
390*7594170eSAndroid Build Coastguard Worker
391*7594170eSAndroid Build Coastguard Worker
392*7594170eSAndroid Build Coastguard Workerif __name__ == "__main__":
393*7594170eSAndroid Build Coastguard Worker    logging.root.setLevel(logging.INFO)
394*7594170eSAndroid Build Coastguard Worker    main()
395