summaryrefslogtreecommitdiffstats
path: root/native_client_sdk/src/build_tools/path_set.py
blob: 24e9957315a490cf2046fcb918ff87f30144c775 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
# Copyright (c) 2012 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

"""Class that contains paths classified into four types."""

import os


class Error(Exception):
  pass


class PathSet(object):
  '''Container for paths classified into four types.

  Paths in this container are broken into four separate collections, each
  accessible as a property (see the "Attributes" section, below).

  Attributes:
    files: A set of plain files, keys are platform-specific normalized path
        names.  Might be empty.
    dirs: A set of directories, keys are platform-specific normalized path
        names.  Might be empty.
    symlinks: A dictionary of symbolic links - these are not followed.  Keys
        are platform-specific normalized path names.  The value of each key is
        the source file of the link (also a platform-specific normalized path).
        Might be empty.
    links: A dictionary of hard links.  Keys are platform-specific normalized
        path names.  The value of each key is the source file of the link (also
        a platform-specific normalized path).  Might be empty.
  '''

  def __init__(self):
    self.Reset()

  def __ior__(self, other):
    '''Override |= to merge |other| into this path set.'''
    (self._files, self._dirs,
     self._symlinks, self._links) = self._MergeWithPathSet(other)
    return self

  def __or__(self, other):
    '''Override | to produce the merger of |self| and |other|.'''
    merged_path_set = PathSet()
    (merged_path_set.files,
     merged_path_set.dirs,
     merged_path_set.symlinks,
     merged_path_set.links) = self._MergeWithPathSet(other)
    return merged_path_set

  def _MergeWithPathSet(self, path_set):
    '''Merge this path set with |path_set|.

    Forms the union of the |_files| and |_dirs| sets, then merges the
    |_symlinks| and |_links| dicts.  The dictionaries are merged in such that
    keys are not duplicated: the values of the keys in |path_set| take
    precedence in the returned dictionaries.

    Any keys in either the symlink or links dictionaries that also exist in
    either of the files or dicts sets are removed from the latter, meaning that
    symlinks or links which overlap file or directory entries take precedence.

    Args:
      path_set: The other path set.  Must be an object with four properties:
          files (a set), dirs (a set), symlinks (a dict), links (a dict).
    Returns:
      A four-tuple (files, dirs, symlinks, links) which is the result of merging
      the two PathSets.
    '''
    def DiscardOverlappingLinks(links_dict):
      '''Discard all overlapping keys from files and dirs.'''
      for link in links_dict:
        self._files.discard(link)
        self._dirs.discard(link)

    DiscardOverlappingLinks(path_set.symlinks)
    DiscardOverlappingLinks(path_set.links)
    return (self._files | path_set.files,
            self._dirs | path_set.dirs,
            dict(self._symlinks.items() + path_set.symlinks.items()),
            dict(self._links.items() + path_set.links.items()))

  @property
  def files(self):
    '''The set of plain files.'''
    return self._files

  @files.setter
  def files(self, new_file_set):
    if isinstance(new_file_set, set):
      self._files = new_file_set
    else:
      raise Error('files property must be a set')

  @property
  def dirs(self):
    '''The set of directories.'''
    return self._dirs

  @dirs.setter
  def dirs(self, new_dir_set):
    if isinstance(new_dir_set, set):
      self._dirs = new_dir_set
    else:
      raise Error('dirs property must be a set')

  @property
  def symlinks(self):
    '''The dictionary of symbolic links.'''
    return self._symlinks

  @symlinks.setter
  def symlinks(self, new_symlinks_dict):
    if isinstance(new_symlinks_dict, dict):
      self._symlinks = new_symlinks_dict
    else:
      raise Error('symlinks property must be a dict')

  @property
  def links(self):
    '''The dictionary of hard links.'''
    return self._links

  @links.setter
  def links(self, new_links_dict):
    if isinstance(new_links_dict, dict):
      self._links = new_links_dict
    else:
      raise Error('links property must be a dict')

  def Reset(self):
    '''Reset all attributes to empty containers.'''
    self._files = set()
    self._dirs = set()
    self._symlinks = dict()
    self._links = dict()

  def PrependPath(self, path_prefix):
    '''Prepend paths in all collections with |path_prefix|.

    All the keys in all the colletions get prepended with |path_prefix|.  The
    resulting path is an os-specific path.

    Args:
      path_prefix: The path to prepend to all collection keys.
    '''
    prepend_path = lambda p: os.path.join(path_prefix, p)
    def PrependToLinkDict(link_dict):
      return dict([prepend_path(p), link] for p, link in link_dict.items())

    self._files = set(map(prepend_path, self._files))
    self._dirs = set(map(prepend_path, self._dirs))
    self._symlinks = PrependToLinkDict(self._symlinks)
    self._links = PrependToLinkDict(self._links)