예제 #1
0
  def __init__(self, cls, filename, lineno, pos, children=None):
    # Initialize with no starting or ending Version
    IDLRelease.__init__(self, None, None)

    self.cls = cls
    self.lineno = lineno
    self.pos = pos
    self.filename = filename
    self.filenode = None
    self.hashes = {}
    self.deps = {}
    self.errors = 0
    self.namespace = None
    self.typelist = None
    self.parent = None
    self.property_node = IDLPropertyNode()

    # A list of unique releases for this node
    self.releases = None

    # A map from any release, to the first unique release
    self.first_release = None

    # self.children is a list of children ordered as defined
    self.children = []
    # Process the passed in list of children, placing ExtAttributes into the
    # property dictionary, and nodes into the local child list in order.  In
    # addition, add nodes to the namespace if the class is in the NamedSet.
    if not children: children = []
    for child in children:
      if child.cls == 'ExtAttribute':
        self.SetProperty(child.name, child.value)
      else:
        self.AddChild(child)
예제 #2
0
class IDLNode(IDLRelease):

    # Set of object IDLNode types which have a name and belong in the namespace.
    NamedSet = set([
        'Enum', 'EnumItem', 'File', 'Function', 'Interface', 'Member', 'Param',
        'Struct', 'Type', 'Typedef'
    ])

    show_versions = False

    def __init__(self, cls, filename, lineno, pos, children=None):
        # Initialize with no starting or ending Version
        IDLRelease.__init__(self, None, None)

        self.cls = cls
        self.lineno = lineno
        self.pos = pos
        self.filename = filename
        self.hashes = {}
        self.deps = {}
        self.errors = 0
        self.namespace = None
        self.typelist = None
        self.parent = None
        self.property_node = IDLPropertyNode()
        # self.children is a list of children ordered as defined
        self.children = []
        # Process the passed in list of children, placing ExtAttributes into the
        # property dictionary, and nodes into the local child list in order.  In
        # addition, add nodes to the namespace if the class is in the NamedSet.
        if not children: children = []
        for child in children:
            if child.cls == 'ExtAttribute':
                self.SetProperty(child.name, child.value)
            else:
                self.AddChild(child)

#
# String related functions
#
#

# Return a string representation of this node

    def __str__(self):
        name = self.GetName()
        ver = IDLRelease.__str__(self)
        if name is None: name = ''
        if not IDLNode.show_versions: ver = ''
        return '%s(%s%s)' % (self.cls, name, ver)

    # Return file and line number for where node was defined
    def Location(self):
        return '%s(%d)' % (self.filename, self.lineno)

    # Log an error for this object
    def Error(self, msg):
        self.errors += 1
        ErrOut.LogLine(self.filename, self.lineno, 0,
                       ' %s %s' % (str(self), msg))
        if self.lineno == 46: raise Exception("huh?")

    # Log a warning for this object
    def Warning(self, msg):
        WarnOut.LogLine(self.filename, self.lineno, 0,
                        ' %s %s' % (str(self), msg))

    def GetName(self):
        return self.GetProperty('NAME')

    def GetNameVersion(self):
        name = self.GetProperty('NAME', default='')
        ver = IDLRelease.__str__(self)
        return '%s%s' % (name, ver)

    # Dump this object and its children
    def Dump(self, depth=0, comments=False, out=sys.stdout):
        if self.cls in ['Comment', 'Copyright']:
            is_comment = True
        else:
            is_comment = False

        # Skip this node if it's a comment, and we are not printing comments
        if not comments and is_comment: return

        tab = ''.rjust(depth * 2)

        if is_comment:
            out.write('%sComment\n' % tab)
            for line in self.GetName().split('\n'):
                out.write('%s  "%s"\n' % (tab, line))
        else:
            out.write('%s%s\n' % (tab, self))
        properties = self.property_node.GetPropertyList()
        if properties:
            out.write('%s  Properties\n' % tab)
            for p in properties:
                if is_comment and p == 'NAME':
                    # Skip printing the name for comments, since we printed above already
                    continue
                out.write('%s    %s : %s\n' % (tab, p, self.GetProperty(p)))
        for child in self.children:
            child.Dump(depth + 1, comments=comments, out=out)

