1*105f6285SAndroid Build Coastguard Worker# Copyright 2020 Google LLC 2*105f6285SAndroid Build Coastguard Worker# 3*105f6285SAndroid Build Coastguard Worker# Licensed under the Apache License, Version 2.0 (the "License"); 4*105f6285SAndroid Build Coastguard Worker# you may not use this file except in compliance with the License. 5*105f6285SAndroid Build Coastguard Worker# You may obtain a copy of the License at 6*105f6285SAndroid Build Coastguard Worker# 7*105f6285SAndroid Build Coastguard Worker# https://www.apache.org/licenses/LICENSE-2.0 8*105f6285SAndroid Build Coastguard Worker# 9*105f6285SAndroid Build Coastguard Worker# Unless required by applicable law or agreed to in writing, software 10*105f6285SAndroid Build Coastguard Worker# distributed under the License is distributed on an "AS IS" BASIS, 11*105f6285SAndroid Build Coastguard Worker# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12*105f6285SAndroid Build Coastguard Worker# See the License for the specific language governing permissions and 13*105f6285SAndroid Build Coastguard Worker# limitations under the License. 14*105f6285SAndroid Build Coastguard Worker 15*105f6285SAndroid Build Coastguard Worker"""Mounts all the projects required by a selected Build target. 16*105f6285SAndroid Build Coastguard Worker 17*105f6285SAndroid Build Coastguard WorkerFor details on how filesystem overlays work see the filesystem overlays 18*105f6285SAndroid Build Coastguard Workersection of the README.md. 19*105f6285SAndroid Build Coastguard Worker""" 20*105f6285SAndroid Build Coastguard Worker 21*105f6285SAndroid Build Coastguard Workerfrom __future__ import absolute_import 22*105f6285SAndroid Build Coastguard Workerfrom __future__ import division 23*105f6285SAndroid Build Coastguard Workerfrom __future__ import print_function 24*105f6285SAndroid Build Coastguard Worker 25*105f6285SAndroid Build Coastguard Workerimport collections 26*105f6285SAndroid Build Coastguard Workerimport os 27*105f6285SAndroid Build Coastguard Workerimport subprocess 28*105f6285SAndroid Build Coastguard Workerimport tempfile 29*105f6285SAndroid Build Coastguard Workerimport xml.etree.ElementTree as ET 30*105f6285SAndroid Build Coastguard Workerfrom . import config 31*105f6285SAndroid Build Coastguard Worker 32*105f6285SAndroid Build Coastguard WorkerBindMount = collections.namedtuple( 33*105f6285SAndroid Build Coastguard Worker 'BindMount', ['source_dir', 'readonly', 'allows_replacement']) 34*105f6285SAndroid Build Coastguard Worker 35*105f6285SAndroid Build Coastguard Worker 36*105f6285SAndroid Build Coastguard Workerclass BindOverlay(object): 37*105f6285SAndroid Build Coastguard Worker """Manages filesystem overlays of Android source tree using bind mounts. 38*105f6285SAndroid Build Coastguard Worker """ 39*105f6285SAndroid Build Coastguard Worker 40*105f6285SAndroid Build Coastguard Worker MAX_BIND_MOUNTS = 10000 41*105f6285SAndroid Build Coastguard Worker 42*105f6285SAndroid Build Coastguard Worker def _HideDir(self, target_dir): 43*105f6285SAndroid Build Coastguard Worker """Temporarily replace the target directory for an empty directory. 44*105f6285SAndroid Build Coastguard Worker 45*105f6285SAndroid Build Coastguard Worker Args: 46*105f6285SAndroid Build Coastguard Worker target_dir: A string path to the target directory. 47*105f6285SAndroid Build Coastguard Worker 48*105f6285SAndroid Build Coastguard Worker Returns: 49*105f6285SAndroid Build Coastguard Worker A string path to the empty directory that replaced the target directory. 50*105f6285SAndroid Build Coastguard Worker """ 51*105f6285SAndroid Build Coastguard Worker empty_dir = tempfile.mkdtemp(prefix='empty_dir_') 52*105f6285SAndroid Build Coastguard Worker self._AddBindMount(empty_dir, target_dir) 53*105f6285SAndroid Build Coastguard Worker return empty_dir 54*105f6285SAndroid Build Coastguard Worker 55*105f6285SAndroid Build Coastguard Worker def _FindBindMountConflict(self, path): 56*105f6285SAndroid Build Coastguard Worker """Finds any path in the bind mounts that conflicts with the provided path. 57*105f6285SAndroid Build Coastguard Worker 58*105f6285SAndroid Build Coastguard Worker Args: 59*105f6285SAndroid Build Coastguard Worker path: A string path to be checked. 60*105f6285SAndroid Build Coastguard Worker 61*105f6285SAndroid Build Coastguard Worker Returns: 62*105f6285SAndroid Build Coastguard Worker A tuple containing a string of the conflicting path in the bind mounts and 63*105f6285SAndroid Build Coastguard Worker whether or not to allow this path to supersede any conflicts. 64*105f6285SAndroid Build Coastguard Worker None, False if there was no conflict found. 65*105f6285SAndroid Build Coastguard Worker """ 66*105f6285SAndroid Build Coastguard Worker conflict_path = None 67*105f6285SAndroid Build Coastguard Worker allows_replacement = False 68*105f6285SAndroid Build Coastguard Worker for bind_destination, bind_mount in self._bind_mounts.items(): 69*105f6285SAndroid Build Coastguard Worker allows_replacement = bind_mount.allows_replacement 70*105f6285SAndroid Build Coastguard Worker # Check if the path is a subdir or the bind destination 71*105f6285SAndroid Build Coastguard Worker if path == bind_destination: 72*105f6285SAndroid Build Coastguard Worker conflict_path = bind_mount.source_dir 73*105f6285SAndroid Build Coastguard Worker break 74*105f6285SAndroid Build Coastguard Worker elif path.startswith(bind_destination + os.sep): 75*105f6285SAndroid Build Coastguard Worker relative_path = os.path.relpath(path, bind_destination) 76*105f6285SAndroid Build Coastguard Worker path_in_source = os.path.join(bind_mount.source_dir, relative_path) 77*105f6285SAndroid Build Coastguard Worker if os.path.exists(path_in_source) and os.listdir(path_in_source): 78*105f6285SAndroid Build Coastguard Worker # A conflicting path exists within this bind mount 79*105f6285SAndroid Build Coastguard Worker # and it's not empty 80*105f6285SAndroid Build Coastguard Worker conflict_path = path_in_source 81*105f6285SAndroid Build Coastguard Worker break 82*105f6285SAndroid Build Coastguard Worker 83*105f6285SAndroid Build Coastguard Worker return conflict_path, allows_replacement 84*105f6285SAndroid Build Coastguard Worker 85*105f6285SAndroid Build Coastguard Worker def _AddOverlay(self, source_dir, overlay_dir, intermediate_work_dir, 86*105f6285SAndroid Build Coastguard Worker skip_subdirs, allowed_projects, destination_dir, 87*105f6285SAndroid Build Coastguard Worker allowed_read_write, contains_read_write, 88*105f6285SAndroid Build Coastguard Worker is_replacement_allowed): 89*105f6285SAndroid Build Coastguard Worker """Adds a single overlay directory. 90*105f6285SAndroid Build Coastguard Worker 91*105f6285SAndroid Build Coastguard Worker Args: 92*105f6285SAndroid Build Coastguard Worker source_dir: A string with the path to the Android platform source. 93*105f6285SAndroid Build Coastguard Worker overlay_dir: A string path to the overlay directory to apply. 94*105f6285SAndroid Build Coastguard Worker intermediate_work_dir: A string path to the intermediate work directory used as the 95*105f6285SAndroid Build Coastguard Worker base for constructing the overlay filesystem. 96*105f6285SAndroid Build Coastguard Worker skip_subdirs: A set of string paths to skip from overlaying. 97*105f6285SAndroid Build Coastguard Worker allowed_projects: If not None, any .git project path not in this list 98*105f6285SAndroid Build Coastguard Worker is excluded from overlaying. 99*105f6285SAndroid Build Coastguard Worker destination_dir: A string with the path to the source with the overlays 100*105f6285SAndroid Build Coastguard Worker applied to it. 101*105f6285SAndroid Build Coastguard Worker allowed_read_write: A function returns true if the path input should 102*105f6285SAndroid Build Coastguard Worker be allowed read/write access. 103*105f6285SAndroid Build Coastguard Worker contains_read_write: A function returns true if the path input contains 104*105f6285SAndroid Build Coastguard Worker a sub-path that should be allowed read/write access. 105*105f6285SAndroid Build Coastguard Worker is_replacement_allowed: A function returns true if the path can replace a 106*105f6285SAndroid Build Coastguard Worker subsequent path. 107*105f6285SAndroid Build Coastguard Worker """ 108*105f6285SAndroid Build Coastguard Worker # Traverse the overlay directory twice 109*105f6285SAndroid Build Coastguard Worker # The first pass only process git projects 110*105f6285SAndroid Build Coastguard Worker # The second time process all other files that are not in git projects 111*105f6285SAndroid Build Coastguard Worker 112*105f6285SAndroid Build Coastguard Worker # We need to process all git projects first because 113*105f6285SAndroid Build Coastguard Worker # the way we process a non-git directory will depend on if 114*105f6285SAndroid Build Coastguard Worker # it contains a git project in a subdirectory or not. 115*105f6285SAndroid Build Coastguard Worker 116*105f6285SAndroid Build Coastguard Worker dirs_with_git_projects = set('/') 117*105f6285SAndroid Build Coastguard Worker for current_dir_origin, subdirs, files in os.walk(overlay_dir): 118*105f6285SAndroid Build Coastguard Worker 119*105f6285SAndroid Build Coastguard Worker if current_dir_origin in skip_subdirs: 120*105f6285SAndroid Build Coastguard Worker del subdirs[:] 121*105f6285SAndroid Build Coastguard Worker continue 122*105f6285SAndroid Build Coastguard Worker 123*105f6285SAndroid Build Coastguard Worker current_dir_relative = os.path.relpath(current_dir_origin, overlay_dir) 124*105f6285SAndroid Build Coastguard Worker current_dir_destination = os.path.normpath( 125*105f6285SAndroid Build Coastguard Worker os.path.join(destination_dir, current_dir_relative)) 126*105f6285SAndroid Build Coastguard Worker 127*105f6285SAndroid Build Coastguard Worker if '.git' in subdirs or '.git' in files or '.bindmount' in files: 128*105f6285SAndroid Build Coastguard Worker # The current dir is a git project 129*105f6285SAndroid Build Coastguard Worker # so just bind mount it 130*105f6285SAndroid Build Coastguard Worker del subdirs[:] 131*105f6285SAndroid Build Coastguard Worker 132*105f6285SAndroid Build Coastguard Worker if '.bindmount' in files or (not allowed_projects or 133*105f6285SAndroid Build Coastguard Worker os.path.relpath(current_dir_origin, source_dir) in allowed_projects): 134*105f6285SAndroid Build Coastguard Worker self._AddBindMount( 135*105f6285SAndroid Build Coastguard Worker current_dir_origin, current_dir_destination, 136*105f6285SAndroid Build Coastguard Worker False if allowed_read_write(current_dir_origin) else True, 137*105f6285SAndroid Build Coastguard Worker is_replacement_allowed( 138*105f6285SAndroid Build Coastguard Worker os.path.basename(overlay_dir), current_dir_relative)) 139*105f6285SAndroid Build Coastguard Worker 140*105f6285SAndroid Build Coastguard Worker current_dir_ancestor = current_dir_origin 141*105f6285SAndroid Build Coastguard Worker while current_dir_ancestor and current_dir_ancestor not in dirs_with_git_projects: 142*105f6285SAndroid Build Coastguard Worker dirs_with_git_projects.add(current_dir_ancestor) 143*105f6285SAndroid Build Coastguard Worker current_dir_ancestor = os.path.dirname(current_dir_ancestor) 144*105f6285SAndroid Build Coastguard Worker 145*105f6285SAndroid Build Coastguard Worker # Process all other files that are not in git projects 146*105f6285SAndroid Build Coastguard Worker for current_dir_origin, subdirs, files in os.walk(overlay_dir): 147*105f6285SAndroid Build Coastguard Worker 148*105f6285SAndroid Build Coastguard Worker if current_dir_origin in skip_subdirs: 149*105f6285SAndroid Build Coastguard Worker del subdirs[:] 150*105f6285SAndroid Build Coastguard Worker continue 151*105f6285SAndroid Build Coastguard Worker 152*105f6285SAndroid Build Coastguard Worker if '.git' in subdirs or '.git' in files or '.bindmount' in files: 153*105f6285SAndroid Build Coastguard Worker del subdirs[:] 154*105f6285SAndroid Build Coastguard Worker continue 155*105f6285SAndroid Build Coastguard Worker 156*105f6285SAndroid Build Coastguard Worker current_dir_relative = os.path.relpath(current_dir_origin, overlay_dir) 157*105f6285SAndroid Build Coastguard Worker current_dir_destination = os.path.normpath( 158*105f6285SAndroid Build Coastguard Worker os.path.join(destination_dir, current_dir_relative)) 159*105f6285SAndroid Build Coastguard Worker 160*105f6285SAndroid Build Coastguard Worker bindCurrentDir = True 161*105f6285SAndroid Build Coastguard Worker 162*105f6285SAndroid Build Coastguard Worker # Directories with git projects can't be bind mounted 163*105f6285SAndroid Build Coastguard Worker # because git projects are individually mounted 164*105f6285SAndroid Build Coastguard Worker if current_dir_origin in dirs_with_git_projects: 165*105f6285SAndroid Build Coastguard Worker bindCurrentDir = False 166*105f6285SAndroid Build Coastguard Worker 167*105f6285SAndroid Build Coastguard Worker # A directory that contains read-write paths should only 168*105f6285SAndroid Build Coastguard Worker # ever be bind mounted if the directory itself is read-write 169*105f6285SAndroid Build Coastguard Worker if contains_read_write(current_dir_origin) and not allowed_read_write(current_dir_origin): 170*105f6285SAndroid Build Coastguard Worker bindCurrentDir = False 171*105f6285SAndroid Build Coastguard Worker 172*105f6285SAndroid Build Coastguard Worker if bindCurrentDir: 173*105f6285SAndroid Build Coastguard Worker # The current dir can be bind mounted wholesale 174*105f6285SAndroid Build Coastguard Worker del subdirs[:] 175*105f6285SAndroid Build Coastguard Worker if allowed_read_write(current_dir_origin): 176*105f6285SAndroid Build Coastguard Worker self._AddBindMount(current_dir_origin, current_dir_destination, False) 177*105f6285SAndroid Build Coastguard Worker else: 178*105f6285SAndroid Build Coastguard Worker self._AddBindMount(current_dir_origin, current_dir_destination, True) 179*105f6285SAndroid Build Coastguard Worker continue 180*105f6285SAndroid Build Coastguard Worker 181*105f6285SAndroid Build Coastguard Worker # If we've made it this far then we're going to process 182*105f6285SAndroid Build Coastguard Worker # each file and subdir individually 183*105f6285SAndroid Build Coastguard Worker 184*105f6285SAndroid Build Coastguard Worker for subdir in subdirs: 185*105f6285SAndroid Build Coastguard Worker subdir_origin = os.path.join(current_dir_origin, subdir) 186*105f6285SAndroid Build Coastguard Worker # Symbolic links to subdirectories 187*105f6285SAndroid Build Coastguard Worker # have to be copied to the intermediate work directory. 188*105f6285SAndroid Build Coastguard Worker # We can't bind mount them because bind mounts dereference 189*105f6285SAndroid Build Coastguard Worker # symbolic links, and the build system filters out any 190*105f6285SAndroid Build Coastguard Worker # directory symbolic links. 191*105f6285SAndroid Build Coastguard Worker if os.path.islink(subdir_origin): 192*105f6285SAndroid Build Coastguard Worker if subdir_origin not in skip_subdirs: 193*105f6285SAndroid Build Coastguard Worker subdir_destination = os.path.join(intermediate_work_dir, 194*105f6285SAndroid Build Coastguard Worker current_dir_relative, subdir) 195*105f6285SAndroid Build Coastguard Worker self._CopyFile(subdir_origin, subdir_destination) 196*105f6285SAndroid Build Coastguard Worker 197*105f6285SAndroid Build Coastguard Worker # bind each file individually then keep traversing 198*105f6285SAndroid Build Coastguard Worker for file in files: 199*105f6285SAndroid Build Coastguard Worker file_origin = os.path.join(current_dir_origin, file) 200*105f6285SAndroid Build Coastguard Worker file_destination = os.path.join(current_dir_destination, file) 201*105f6285SAndroid Build Coastguard Worker if allowed_read_write(file_origin): 202*105f6285SAndroid Build Coastguard Worker self._AddBindMount(file_origin, file_destination, False) 203*105f6285SAndroid Build Coastguard Worker else: 204*105f6285SAndroid Build Coastguard Worker self._AddBindMount(file_origin, file_destination, True) 205*105f6285SAndroid Build Coastguard Worker 206*105f6285SAndroid Build Coastguard Worker 207*105f6285SAndroid Build Coastguard Worker def _AddArtifactDirectories(self, source_dir, destination_dir, skip_subdirs): 208*105f6285SAndroid Build Coastguard Worker """Add directories that were not synced as workspace source. 209*105f6285SAndroid Build Coastguard Worker 210*105f6285SAndroid Build Coastguard Worker Args: 211*105f6285SAndroid Build Coastguard Worker source_dir: A string with the path to the Android platform source. 212*105f6285SAndroid Build Coastguard Worker destination_dir: A string with the path to the source where the overlays 213*105f6285SAndroid Build Coastguard Worker will be applied. 214*105f6285SAndroid Build Coastguard Worker skip_subdirs: A set of string paths to be skipped from overlays. 215*105f6285SAndroid Build Coastguard Worker 216*105f6285SAndroid Build Coastguard Worker Returns: 217*105f6285SAndroid Build Coastguard Worker A list of string paths to be skipped from overlaying. 218*105f6285SAndroid Build Coastguard Worker """ 219*105f6285SAndroid Build Coastguard Worker 220*105f6285SAndroid Build Coastguard Worker # Ensure the main out directory exists 221*105f6285SAndroid Build Coastguard Worker main_out_dir = os.path.join(source_dir, 'out') 222*105f6285SAndroid Build Coastguard Worker if not os.path.exists(main_out_dir): 223*105f6285SAndroid Build Coastguard Worker os.makedirs(main_out_dir) 224*105f6285SAndroid Build Coastguard Worker 225*105f6285SAndroid Build Coastguard Worker for subdir in os.listdir(source_dir): 226*105f6285SAndroid Build Coastguard Worker if subdir.startswith('out'): 227*105f6285SAndroid Build Coastguard Worker out_origin = os.path.join(source_dir, subdir) 228*105f6285SAndroid Build Coastguard Worker if out_origin in skip_subdirs: 229*105f6285SAndroid Build Coastguard Worker continue 230*105f6285SAndroid Build Coastguard Worker out_destination = os.path.join(destination_dir, subdir) 231*105f6285SAndroid Build Coastguard Worker self._AddBindMount(out_origin, out_destination, False) 232*105f6285SAndroid Build Coastguard Worker skip_subdirs.add(out_origin) 233*105f6285SAndroid Build Coastguard Worker 234*105f6285SAndroid Build Coastguard Worker repo_origin = os.path.join(source_dir, '.repo') 235*105f6285SAndroid Build Coastguard Worker if os.path.exists(repo_origin): 236*105f6285SAndroid Build Coastguard Worker repo_destination = os.path.normpath( 237*105f6285SAndroid Build Coastguard Worker os.path.join(destination_dir, '.repo')) 238*105f6285SAndroid Build Coastguard Worker self._AddBindMount(repo_origin, repo_destination, True) 239*105f6285SAndroid Build Coastguard Worker skip_subdirs.add(repo_origin) 240*105f6285SAndroid Build Coastguard Worker 241*105f6285SAndroid Build Coastguard Worker return skip_subdirs 242*105f6285SAndroid Build Coastguard Worker 243*105f6285SAndroid Build Coastguard Worker def _AddOverlays(self, source_dir, overlay_dirs, destination_dir, 244*105f6285SAndroid Build Coastguard Worker skip_subdirs, allowed_projects, allowed_read_write, 245*105f6285SAndroid Build Coastguard Worker contains_read_write, is_replacement_allowed): 246*105f6285SAndroid Build Coastguard Worker """Add the selected overlay directories. 247*105f6285SAndroid Build Coastguard Worker 248*105f6285SAndroid Build Coastguard Worker Args: 249*105f6285SAndroid Build Coastguard Worker source_dir: A string with the path to the Android platform source. 250*105f6285SAndroid Build Coastguard Worker overlay_dirs: A list of strings with the paths to the overlay 251*105f6285SAndroid Build Coastguard Worker directory to apply. 252*105f6285SAndroid Build Coastguard Worker destination_dir: A string with the path to the source where the overlays 253*105f6285SAndroid Build Coastguard Worker will be applied. 254*105f6285SAndroid Build Coastguard Worker skip_subdirs: A set of string paths to be skipped from overlays. 255*105f6285SAndroid Build Coastguard Worker allowed_projects: If not None, any .git project path not in this list 256*105f6285SAndroid Build Coastguard Worker is excluded from overlaying. 257*105f6285SAndroid Build Coastguard Worker allowed_read_write: A function returns true if the path input should 258*105f6285SAndroid Build Coastguard Worker be allowed read/write access. 259*105f6285SAndroid Build Coastguard Worker contains_read_write: A function returns true if the path input contains 260*105f6285SAndroid Build Coastguard Worker a sub-path that should be allowed read/write access. 261*105f6285SAndroid Build Coastguard Worker is_replacement_allowed: A function returns true if the path can replace a 262*105f6285SAndroid Build Coastguard Worker subsequent path. 263*105f6285SAndroid Build Coastguard Worker """ 264*105f6285SAndroid Build Coastguard Worker 265*105f6285SAndroid Build Coastguard Worker # Create empty intermediate workdir 266*105f6285SAndroid Build Coastguard Worker intermediate_work_dir = self._HideDir(destination_dir) 267*105f6285SAndroid Build Coastguard Worker overlay_dirs.append(source_dir) 268*105f6285SAndroid Build Coastguard Worker 269*105f6285SAndroid Build Coastguard Worker skip_subdirs = self._AddArtifactDirectories(source_dir, destination_dir, 270*105f6285SAndroid Build Coastguard Worker skip_subdirs) 271*105f6285SAndroid Build Coastguard Worker 272*105f6285SAndroid Build Coastguard Worker 273*105f6285SAndroid Build Coastguard Worker # Bind mount each overlay directory using a 274*105f6285SAndroid Build Coastguard Worker # depth first traversal algorithm. 275*105f6285SAndroid Build Coastguard Worker # 276*105f6285SAndroid Build Coastguard Worker # The algorithm described works under the condition that the overlaid file 277*105f6285SAndroid Build Coastguard Worker # systems do not have conflicting projects or that the conflict path is 278*105f6285SAndroid Build Coastguard Worker # specifically called-out as a replacement path. 279*105f6285SAndroid Build Coastguard Worker # 280*105f6285SAndroid Build Coastguard Worker # The results of attempting to overlay two git projects on top 281*105f6285SAndroid Build Coastguard Worker # of each other are unpredictable and may push the limits of bind mounts. 282*105f6285SAndroid Build Coastguard Worker 283*105f6285SAndroid Build Coastguard Worker skip_subdirs.add(os.path.join(source_dir, 'overlays')) 284*105f6285SAndroid Build Coastguard Worker 285*105f6285SAndroid Build Coastguard Worker for overlay_dir in overlay_dirs: 286*105f6285SAndroid Build Coastguard Worker self._AddOverlay(source_dir, overlay_dir, intermediate_work_dir, 287*105f6285SAndroid Build Coastguard Worker skip_subdirs, allowed_projects, destination_dir, 288*105f6285SAndroid Build Coastguard Worker allowed_read_write, contains_read_write, 289*105f6285SAndroid Build Coastguard Worker is_replacement_allowed) 290*105f6285SAndroid Build Coastguard Worker 291*105f6285SAndroid Build Coastguard Worker 292*105f6285SAndroid Build Coastguard Worker def _AddBindMount(self, 293*105f6285SAndroid Build Coastguard Worker source_dir, 294*105f6285SAndroid Build Coastguard Worker destination_dir, 295*105f6285SAndroid Build Coastguard Worker readonly=False, 296*105f6285SAndroid Build Coastguard Worker allows_replacement=False): 297*105f6285SAndroid Build Coastguard Worker """Adds a bind mount for the specified directory. 298*105f6285SAndroid Build Coastguard Worker 299*105f6285SAndroid Build Coastguard Worker Args: 300*105f6285SAndroid Build Coastguard Worker source_dir: A string with the path of a source directory to bind. 301*105f6285SAndroid Build Coastguard Worker It must already exist. 302*105f6285SAndroid Build Coastguard Worker destination_dir: A string with the path ofa destination 303*105f6285SAndroid Build Coastguard Worker directory to bind the source into. If it does not exist, 304*105f6285SAndroid Build Coastguard Worker it will be created. 305*105f6285SAndroid Build Coastguard Worker readonly: A flag to indicate whether this path should be bind mounted 306*105f6285SAndroid Build Coastguard Worker with read-only access. 307*105f6285SAndroid Build Coastguard Worker allow_replacement: A flag to indicate whether this path is allowed to replace a 308*105f6285SAndroid Build Coastguard Worker conflicting path. 309*105f6285SAndroid Build Coastguard Worker """ 310*105f6285SAndroid Build Coastguard Worker conflict_path, replacement = self._FindBindMountConflict(destination_dir) 311*105f6285SAndroid Build Coastguard Worker if conflict_path and not replacement: 312*105f6285SAndroid Build Coastguard Worker raise ValueError("Project %s could not be overlaid at %s " 313*105f6285SAndroid Build Coastguard Worker "because it conflicts with %s" 314*105f6285SAndroid Build Coastguard Worker % (source_dir, destination_dir, conflict_path)) 315*105f6285SAndroid Build Coastguard Worker elif not conflict_path: 316*105f6285SAndroid Build Coastguard Worker if len(self._bind_mounts) >= self.MAX_BIND_MOUNTS: 317*105f6285SAndroid Build Coastguard Worker raise ValueError("Bind mount limit of %s reached" % self.MAX_BIND_MOUNTS) 318*105f6285SAndroid Build Coastguard Worker self._bind_mounts[destination_dir] = BindMount( 319*105f6285SAndroid Build Coastguard Worker source_dir=source_dir, 320*105f6285SAndroid Build Coastguard Worker readonly=readonly, 321*105f6285SAndroid Build Coastguard Worker allows_replacement=allows_replacement) 322*105f6285SAndroid Build Coastguard Worker 323*105f6285SAndroid Build Coastguard Worker def _CopyFile(self, source_path, dest_path): 324*105f6285SAndroid Build Coastguard Worker """Copies a file to the specified destination. 325*105f6285SAndroid Build Coastguard Worker 326*105f6285SAndroid Build Coastguard Worker Args: 327*105f6285SAndroid Build Coastguard Worker source_path: A string with the path of a source file to copy. It must 328*105f6285SAndroid Build Coastguard Worker exist. 329*105f6285SAndroid Build Coastguard Worker dest_path: A string with the path to copy the file to. It should not 330*105f6285SAndroid Build Coastguard Worker exist. 331*105f6285SAndroid Build Coastguard Worker """ 332*105f6285SAndroid Build Coastguard Worker dest_dir = os.path.dirname(dest_path) 333*105f6285SAndroid Build Coastguard Worker if not os.path.exists(dest_dir): 334*105f6285SAndroid Build Coastguard Worker os.makedirs(dest_dir) 335*105f6285SAndroid Build Coastguard Worker subprocess.check_call(['cp', '--no-dereference', source_path, dest_path]) 336*105f6285SAndroid Build Coastguard Worker 337*105f6285SAndroid Build Coastguard Worker def GetBindMounts(self): 338*105f6285SAndroid Build Coastguard Worker """Enumerates all bind mounts required by this Overlay. 339*105f6285SAndroid Build Coastguard Worker 340*105f6285SAndroid Build Coastguard Worker Returns: 341*105f6285SAndroid Build Coastguard Worker An ordered dict of BindMount objects keyed by destination path string. 342*105f6285SAndroid Build Coastguard Worker The order of the bind mounts does matter, this is why it's an ordered 343*105f6285SAndroid Build Coastguard Worker dict instead of a standard dict. 344*105f6285SAndroid Build Coastguard Worker """ 345*105f6285SAndroid Build Coastguard Worker return self._bind_mounts 346*105f6285SAndroid Build Coastguard Worker 347*105f6285SAndroid Build Coastguard Worker def _GetReadWriteFunction(self, build_config, source_dir): 348*105f6285SAndroid Build Coastguard Worker """Returns a function that tells you how to mount a path. 349*105f6285SAndroid Build Coastguard Worker 350*105f6285SAndroid Build Coastguard Worker Args: 351*105f6285SAndroid Build Coastguard Worker build_config: A config.BuildConfig instance of the build target to be 352*105f6285SAndroid Build Coastguard Worker prepared. 353*105f6285SAndroid Build Coastguard Worker source_dir: A string with the path to the Android platform source. 354*105f6285SAndroid Build Coastguard Worker 355*105f6285SAndroid Build Coastguard Worker Returns: 356*105f6285SAndroid Build Coastguard Worker A function that takes a string path as an input and returns 357*105f6285SAndroid Build Coastguard Worker True if the path should be mounted read-write or False if 358*105f6285SAndroid Build Coastguard Worker the path should be mounted read-only. 359*105f6285SAndroid Build Coastguard Worker """ 360*105f6285SAndroid Build Coastguard Worker 361*105f6285SAndroid Build Coastguard Worker # The read/write allowlist provides paths relative to the source dir. It 362*105f6285SAndroid Build Coastguard Worker # needs to be updated with absolute paths to make lookup possible. 363*105f6285SAndroid Build Coastguard Worker rw_allowlist = {os.path.join(source_dir, p) for p in build_config.allow_readwrite} 364*105f6285SAndroid Build Coastguard Worker 365*105f6285SAndroid Build Coastguard Worker def AllowReadWrite(path): 366*105f6285SAndroid Build Coastguard Worker return build_config.allow_readwrite_all or path in rw_allowlist 367*105f6285SAndroid Build Coastguard Worker 368*105f6285SAndroid Build Coastguard Worker return AllowReadWrite 369*105f6285SAndroid Build Coastguard Worker 370*105f6285SAndroid Build Coastguard Worker def _GetContainsReadWriteFunction(self, build_config, source_dir): 371*105f6285SAndroid Build Coastguard Worker """Returns a function that tells you if a directory contains a read-write dir 372*105f6285SAndroid Build Coastguard Worker 373*105f6285SAndroid Build Coastguard Worker Args: 374*105f6285SAndroid Build Coastguard Worker build_config: A config.BuildConfig instance of the build target to be 375*105f6285SAndroid Build Coastguard Worker prepared. 376*105f6285SAndroid Build Coastguard Worker source_dir: A string with the path to the Android platform source. 377*105f6285SAndroid Build Coastguard Worker 378*105f6285SAndroid Build Coastguard Worker Returns: 379*105f6285SAndroid Build Coastguard Worker A function that takes a string path as an input and returns 380*105f6285SAndroid Build Coastguard Worker True if the path contains a read-write path 381*105f6285SAndroid Build Coastguard Worker """ 382*105f6285SAndroid Build Coastguard Worker 383*105f6285SAndroid Build Coastguard Worker # Get all dirs with allowed read-write 384*105f6285SAndroid Build Coastguard Worker # and all their ancestor directories 385*105f6285SAndroid Build Coastguard Worker contains_rw = set() 386*105f6285SAndroid Build Coastguard Worker for path in build_config.allow_readwrite: 387*105f6285SAndroid Build Coastguard Worker while path not in ["", "/"]: 388*105f6285SAndroid Build Coastguard Worker # The read/write allowlist provides paths relative to the source dir. It 389*105f6285SAndroid Build Coastguard Worker # needs to be updated with absolute paths to make lookup possible. 390*105f6285SAndroid Build Coastguard Worker contains_rw.add(os.path.join(source_dir, path)) 391*105f6285SAndroid Build Coastguard Worker path = os.path.dirname(path) 392*105f6285SAndroid Build Coastguard Worker 393*105f6285SAndroid Build Coastguard Worker def ContainsReadWrite(path): 394*105f6285SAndroid Build Coastguard Worker return build_config.allow_readwrite_all or path in contains_rw 395*105f6285SAndroid Build Coastguard Worker 396*105f6285SAndroid Build Coastguard Worker return ContainsReadWrite 397*105f6285SAndroid Build Coastguard Worker 398*105f6285SAndroid Build Coastguard Worker def _GetAllowedProjects(self, build_config): 399*105f6285SAndroid Build Coastguard Worker """Returns a set of paths that are allowed to contain .git projects. 400*105f6285SAndroid Build Coastguard Worker 401*105f6285SAndroid Build Coastguard Worker Args: 402*105f6285SAndroid Build Coastguard Worker build_config: A config.BuildConfig instance of the build target to be 403*105f6285SAndroid Build Coastguard Worker prepared. 404*105f6285SAndroid Build Coastguard Worker 405*105f6285SAndroid Build Coastguard Worker Returns: 406*105f6285SAndroid Build Coastguard Worker If the target has an allowed projects file: a set of paths. Any .git 407*105f6285SAndroid Build Coastguard Worker project path not in this set should be excluded from overlaying. 408*105f6285SAndroid Build Coastguard Worker Otherwise: None 409*105f6285SAndroid Build Coastguard Worker """ 410*105f6285SAndroid Build Coastguard Worker if not build_config.allowed_projects_file: 411*105f6285SAndroid Build Coastguard Worker return None 412*105f6285SAndroid Build Coastguard Worker allowed_projects = ET.parse(build_config.allowed_projects_file) 413*105f6285SAndroid Build Coastguard Worker paths = set() 414*105f6285SAndroid Build Coastguard Worker for child in allowed_projects.getroot().findall("project"): 415*105f6285SAndroid Build Coastguard Worker paths.add(child.attrib.get("path", child.attrib["name"])) 416*105f6285SAndroid Build Coastguard Worker return paths 417*105f6285SAndroid Build Coastguard Worker 418*105f6285SAndroid Build Coastguard Worker def _IsReplacementAllowedFunction(self, build_config): 419*105f6285SAndroid Build Coastguard Worker """Returns a function to determin if a given path is replaceable. 420*105f6285SAndroid Build Coastguard Worker 421*105f6285SAndroid Build Coastguard Worker Args: 422*105f6285SAndroid Build Coastguard Worker build_config: A config.BuildConfig instance of the build target to be 423*105f6285SAndroid Build Coastguard Worker prepared. 424*105f6285SAndroid Build Coastguard Worker 425*105f6285SAndroid Build Coastguard Worker Returns: 426*105f6285SAndroid Build Coastguard Worker A function that takes an overlay name and string path as input and 427*105f6285SAndroid Build Coastguard Worker returns True if the path is replaceable. 428*105f6285SAndroid Build Coastguard Worker """ 429*105f6285SAndroid Build Coastguard Worker def is_replacement_allowed_func(overlay_name, path): 430*105f6285SAndroid Build Coastguard Worker for overlay in build_config.overlays: 431*105f6285SAndroid Build Coastguard Worker if overlay_name == overlay.name and path in overlay.replacement_paths: 432*105f6285SAndroid Build Coastguard Worker return True 433*105f6285SAndroid Build Coastguard Worker return False 434*105f6285SAndroid Build Coastguard Worker 435*105f6285SAndroid Build Coastguard Worker return is_replacement_allowed_func 436*105f6285SAndroid Build Coastguard Worker 437*105f6285SAndroid Build Coastguard Worker def __init__(self, 438*105f6285SAndroid Build Coastguard Worker build_target, 439*105f6285SAndroid Build Coastguard Worker source_dir, 440*105f6285SAndroid Build Coastguard Worker cfg, 441*105f6285SAndroid Build Coastguard Worker whiteout_list = [], 442*105f6285SAndroid Build Coastguard Worker destination_dir=None, 443*105f6285SAndroid Build Coastguard Worker quiet=False): 444*105f6285SAndroid Build Coastguard Worker """Inits Overlay with the details of what is going to be overlaid. 445*105f6285SAndroid Build Coastguard Worker 446*105f6285SAndroid Build Coastguard Worker Args: 447*105f6285SAndroid Build Coastguard Worker build_target: A string with the name of the build target to be prepared. 448*105f6285SAndroid Build Coastguard Worker source_dir: A string with the path to the Android platform source. 449*105f6285SAndroid Build Coastguard Worker cfg: A config.Config instance. 450*105f6285SAndroid Build Coastguard Worker whiteout_list: A list of directories to hide from the build system. 451*105f6285SAndroid Build Coastguard Worker destination_dir: A string with the path where the overlay filesystem 452*105f6285SAndroid Build Coastguard Worker will be created. If none is provided, the overlay filesystem 453*105f6285SAndroid Build Coastguard Worker will be applied directly on top of source_dir. 454*105f6285SAndroid Build Coastguard Worker quiet: A boolean that, when True, suppresses debug output. 455*105f6285SAndroid Build Coastguard Worker """ 456*105f6285SAndroid Build Coastguard Worker self._quiet = quiet 457*105f6285SAndroid Build Coastguard Worker 458*105f6285SAndroid Build Coastguard Worker if not destination_dir: 459*105f6285SAndroid Build Coastguard Worker destination_dir = source_dir 460*105f6285SAndroid Build Coastguard Worker 461*105f6285SAndroid Build Coastguard Worker self._overlay_dirs = None 462*105f6285SAndroid Build Coastguard Worker # The order of the bind mounts does matter, this is why it's an ordered 463*105f6285SAndroid Build Coastguard Worker # dict instead of a standard dict. 464*105f6285SAndroid Build Coastguard Worker self._bind_mounts = collections.OrderedDict() 465*105f6285SAndroid Build Coastguard Worker 466*105f6285SAndroid Build Coastguard Worker # We will be repeateadly searching for items to skip so a set 467*105f6285SAndroid Build Coastguard Worker # seems appropriate 468*105f6285SAndroid Build Coastguard Worker skip_subdirs = set(whiteout_list) 469*105f6285SAndroid Build Coastguard Worker 470*105f6285SAndroid Build Coastguard Worker build_config = cfg.get_build_config(build_target) 471*105f6285SAndroid Build Coastguard Worker 472*105f6285SAndroid Build Coastguard Worker allowed_read_write = self._GetReadWriteFunction(build_config, source_dir) 473*105f6285SAndroid Build Coastguard Worker contains_read_write = self._GetContainsReadWriteFunction(build_config, source_dir) 474*105f6285SAndroid Build Coastguard Worker allowed_projects = self._GetAllowedProjects(build_config) 475*105f6285SAndroid Build Coastguard Worker is_replacement_allowed = self._IsReplacementAllowedFunction(build_config) 476*105f6285SAndroid Build Coastguard Worker 477*105f6285SAndroid Build Coastguard Worker overlay_dirs = [] 478*105f6285SAndroid Build Coastguard Worker for overlay in build_config.overlays: 479*105f6285SAndroid Build Coastguard Worker overlay_dir = os.path.join(source_dir, 'overlays', overlay.name) 480*105f6285SAndroid Build Coastguard Worker overlay_dirs.append(overlay_dir) 481*105f6285SAndroid Build Coastguard Worker 482*105f6285SAndroid Build Coastguard Worker self._AddOverlays( 483*105f6285SAndroid Build Coastguard Worker source_dir, overlay_dirs, destination_dir, 484*105f6285SAndroid Build Coastguard Worker skip_subdirs, allowed_projects, allowed_read_write, contains_read_write, 485*105f6285SAndroid Build Coastguard Worker is_replacement_allowed) 486*105f6285SAndroid Build Coastguard Worker 487*105f6285SAndroid Build Coastguard Worker # If specified for this target, create a custom filesystem view 488*105f6285SAndroid Build Coastguard Worker for path_relative_from, path_relative_to in build_config.views: 489*105f6285SAndroid Build Coastguard Worker path_from = os.path.join(source_dir, path_relative_from) 490*105f6285SAndroid Build Coastguard Worker if os.path.isfile(path_from) or os.path.isdir(path_from): 491*105f6285SAndroid Build Coastguard Worker path_to = os.path.join(destination_dir, path_relative_to) 492*105f6285SAndroid Build Coastguard Worker if allowed_read_write(path_from): 493*105f6285SAndroid Build Coastguard Worker self._AddBindMount(path_from, path_to, False) 494*105f6285SAndroid Build Coastguard Worker else: 495*105f6285SAndroid Build Coastguard Worker self._AddBindMount(path_from, path_to, True) 496*105f6285SAndroid Build Coastguard Worker else: 497*105f6285SAndroid Build Coastguard Worker raise ValueError("Path '%s' must be a file or directory" % path_from) 498*105f6285SAndroid Build Coastguard Worker 499*105f6285SAndroid Build Coastguard Worker self._overlay_dirs = overlay_dirs 500*105f6285SAndroid Build Coastguard Worker if not self._quiet: 501*105f6285SAndroid Build Coastguard Worker print('Applied overlays ' + ' '.join(self._overlay_dirs)) 502