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