#
# Search related functions
#

# Check if node is of a given type

    def IsA(self, *typelist):
        if self.cls in typelist: return True
        return False

    # Get a list of objects for this key
    def GetListOf(self, *keys):
        out = []
        for child in self.children:
            if child.cls in keys: out.append(child)
        return out

    def GetOneOf(self, *keys):
        out = self.GetListOf(*keys)
        if out: return out[0]
        return None

    def SetParent(self, parent):
        self.property_node.AddParent(parent)
        self.parent = parent

    def AddChild(self, node):
        node.SetParent(self)
        self.children.append(node)

    # Get a list of all children
    def GetChildren(self):
        return self.children

    # Get a list of all children of a given version
    def GetChildrenVersion(self, version):
        out = []
        for child in self.children:
            if child.IsVersion(version): out.append(child)
        return out

    # Get a list of all children in a given range
    def GetChildrenRange(self, vmin, vmax):
        out = []
        for child in self.children:
            if child.IsRange(vmin, vmax): out.append(child)
        return out

    def FindVersion(self, name, version):
        node = self.namespace.FindNode(name, version)
        if not node and self.parent:
            node = self.parent.FindVersion(name, version)
        return node

    def FindRange(self, name, vmin, vmax):
        nodes = self.namespace.FindNodes(name, vmin, vmax)
        if not nodes and self.parent:
            nodes = self.parent.FindVersion(name, vmin, vmax)
        return nodes

    def GetType(self, release):
        if not self.typelist: return None
        return self.typelist.FindRelease(release)

    def GetHash(self, release):
        hashval = self.hashes.get(release, None)
        if hashval is None:
            hashval = hashlib.sha1()
            hashval.update(self.cls)
            for key in self.property_node.GetPropertyList():
                val = self.GetProperty(key)
                hashval.update('%s=%s' % (key, str(val)))
            typeref = self.GetType(release)
            if typeref:
                hashval.update(typeref.GetHash(release))
            for child in self.GetChildren():
                if child.IsA('Copyright', 'Comment', 'Label'): continue
                if not child.IsRelease(release):
                    continue
                hashval.update(child.GetHash(release))
            self.hashes[release] = hashval
        return hashval.hexdigest()

    def GetDeps(self, release):
        deps = self.deps.get(release, None)
        if deps is None:
            deps = set([self])
            for child in self.GetChildren():
                deps |= child.GetDeps(release)
            typeref = self.GetType(release)
            if typeref: deps |= typeref.GetDeps(release)
            self.deps[release] = deps
        return deps

    def GetVersion(self, release):
        filenode = self.GetProperty('FILE')
        if not filenode:
            return None
        return filenode.release_map.GetVersion(release)

    def GetRelease(self, version):
        filenode = self.GetProperty('FILE')
        if not filenode:
            return None
        return filenode.release_map.GetRelease(version)

    def GetUniqueReleases(self, releases):
        # Given a list of global release, return a subset of releases
        # for this object that change.
        last_hash = None
        builds = []
        filenode = self.GetProperty('FILE')
        file_releases = filenode.release_map.GetReleases()

        # Generate a set of unique releases for this object based on versions
        # available in this file's release labels.
        for rel in file_releases:
            # Check if this object is valid for the release in question.
            if not self.IsRelease(rel): continue
            # Only add it if the hash is different.
            cur_hash = self.GetHash(rel)
            if last_hash != cur_hash:
                builds.append(rel)
            last_hash = cur_hash

        # Remap the requested releases to releases in the unique build set to
        # use first available release names and remove duplicates.
        # UNIQUE VERSION: 'M13', 'M14', 'M17'
        # REQUESTED RANGE: 'M15', 'M16', 'M17', 'M18'
        # REMAP RESULT:  'M14', 'M17'
        out_list = []
        build_len = len(builds)
        build_index = 0
        rel_len = len(releases)
        rel_index = 0

        while build_index < build_len and rel_index < rel_len:
            while rel_index < rel_len and releases[rel_index] < builds[
                    build_index]:
                rel_index = rel_index + 1

            # If we've reached the end of the request list, we must be done
            if rel_index == rel_len:
                break

            # Check this current request
            cur = releases[rel_index]
            while build_index < build_len and cur >= builds[build_index]:
                build_index = build_index + 1

            out_list.append(builds[build_index - 1])
            rel_index = rel_index + 1
        return out_list

    def SetProperty(self, name, val):
        self.property_node.SetProperty(name, val)

    def GetProperty(self, name, default=None):
        return self.property_node.GetProperty(name, default)

    def Traverse(self, data, func):
        func(self, data)
        for child in self.children:
            child.Traverse(data, func)
