xref: /aosp_15_r20/build/bazel/scripts/incremental_build/cuj.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 abc
15*7594170eSAndroid Build Coastguard Workerimport dataclasses
16*7594170eSAndroid Build Coastguard Workerimport enum
17*7594170eSAndroid Build Coastguard Workerimport logging
18*7594170eSAndroid Build Coastguard Workerimport os
19*7594170eSAndroid Build Coastguard Workerfrom pathlib import Path
20*7594170eSAndroid Build Coastguard Workerfrom typing import Callable, Iterable, TypeAlias
21*7594170eSAndroid Build Coastguard Worker
22*7594170eSAndroid Build Coastguard Workerimport util
23*7594170eSAndroid Build Coastguard Workerfrom util import BuildType
24*7594170eSAndroid Build Coastguard Worker
25*7594170eSAndroid Build Coastguard WorkerAction: TypeAlias = Callable[[], None]
26*7594170eSAndroid Build Coastguard WorkerVerifier: TypeAlias = Callable[[], None]
27*7594170eSAndroid Build Coastguard Worker
28*7594170eSAndroid Build Coastguard Worker
29*7594170eSAndroid Build Coastguard Workerdef de_src(p: Path) -> str:
30*7594170eSAndroid Build Coastguard Worker    return str(p.relative_to(util.get_top_dir()))
31*7594170eSAndroid Build Coastguard Worker
32*7594170eSAndroid Build Coastguard Worker
33*7594170eSAndroid Build Coastguard Workerdef src(p: str) -> Path:
34*7594170eSAndroid Build Coastguard Worker    return util.get_top_dir().joinpath(p)
35*7594170eSAndroid Build Coastguard Worker
36*7594170eSAndroid Build Coastguard Worker
37*7594170eSAndroid Build Coastguard Workerclass InWorkspace(enum.Enum):
38*7594170eSAndroid Build Coastguard Worker    """For a given file in the source tree, the counterpart in the symlink forest
39*7594170eSAndroid Build Coastguard Worker    could be one of these kinds.
40*7594170eSAndroid Build Coastguard Worker    """
41*7594170eSAndroid Build Coastguard Worker
42*7594170eSAndroid Build Coastguard Worker    SYMLINK = enum.auto()
43*7594170eSAndroid Build Coastguard Worker    NOT_UNDER_SYMLINK = enum.auto()
44*7594170eSAndroid Build Coastguard Worker    UNDER_SYMLINK = enum.auto()
45*7594170eSAndroid Build Coastguard Worker    OMISSION = enum.auto()
46*7594170eSAndroid Build Coastguard Worker
47*7594170eSAndroid Build Coastguard Worker    @staticmethod
48*7594170eSAndroid Build Coastguard Worker    def ws_counterpart(src_path: Path) -> Path:
49*7594170eSAndroid Build Coastguard Worker        return util.get_out_dir().joinpath("soong/workspace").joinpath(de_src(src_path))
50*7594170eSAndroid Build Coastguard Worker
51*7594170eSAndroid Build Coastguard Worker    def verifier(self, src_path: Path) -> Verifier:
52*7594170eSAndroid Build Coastguard Worker        @skip_for(BuildType.SOONG_ONLY)
53*7594170eSAndroid Build Coastguard Worker        def f():
54*7594170eSAndroid Build Coastguard Worker            ws_path = InWorkspace.ws_counterpart(src_path)
55*7594170eSAndroid Build Coastguard Worker            actual: InWorkspace | None = None
56*7594170eSAndroid Build Coastguard Worker            if ws_path.is_symlink():
57*7594170eSAndroid Build Coastguard Worker                actual = InWorkspace.SYMLINK
58*7594170eSAndroid Build Coastguard Worker                if not ws_path.exists():
59*7594170eSAndroid Build Coastguard Worker                    logging.warning("Dangling symlink %s", ws_path)
60*7594170eSAndroid Build Coastguard Worker            elif not ws_path.exists():
61*7594170eSAndroid Build Coastguard Worker                actual = InWorkspace.OMISSION
62*7594170eSAndroid Build Coastguard Worker            else:
63*7594170eSAndroid Build Coastguard Worker                for p in ws_path.parents:
64*7594170eSAndroid Build Coastguard Worker                    if not p.is_relative_to(util.get_out_dir()):
65*7594170eSAndroid Build Coastguard Worker                        actual = InWorkspace.NOT_UNDER_SYMLINK
66*7594170eSAndroid Build Coastguard Worker                        break
67*7594170eSAndroid Build Coastguard Worker                    if p.is_symlink():
68*7594170eSAndroid Build Coastguard Worker                        actual = InWorkspace.UNDER_SYMLINK
69*7594170eSAndroid Build Coastguard Worker                        break
70*7594170eSAndroid Build Coastguard Worker
71*7594170eSAndroid Build Coastguard Worker            if self != actual:
72*7594170eSAndroid Build Coastguard Worker                raise AssertionError(
73*7594170eSAndroid Build Coastguard Worker                    f"{ws_path} expected {self.name} but got {actual.name}"
74*7594170eSAndroid Build Coastguard Worker                )
75*7594170eSAndroid Build Coastguard Worker            logging.info(f"VERIFIED {de_src(ws_path)} {self.name}")
76*7594170eSAndroid Build Coastguard Worker
77*7594170eSAndroid Build Coastguard Worker        return f
78*7594170eSAndroid Build Coastguard Worker
79*7594170eSAndroid Build Coastguard Worker
80*7594170eSAndroid Build Coastguard Workerdef skip_for(*build_types: util.BuildType):
81*7594170eSAndroid Build Coastguard Worker    def decorator(func: Callable[[], any]) -> Callable[[], any]:
82*7594170eSAndroid Build Coastguard Worker        def wrapper():
83*7594170eSAndroid Build Coastguard Worker            if util.CURRENT_BUILD_TYPE not in build_types:
84*7594170eSAndroid Build Coastguard Worker                return func()
85*7594170eSAndroid Build Coastguard Worker
86*7594170eSAndroid Build Coastguard Worker        return wrapper
87*7594170eSAndroid Build Coastguard Worker
88*7594170eSAndroid Build Coastguard Worker    return decorator
89*7594170eSAndroid Build Coastguard Worker
90*7594170eSAndroid Build Coastguard Worker
91*7594170eSAndroid Build Coastguard Worker@skip_for(BuildType.SOONG_ONLY)
92*7594170eSAndroid Build Coastguard Workerdef verify_symlink_forest_has_only_symlink_leaves():
93*7594170eSAndroid Build Coastguard Worker    """Verifies that symlink forest has only symlinks or directories but no
94*7594170eSAndroid Build Coastguard Worker    files except for merged BUILD.bazel files"""
95*7594170eSAndroid Build Coastguard Worker
96*7594170eSAndroid Build Coastguard Worker    top_in_ws = InWorkspace.ws_counterpart(util.get_top_dir())
97*7594170eSAndroid Build Coastguard Worker
98*7594170eSAndroid Build Coastguard Worker    for root, _, files in os.walk(top_in_ws, topdown=True, followlinks=False):
99*7594170eSAndroid Build Coastguard Worker        for file in files:
100*7594170eSAndroid Build Coastguard Worker            if file == "soong_build_mtime" and top_in_ws.samefile(root):
101*7594170eSAndroid Build Coastguard Worker                continue
102*7594170eSAndroid Build Coastguard Worker            f = Path(root).joinpath(file)
103*7594170eSAndroid Build Coastguard Worker            if file != "BUILD.bazel" and not f.is_symlink():
104*7594170eSAndroid Build Coastguard Worker                raise AssertionError(f"{f} unexpected in symlink forest")
105*7594170eSAndroid Build Coastguard Worker
106*7594170eSAndroid Build Coastguard Worker    logging.info("VERIFIED Symlink Forest has no real files except BUILD.bazel")
107*7594170eSAndroid Build Coastguard Worker
108*7594170eSAndroid Build Coastguard Worker
109*7594170eSAndroid Build Coastguard Worker@dataclasses.dataclass(frozen=True)
110*7594170eSAndroid Build Coastguard Workerclass CujStep:
111*7594170eSAndroid Build Coastguard Worker    verb: str
112*7594170eSAndroid Build Coastguard Worker    """a human-readable description"""
113*7594170eSAndroid Build Coastguard Worker    apply_change: Action
114*7594170eSAndroid Build Coastguard Worker    """user action(s) that are performed prior to a build attempt"""
115*7594170eSAndroid Build Coastguard Worker    verify: Verifier = verify_symlink_forest_has_only_symlink_leaves
116*7594170eSAndroid Build Coastguard Worker    """post-build assertions, i.e. tests.
117*7594170eSAndroid Build Coastguard Worker  Should raise `Exception` for failures.
118*7594170eSAndroid Build Coastguard Worker  """
119*7594170eSAndroid Build Coastguard Worker
120*7594170eSAndroid Build Coastguard Worker
121*7594170eSAndroid Build Coastguard Workerclass CujGroup(abc.ABC):
122*7594170eSAndroid Build Coastguard Worker    """A sequence of steps to be performed, such that at the end of all steps the
123*7594170eSAndroid Build Coastguard Worker    initial state of the source tree is attained.
124*7594170eSAndroid Build Coastguard Worker    NO attempt is made to achieve atomicity programmatically. It is left as the
125*7594170eSAndroid Build Coastguard Worker    responsibility of the user.
126*7594170eSAndroid Build Coastguard Worker    """
127*7594170eSAndroid Build Coastguard Worker    def __init__(self, description: str):
128*7594170eSAndroid Build Coastguard Worker        self._desc = description
129*7594170eSAndroid Build Coastguard Worker
130*7594170eSAndroid Build Coastguard Worker    @property
131*7594170eSAndroid Build Coastguard Worker    def description(self)-> str:
132*7594170eSAndroid Build Coastguard Worker        return self._desc
133*7594170eSAndroid Build Coastguard Worker
134*7594170eSAndroid Build Coastguard Worker    @abc.abstractmethod
135*7594170eSAndroid Build Coastguard Worker    def get_steps(self) -> Iterable[CujStep]:
136*7594170eSAndroid Build Coastguard Worker        pass
137*7594170eSAndroid Build Coastguard Worker
138*7594170eSAndroid Build Coastguard Worker
139*7594170eSAndroid Build Coastguard Workerdef sequence(*vs: Callable[[], None]) -> Callable[[], None]:
140*7594170eSAndroid Build Coastguard Worker    def f():
141*7594170eSAndroid Build Coastguard Worker        for v in vs:
142*7594170eSAndroid Build Coastguard Worker            v()
143*7594170eSAndroid Build Coastguard Worker
144*7594170eSAndroid Build Coastguard Worker    return f
145