mirror_path.py   [plain text]


#!/usr/bin/python2.4

# Copyright 2007 Google Inc.
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
# USA.

# """Memoizing, piecemeal mirroring of directory and link structure."""

__author__ = "Nils Klarlund"


import os
import os.path

import cache_basics

class MirrorPath(object):
  """Make a caching structure for copying all parts of the paths that
  method DoPath is called with. This includes replication of symbolic
  links.  But the targets of symbolic links are absolutized: they are
  replaced by the realpath of the original target, whether this target
  was relative or absolute.

  Also, remember all directories that had to be followed to find out what paths
  mean.  This is of particular importance to the '..' operator, which may
  involve temporary excursions into directories that otherwise contain no files
  of relevance to the build. But the directories must still be replicated on the
  server for the semantics of '..' to work.  These are called must_exist_dirs.
  """

  def __init__(self,
	       simple_build_stat,
	       canonical_path,
               realpath_map,
               systemdir_prefix_cache):
    """Constructor.

    Arguments:
      simple_build_stat: object of type SimpleBuildStat
      canonical_path: function of type CanonicalPath
      realpath_map: a CanonicalMapToIndex; see cache_basics.py
      systemdir_prefix_cache: a SystemdirPrefixCache; see cache_basics.py.
    """
    assert isinstance(simple_build_stat, cache_basics.SimpleBuildStat)
    assert isinstance(canonical_path, cache_basics.CanonicalPath)
    # All links encountered so far.
    self.links = []
    # We cache tuples (filepath, current_dir_idx) for which we've already fixed
    # up the symbolic links.
    self.link_stat = set([])
    # Usual abbreviations.
    self.simple_build_stat = simple_build_stat
    self.canonical_path = canonical_path
    self.must_exist_dirs = []
    self.realpath_map = realpath_map
    self.systemdir_prefix_cache = systemdir_prefix_cache

  def Links(self):
    """Return the list of symbolic links created."""
    return self.links

  def MustExistDirs(self):
    return self.must_exist_dirs

  def DoPath(self, filepath, current_dir_idx, root):
    """Mirror the parts of filepath not yet created under root.

    Arguments:
      filepath: a string, which is relative or absolute filename
      current_dir_idx: a directory index
      root: a string denoting an absolute path for an existing directory
    """
    assert isinstance(filepath, str)
    assert isinstance(current_dir_idx, int)
    assert isinstance(root, str)
    assert root[0] == '/' and root[-1] != '/'
    assert os.path.isdir(root), root

    link_stat = self.link_stat
    lookup = self.simple_build_stat.Lookup
    # Working from the end (in the hope that a cache lookup will reveal
    # the futility of further work), make sure that intermediate
    # destinations exist, and replicate symbolic links where necessary.
    while filepath and filepath != '/':
      if (filepath, current_dir_idx) in link_stat:
        # Filepath is already mirrored
        return
      link_stat.add((filepath, current_dir_idx))

      # Process suffix of filepath by
      # - making sure that the mirrored real path of the prefix exists,
      # - and that the suffix if a symbolic link
      # is replicated as a symbolic link.
      assert filepath[-1] != '/', filepath

      # Now identify the potential symbolic link at the end of filepath
      (prefix_filepath, suffix) = os.path.split(filepath)
      # Calculate the real position of the destination of the prefix
      prefix_real = self.canonical_path.Canonicalize(prefix_filepath)

      if prefix_real == '/': prefix_real = ''

      # And, its counterpart under root
      root_prefix_real = root + prefix_real

      # Make sure that the parent, root_prefix_real, is there
      if not lookup(root_prefix_real):
        # We have not been in this real location before.
        if not os.path.isdir(root_prefix_real):
          # Now check that the parent of the link is not under a default system
          # dir.  If it is, then we assume that the parent and indeed the
          # link itself exist on the server as well, and thus, don't need to
          # be mirrored.
          realpath_map = self.realpath_map
          realpath_idx = realpath_map.Index(prefix_real)
          if not self.systemdir_prefix_cache.StartsWithSystemdir(realpath_idx,
                                                                 realpath_map):
            # Not under default system dir.  Mark this directory as one that
            # must always be created on the server.
            self.must_exist_dirs.append(root_prefix_real)
            # Create parent path in mirror directory.
            os.makedirs(root_prefix_real)
          else:
            break
        self.simple_build_stat.cache[root_prefix_real] = True
      assert os.path.isdir(root_prefix_real)
      # Create the mirrored symbolic link if applicable.
      if os.path.islink(filepath):
        link_name = root_prefix_real + '/' + suffix
        if not os.path.exists(link_name):
          os.symlink(self.canonical_path.Canonicalize(filepath),
                     link_name)
          self.links.append(link_name)
      filepath = prefix_filepath