예제 #3
0
class IDLNode(IDLRelease):

  # Set of object IDLNode types which have a name and belong in the namespace.
  NamedSet = set(['Enum', 'EnumItem', 'File', 'Function', 'Interface',
                  'Member', 'Param', 'Struct', 'Type', 'Typedef'])

  show_versions = False
  def __init__(self, cls, filename, lineno, pos, children=None):
    # Initialize with no starting or ending Version
    IDLRelease.__init__(self, None, None)

    self.cls = cls
    self.lineno = lineno
    self.pos = pos
    self.filename = filename
    self.filenode = None
    self.hashes = {}
    self.deps = {}
    self.errors = 0
    self.namespace = None
    self.typelist = None
    self.parent = None
    self.property_node = IDLPropertyNode()

    # A list of unique releases for this node
    self.releases = None

    # A map from any release, to the first unique release
    self.first_release = None

    # self.children is a list of children ordered as defined
    self.children = []
    # Process the passed in list of children, placing ExtAttributes into the
    # property dictionary, and nodes into the local child list in order.  In
    # addition, add nodes to the namespace if the class is in the NamedSet.
    if not children: children = []
    for child in children:
      if child.cls == 'ExtAttribute':
        self.SetProperty(child.name, child.value)
      else:
        self.AddChild(child)

#
# String related functions
#
#

  # Return a string representation of this node
  def __str__(self):
    name = self.GetName()
    ver = IDLRelease.__str__(self)
    if name is None: name = ''
    if not IDLNode.show_versions: ver = ''
    return '%s(%s%s)' % (self.cls, name, ver)

  # Return file and line number for where node was defined
  def Location(self):
    return '%s(%d)' % (self.filename, self.lineno)

  # Log an error for this object
  def Error(self, msg):
    self.errors += 1
    ErrOut.LogLine(self.filename, self.lineno, 0, ' %s %s' %
                   (str(self), msg))
    if self.filenode:
      errcnt = self.filenode.GetProperty('ERRORS', 0)
      self.filenode.SetProperty('ERRORS', errcnt + 1)

  # Log a warning for this object
  def Warning(self, msg):
    WarnOut.LogLine(self.filename, self.lineno, 0, ' %s %s' %
                    (str(self), msg))

  def GetName(self):
    return self.GetProperty('NAME')

  def GetNameVersion(self):
    name = self.GetProperty('NAME', default='')
    ver = IDLRelease.__str__(self)
    return '%s%s' % (name, ver)

  # Dump this object and its children
  def Dump(self, depth=0, comments=False, out=sys.stdout):
    if self.cls in ['Comment', 'Copyright']:
      is_comment = True
    else:
      is_comment = False

    # Skip this node if it's a comment, and we are not printing comments
    if not comments and is_comment: return

    tab = ''.rjust(depth * 2)
    if is_comment:
      out.write('%sComment\n' % tab)
      for line in self.GetName().split('\n'):
        out.write('%s  "%s"\n' % (tab, line))
    else:
      ver = IDLRelease.__str__(self)
      if self.releases:
        release_list = ': ' + ' '.join(self.releases)
      else:
        release_list = ': undefined'
      out.write('%s%s%s%s\n' % (tab, self, ver, release_list))
    if self.typelist:
      out.write('%s  Typelist: %s\n' % (tab, self.typelist.GetReleases()[0]))
    properties = self.property_node.GetPropertyList()
    if properties:
      out.write('%s  Properties\n' % tab)
      for p in properties:
        if is_comment and p == 'NAME':
          # Skip printing the name for comments, since we printed above already
          continue
        out.write('%s    %s : %s\n' % (tab, p, self.GetProperty(p)))
    for child in self.children:
      child.Dump(depth+1, comments=comments, out=out)

