manifest.py revision 2690
1516N/A#!/usr/bin/python
39N/A#
39N/A# CDDL HEADER START
39N/A#
39N/A# The contents of this file are subject to the terms of the
39N/A# Common Development and Distribution License (the "License").
39N/A# You may not use this file except in compliance with the License.
39N/A#
39N/A# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
39N/A# or http://www.opensolaris.org/os/licensing.
39N/A# See the License for the specific language governing permissions
39N/A# and limitations under the License.
39N/A#
39N/A# When distributing Covered Code, include this CDDL HEADER in each
39N/A# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
39N/A# If applicable, add the following below this CDDL HEADER, with the
39N/A# fields enclosed by brackets "[]" replaced with your own identifying
39N/A# information: Portions Copyright [yyyy] [name of copyright owner]
39N/A#
39N/A# CDDL HEADER END
39N/A#
926N/A
926N/A#
2608N/A# Copyright (c) 2007, 2012, Oracle and/or its affiliates. All rights reserved.
926N/A#
39N/A
2453N/Afrom collections import namedtuple, defaultdict
342N/Aimport errno
1516N/Aimport hashlib
1636N/Aimport os
1386N/Aimport tempfile
2639N/Afrom itertools import groupby, chain, repeat, izip
2639N/Afrom operator import itemgetter
39N/A
51N/Aimport pkg.actions as actions
2073N/Aimport pkg.client.api_errors as apx
2144N/Aimport pkg.misc as misc
1066N/Aimport pkg.portable as portable
1231N/Aimport pkg.variant as variant
2453N/Aimport pkg.version as version
1352N/A
1890N/Afrom pkg.misc import EmptyDict, EmptyI, expanddirs, PKG_FILE_MODE, PKG_DIR_MODE
296N/Afrom pkg.actions.attribute import AttributeAction
39N/A
2690N/Aclass ManifestDifference(
2690N/A namedtuple("ManifestDifference", "added changed removed")):
2690N/A
2690N/A __slots__ = []
2690N/A
2690N/A __state__desc = tuple([
2690N/A [ ( actions.generic.NSG, actions.generic.NSG ) ],
2690N/A [ ( actions.generic.NSG, actions.generic.NSG ) ],
2690N/A [ ( actions.generic.NSG, actions.generic.NSG ) ],
2690N/A ])
2690N/A
2690N/A __state__commonize = frozenset([
2690N/A actions.generic.NSG,
2690N/A ])
2690N/A
2690N/A @staticmethod
2690N/A def getstate(obj, je_state=None):
2690N/A """Returns the serialized state of this object in a format
2690N/A that that can be easily stored using JSON, pickle, etc."""
2690N/A return misc.json_encode(ManifestDifference.__name__,
2690N/A tuple(obj),
2690N/A ManifestDifference.__state__desc,
2690N/A commonize=ManifestDifference.__state__commonize,
2690N/A je_state=je_state)
2690N/A
2690N/A @staticmethod
2690N/A def fromstate(state, jd_state=None):
2690N/A """Allocate a new object using previously serialized state
2690N/A obtained via getstate()."""
2690N/A
2690N/A # decode serialized state into python objects
2690N/A state = misc.json_decode(ManifestDifference.__name__,
2690N/A state,
2690N/A ManifestDifference.__state__desc,
2690N/A commonize=ManifestDifference.__state__commonize,
2690N/A jd_state=jd_state)
2690N/A
2690N/A return ManifestDifference(*state)
1713N/A
39N/Aclass Manifest(object):
39N/A """A Manifest is the representation of the actions composing a specific
39N/A package version on both the client and the repository. Both purposes
39N/A utilize the same storage format.
39N/A
205N/A The serialized structure of a manifest is an unordered list of actions.
39N/A
205N/A The special action, "set", represents a package attribute.
39N/A
39N/A The reserved attribute, "fmri", represents the package and version
39N/A described by this manifest. It is available as a string via the
39N/A attributes dictionary, and as an FMRI object from the fmri member.
39N/A
39N/A The list of manifest-wide reserved attributes is
39N/A
39N/A base_directory Default base directory, for non-user images.
39N/A fmri Package FMRI.
39N/A isa Package is intended for a list of ISAs.
39N/A platform Package is intended for a list of platforms.
39N/A relocatable Suitable for User Image.
39N/A
39N/A All non-prefixed attributes are reserved to the framework. Third
39N/A parties may prefix their attributes with a reversed domain name, domain
39N/A name, or stock symbol. An example might be
39N/A
39N/A com.example,supported
39N/A
39N/A as an indicator that a specific package version is supported by the
39N/A vendor, example.com.
39N/A
48N/A manifest.null is provided as the null manifest. Differences against the
48N/A null manifest result in the complete set of attributes and actions of
48N/A the non-null manifest, meaning that all operations can be viewed as
48N/A tranitions between the manifest being installed and the manifest already
48N/A present in the image (which may be the null manifest).
59N/A """
48N/A
2073N/A def __init__(self, pfmri=None):
2073N/A self.fmri = pfmri
39N/A
2639N/A self._cache = {}
2639N/A self._facets = None # facets seen in package
2639N/A self._variants = None # variants seen in package
39N/A self.actions = []
567N/A self.actions_bytype = {}
838N/A self.attributes = {} # package-wide attributes
1890N/A self.signatures = EmptyDict
2608N/A self.excludes = EmptyI
39N/A
39N/A def __str__(self):
39N/A r = ""
1431N/A if "pkg.fmri" not in self.attributes and self.fmri != None:
1431N/A r += "set name=pkg.fmri value=%s\n" % self.fmri
39N/A
227N/A for act in sorted(self.actions):
315N/A r += "%s\n" % act
315N/A return r
39N/A
1352N/A def as_lines(self):
1352N/A """A generator function that returns the unsorted manifest
1352N/A contents as lines of text."""
1352N/A
1431N/A if "pkg.fmri" not in self.attributes and self.fmri != None:
1431N/A yield "set name=pkg.fmri value=%s\n" % self.fmri
429N/A
315N/A for act in self.actions:
1352N/A yield "%s\n" % act
429N/A
1352N/A def tostr_unsorted(self):
1352N/A return "".join((l for l in self.as_lines()))
39N/A
926N/A def difference(self, origin, origin_exclude=EmptyI,
926N/A self_exclude=EmptyI):
203N/A """Return three lists of action pairs representing origin and
203N/A destination actions. The first list contains the pairs
203N/A representing additions, the second list contains the pairs
203N/A representing updates, and the third list contains the pairs
1045N/A representing removals. All three lists are in the order in
1045N/A which they should be executed."""
72N/A # XXX Do we need to find some way to assert that the keys are
72N/A # all unique?
59N/A
2054N/A if isinstance(origin, EmptyFactoredManifest):
1045N/A # No origin was provided, so nothing has been changed or
1045N/A # removed; only added. In addition, this doesn't need
1045N/A # to be sorted since the caller likely already does
1045N/A # (such as pkgplan/imageplan).
1713N/A return ManifestDifference(
1045N/A [(None, a) for a in self.gen_actions(self_exclude)],
1045N/A [], [])
1045N/A
2084N/A def hashify(v):
2084N/A """handle key values that may be lists"""
2639N/A if type(v) is not list:
2084N/A return v
2639N/A return frozenset(v)
2084N/A
72N/A sdict = dict(
2084N/A ((a.name, hashify(a.attrs.get(a.key_attr, id(a)))), a)
838N/A for a in self.gen_actions(self_exclude)
72N/A )
72N/A odict = dict(
2084N/A ((a.name, hashify(a.attrs.get(a.key_attr, id(a)))), a)
838N/A for a in origin.gen_actions(origin_exclude)
72N/A )
59N/A
2639N/A sset = set(sdict.iterkeys())
2639N/A oset = set(odict.iterkeys())
59N/A
72N/A added = [(None, sdict[i]) for i in sset - oset]
72N/A removed = [(odict[i], None) for i in oset - sset]
72N/A changed = [
72N/A (odict[i], sdict[i])
72N/A for i in oset & sset
2144N/A if odict[i].different(sdict[i])
72N/A ]
59N/A
72N/A # XXX Do changed actions need to be sorted at all? This is
72N/A # likely to be the largest list, so we might save significant
72N/A # time by not sorting. Should we sort above? Insert into a
72N/A # sorted list?
59N/A
72N/A # singlesort = lambda x: x[0] or x[1]
2639N/A addsort = itemgetter(1)
2639N/A remsort = itemgetter(0)
2639N/A removed.sort(key=remsort, reverse=True)
2639N/A added.sort(key=addsort)
2639N/A changed.sort(key=addsort)
59N/A
1713N/A return ManifestDifference(added, changed, removed)
59N/A
838N/A @staticmethod
2240N/A def comm(compare_m):
838N/A """Like the unix utility comm, except that this function
838N/A takes an arbitrary number of manifests and compares them,
838N/A returning a tuple consisting of each manifest's actions
926N/A that are not the same for all manifests, followed by a
838N/A list of actions that are the same in each manifest."""
838N/A
2240N/A # Must specify at least one manifest.
2240N/A assert compare_m
2284N/A dups = []
2240N/A
838N/A # construct list of dictionaries of actions in each
2240N/A # manifest, indexed by unique key and variant combination
2240N/A m_dicts = []
2240N/A for m in compare_m:
2240N/A m_dict = {}
2240N/A for a in m.gen_actions():
2240N/A # The unique key for each action is based on its
2240N/A # type, key attribute, and unique variants set
2240N/A # on the action.
2240N/A try:
2317N/A key = set(a.attrlist(a.key_attr))
2240N/A key.update(
2240N/A "%s=%s" % (v, a.attrs[v])
2240N/A for v in a.get_varcet_keys()[0]
2240N/A )
2240N/A key = tuple(key)
2240N/A except KeyError:
2240N/A # If there is no key attribute for the
2240N/A # action, then fallback to the object
2240N/A # id for the action as its identifier.
2240N/A key = (id(a),)
2240N/A
2284N/A # catch duplicate actions here...
2284N/A if m_dict.setdefault((a.name, key), a) != a:
2284N/A dups.append((m_dict[(a.name, key)], a))
2284N/A
2240N/A m_dicts.append(m_dict)
2240N/A
2284N/A if dups:
2284N/A raise ManifestError(duplicates=dups)
2284N/A
838N/A # construct list of key sets in each dict
838N/A m_sets = [
838N/A set(m.keys())
838N/A for m in m_dicts
838N/A ]
838N/A
838N/A common_keys = reduce(lambda a, b: a & b, m_sets)
926N/A
838N/A # determine which common_keys have common actions
838N/A for k in common_keys.copy():
838N/A for i in range(len(m_dicts) - 1):
838N/A if m_dicts[i][k].different(
838N/A m_dicts[i + 1][k]):
838N/A common_keys.remove(k)
838N/A break
845N/A return tuple(
845N/A [
926N/A [m_dicts[i][k] for k in m_sets[i] - common_keys]
838N/A for i in range(len(m_dicts))
845N/A ]
845N/A +
845N/A [
838N/A [ m_dicts[0][k] for k in common_keys ]
845N/A ]
845N/A )
838N/A
926N/A def combined_difference(self, origin, ov=EmptyI, sv=EmptyI):
203N/A """Where difference() returns three lists, combined_difference()
315N/A returns a single list of the concatenation of the three."""
838N/A return list(chain(*self.difference(origin, ov, sv)))
203N/A
838N/A def humanized_differences(self, other, ov=EmptyI, sv=EmptyI):
48N/A """Output expects that self is newer than other. Use of sets
48N/A requires that we convert the action objects into some marshalled
48N/A form, otherwise set member identities are derived from the
48N/A object pointers, rather than the contents."""
48N/A
838N/A l = self.difference(other, ov, sv)
203N/A out = ""
48N/A
203N/A for src, dest in chain(*l):
72N/A if not src:
181N/A out += "+ %s\n" % str(dest)
72N/A elif not dest:
181N/A out += "- %s\n" + str(src)
46N/A else:
181N/A out += "%s -> %s\n" % (src, dest)
237N/A return out
46N/A
2453N/A def _gen_dirs_to_str(self):
2453N/A """Generate contents of dircache file containing all dirctories
2453N/A referenced explicitly or implicitly from self.actions. Include
2453N/A variants as values; collapse variants where possible."""
2339N/A
2453N/A def gen_references(a):
2453N/A for d in expanddirs(a.directory_references()):
2453N/A yield d
2453N/A
2453N/A dirs = self._actions_to_dict(gen_references)
2339N/A for d in dirs:
2339N/A for v in dirs[d]:
2339N/A yield "dir path=%s %s\n" % \
2339N/A (d, " ".join("%s=%s" % t \
2339N/A for t in v.iteritems()))
2339N/A
2453N/A def _gen_mediators_to_str(self):
2453N/A """Generate contents of mediatorcache file containing all
2453N/A mediators referenced explicitly or implicitly from self.actions.
2453N/A Include variants as values; collapse variants where possible."""
2453N/A
2453N/A def gen_references(a):
2453N/A if (a.name == "link" or a.name == "hardlink") and \
2453N/A "mediator" in a.attrs:
2453N/A yield (a.attrs.get("mediator"),
2453N/A a.attrs.get("mediator-priority"),
2453N/A a.attrs.get("mediator-version"),
2453N/A a.attrs.get("mediator-implementation"))
2339N/A
2453N/A mediators = self._actions_to_dict(gen_references)
2453N/A for mediation, mvariants in mediators.iteritems():
2453N/A values = {
2453N/A "mediator-priority": mediation[1],
2453N/A "mediator-version": mediation[2],
2453N/A "mediator-implementation": mediation[3],
2453N/A }
2453N/A for mvariant in mvariants:
2453N/A a = "set name=pkg.mediator " \
2453N/A "value=%s %s %s\n".rstrip() % (mediation[0],
2453N/A " ".join((
2453N/A "=".join(t)
2453N/A for t in values.iteritems()
2453N/A if t[1]
2453N/A )),
2453N/A " ".join((
2453N/A "=".join(t)
2453N/A for t in mvariant.iteritems()
2453N/A ))
2453N/A )
2453N/A yield a
2453N/A
2453N/A def _actions_to_dict(self, references):
2453N/A """create dictionary of all actions referenced explicitly or
2453N/A implicitly from self.actions... include variants as values;
2453N/A collapse variants where possible"""
2453N/A
2453N/A refs = {}
2339N/A # build a dictionary containing all directories tagged w/
2339N/A # variants
2339N/A for a in self.actions:
2339N/A v, f = a.get_varcet_keys()
2339N/A variants = dict((name, a.attrs[name]) for name in v + f)
2453N/A for ref in references(a):
2453N/A if ref not in refs:
2453N/A refs[ref] = [variants]
2453N/A elif variants not in refs[ref]:
2453N/A refs[ref].append(variants)
2339N/A
2453N/A # remove any tags if any entries are always delivered (NULL)
2453N/A for ref in refs:
2453N/A if {} in refs[ref]:
2453N/A refs[ref] = [{}]
2339N/A continue
2453N/A # could collapse refs where all variants are present
2453N/A # (the current logic only collapses them if at least
2453N/A # one reference is delivered without a facet or
2453N/A # variant)
2453N/A return refs
2339N/A
2339N/A def get_directories(self, excludes):
2339N/A """ return a list of directories implicitly or
2339N/A explicitly referenced by this object"""
2339N/A
2608N/A if self.excludes == excludes:
2608N/A excludes = EmptyI
2608N/A assert excludes == EmptyI or self.excludes == EmptyI
2339N/A try:
2453N/A alist = self._cache["manifest.dircache"]
2453N/A except KeyError:
2339N/A # generate actions that contain directories
2453N/A alist = self._cache["manifest.dircache"] = [
2639N/A actions.fromstr(s.rstrip())
2540N/A for s in self._gen_dirs_to_str()
2339N/A ]
2339N/A
2339N/A s = set([
2339N/A a.attrs["path"]
2339N/A for a in alist
2639N/A if not excludes or a.include_this(excludes)
2339N/A ])
2339N/A
2339N/A return list(s)
2339N/A
2453N/A def gen_mediators(self, excludes=EmptyI):
2453N/A """A generator function that yields tuples of the form (mediator,
2453N/A mediations) expressing the set of possible mediations for this
2453N/A package, where 'mediations' is a set() of possible mediations for
2453N/A the mediator. Each mediation is a tuple of the form (priority,
2453N/A version, implementation).
2453N/A """
2453N/A
2608N/A if self.excludes == excludes:
2608N/A excludes = EmptyI
2608N/A assert excludes == EmptyI or self.excludes == EmptyI
2453N/A try:
2453N/A alist = self._cache["manifest.mediatorcache"]
2453N/A except KeyError:
2453N/A # generate actions that contain mediators
2453N/A alist = self._cache["manifest.mediatorcache"] = [
2639N/A actions.fromstr(s.rstrip())
2540N/A for s in self._gen_mediators_to_str()
2453N/A ]
2453N/A
2453N/A ret = defaultdict(set)
2453N/A for attrs in (
2453N/A act.attrs
2453N/A for act in alist
2639N/A if not excludes or act.include_this(excludes)):
2453N/A med_ver = attrs.get("mediator-version")
2453N/A if med_ver:
2453N/A try:
2453N/A med_ver = version.Version(med_ver,
2453N/A "5.11")
2453N/A except version.VersionError:
2453N/A # Consider this mediation unavailable
2453N/A # if it can't be parsed for whatever
2453N/A # reason.
2453N/A continue
2453N/A
2453N/A ret[attrs["value"]].add((
2453N/A attrs.get("mediator-priority"),
2453N/A med_ver,
2453N/A attrs.get("mediator-implementation"),
2453N/A ))
2453N/A
2453N/A for m in ret:
2453N/A yield m, ret[m]
2453N/A
838N/A def gen_actions(self, excludes=EmptyI):
838N/A """Generate actions in manifest through ordered callable list"""
2608N/A
2608N/A if self.excludes == excludes:
2608N/A excludes = EmptyI
2608N/A assert excludes == EmptyI or self.excludes == EmptyI
838N/A for a in self.actions:
838N/A for c in excludes:
838N/A if not c(a):
838N/A break
838N/A else:
838N/A yield a
342N/A
926N/A def gen_actions_by_type(self, atype, excludes=EmptyI):
838N/A """Generate actions in the manifest of type "type"
838N/A through ordered callable list"""
2608N/A
2608N/A if self.excludes == excludes:
2608N/A excludes = EmptyI
2608N/A assert excludes == EmptyI or self.excludes == EmptyI
926N/A for a in self.actions_bytype.get(atype, []):
838N/A for c in excludes:
838N/A if not c(a):
838N/A break
838N/A else:
838N/A yield a
926N/A
2453N/A def gen_actions_by_types(self, atypes, excludes=EmptyI):
2453N/A """Generate actions in the manifest of types "atypes"
2453N/A through ordered callable list."""
2608N/A
2453N/A for atype in atypes:
2453N/A for a in self.gen_actions_by_type(atype,
2453N/A excludes=excludes):
2453N/A yield a
2453N/A
926N/A def gen_key_attribute_value_by_type(self, atype, excludes=EmptyI):
615N/A """Generate the value of the key atrribute for each action
615N/A of type "type" in the manifest."""
615N/A
615N/A return (
615N/A a.attrs.get(a.key_attr)
926N/A for a in self.gen_actions_by_type(atype, excludes)
615N/A )
615N/A
838N/A def duplicates(self, excludes=EmptyI):
111N/A """Find actions in the manifest which are duplicates (i.e.,
111N/A represent the same object) but which are not identical (i.e.,
111N/A have all the same attributes)."""
111N/A
111N/A def fun(a):
111N/A """Return a key on which actions can be sorted."""
111N/A return a.name, a.attrs.get(a.key_attr, id(a))
111N/A
113N/A alldups = []
926N/A acts = [a for a in self.gen_actions(excludes)]
838N/A
926N/A for k, g in groupby(sorted(acts, key=fun), fun):
113N/A glist = list(g)
113N/A dups = set()
113N/A for i in range(len(glist) - 1):
113N/A if glist[i].different(glist[i + 1]):
113N/A dups.add(glist[i])
113N/A dups.add(glist[i + 1])
113N/A if dups:
113N/A alldups.append((k, dups))
113N/A return alldups
111N/A
1500N/A def __content_to_actions(self, content):
1432N/A accumulate = ""
1542N/A lineno = 0
1970N/A errors = []
2073N/A
2073N/A if isinstance(content, basestring):
2073N/A # Get an iterable for the string.
2073N/A content = content.splitlines()
2073N/A
2073N/A for l in content:
1542N/A lineno += 1
146N/A l = l.lstrip()
1432N/A if l.endswith("\\"): # allow continuation chars
1432N/A accumulate += l[0:-1] # elide backslash
50N/A continue
1432N/A elif accumulate:
1432N/A l = accumulate + l
1432N/A accumulate = ""
51N/A
1636N/A if not l or l[0] == "#": # ignore blank lines & comments
1432N/A continue
1507N/A
51N/A try:
1500N/A yield actions.fromstr(l)
591N/A except actions.ActionError, e:
1970N/A # Accumulate errors and continue so that as
1970N/A # much of the action data as possible can be
1970N/A # parsed.
591N/A e.fmri = self.fmri
1542N/A e.lineno = lineno
1970N/A errors.append(e)
1970N/A
1970N/A if errors:
2073N/A raise apx.InvalidPackageErrors(errors)
1500N/A
2073N/A def set_content(self, content=None, excludes=EmptyI, pathname=None,
2073N/A signatures=False):
1713N/A """Populate the manifest with actions.
1713N/A
2073N/A 'content' is an optional value containing either the text
2073N/A representation of the manifest or an iterable of
2073N/A action objects.
1713N/A
2608N/A 'excludes' is optional. If provided it must be a length two
2608N/A list with the variants to be excluded as the first element and
2613N/A the facets to be excluded as the second element.
2284N/A
2073N/A 'pathname' is an optional filename containing the location of
2073N/A the manifest content.
1713N/A
2073N/A 'signatures' is an optional boolean value that indicates whether
2073N/A a manifest signature should be generated. This is only possible
2073N/A when 'content' is a string or 'pathname' is provided.
1713N/A """
1713N/A
2073N/A assert content is not None or pathname is not None
2073N/A assert not (content and pathname)
2073N/A
1500N/A self.actions = []
1500N/A self.actions_bytype = {}
2639N/A self._variants = None
2639N/A self._facets = None
1500N/A self.attributes = {}
2613N/A self._cache = {}
51N/A
1500N/A # So we could build up here the type/key_attr dictionaries like
1500N/A # sdict and odict in difference() above, and have that be our
1500N/A # main datastore, rather than the simple list we have now. If
1500N/A # we do that here, we can even assert that the "same" action
1500N/A # can't be in a manifest twice. (The problem of having the same
1500N/A # action more than once in packages that can be installed
1500N/A # together has to be solved somewhere else, though.)
2073N/A if pathname:
2073N/A try:
2073N/A with open(pathname, "rb") as mfile:
2073N/A content = mfile.read()
2073N/A except EnvironmentError, e:
2073N/A raise apx._convert_error(e)
1890N/A if isinstance(content, basestring):
1500N/A if signatures:
2073N/A # Generate manifest signature based upon
2073N/A # input content, but only if signatures
2073N/A # were requested.
1500N/A self.signatures = {
1500N/A "sha-1": self.hash_create(content)
1500N/A }
1500N/A content = self.__content_to_actions(content)
926N/A
1500N/A for action in content:
2026N/A self.add_action(action, excludes)
2608N/A self.excludes = excludes
2608N/A # Make sure that either no excludes were provided or that both
2608N/A # variants and facet excludes were.
2608N/A assert len(self.excludes) in (0, 2)
1500N/A
2026N/A def exclude_content(self, excludes):
2026N/A """Remove any actions from the manifest which should be
2026N/A excluded."""
2026N/A
2073N/A self.set_content(content=self.actions, excludes=excludes)
2026N/A
2026N/A def add_action(self, action, excludes):
1500N/A """Performs any needed transformations on the action then adds
1500N/A it to the manifest.
1500N/A
1500N/A The "action" parameter is the action object that should be
1500N/A added to the manifest.
123N/A
1500N/A The "excludes" parameter is the variants to exclude from the
1500N/A manifest."""
877N/A
2639N/A attrs = action.attrs
2639N/A aname = action.name
1500N/A
2639N/A # XXX handle legacy transition issues; not needed once support
2639N/A # for upgrading images from older releases (< build 151) has
2639N/A # been removed.
2639N/A if "opensolaris.zone" in attrs and \
2639N/A "variant.opensolaris.zone" not in attrs:
2639N/A attrs["variant.opensolaris.zone"] = \
2639N/A attrs["opensolaris.zone"]
2639N/A
2639N/A if aname == "set" and attrs["name"] == "authority":
1500N/A # Translate old action to new.
2639N/A attrs["name"] = "publisher"
2639N/A
2639N/A if excludes and not action.include_this(excludes):
2639N/A return
1500N/A
2639N/A if self._variants:
2639N/A # Reset facet/variant cache if needed (if one is set,
2639N/A # then both are set, so only need to check for one).
2639N/A self._facets = None
2639N/A self._variants = None
1500N/A
1500N/A self.actions.append(action)
2639N/A try:
2639N/A self.actions_bytype[aname].append(action)
2639N/A except KeyError:
2639N/A self.actions_bytype.setdefault(aname, []).append(action)
838N/A
1500N/A # add any set actions to attributes
2639N/A if aname == "set":
1500N/A self.fill_attributes(action)
39N/A
956N/A def fill_attributes(self, action):
956N/A """Fill attribute array w/ set action contents."""
956N/A try:
956N/A keyvalue = action.attrs["name"]
1431N/A if keyvalue == "fmri":
1431N/A keyvalue = "pkg.fmri"
956N/A if keyvalue not in self.attributes:
956N/A self.attributes[keyvalue] = \
956N/A action.attrs["value"]
956N/A except KeyError: # ignore broken set actions
956N/A pass
956N/A
941N/A @staticmethod
1007N/A def search_dict(file_path, excludes, return_line=False,
1007N/A log=None):
1007N/A """Produces the search dictionary for a specific manifest.
1007N/A A dictionary is constructed which maps a tuple of token,
1007N/A action type, key, and the value that matched the token to
1007N/A the byte offset into the manifest file. file_path is the
1007N/A path to the manifest file. excludes is the variants which
1007N/A should be allowed in this image. return_line is a debugging
1007N/A flag which makes the function map the information to the
1007N/A string of the line, rather than the byte offset to allow
1007N/A easier debugging."""
1007N/A
1007N/A if log is None:
1007N/A log = lambda x: None
1100N/A
1407N/A file_handle = file(file_path, "rb")
941N/A cur_pos = 0
941N/A line = file_handle.readline()
144N/A action_dict = {}
941N/A def __handle_list(lst, cp):
1100N/A """Translates what actions.generate_indices produces
1100N/A into a dictionary mapping token, action_name, key, and
1100N/A the value that should be displayed for matching that
1100N/A token to byte offsets into the manifest file.
1100N/A
1100N/A The "lst" parameter is the data to be converted.
1100N/A
1100N/A The "cp" parameter is the byte offset into the file
1100N/A for the action which produced lst."""
1100N/A
941N/A for action_name, subtype, tok, full_value in lst:
941N/A if action_name == "set":
941N/A if full_value is None:
941N/A full_value = tok
941N/A else:
941N/A if full_value is None:
941N/A full_value = subtype
941N/A if full_value is None:
941N/A full_value = action_name
941N/A if isinstance(tok, list):
941N/A __handle_list([
941N/A (action_name, subtype, t,
941N/A full_value)
941N/A for t in tok
941N/A ], cp)
429N/A else:
941N/A if (tok, action_name, subtype,
941N/A full_value) in action_dict:
941N/A action_dict[(tok, action_name,
941N/A subtype, full_value)
941N/A ].append(cp)
429N/A else:
941N/A action_dict[(tok, action_name,
941N/A subtype, full_value)] = [cp]
941N/A while line:
941N/A l = line.strip()
941N/A if l and l[0] != "#":
1007N/A try:
1007N/A action = actions.fromstr(l)
1234N/A except actions.ActionError, e:
1007N/A log((_("%(fp)s:\n%(e)s") %
1007N/A { "fp": file_path, "e": e }))
1007N/A else:
2639N/A if not excludes or \
2639N/A action.include_this(excludes):
1007N/A if action.attrs.has_key("path"):
1007N/A np = action.attrs["path"].lstrip(os.path.sep)
1007N/A action.attrs["path"] = \
1007N/A np
1007N/A try:
1007N/A inds = action.generate_indices()
1007N/A except KeyError, k:
1007N/A log(_("%(fp)s contains "
1007N/A "an action which is"
1007N/A " missing the "
1007N/A "expected attribute"
1007N/A ": %(at)s.\nThe "
1007N/A "action is:"
1007N/A "%(act)s") %
1007N/A {
1007N/A "fp": file_path,
1007N/A "at": k.args[0],
1007N/A "act":l
1007N/A })
1007N/A else:
1007N/A arg = cur_pos
1007N/A if return_line:
1007N/A arg = l
1007N/A __handle_list(inds, arg)
941N/A cur_pos = file_handle.tell()
941N/A line = file_handle.readline()
941N/A file_handle.close()
144N/A return action_dict
144N/A
1472N/A @staticmethod
1472N/A def hash_create(mfstcontent):
1472N/A """This method takes a string representing the on-disk
1472N/A manifest content, and returns a hash value."""
1352N/A
1516N/A sha_1 = hashlib.sha1()
1890N/A if isinstance(mfstcontent, unicode):
1890N/A # Byte stream expected, so pass encoded.
1890N/A sha_1.update(mfstcontent.encode("utf-8"))
1890N/A else:
1890N/A sha_1.update(mfstcontent)
1352N/A
1472N/A return sha_1.hexdigest()
1352N/A
1352N/A def validate(self, signatures):
1352N/A """Verifies whether the signatures for the contents of
1352N/A the manifest match the specified signature data. Raises
1352N/A the 'BadManifestSignatures' exception on failure."""
1352N/A
1352N/A if signatures != self.signatures:
2073N/A raise apx.BadManifestSignatures(self.fmri)
1352N/A
429N/A def store(self, mfst_path):
429N/A """Store the manifest contents to disk."""
144N/A
1386N/A t_dir = os.path.dirname(mfst_path)
1386N/A t_prefix = os.path.basename(mfst_path) + "."
1386N/A
1636N/A try:
1636N/A os.makedirs(t_dir, mode=PKG_DIR_MODE)
1636N/A except EnvironmentError, e:
1636N/A if e.errno == errno.EACCES:
2073N/A raise apx.PermissionsException(e.filename)
1636N/A if e.errno == errno.EROFS:
2073N/A raise apx.ReadOnlyFileSystemException(
1636N/A e.filename)
1636N/A if e.errno != errno.EEXIST:
1636N/A raise
1386N/A
1636N/A try:
1636N/A fd, fn = tempfile.mkstemp(dir=t_dir, prefix=t_prefix)
1636N/A except EnvironmentError, e:
1636N/A if e.errno == errno.EACCES:
2073N/A raise apx.PermissionsException(e.filename)
1636N/A if e.errno == errno.EROFS:
2284N/A raise apx.ReadOnlyFileSystemException(
1636N/A e.filename)
1636N/A raise
1636N/A
1386N/A mfile = os.fdopen(fd, "wb")
315N/A
315N/A #
315N/A # We specifically avoid sorting manifests before writing
315N/A # them to disk-- there's really no point in doing so, since
315N/A # we'll sort actions globally during packaging operations.
315N/A #
315N/A mfile.write(self.tostr_unsorted())
315N/A mfile.close()
1636N/A
1636N/A try:
1636N/A os.chmod(fn, PKG_FILE_MODE)
1636N/A portable.rename(fn, mfst_path)
1636N/A except EnvironmentError, e:
1636N/A if e.errno == errno.EACCES:
2073N/A raise apx.PermissionsException(e.filename)
1636N/A if e.errno == errno.EROFS:
2073N/A raise apx.ReadOnlyFileSystemException(
1636N/A e.filename)
1636N/A raise
144N/A
838N/A def get_variants(self, name):
838N/A if name not in self.attributes:
838N/A return None
926N/A variants = self.attributes[name]
926N/A if not isinstance(variants, str):
926N/A return variants
926N/A return [variants]
838N/A
1231N/A def get_all_variants(self):
1231N/A """Return a dictionary mapping variant tags to their values."""
2091N/A return variant.VariantCombinationTemplate(dict((
1231N/A (name, self.attributes[name])
1231N/A for name in self.attributes
1231N/A if name.startswith("variant.")
1231N/A )))
1231N/A
205N/A def get(self, key, default):
257N/A try:
257N/A return self[key]
257N/A except KeyError:
205N/A return default
205N/A
1461N/A def getbool(self, key, default):
1461N/A """Returns the boolean of the value of the attribute 'key'."""
1461N/A
1461N/A ret = self.get(key, default).lower()
1461N/A if ret == "true":
1461N/A return True
1461N/A elif ret == "false":
1461N/A return False
1461N/A else:
1461N/A raise ValueError(_("Attribute value '%s' not 'true' or "
1461N/A "'false'" % ret))
1461N/A
1044N/A def get_size(self, excludes=EmptyI):
1044N/A """Returns an integer representing the total size, in bytes, of
1044N/A the Manifest's data payload.
1044N/A
1044N/A 'excludes' is a list of variants which should be allowed when
1044N/A calculating the total.
1044N/A """
1044N/A
1044N/A size = 0
1044N/A for a in self.gen_actions(excludes):
2286N/A size += a.get_size()
1044N/A return size
1044N/A
2639N/A def __load_varcets(self):
2639N/A """Private helper function to populate list of facets and
2639N/A variants on-demand."""
2639N/A
2639N/A self._facets = {}
2639N/A self._variants = {}
2639N/A for action in self.gen_actions():
2639N/A # append any variants and facets to manifest dict
2639N/A attrs = action.attrs
2639N/A v_list, f_list = action.get_varcet_keys()
2639N/A
2639N/A if not (v_list or f_list):
2639N/A continue
2639N/A
2639N/A try:
2639N/A for v, d in chain(
2639N/A izip(v_list, repeat(self._variants)),
2639N/A izip(f_list, repeat(self._facets))):
2639N/A try:
2639N/A d[v].add(attrs[v])
2639N/A except KeyError:
2639N/A d[v] = set([attrs[v]])
2639N/A except TypeError:
2639N/A # Lists can't be set elements.
2639N/A raise actions.InvalidActionError(action,
2639N/A _("%(forv)s '%(v)s' specified multiple times") %
2639N/A {"forv": v.split(".", 1)[0], "v": v})
2639N/A
2639N/A def __get_facets(self):
2639N/A if self._facets is None:
2639N/A self.__load_varcets()
2639N/A return self._facets
2639N/A
2639N/A def __get_variants(self):
2639N/A if self._variants is None:
2639N/A self.__load_varcets()
2639N/A return self._variants
2639N/A
205N/A def __getitem__(self, key):
838N/A """Return the value for the package attribute 'key'."""
838N/A return self.attributes[key]
205N/A
296N/A def __setitem__(self, key, value):
296N/A """Set the value for the package attribute 'key' to 'value'."""
838N/A self.attributes[key] = value
296N/A for a in self.actions:
296N/A if a.name == "set" and a.attrs["name"] == key:
296N/A a.attrs["value"] = value
296N/A return
296N/A
296N/A new_attr = AttributeAction(None, name=key, value=value)
296N/A self.actions.append(new_attr)
1461N/A self.actions_bytype.setdefault("set", []).append(new_attr)
296N/A
205N/A def __contains__(self, key):
838N/A return key in self.attributes
205N/A
2639N/A facets = property(lambda self: self.__get_facets())
2639N/A variants = property(lambda self: self.__get_variants())
2639N/A
48N/Anull = Manifest()
956N/A
2054N/Aclass FactoredManifest(Manifest):
2054N/A """This class serves as a wrapper for the Manifest class for callers
2054N/A that need efficient access to package data on a per-action type basis.
2054N/A It achieves this by partitioning the manifest into multiple files (one
2054N/A per action type) and then storing an on-disk cache of the directories
2054N/A explictly and implicitly referenced by the manifest each tagged with
2054N/A the appropriate variants/facets."""
956N/A
2073N/A def __init__(self, fmri, cache_root, contents=None, excludes=EmptyI,
2073N/A pathname=None):
2073N/A """Raises KeyError exception if factored manifest is not present
2073N/A and contents are None; delays reading of manifest until required
2073N/A if cache file is present.
1386N/A
2073N/A 'fmri' is a PkgFmri object representing the identity of the
2073N/A package.
2073N/A
2073N/A 'cache_root' is the pathname of the directory where the manifest
2073N/A and cache files should be stored or loaded from.
2073N/A
2073N/A 'contents' is an optional string to use as the contents of the
2073N/A manifest if a cached copy does not already exist.
956N/A
2608N/A 'excludes' is optional. If provided it must be a length two
2608N/A list with the variants to be excluded as the first element and
2608N/A the facets to be exclduded as the second element.
956N/A
2073N/A 'pathname' is an optional string containing the pathname of a
2073N/A manifest. If not provided, it is assumed that the manifest is
2073N/A stored in a file named 'manifest' in the directory indicated by
2144N/A 'cache_root'. If provided, and contents is also provided, then
2144N/A 'contents' will be stored in 'pathname' if it does not already
2144N/A exist.
2073N/A """
2073N/A
2073N/A Manifest.__init__(self, fmri)
2073N/A self.__cache_root = cache_root
2073N/A self.__pathname = pathname
2608N/A # Make sure that either no excludes were provided or that both
2608N/A # variants and facet excludes were.
2631N/A assert len(excludes) in (0, 2)
2073N/A self.loaded = False
956N/A
956N/A # Do we have a cached copy?
2073N/A if not os.path.exists(self.pathname):
956N/A if not contents:
956N/A raise KeyError, fmri
956N/A # we have no cached copy; save one
956N/A # don't specify excludes so on-disk copy has
956N/A # all variants
2073N/A self.set_content(content=contents)
956N/A self.__finiload()
967N/A if self.__storeback():
967N/A self.__unload()
2631N/A if excludes:
2073N/A self.exclude_content(excludes)
956N/A return
956N/A
956N/A # we have a cached copy of the manifest
2073N/A mdpath = self.__cache_path("manifest.dircache")
1507N/A
956N/A # have we computed the dircache?
956N/A if not os.path.exists(mdpath): # we're adding cache
956N/A self.excludes = EmptyI # to existing manifest
956N/A self.__load()
967N/A if self.__storeback():
967N/A self.__unload()
2631N/A if excludes:
956N/A self.excludes = excludes
956N/A self.__load()
2631N/A return
2631N/A self.exclude_content(excludes)
956N/A
2073N/A def __cache_path(self, name):
2073N/A return os.path.join(self.__cache_root, name)
2073N/A
956N/A def __load(self):
956N/A """Load all manifest contents from on-disk copy of manifest"""
2073N/A self.set_content(excludes=self.excludes, pathname=self.pathname)
956N/A self.__finiload()
956N/A
967N/A def __unload(self):
967N/A """Unload manifest; used to reduce peak memory comsumption
967N/A when downloading new manifests"""
967N/A self.actions = []
967N/A self.actions_bytype = {}
2639N/A self._variants = None
2639N/A self._facets = None
1507N/A self.attributes = {}
967N/A self.loaded = False
1507N/A
956N/A def __finiload(self):
1507N/A """Finish loading.... this part of initialization is common
956N/A to multiple code paths"""
956N/A self.loaded = True
956N/A
956N/A def __storeback(self):
956N/A """ store the current action set; also create per-type
967N/A caches. Return True if data was saved, False if not"""
956N/A assert self.loaded
956N/A try:
2073N/A self.store(self.pathname)
956N/A self.__storebytype()
967N/A return True
2073N/A except apx.PermissionsException:
956N/A # this allows us to try to cache new manifests
1672N/A # when non-root w/o failures.
967N/A return False
956N/A
956N/A def __storebytype(self):
956N/A """ create manifest.<typename> files to accelerate partial
1507N/A parsing of manifests. Separate from __storeback code to
956N/A allow upgrade to reuse existing on disk manifests"""
956N/A
956N/A assert self.loaded
956N/A
2073N/A t_dir = self.__cache_root
1386N/A
2144N/A # Ensure target cache directory and intermediates exist.
2144N/A misc.makedirs(t_dir)
2144N/A
956N/A # create per-action type cache; use rename to avoid
1507N/A # corrupt files if ^C'd in the middle
956N/A for n in self.actions_bytype.keys():
1386N/A t_prefix = "manifest.%s." % n
1386N/A
1386N/A fd, fn = tempfile.mkstemp(dir=t_dir, prefix=t_prefix)
1386N/A f = os.fdopen(fd, "wb")
1386N/A
956N/A for a in self.actions_bytype[n]:
956N/A f.write("%s\n" % a)
956N/A f.close()
1507N/A os.chmod(fn, PKG_FILE_MODE)
2073N/A portable.rename(fn, self.__cache_path("manifest.%s" % n))
1386N/A
2453N/A def create_cache(name, refs):
2453N/A try:
2453N/A fd, fn = tempfile.mkstemp(dir=t_dir,
2453N/A prefix="manifest.dircache.")
2453N/A with os.fdopen(fd, "wb") as f:
2453N/A f.writelines(refs())
2453N/A os.chmod(fn, PKG_FILE_MODE)
2453N/A portable.rename(fn, self.__cache_path(name))
2453N/A except EnvironmentError, e:
2453N/A raise apx._convert_error(e)
956N/A
2453N/A create_cache("manifest.dircache", self._gen_dirs_to_str)
2453N/A create_cache("manifest.mediatorcache",
2453N/A self._gen_mediators_to_str)
956N/A
1352N/A @staticmethod
2073N/A def clear_cache(cache_root):
2073N/A """Remove all manifest cache files found in the given directory
2073N/A (excluding the manifest itself).
2073N/A """
2035N/A
2035N/A try:
2073N/A for cname in os.listdir(cache_root):
2035N/A if not cname.startswith("manifest."):
2035N/A continue
2035N/A try:
2035N/A portable.remove(os.path.join(
2073N/A cache_root, cname))
2035N/A except EnvironmentError, e:
2035N/A if e.errno != errno.ENOENT:
2035N/A raise
2035N/A except EnvironmentError, e:
2475N/A if e.errno != errno.ENOENT:
2475N/A # Only raise error if failure wasn't due to
2475N/A # cache directory not existing.
2475N/A raise apx._convert_error(e)
2035N/A
2453N/A def __load_cached_data(self, name):
2453N/A """Private helper function for loading arbitrary cached manifest
2453N/A data.
2453N/A """
1507N/A
2453N/A mpath = self.__cache_path(name)
956N/A if not os.path.exists(mpath):
956N/A # no cached copy
956N/A if not self.loaded:
956N/A # need to load from disk
956N/A self.__load()
2339N/A assert self.loaded
2453N/A return
2339N/A
2453N/A # we have cached copy on disk; use it
2453N/A try:
2453N/A with open(mpath, "rb") as f:
2453N/A self._cache[name] = [
2613N/A a for a in
2613N/A (
2639N/A actions.fromstr(s.rstrip())
2613N/A for s in f
2613N/A )
2639N/A if not self.excludes or
2639N/A a.include_this(self.excludes)
2453N/A ]
2453N/A except EnvironmentError, e:
2453N/A raise apx._convert_error(e)
2453N/A
2453N/A def get_directories(self, excludes):
2453N/A """ return a list of directories implicitly or explicitly
2453N/A referenced by this object
2453N/A """
2453N/A self.__load_cached_data("manifest.dircache")
2339N/A return Manifest.get_directories(self, excludes)
956N/A
956N/A def gen_actions_by_type(self, atype, excludes=EmptyI):
956N/A """ generate actions of the specified type;
956N/A use already in-memory stuff if already loaded,
956N/A otherwise use per-action types files"""
956N/A
956N/A if self.loaded: #if already loaded, use in-memory cached version
956N/A # invoke subclass method to generate action by action
1507N/A for a in Manifest.gen_actions_by_type(self, atype,
956N/A excludes):
956N/A yield a
956N/A return
956N/A
2613N/A # This checks if we've already written out the factored
2205N/A # manifest files. If so, we'll use it, and if not, then
2205N/A # we'll load the full manifest.
2073N/A mpath = self.__cache_path("manifest.dircache")
956N/A
956N/A if not os.path.exists(mpath):
956N/A # no cached copy :-(
956N/A if not self.loaded:
956N/A # get manifest from disk
956N/A self.__load()
956N/A # invoke subclass method to generate action by action
1507N/A for a in Manifest.gen_actions_by_type(self, atype,
956N/A excludes):
956N/A yield a
956N/A else:
2613N/A if excludes == EmptyI:
2613N/A excludes = self.excludes
2613N/A assert excludes == self.excludes or \
2613N/A self.excludes == EmptyI
956N/A # we have a cached copy - use it
2073N/A mpath = self.__cache_path("manifest.%s" % atype)
956N/A
956N/A if not os.path.exists(mpath):
956N/A return # no such action in this manifest
956N/A
2639N/A with open(mpath, "rb") as f:
2639N/A for l in f:
2639N/A a = actions.fromstr(l.rstrip())
2639N/A if not excludes or \
2639N/A a.include_this(excludes):
2639N/A yield a
956N/A
2453N/A def gen_mediators(self, excludes):
2453N/A """A generator function that yields set actions expressing the
2453N/A set of possible mediations for this package.
2453N/A """
2453N/A self.__load_cached_data("manifest.mediatorcache")
2453N/A return Manifest.gen_mediators(self, excludes)
2453N/A
956N/A def __load_attributes(self):
956N/A """Load attributes dictionary from cached set actions;
956N/A this speeds up pkg info a lot"""
956N/A
2073N/A mpath = self.__cache_path("manifest.set")
956N/A if not os.path.exists(mpath):
956N/A return False
2639N/A with open(mpath, "rb") as f:
2639N/A for l in f:
2639N/A a = actions.fromstr(l.rstrip())
2639N/A if not self.excludes or \
2639N/A a.include_this(self.excludes):
2639N/A self.fill_attributes(a)
1507N/A return True
1507N/A
956N/A def __getitem__(self, key):
956N/A if not self.loaded and not self.__load_attributes():
956N/A self.__load()
956N/A return Manifest.__getitem__(self, key)
1507N/A
956N/A def __setitem__(self, key, value):
2054N/A """No assignments to factored manifests allowed."""
2054N/A assert "FactoredManifests are not dicts"
956N/A
956N/A def __contains__(self, key):
956N/A if not self.loaded and not self.__load_attributes():
956N/A self.__load()
956N/A return Manifest.__contains__(self, key)
956N/A
956N/A def get(self, key, default):
956N/A try:
956N/A return self[key]
956N/A except KeyError:
956N/A return default
956N/A
956N/A def get_variants(self, name):
956N/A if not self.loaded and not self.__load_attributes():
956N/A self.__load()
956N/A return Manifest.get_variants(self, name)
956N/A
1376N/A def get_all_variants(self):
1376N/A if not self.loaded and not self.__load_attributes():
1376N/A self.__load()
1376N/A return Manifest.get_all_variants(self)
1376N/A
956N/A @staticmethod
2073N/A def search_dict(cache_path, excludes, return_line=False):
2073N/A return Manifest.search_dict(cache_path, excludes,
956N/A return_line=return_line)
956N/A
956N/A def gen_actions(self, excludes=EmptyI):
956N/A if not self.loaded:
956N/A self.__load()
956N/A return Manifest.gen_actions(self, excludes=excludes)
956N/A
2026N/A def __str__(self, excludes=EmptyI):
2026N/A if not self.loaded:
2026N/A self.__load()
2026N/A return Manifest.__str__(self)
2026N/A
956N/A def duplicates(self, excludes=EmptyI):
956N/A if not self.loaded:
956N/A self.__load()
956N/A return Manifest.duplicates(self, excludes=excludes)
956N/A
1352N/A def difference(self, origin, origin_exclude=EmptyI,
1352N/A self_exclude=EmptyI):
956N/A if not self.loaded:
956N/A self.__load()
1507N/A return Manifest.difference(self, origin,
1507N/A origin_exclude=origin_exclude,
956N/A self_exclude=self_exclude)
956N/A
2073N/A @property
2073N/A def pathname(self):
2073N/A """The absolute pathname of the file containing the manifest."""
2073N/A
2073N/A if self.__pathname:
2073N/A return self.__pathname
2073N/A return os.path.join(self.__cache_root, "manifest")
2073N/A
1507N/A
2054N/Aclass EmptyFactoredManifest(Manifest):
964N/A """Special class for pkgplan's need for a empty manifest;
964N/A the regular null manifest doesn't support get_directories
2054N/A and making the factored manifest code handle this case is
964N/A too ugly..."""
2073N/A
964N/A def __init__(self):
964N/A Manifest.__init__(self)
964N/A
1045N/A def difference(self, origin, origin_exclude=EmptyI,
1045N/A self_exclude=EmptyI):
1045N/A """Return three lists of action pairs representing origin and
1045N/A destination actions. The first list contains the pairs
1045N/A representing additions, the second list contains the pairs
1045N/A representing updates, and the third list contains the pairs
1045N/A representing removals. All three lists are in the order in
1045N/A which they should be executed."""
1045N/A
1045N/A # The difference for this case is simply everything in the
1045N/A # origin has been removed. This is an optimization for
1045N/A # uninstall.
1713N/A return ManifestDifference([], [],
1505N/A [(a, None) for a in origin.gen_actions(origin_exclude)])
1045N/A
1352N/A @staticmethod
1352N/A def get_directories(excludes):
964N/A return []
964N/A
2608N/A def exclude_content(self, *args, **kwargs):
2608N/A # This method is overridden so that self.excludes is never set
2608N/A # on the singleton NullFactoredManifest.
2608N/A return
2608N/A
2608N/A def set_content(self, *args, **kwargs):
2608N/A raise RuntimeError("Cannot call set_content on an "
2608N/A "EmptyFactoredManifest")
2608N/A
2054N/ANullFactoredManifest = EmptyFactoredManifest()
2284N/A
2284N/Aclass ManifestError(Exception):
2284N/A """Simple Exception class to handle manifest specific errors"""
2284N/A
2284N/A def __init__(self, duplicates=EmptyI):
2453N/A self.__duplicates = duplicates
2284N/A
2284N/A def __str__(self):
2284N/A ret = []
2284N/A for d in self.__duplicates:
2284N/A ret.append("%s\n%s\n\n" % d)
2284N/A
2284N/A return "\n".join(ret)
2284N/A
2284N/A