xref: /aosp_15_r20/tools/treble/build/sandbox/overlay.py (revision 105f628577ac4ba0e277a494fbb614ed8c12a994)
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