#
# Search related functions
#
  # Check if node is of a given type
  def IsA(self, *typelist):
    if self.cls in typelist: return True
    return False

  # Get a list of objects for this key
  def GetListOf(self, *keys):
    out = []
    for child in self.children:
      if child.cls in keys: out.append(child)
    return out

  def GetOneOf(self, *keys):
    out = self.GetListOf(*keys)
    if out: return out[0]
    return None

  def SetParent(self, parent):
    self.property_node.AddParent(parent)
    self.parent = parent

  def AddChild(self, node):
    node.SetParent(self)
    self.children.append(node)

  # Get a list of all children
  def GetChildren(self):
    return self.children

  # Get a list of all children of a given version
  def GetChildrenVersion(self, version):
    out = []
    for child in self.children:
      if child.IsVersion(version): out.append(child)
    return out

  # Get a list of all children in a given range
  def GetChildrenRange(self, vmin, vmax):
    out = []
    for child in self.children:
      if child.IsRange(vmin, vmax): out.append(child)
    return out

  def FindVersion(self, name, version):
    node = self.namespace.FindNode(name, version)
    if not node and self.parent:
      node = self.parent.FindVersion(name, version)
    return node

  def FindRange(self, name, vmin, vmax):
    nodes = self.namespace.FindNodes(name, vmin, vmax)
    if not nodes and self.parent:
      nodes = self.parent.FindVersion(name, vmin, vmax)
    return nodes

  def GetType(self, release):
    if not self.typelist: return None
    return self.typelist.FindRelease(release)

  def GetHash(self, release):
    hashval = self.hashes.get(release, None)
    if hashval is None:
      hashval = hashlib.sha1()
      hashval.update(self.cls)
      for key in self.property_node.GetPropertyList():
        val = self.GetProperty(key)
        hashval.update('%s=%s' % (key, str(val)))
      typeref = self.GetType(release)
      if typeref:
        hashval.update(typeref.GetHash(release))
      for child in self.GetChildren():
        if child.IsA('Copyright', 'Comment', 'Label'): continue
        if not child.IsRelease(release):
          continue
        hashval.update( child.GetHash(release) )
      self.hashes[release] = hashval
    return hashval.hexdigest()

  def GetDeps(self, release, visited=None):
    visited = visited or set()

    # If this release is not valid for this object, then done.
    if not self.IsRelease(release) or self.IsA('Comment', 'Copyright'):
      return set([])

    # If we have cached the info for this release, return the cached value
    deps = self.deps.get(release, None)
    if deps is not None:
      return deps

    # If we are already visited, then return
    if self in visited:
      return set([self])

    # Otherwise, build the dependency list
    visited |= set([self])
    deps = set([self])

    # Get child deps
    for child in self.GetChildren():
      deps |= child.GetDeps(release, visited)
      visited |= set(deps)

    # Get type deps
    typeref = self.GetType(release)
    if typeref:
      deps |= typeref.GetDeps(release, visited)

    self.deps[release] = deps
    return deps

  def GetVersion(self, release):
    filenode = self.GetProperty('FILE')
    if not filenode:
      return None
    return filenode.release_map.GetVersion(release)

  def GetUniqueReleases(self, releases):
    """Return the unique set of first releases corresponding to input

    Since we are returning the corresponding 'first' version for a
    release, we may return a release version prior to the one in the list."""
    my_min, my_max = self.GetMinMax(releases)
    if my_min > releases[-1] or my_max < releases[0]:
      return []

    out = set()
    for rel in releases:
      remapped = self.first_release[rel]
      if not remapped: continue
      out |= set([remapped])
    out = sorted(out)
    return out


  def GetRelease(self, version):
    filenode = self.GetProperty('FILE')
    if not filenode:
      return None
    return filenode.release_map.GetRelease(version)

  def _GetReleases(self, releases):
    if not self.releases:
      my_min, my_max = self.GetMinMax(releases)
      my_releases = [my_min]
      if my_max != releases[-1]:
        my_releases.append(my_max)
      my_releases = set(my_releases)
      for child in self.GetChildren():
        if child.IsA('Copyright', 'Comment', 'Label'):
          continue
        my_releases |= child.GetReleases(releases)
      self.releases = my_releases
    return self.releases


  def _GetReleaseList(self, releases, visited=set()):
    if not self.releases:
      # If we are unversionable, then return first available release
      if self.IsA('Comment', 'Copyright', 'Label'):
        self.releases = []
        return self.releases

      # Generate the first and if deprecated within this subset, the
      # last release for this node
      my_min, my_max = self.GetMinMax(releases)

      if my_max != releases[-1]:
        my_releases = set([my_min, my_max])
      else:
        my_releases = set([my_min])

      # Break cycle if we reference ourselves
      if self in visited:
        return [my_min]

      visited |= set([self])

      # Files inherit all thier releases from items in the file
      if self.IsA('AST', 'File'):
        my_releases = set()

      # Visit all children
      child_releases = set()
      for child in self.children:
        child_releases |= set(child._GetReleaseList(releases, visited))
        visited |= set(child_releases)

      # Visit my type
      type_releases = set()
      if self.typelist:
        type_list = self.typelist.GetReleases()
        for typenode in type_list:
          type_releases |= set(typenode._GetReleaseList(releases, visited))
          visited |= set(type_releases)

        type_release_list = sorted(type_releases)
        if my_min < type_release_list[0]:
          type_node = type_list[0]
          self.Error('requires %s in %s which is undefined at %s.' % (
              type_node, type_node.filename, my_min))

      for rel in child_releases:
        if rel >= my_min and rel <= my_max:
          my_releases |= set([rel])

      self.releases = sorted(my_releases)

    return self.releases

  def GetReleaseList(self):
    return self.releases

  def BuildReleaseMap(self, releases):
    unique_list = self._GetReleaseList(releases)
    my_min, my_max = self.GetMinMax(releases)

    self.first_release = {}
    last_rel = None
    for rel in releases:
      if rel in unique_list:
        last_rel = rel
      self.first_release[rel] = last_rel
      if rel == my_max:
        last_rel = None

  def SetProperty(self, name, val):
    self.property_node.SetProperty(name, val)

  def GetProperty(self, name, default=None):
    return self.property_node.GetProperty(name, default)

  def Traverse(self, data, func):
    func(self, data)
    for child in self.children:
      child.Traverse(data, func)
예제 #4
0
class IDLNode(IDLRelease):

    # Set of object IDLNode types which have a name and belong in the namespace.
    NamedSet = set([
        'Enum', 'EnumItem', 'File', 'Function', 'Interface', 'Member', 'Param',
        'Struct', 'Type', 'Typedef'
    ])

    def __init__(self, cls, filename, lineno, pos, children=None):
        # Initialize with no starting or ending Version
        IDLRelease.__init__(self, None, None)

        self.cls = cls
        self.lineno = lineno
        self.pos = pos
        self._filename = filename
        self._deps = {}
        self.errors = 0
        self.namespace = None
        self.typelist = None
        self.parent = None
        self._property_node = IDLPropertyNode()
        self._unique_releases = None

        # A list of unique releases for this node
        self.releases = None

        # A map from any release, to the first unique release
        self.first_release = None

        # self._children is a list of children ordered as defined
        self._children = []
        # Process the passed in list of children, placing ExtAttributes into the
        # property dictionary, and nodes into the local child list in order.  In
        # addition, add nodes to the namespace if the class is in the NamedSet.
        if children:
            for child in children:
                if child.cls == 'ExtAttribute':
                    self.SetProperty(child.name, child.value)
                else:
                    self.AddChild(child)

    def __str__(self):
        name = self.GetName()
        if name is None:
            name = ''
        return '%s(%s)' % (self.cls, name)

    def Location(self):
        """Return a file and line number for where this node was defined."""
        return '%s(%d)' % (self._filename, self.lineno)

    def Error(self, msg):
        """Log an error for this object."""
        self.errors += 1
        ErrOut.LogLine(self._filename, self.lineno, 0,
                       ' %s %s' % (str(self), msg))
        filenode = self.GetProperty('FILE')
        if filenode:
            errcnt = filenode.GetProperty('ERRORS')
            if not errcnt:
                errcnt = 0
            filenode.SetProperty('ERRORS', errcnt + 1)

    def Warning(self, msg):
        """Log a warning for this object."""
        WarnOut.LogLine(self._filename, self.lineno, 0,
                        ' %s %s' % (str(self), msg))

    def GetName(self):
        return self.GetProperty('NAME')

    def Dump(self, depth=0, comments=False, out=sys.stdout):
        """Dump this object and its children"""
        if self.cls in ['Comment', 'Copyright']:
            is_comment = True
        else:
            is_comment = False

        # Skip this node if it's a comment, and we are not printing comments
        if not comments and is_comment:
            return

        tab = ''.rjust(depth * 2)
        if is_comment:
            out.write('%sComment\n' % tab)
            for line in self.GetName().split('\n'):
                out.write('%s  "%s"\n' % (tab, line))
        else:
            ver = IDLRelease.__str__(self)
            if self.releases:
                release_list = ': ' + ' '.join(self.releases)
            else:
                release_list = ': undefined'
            out.write('%s%s%s%s\n' % (tab, self, ver, release_list))
        if self.typelist:
            out.write('%s  Typelist: %s\n' %
                      (tab, self.typelist.GetReleases()[0]))
        properties = self._property_node.GetPropertyList()
        if properties:
            out.write('%s  Properties\n' % tab)
            for p in properties:
                if is_comment and p == 'NAME':
                    # Skip printing the name for comments, since we printed above already
                    continue
                out.write('%s    %s : %s\n' % (tab, p, self.GetProperty(p)))
        for child in self._children:
            child.Dump(depth + 1, comments=comments, out=out)

    def IsA(self, *typelist):
        """Check if node is of a given type."""
        return self.cls in typelist

    def GetListOf(self, *keys):
        """Get a list of objects for the given key(s)."""
        out = []
        for child in self._children:
            if child.cls in keys:
                out.append(child)
        return out

    def GetOneOf(self, *keys):
        """Get an object for the given key(s)."""
        out = self.GetListOf(*keys)
        if out:
            return out[0]
        return None

    def SetParent(self, parent):
        self._property_node.AddParent(parent)
        self.parent = parent

    def AddChild(self, node):
        node.SetParent(self)
        self._children.append(node)

    # Get a list of all children
    def GetChildren(self):
        return self._children

    def GetType(self, release):
        if not self.typelist:
            return None
        return self.typelist.FindRelease(release)

    def GetDeps(self, release, visited=None):
        visited = visited or set()

        # If this release is not valid for this object, then done.
        if not self.IsRelease(release) or self.IsA('Comment', 'Copyright'):
            return set([])

        # If we have cached the info for this release, return the cached value
        deps = self._deps.get(release, None)
        if deps is not None:
            return deps

        # If we are already visited, then return
        if self in visited:
            return set([self])

        # Otherwise, build the dependency list
        visited |= set([self])
        deps = set([self])

        # Get child deps
        for child in self.GetChildren():
            deps |= child.GetDeps(release, visited)
            visited |= set(deps)

        # Get type deps
        typeref = self.GetType(release)
        if typeref:
            deps |= typeref.GetDeps(release, visited)

        self._deps[release] = deps
        return deps

    def GetVersion(self, release):
        filenode = self.GetProperty('FILE')
        if not filenode:
            return None
        return filenode.release_map.GetVersion(release)

    def GetUniqueReleases(self, releases):
        """Return the unique set of first releases corresponding to input

    Since we are returning the corresponding 'first' version for a
    release, we may return a release version prior to the one in the list."""
        my_min, my_max = self.GetMinMax(releases)
        if my_min > releases[-1] or my_max < releases[0]:
            return []

        out = set()
        for rel in releases:
            remapped = self.first_release[rel]
            if not remapped:
                continue
            out |= set([remapped])

        # Cache the most recent set of unique_releases
        self._unique_releases = sorted(out)
        return self._unique_releases

    def LastRelease(self, release):
        # Get the most recent release from the most recently generated set of
        # cached unique releases.
        if self._unique_releases and self._unique_releases[-1] > release:
            return False
        return True

    def GetRelease(self, version):
        filenode = self.GetProperty('FILE')
        if not filenode:
            return None
        return filenode.release_map.GetRelease(version)

    def _GetReleaseList(self, releases, visited=None):
        visited = visited or set()
        if not self.releases:
            # If we are unversionable, then return first available release
            if self.IsA('Comment', 'Copyright', 'Label'):
                self.releases = []
                return self.releases

            # Generate the first and if deprecated within this subset, the
            # last release for this node
            my_min, my_max = self.GetMinMax(releases)

            if my_max != releases[-1]:
                my_releases = set([my_min, my_max])
            else:
                my_releases = set([my_min])

            r = self.GetRelease(self.GetProperty('version'))
            if not r in my_releases:
                my_releases |= set([r])

            # Break cycle if we reference ourselves
            if self in visited:
                return [my_min]

            visited |= set([self])

            # Files inherit all their releases from items in the file
            if self.IsA('AST', 'File'):
                my_releases = set()

            # Visit all children
            child_releases = set()

            # Exclude sibling results from parent visited set
            cur_visits = visited

            for child in self._children:
                child_releases |= set(
                    child._GetReleaseList(releases, cur_visits))
                visited |= set(child_releases)

            # Visit my type
            type_releases = set()
            if self.typelist:
                type_list = self.typelist.GetReleases()
                for typenode in type_list:
                    type_releases |= set(
                        typenode._GetReleaseList(releases, cur_visits))

                type_release_list = sorted(type_releases)
                if my_min < type_release_list[0]:
                    type_node = type_list[0]
                    self.Error('requires %s in %s which is undefined at %s.' %
                               (type_node, type_node._filename, my_min))

            for rel in child_releases | type_releases:
                if rel >= my_min and rel <= my_max:
                    my_releases |= set([rel])

            self.releases = sorted(my_releases)
        return self.releases

    def BuildReleaseMap(self, releases):
        unique_list = self._GetReleaseList(releases)
        _, my_max = self.GetMinMax(releases)

        self.first_release = {}
        last_rel = None
        for rel in releases:
            if rel in unique_list:
                last_rel = rel
            self.first_release[rel] = last_rel
            if rel == my_max:
                last_rel = None

    def SetProperty(self, name, val):
        self._property_node.SetProperty(name, val)

    def GetProperty(self, name):
        return self._property_node.GetProperty(name)

    def GetPropertyLocal(self, name):
        return self._property_node.GetPropertyLocal(name)

    def NodeIsDevOnly(self):
        """Returns true iff a node is only in dev channel."""
        return self.GetProperty(
            'dev_version') and not self.GetProperty('version')

    def DevInterfaceMatchesStable(self, release):
        """Returns true if an interface has an equivalent stable version."""
        assert (self.IsA('Interface'))
        for child in self.GetListOf('Member'):
            unique = child.GetUniqueReleases([release])
            if not unique or not child.InReleases([release]):
                continue
            if child.NodeIsDevOnly():
                return False
        return True