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#
3312N/A# Copyright (c) 2007, 2016, Oracle and/or its affiliates. All rights reserved.
926N/A#
39N/A
3339N/Afrom __future__ import print_function
2453N/Afrom collections import namedtuple, defaultdict
3194N/Afrom functools import reduce
3194N/A
342N/Aimport errno
3041N/Aimport fnmatch
1516N/Aimport hashlib
1636N/Aimport os
3041N/Aimport re
3234N/Aimport six
1386N/Aimport tempfile
3234N/Afrom itertools import groupby, chain, product, repeat
2639N/Afrom operator import itemgetter
3234N/Afrom six.moves import zip
39N/A
51N/Aimport pkg.actions as actions
2073N/Aimport pkg.client.api_errors as apx
2910N/Aimport pkg.facet as facet
3110N/Aimport pkg.fmri as fmri
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
2876N/Afrom pkg.actions.directory import DirectoryAction
39N/A
3041N/Adef _compile_fnpats(fn_pats):
3041N/A """Private helper function that returns a compiled version of a
3041N/A dictionary of fnmatch patterns."""
3041N/A
3041N/A return dict(
3041N/A (key, [
3041N/A re.compile(fnmatch.translate(pat), re.IGNORECASE).match
3041N/A for pat in pats
3041N/A ])
3234N/A for (key, pats) in six.iteritems(fn_pats)
3041N/A )
3041N/A
3041N/A
3041N/Adef _attr_matches(action, attr_match):
3041N/A """Private helper function: given an action, return True if any of its
3041N/A attributes' values matches the pattern for the same attribute in the
3041N/A attr_match dictionary, and False otherwise. Note that the patterns must
3041N/A be pre-comiled using re.compile() or _compile_fnpats."""
3041N/A
3041N/A if not attr_match:
3041N/A return True
3041N/A
3234N/A for (attr, matches) in six.iteritems(attr_match):
3041N/A if attr in action.attrs:
3041N/A for match in matches:
3041N/A for attrval in action.attrlist(attr):
3041N/A if match(attrval):
3041N/A return True
3041N/A return False
3041N/A
3041N/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 = {}
2910N/A self._absent_cache = []
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
3110N/A if pfmri is not None:
3110N/A if not isinstance(pfmri, fmri.PkgFmri):
3110N/A pfmri = fmri.PkgFmri(pfmri)
3110N/A self.publisher = pfmri.publisher
3110N/A else:
3110N/A self.publisher = None
39N/A
39N/A def __str__(self):
39N/A r = ""
1431N/A if "pkg.fmri" not in self.attributes and self.fmri != None:
3158N/A r += "set name=pkg.fmri value={0}\n".format(self.fmri)
39N/A
227N/A for act in sorted(self.actions):
3158N/A r += "{0}\n".format(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:
3158N/A yield "set name=pkg.fmri value={0}\n".format(self.fmri)
429N/A
315N/A for act in self.actions:
3158N/A yield "{0}\n".format(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,
3445N/A self_exclude=EmptyI, pkgplan=None, cmp_policy=None):
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(
3041N/A [(None, a) for a in self.gen_actions(
3041N/A excludes=self_exclude)], [], [])
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
2854N/A return tuple(v)
2084N/A
2842N/A def dictify(mf, excludes):
2842N/A # Transform list of actions into a dictionary keyed by
2842N/A # action key attribute, key attribute and mediator, or
2842N/A # id if there is no key attribute.
3041N/A for a in mf.gen_actions(excludes=excludes):
2842N/A if (a.name == "link" or
2842N/A a.name == "hardlink") and \
2842N/A a.attrs.get("mediator"):
2854N/A akey = (a.name, tuple([
2854N/A a.attrs[a.key_attr],
2842N/A a.attrs.get("mediator-version"),
2842N/A a.attrs.get("mediator-implementation")
2842N/A ]))
2842N/A else:
2842N/A akey = (a.name, hashify(a.attrs.get(
2842N/A a.key_attr, id(a))))
2842N/A yield (akey, a)
2842N/A
2842N/A sdict = dict(dictify(self, self_exclude))
2842N/A odict = dict(dictify(origin, origin_exclude))
59N/A
3234N/A sset = set(six.iterkeys(sdict))
3234N/A oset = set(six.iterkeys(odict))
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
3402N/A if odict[i].different(sdict[i], pkgplan=pkgplan,
3445N/A cmp_policy=cmp_policy)
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
3445N/A def comm(compare_m, cmp_policy=None):
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
3402N/A list of actions that are the same in each manifest.
3402N/A
3402N/A Content hashes for action payloads may be present in
3402N/A both signature-included and signature-excluded
3445N/A variants. In most cases (cmp_policy=None), we only
3402N/A want to compare the signature-included variants. When
3402N/A no-signature-included comparison is requested, simply
3445N/A pass cmp_policy=CMP_UNSIGNED through to Action.different().
3402N/A """
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))
3212N/A if (a.name == "link" or
3212N/A a.name == "hardlink") and \
3212N/A a.attrs.get("mediator"):
3212N/A for v in ("mediator-version",
3212N/A "mediator-implementation"):
3212N/A key.update([
3212N/A "{0}={1}".format(v,
3212N/A a.attrs.get(v))])
2240N/A key.update(
3158N/A "{0}={1}".format(v, a.attrs[v])
2240N/A for v in a.get_varcet_keys()[0]
2240N/A )
3212N/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(
3402N/A m_dicts[i + 1][k],
3445N/A cmp_policy=cmp_policy):
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:
3158N/A out += "+ {0}\n".format(str(dest))
72N/A elif not dest:
3158N/A out += "- {0}\n" + str(src)
46N/A else:
3158N/A out += "{0} -> {1}\n".format(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]:
2876N/A a = DirectoryAction(path=d, **v)
2876N/A yield str(a) + "\n"
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)
3234N/A for mediation, mvariants in six.iteritems(mediators):
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 " \
3158N/A "value={0} {1} {2}\n".format(mediation[0],
2453N/A " ".join((
2453N/A "=".join(t)
3234N/A for t in six.iteritems(values)
2453N/A if t[1]
2453N/A )),
2453N/A " ".join((
2453N/A "=".join(t)
3234N/A for t in six.iteritems(mvariant)
2453N/A ))
2453N/A )
2453N/A yield a
2453N/A
2910N/A def _gen_attrs_to_str(self):
2910N/A """Generate set action supplemental data containing all facets
2910N/A and variants from self.actions and size information. Each
2910N/A returned line must be newline-terminated."""
2910N/A
2910N/A emit_variants = "pkg.variant" not in self
2910N/A emit_facets = "pkg.facet" not in self
2910N/A emit_sizes = "pkg.size" not in self and "pkg.csize" not in self
2910N/A
2910N/A if not any((emit_variants, emit_facets, emit_sizes)):
2910N/A # Package already has these attributes.
2910N/A return
2910N/A
2910N/A # List of possible variants and possible values for them.
2910N/A variants = defaultdict(set)
2910N/A
2910N/A # Seed with declared set of variants as actions may be common to
2910N/A # both and so will not be tagged with variant.
2910N/A for name in self.attributes:
2910N/A if name[:8] == "variant.":
2910N/A variants[name] = set(self.attributes[name])
2910N/A
2910N/A # List of possible facets and under what variant combinations
2910N/A # they were seen.
2910N/A facets = defaultdict(set)
2910N/A
2910N/A # Unique (facet, value) (variant, value) combinations.
2910N/A refs = defaultdict(lambda: defaultdict(int))
2910N/A
2910N/A for a in self.gen_actions():
2910N/A name = a.name
2910N/A attrs = a.attrs
2910N/A if name == "set":
2910N/A if attrs["name"][:12] == "pkg.variant":
2910N/A emit_variants = False
2910N/A elif attrs["name"][:9] == "pkg.facet":
2910N/A emit_facets = False
2910N/A
2910N/A afacets = []
2910N/A avariants = []
3234N/A for attr, val in six.iteritems(attrs):
2910N/A if attr[:8] == "variant.":
2910N/A variants[attr].add(val)
2910N/A avariants.append((attr, val))
2910N/A elif attr[:6] == "facet.":
2910N/A afacets.append((attr, val))
2910N/A
2910N/A for name, val in afacets:
2910N/A # Facet applicable to this particular variant
2910N/A # combination.
2910N/A varkey = tuple(sorted(avariants))
2910N/A facets[varkey].add(name)
2910N/A
2910N/A # This *must* be sorted to ensure reproducible set
2910N/A # action generation for sizes and to ensure each
2910N/A # combination is actually unique.
2910N/A varcetkeys = tuple(sorted(chain(afacets, avariants)))
2910N/A refs[varcetkeys]["csize"] += misc.get_pkg_otw_size(a)
2910N/A if name == "signature":
2910N/A refs[varcetkeys]["csize"] += \
2910N/A a.get_action_chain_csize()
2910N/A refs[varcetkeys]["size"] += a.get_size()
2910N/A
2910N/A # Prevent scope leak.
2910N/A afacets = avariants = attrs = varcetkeys = None
2910N/A
2910N/A if emit_variants:
2910N/A # Unnecessary if we can guarantee all variants will be
2910N/A # declared at package level. Omit the "variant." prefix
2910N/A # from attribute values since that's implicit and can be
2910N/A # added back when the action is parsed.
3158N/A yield "{0}\n".format(AttributeAction(None,
3158N/A name="pkg.variant",
3158N/A value=sorted(v[8:] for v in variants)))
2910N/A
2910N/A # Emit a set action for every variant used with possible values
2910N/A # if one does not already exist.
2910N/A for name in variants:
2910N/A # merge_facets needs the variant values sorted and this
2910N/A # is desirable when generating the variant attr anyway.
2910N/A variants[name] = sorted(variants[name])
2910N/A if name not in self.attributes:
3158N/A yield "{0}\n".format(AttributeAction(None,
3158N/A name=name, value=variants[name]))
2910N/A
2910N/A if emit_facets:
2910N/A # Get unvarianted facet set.
2910N/A cfacets = facets.pop((), set())
2910N/A
2910N/A # For each variant combination, remove unvarianted
2910N/A # facets since they are common to all variants.
3339N/A for varkey, fnames in list(facets.items()):
2910N/A fnames.difference_update(cfacets)
2910N/A if not fnames:
2910N/A # No facets unique to this combo;
2910N/A # discard.
2910N/A del facets[varkey]
2910N/A
2910N/A # If all possible variant combinations supported by the
2910N/A # package have at least one facet, then the intersection
2910N/A # of facets for all variants can be merged with the
2910N/A # common set.
2910N/A merge_facets = len(facets) > 0
2910N/A if merge_facets:
2910N/A # Determine unique set of variant combinations
2910N/A # seen for faceted actions.
2910N/A vcombos = set((
2910N/A tuple(
2910N/A vpair[0]
2910N/A for vpair in varkey
2910N/A )
2910N/A for varkey in facets
2910N/A ))
2910N/A
2910N/A # For each unique variant combination, determine
2910N/A # if the cartesian product of all variant values
2910N/A # supported by the package for the combination
2910N/A # has been seen. In other words, if the
2910N/A # combination is ((variant.arch,)) and the
2910N/A # package supports (i386, sparc), then both
2910N/A # (variant.arch, i386) and (variant.arch, sparc)
2910N/A # must exist. This code assumes variant values
2910N/A # for each variant are already sorted.
2910N/A for pair in chain.from_iterable(
2910N/A product(*(
2910N/A tuple((name, val)
2910N/A for val in variants[name])
2910N/A for name in vcombo)
2910N/A )
2910N/A for vcombo in vcombos
2910N/A ):
2910N/A if pair not in facets:
2910N/A # If any combination the package
2910N/A # supports has not been seen for
2910N/A # one or more facets, then some
2910N/A # facets are unique to one or
2910N/A # more combinations.
2910N/A merge_facets = False
2910N/A break
2910N/A
2910N/A if merge_facets:
2910N/A # Merge the facets common to all variants if safe;
2910N/A # if we always merged them, then facets only
2910N/A # used by a single variant (think i386-only or
2910N/A # sparc-only content) would be seen unvarianted
2910N/A # (that's bad).
3234N/A vfacets = list(facets.values())
2910N/A vcfacets = vfacets[0].intersection(*vfacets[1:])
2910N/A
2910N/A if vcfacets:
2910N/A # At least one facet is shared between
2910N/A # all variant combinations; move the
2910N/A # common ones to the unvarianted set.
2910N/A cfacets.update(vcfacets)
2910N/A
2910N/A # Remove facets common to all combos.
3339N/A for varkey, fnames in list(
3339N/A facets.items()):
2910N/A fnames.difference_update(vcfacets)
2910N/A if not fnames:
2910N/A # No facets unique to
2910N/A # this combo; discard.
2910N/A del facets[varkey]
2910N/A
2910N/A # Omit the "facet." prefix from attribute values since
2910N/A # that's implicit and can be added back when the action
2910N/A # is parsed.
2910N/A val = sorted(f[6:] for f in cfacets)
2910N/A if not val:
2910N/A # If we don't do this, action stringify will
2910N/A # emit this as "set name=pkg.facet" which is
2910N/A # then transformed to "set name=name
2910N/A # value=pkg.facet". Not what we wanted, but is
2910N/A # expected for historical reasons.
2910N/A val = ""
2910N/A
2910N/A # Always emit an action enumerating the list of facets
2910N/A # common to all variants, even if there aren't any.
2910N/A # That way if there are also no variant-specific facets,
2910N/A # package operations will know that no facets are used
2910N/A # by the package instead of having to scan the whole
2910N/A # manifest.
3158N/A yield "{0}\n".format(AttributeAction(None,
3158N/A name="pkg.facet.common", value=val))
2910N/A
2910N/A # Now emit a pkg.facet action for each variant
2910N/A # combination containing the list of facets unique to
2910N/A # that combination.
3234N/A for varkey, fnames in six.iteritems(facets):
2910N/A # A unique key for each combination is needed,
2910N/A # and using a hash obfuscates that interface
2910N/A # while giving us a reliable way to generate
2910N/A # a reproducible, unique identifier. The key
2910N/A # string below looks like this before hashing:
2910N/A # variant.archi386variant.debug.osnetTrue...
2910N/A key = hashlib.sha1(
3339N/A misc.force_bytes("".join(
3339N/A "{0}{1}".format(*v) for v in varkey))
2910N/A ).hexdigest()
2910N/A
2910N/A # Omit the "facet." prefix from attribute values
2910N/A # since that's implicit and can be added back
2910N/A # when the action is parsed.
2910N/A act = AttributeAction(None,
3158N/A name="pkg.facet.{0}".format(key),
2910N/A value=sorted(f[6:] for f in fnames))
2910N/A attrs = act.attrs
2910N/A # Tag action with variants.
2910N/A for v in varkey:
2910N/A attrs[v[0]] = v[1]
3158N/A yield "{0}\n".format(act)
2910N/A
2910N/A # Emit pkg.[c]size attribute for [compressed] size of package
2910N/A # for each facet/variant combination.
2910N/A csize = 0
2910N/A size = 0
2910N/A for varcetkeys in refs:
2910N/A rcsize = refs[varcetkeys]["csize"]
2910N/A rsize = refs[varcetkeys]["size"]
2910N/A
2910N/A if not varcetkeys:
2910N/A # For unfaceted/unvarianted actions, keep a
2910N/A # running total so a single [c]size action can
2910N/A # be generated.
2910N/A csize += rcsize
2910N/A size += rsize
2910N/A continue
2910N/A
2910N/A if emit_sizes and (rcsize > 0 or rsize > 0):
2910N/A # Only emit if > 0; actions may be
2910N/A # faceted/variant without payload.
2910N/A
2910N/A # A unique key for each combination is needed,
2910N/A # and using a hash obfuscates that interface
2910N/A # while giving us a reliable way to generate
2910N/A # a reproducible, unique identifier. The key
2910N/A # string below looks like this before hashing:
2910N/A # facet.docTruevariant.archi386...
3339N/A key = hashlib.sha1(misc.force_bytes(
3158N/A "".join("{0}{1}".format(*v) for v in varcetkeys)
3339N/A )).hexdigest()
2910N/A
2910N/A # The sizes are abbreviated in the name of byte
2910N/A # conservation.
2910N/A act = AttributeAction(None,
3158N/A name="pkg.sizes.{0}".format(key),
3158N/A value=["csz={0}".format(rcsize),
3158N/A "sz={0}".format(rsize)])
2910N/A attrs = act.attrs
2910N/A for v in varcetkeys:
2910N/A attrs[v[0]] = v[1]
3158N/A yield "{0}\n".format(act)
2910N/A
2910N/A if emit_sizes:
2910N/A act = AttributeAction(None, name="pkg.sizes.common",
3158N/A value=["csz={0}".format(csize),
3158N/A "sz={0}".format(size)])
3158N/A yield "{0}\n".format(act)
2910N/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 = {}
2910N/A # build a dictionary containing all actions 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
3110N/A if not excludes or a.include_this(excludes,
3110N/A publisher=self.publisher)
2339N/A ])
2339N/A
2339N/A return list(s)
2339N/A
2910N/A def gen_facets(self, excludes=EmptyI, patterns=EmptyI):
2910N/A """A generator function that returns the supported facet
2910N/A attributes (strings) for this package based on the specified (or
2910N/A current) excludes that also match at least one of the patterns
2910N/A provided. Facets must be true or false so a list of possible
2910N/A facet values is not returned."""
2910N/A
2910N/A if self.excludes == excludes:
2910N/A excludes = EmptyI
2910N/A assert excludes == EmptyI or self.excludes == EmptyI
2910N/A
2910N/A try:
2910N/A facets = self["pkg.facet"]
2910N/A except KeyError:
2910N/A facets = None
2910N/A
2910N/A if facets is not None and excludes == EmptyI:
2910N/A # No excludes? Then use the pre-determined set of
2910N/A # facets.
2910N/A for f in misc.yield_matching("facet.", facets, patterns):
2910N/A yield f
2910N/A return
2910N/A
2910N/A # If different excludes were specified, then look for pkg.facet
2910N/A # actions containing the list of facets.
2910N/A found = False
2910N/A seen = set()
2910N/A for a in self.gen_actions_by_type("set", excludes=excludes):
2910N/A if a.attrs["name"][:10] == "pkg.facet.":
2910N/A # Either a pkg.facet.common action or a
2910N/A # pkg.facet.X variant-specific action.
2910N/A found = True
2910N/A val = a.attrlist("value")
2910N/A if len(val) == 1 and val[0] == "":
2910N/A # No facets.
2910N/A continue
2910N/A
2910N/A for f in misc.yield_matching("facet.", (
3158N/A "facet.{0}".format(n)
2910N/A for n in val
2910N/A ), patterns):
2910N/A if f in seen:
2910N/A # Prevent duplicates; it's
2910N/A # possible a given facet may be
2910N/A # valid for more than one unique
2910N/A # variant combination that's
2910N/A # allowed by current excludes.
2910N/A continue
2910N/A
2910N/A seen.add(f)
2910N/A yield f
2910N/A
2910N/A if not found:
2910N/A # Fallback to sifting actions to yield possible.
2910N/A facets = self._get_varcets(excludes=excludes)[1]
2910N/A for f in misc.yield_matching("facet.", facets, patterns):
2910N/A yield f
2910N/A
2910N/A def gen_variants(self, excludes=EmptyI, patterns=EmptyI):
2910N/A """A generator function that yields a list of tuples of the form
2910N/A (variant, [values]). Where 'variant' is the variant attribute
2910N/A name (e.g. 'variant.arch') and '[values]' is a list of the
2910N/A variant values supported by this package. Variants returned are
2910N/A those allowed by the specified (or current) excludes that also
2910N/A match at least one of the patterns provided."""
2910N/A
2910N/A if self.excludes == excludes:
2910N/A excludes = EmptyI
2910N/A assert excludes == EmptyI or self.excludes == EmptyI
2910N/A
2910N/A try:
2910N/A variants = self["pkg.variant"]
2910N/A except KeyError:
2910N/A variants = None
2910N/A
2910N/A if variants is not None and excludes == EmptyI:
2910N/A # No excludes? Then use the pre-determined set of
2910N/A # variants.
2910N/A for v in misc.yield_matching("variant.", variants,
2910N/A patterns):
2910N/A yield v, self.attributes.get(v, [])
2910N/A return
2910N/A
2910N/A # If different excludes were specified, then look for
2910N/A # pkg.variant action containing the list of variants.
2910N/A found = False
2910N/A variants = defaultdict(set)
2910N/A for a in self.gen_actions_by_type("set", excludes=excludes):
2910N/A aname = a.attrs["name"]
2910N/A if aname == "pkg.variant":
2910N/A val = a.attrlist("value")
2910N/A if len(val) == 1 and val[0] == "":
2910N/A # No variants.
2910N/A return
2910N/A for v in val:
2910N/A found = True
2910N/A # Ensure variant entries exist (debug
2910N/A # variants may not) via defaultdict.
3158N/A variants["variant.{0}".format(v)]
2910N/A elif aname[:8] == "variant.":
2910N/A for v in a.attrlist("value"):
2910N/A found = True
2910N/A variants[aname].add(v)
2910N/A
2910N/A if not found:
2910N/A # Fallback to sifting actions to get possible.
2910N/A variants = self._get_varcets(excludes=excludes)[0]
2910N/A
2910N/A for v in misc.yield_matching("variant.", variants, patterns):
2910N/A yield v, variants[v]
2910N/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:
2958N/A med_ver = version.Version(med_ver)
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
3041N/A def gen_actions(self, attr_match=None, 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
3041N/A
3041N/A if attr_match:
3041N/A attr_match = _compile_fnpats(attr_match)
3041N/A
3110N/A pub = self.publisher
838N/A for a in self.actions:
838N/A for c in excludes:
3110N/A if not c(a, publisher=pub):
838N/A break
838N/A else:
3041N/A # These conditions are split by performance.
3041N/A if not attr_match:
3041N/A yield a
3041N/A elif _attr_matches(a, attr_match):
3041N/A yield a
342N/A
3041N/A def gen_actions_by_type(self, atype, attr_match=None, 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
3041N/A
3041N/A if attr_match:
3041N/A attr_match = _compile_fnpats(attr_match)
3041N/A
3110N/A pub = self.publisher
926N/A for a in self.actions_bytype.get(atype, []):
838N/A for c in excludes:
3110N/A if not c(a, publisher=pub):
838N/A break
838N/A else:
3041N/A # These conditions are split by performance.
3041N/A if not attr_match:
3041N/A yield a
3041N/A elif _attr_matches(a, attr_match):
3041N/A yield a
926N/A
3041N/A def gen_actions_by_types(self, atypes, attr_match=None, 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,
3041N/A attr_match=attr_match, excludes=excludes):
2453N/A yield a
2453N/A
926N/A def gen_key_attribute_value_by_type(self, atype, excludes=EmptyI):
2843N/A """Generate the value of the key attribute for each action
615N/A of type "type" in the manifest."""
615N/A
615N/A return (
615N/A a.attrs.get(a.key_attr)
3041N/A for a in self.gen_actions_by_type(atype, excludes=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 = []
3041N/A acts = [a for a in self.gen_actions(excludes=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):
3312N/A """Parse manifest content, stripping line-continuation
3312N/A characters from the input as it is read; this results in actions
3312N/A with values across multiple lines being passed to the
3312N/A action parsing code whitespace-separated instead.
3312N/A
3312N/A For example:
3312N/A
3312N/A set name=pkg.summary \
3312N/A value="foo"
3312N/A set name=pkg.description value="foo " \
3312N/A "bar baz"
3312N/A
3312N/A ...will each be passed to action parsing as:
3312N/A
3312N/A set name=pkg.summary value="foo"
3312N/A set name=pkg.description value="foo " "bar baz"
3312N/A """
3312N/A
1432N/A accumulate = ""
1542N/A lineno = 0
1970N/A errors = []
2073N/A
3234N/A if isinstance(content, six.string_types):
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)
3171N/A except actions.ActionError as 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 = {}
1500N/A self.attributes = {}
2613N/A self._cache = {}
2910N/A self._absent_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:
3339N/A with open(pathname, "r") as mfile:
2073N/A content = mfile.read()
3171N/A except EnvironmentError as e:
2073N/A raise apx._convert_error(e)
3339N/A
3339N/A if six.PY3 and isinstance(content, bytes):
3339N/A raise TypeError("content must be str, not bytes")
3339N/A
3234N/A if isinstance(content, six.string_types):
1500N/A if signatures:
2073N/A # Generate manifest signature based upon
2073N/A # input content, but only if signatures
2962N/A # were requested. In order to interoperate with
2962N/A # older clients, we must use sha-1 here.
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
3110N/A # variants and facet excludes were or that variant, facet and
3110N/A # hydrate excludes were.
3110N/A assert len(self.excludes) != 1
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
3110N/A if excludes and not action.include_this(excludes,
3110N/A publisher=self.publisher):
2639N/A return
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"]
2910N/A if keyvalue[:10] == "pkg.sizes.":
2910N/A # To reduce manifest bloat, size and csize
2910N/A # are set on a single action so need splitting
2910N/A # into separate attributes.
2910N/A attrval = action.attrlist("value")
2910N/A for entry in attrval:
2910N/A szname, szval = entry.split("=", 1)
2910N/A if szname == "sz":
2910N/A szname = "pkg.size"
2910N/A elif szname == "csz":
2910N/A szname = "pkg.csize"
2910N/A else:
2910N/A # Skip unknowns.
2910N/A continue
2910N/A
2910N/A self.attributes.setdefault(szname, 0)
2910N/A self.attributes[szname] += int(szval)
2910N/A return
2910N/A except (KeyError, TypeError, ValueError):
2910N/A # ignore broken set actions
956N/A pass
956N/A
2910N/A # Ensure facet and variant attributes are always lists.
2910N/A if keyvalue[:10] == "pkg.facet.":
2910N/A # Possible facets list is spread over multiple actions.
2910N/A val = action.attrlist("value")
2910N/A if len(val) == 1 and val[0] == "":
2910N/A # No facets.
2910N/A val = []
2910N/A
2910N/A seen = self.attributes.setdefault("pkg.facet", [])
2910N/A for f in val:
3158N/A entry = "facet.{0}".format(f)
2910N/A if entry not in seen:
2910N/A # Prevent duplicates; it's possible a
2910N/A # given facet may be valid for more than
2910N/A # one unique variant combination that's
2910N/A # allowed by current excludes.
2910N/A seen.append(f)
2910N/A return
2910N/A elif keyvalue == "pkg.variant":
2910N/A val = action.attrlist("value")
2910N/A if len(val) == 1 and val[0] == "":
2910N/A # No variants.
2910N/A val = []
2910N/A
2910N/A self.attributes[keyvalue] = [
3158N/A "variant.{0}".format(v)
2910N/A for v in val
2910N/A ]
2910N/A return
2910N/A elif keyvalue[:8] == "variant.":
2910N/A self.attributes[keyvalue] = action.attrlist("value")
2910N/A return
2910N/A
2910N/A if keyvalue == "fmri":
2910N/A # Ancient manifest compatibility.
2910N/A keyvalue = "pkg.fmri"
2910N/A self.attributes[keyvalue] = action.attrs["value"]
2910N/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
2704N/A try:
3339N/A file_handle = open(file_path, "r")
3171N/A except EnvironmentError as e:
2704N/A if e.errno != errno.ENOENT:
2704N/A raise
3158N/A log((_("{fp}:\n{e}").format(
3158N/A fp=file_path, e=e)))
2704N/A return {}
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)
3171N/A except actions.ActionError as e:
3158N/A log((_("{fp}:\n{e}").format(
3158N/A fp=file_path, e=e)))
1007N/A else:
2639N/A if not excludes or \
2639N/A action.include_this(excludes):
3194N/A if "path" in action.attrs:
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()
3171N/A except KeyError as k:
3158N/A log(_("{fp} contains "
1007N/A "an action which is"
1007N/A " missing the "
1007N/A "expected attribute"
3158N/A ": {at}.\nThe "
1007N/A "action is:"
3158N/A "{act}").format(
3158N/A fp=file_path,
3158N/A at=k.args[0],
3158N/A act=l
3158N/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
2962N/A # This must be an SHA-1 hash in order to interoperate with
2962N/A # older clients.
1516N/A sha_1 = hashlib.sha1()
3234N/A if isinstance(mfstcontent, six.text_type):
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)
3171N/A except EnvironmentError as 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)
3171N/A except EnvironmentError as 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
3339N/A mfile = os.fdopen(fd, "w")
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)
3171N/A except EnvironmentError as 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:
3158N/A raise ValueError(_("Attribute value '{0}' not 'true' or "
3158N/A "'false'".format(ret)))
1461N/A
1044N/A def get_size(self, excludes=EmptyI):
2910N/A """Returns an integer tuple of the form (size, csize), where
2910N/A 'size' represents the total uncompressed size, in bytes, of the
2910N/A Manifest's data payload, and 'csize' represents the compressed
2910N/A version of that.
1044N/A
2910N/A 'excludes' is a list of a list of variants and facets which
2910N/A should be allowed when calculating the total."""
1044N/A
2910N/A if self.excludes == excludes:
2910N/A excludes = EmptyI
2910N/A assert excludes == EmptyI or self.excludes == EmptyI
2910N/A
2910N/A csize = 0
1044N/A size = 0
2910N/A
2910N/A attrs = self.attributes
2910N/A if ("pkg.size" in attrs and "pkg.csize" in attrs) and \
2910N/A (excludes == EmptyI or self.excludes == excludes):
2910N/A # If specified excludes match loaded excludes, then use
2910N/A # cached attributes; this is safe as manifest attributes
2910N/A # are reset or updated every time exclude_content,
2910N/A # set_content, or add_action is called.
2910N/A return (attrs["pkg.size"], attrs["pkg.csize"])
2910N/A
2910N/A for a in self.gen_actions(excludes=excludes):
2286N/A size += a.get_size()
2910N/A csize += misc.get_pkg_otw_size(a)
2910N/A
2910N/A if excludes == EmptyI:
2910N/A # Cache for future calls.
2910N/A attrs["pkg.size"] = size
2910N/A attrs["pkg.csize"] = csize
2910N/A
2910N/A return (size, csize)
1044N/A
2910N/A def _get_varcets(self, excludes=EmptyI):
2910N/A """Private helper function to get list of facets/variants."""
2910N/A
2910N/A variants = defaultdict(set)
2910N/A facets = defaultdict(set)
2639N/A
2910N/A nexcludes = excludes
2910N/A if nexcludes:
2910N/A # Facet filtering should never be applied when excluding
2910N/A # actions; only variant filtering. This is ugly, but
2910N/A # our current variant/facet filtering system doesn't
2910N/A # allow you to be selective and various bits in
2910N/A # pkg.manifest assume you always filter on both so we
2910N/A # have to fake up a filter for facets.
3339N/A if six.PY2:
3339N/A nexcludes = [
3339N/A x for x in excludes
3339N/A if x.__func__ != facet._allow_facet
3339N/A ]
3339N/A else:
3339N/A nexcludes = [
3339N/A x for x in excludes
3339N/A if x.__func__ != facet.Facets.allow_action
3339N/A ]
3110N/A # Excludes list must always have zero or 2+ items; so
2910N/A # fake second entry.
3110N/A nexcludes.append(lambda x, publisher: True)
3110N/A assert len(nexcludes) > 1
2910N/A
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:
3234N/A for v, d in zip(v_list, repeat(variants)):
2910N/A d[v].add(attrs[v])
2910N/A
2910N/A if not excludes or action.include_this(
3110N/A nexcludes, publisher=self.publisher):
2910N/A # While variants are package level (you
2910N/A # can't install a package without
2910N/A # setting the variant first), facets
2910N/A # from the current action should only be
2910N/A # included if the action is not
2910N/A # excluded.
3234N/A for v, d in zip(f_list, repeat(facets)):
2639N/A d[v].add(attrs[v])
2639N/A except TypeError:
2639N/A # Lists can't be set elements.
2639N/A raise actions.InvalidActionError(action,
3158N/A _("{forv} '{v}' specified multiple times").format(
3158N/A forv=v.split(".", 1)[0], v=v))
2639N/A
2910N/A return (variants, facets)
3194N/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
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
3110N/A # Make sure that either no excludes were provided or 2+ excludes
3110N/A # were.
3110N/A assert len(self.excludes) != 1
2073N/A self.loaded = False
956N/A
956N/A # Do we have a cached copy?
2073N/A if not os.path.exists(self.pathname):
2814N/A if contents is None:
3194N/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 = {}
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
2910N/A # create per-action type cache; use rename to avoid corrupt
2910N/A # files if ^C'd in the middle. All action types are considered
2910N/A # so that empty cache files are created if no action of that
2910N/A # type exists for the package (avoids full manifest loads
2910N/A # later).
3234N/A for n, acts in six.iteritems(self.actions_bytype):
3158N/A t_prefix = "manifest.{0}.".format(n)
1386N/A
2910N/A try:
2910N/A fd, fn = tempfile.mkstemp(dir=t_dir,
2910N/A prefix=t_prefix)
3171N/A except EnvironmentError as e:
2910N/A raise apx._convert_error(e)
1386N/A
3339N/A f = os.fdopen(fd, "w")
2910N/A try:
2910N/A for a in acts:
3158N/A f.write("{0}\n".format(a))
2910N/A if n == "set":
2910N/A # Add supplemental action data; yes this
2910N/A # does mean the cache is not the same as
2910N/A # retrieved manifest, but that's ok.
2910N/A # Signature verification is done using
2910N/A # the raw manifest.
2910N/A f.writelines(self._gen_attrs_to_str())
3171N/A except EnvironmentError as e:
2910N/A raise apx._convert_error(e)
2910N/A finally:
2910N/A f.close()
2910N/A
2910N/A try:
2910N/A os.chmod(fn, PKG_FILE_MODE)
2910N/A portable.rename(fn,
3158N/A self.__cache_path("manifest.{0}".format(n)))
3171N/A except EnvironmentError as e:
2910N/A raise apx._convert_error(e)
1386N/A
2453N/A def create_cache(name, refs):
2453N/A try:
2453N/A fd, fn = tempfile.mkstemp(dir=t_dir,
2910N/A prefix=name + ".")
3339N/A with os.fdopen(fd, "w") as f:
2453N/A f.writelines(refs())
2453N/A os.chmod(fn, PKG_FILE_MODE)
2453N/A portable.rename(fn, self.__cache_path(name))
3171N/A except EnvironmentError as 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
3034N/A (excluding the manifest itself) and the cache_root if it is
3034N/A empty afterwards.
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))
3171N/A except EnvironmentError as e:
2035N/A if e.errno != errno.ENOENT:
2035N/A raise
3034N/A
3034N/A # Ensure cache dir is removed if the last cache file is
3034N/A # removed; we don't care if it fails.
3034N/A try:
3034N/A os.rmdir(cache_root)
3034N/A except:
3034N/A pass
3171N/A except EnvironmentError as 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)
2876N/A if os.path.exists(mpath):
2876N/A # we have cached copy on disk; use it
2876N/A try:
3339N/A with open(mpath, "r") as f:
2876N/A self._cache[name] = [
2876N/A a for a in
2876N/A (
2876N/A actions.fromstr(s.rstrip())
2876N/A for s in f
2876N/A )
2876N/A if not self.excludes or
3110N/A a.include_this(self.excludes,
3110N/A publisher=self.publisher)
2876N/A ]
2876N/A return
3171N/A except EnvironmentError as e:
2876N/A raise apx._convert_error(e)
3171N/A except actions.ActionError as e:
2876N/A # Cache file is malformed; hopefully due to bugs
2876N/A # that have been resolved (as opposed to actual
2876N/A # corruption). Assume we should just ignore the
2876N/A # cache and load action data.
2876N/A try:
2876N/A self.clear_cache(self.__cache_root)
3171N/A except Exception as e:
2876N/A # Ignore errors encountered during cache
2876N/A # dump for this specific case.
2876N/A pass
2339N/A
2876N/A # no cached copy
2876N/A if not self.loaded:
2876N/A # need to load from disk
2876N/A self.__load()
2876N/A assert self.loaded
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
3041N/A def gen_actions_by_type(self, atype, attr_match=None, 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,
3041N/A attr_match=attr_match, excludes=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,
3041N/A attr_match=attr_match, excludes=excludes):
956N/A yield a
2910N/A return
2910N/A
2910N/A if excludes == EmptyI:
2910N/A excludes = self.excludes
2910N/A assert excludes == self.excludes or self.excludes == EmptyI
956N/A
2910N/A if atype in self._absent_cache:
2910N/A # No such action in the manifest; must be done *after*
2910N/A # asserting excludes are correct to avoid hiding
2910N/A # failures.
2910N/A return
956N/A
2910N/A # Assume a cached copy exists; if not, tag the action type to
2910N/A # avoid pointless I/O later.
3158N/A mpath = self.__cache_path("manifest.{0}".format(atype))
2910N/A
3041N/A if attr_match:
3041N/A attr_match = _compile_fnpats(attr_match)
3041N/A
2910N/A try:
3339N/A with open(mpath, "r") as f:
2639N/A for l in f:
2639N/A a = actions.fromstr(l.rstrip())
3041N/A if (excludes and
3110N/A not a.include_this(excludes,
3110N/A publisher=self.publisher)):
3041N/A continue
3041N/A # These conditions are split by
3041N/A # performance.
3041N/A if not attr_match:
2639N/A yield a
3041N/A elif _attr_matches(a, attr_match):
3041N/A yield a
3041N/A
3171N/A except EnvironmentError as e:
2910N/A if e.errno == errno.ENOENT:
2910N/A self._absent_cache.append(atype)
2910N/A return # no such action in this manifest
2910N/A raise apx._convert_error(e)
956N/A
2910N/A def gen_facets(self, excludes=EmptyI, patterns=EmptyI):
2910N/A """A generator function that returns the supported facet
2910N/A attributes (strings) for this package based on the specified (or
2910N/A current) excludes that also match at least one of the patterns
2910N/A provided. Facets must be true or false so a list of possible
2910N/A facet values is not returned."""
2910N/A
2910N/A if not self.loaded and not self.__load_attributes():
2910N/A self.__load()
2910N/A return Manifest.gen_facets(self, excludes=excludes,
2910N/A patterns=patterns)
2910N/A
2910N/A def gen_variants(self, excludes=EmptyI, patterns=EmptyI):
2910N/A """A generator function that yields a list of tuples of the form
2910N/A (variant, [values]). Where 'variant' is the variant attribute
2910N/A name (e.g. 'variant.arch') and '[values]' is a list of the
2910N/A variant values supported by this package. Variants returned are
2910N/A those allowed by the specified (or current) excludes that also
2910N/A match at least one of the patterns provided."""
2910N/A
2910N/A if not self.loaded and not self.__load_attributes():
2910N/A self.__load()
2910N/A return Manifest.gen_variants(self, excludes=excludes,
2910N/A patterns=patterns)
2910N/A
2910N/A def gen_mediators(self, excludes=EmptyI):
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")
2910N/A return Manifest.gen_mediators(self, excludes=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
3339N/A with open(mpath, "r") as f:
2639N/A for l in f:
2639N/A a = actions.fromstr(l.rstrip())
2639N/A if not self.excludes or \
3110N/A a.include_this(self.excludes,
3110N/A publisher=self.publisher):
2639N/A self.fill_attributes(a)
2910N/A
1507N/A return True
1507N/A
2910N/A def get_size(self, excludes=EmptyI):
2910N/A """Returns an integer tuple of the form (size, csize), where
2910N/A 'size' represents the total uncompressed size, in bytes, of the
2910N/A Manifest's data payload, and 'csize' represents the compressed
2910N/A version of that.
2910N/A
2910N/A 'excludes' is a list of a list of variants and facets which
2910N/A should be allowed when calculating the total."""
2910N/A if not self.loaded and not self.__load_attributes():
2910N/A self.__load()
2910N/A return Manifest.get_size(self, excludes=excludes)
2910N/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
3041N/A def gen_actions(self, attr_match=None, excludes=EmptyI):
956N/A if not self.loaded:
956N/A self.__load()
3041N/A return Manifest.gen_actions(self, attr_match=attr_match,
3041N/A 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,
3445N/A self_exclude=EmptyI, pkgplan=None, cmp_policy=None):
956N/A if not self.loaded:
956N/A self.__load()
1507N/A return Manifest.difference(self, origin,
1507N/A origin_exclude=origin_exclude,
3402N/A self_exclude=self_exclude,
3402N/A pkgplan=pkgplan,
3445N/A cmp_policy=cmp_policy)
956N/A
2928N/A def store(self, mfst_path):
2928N/A """Store the manifest contents to disk."""
2928N/A if not self.loaded:
2928N/A self.__load()
2928N/A super(FactoredManifest, self).store(mfst_path)
2928N/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,
3445N/A self_exclude=EmptyI, pkgplan=None, cmp_policy=None):
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([], [],
3041N/A [(a, None) for a in origin.gen_actions(excludes=
3041N/A 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:
3158N/A ret.append("{0}\n{1}\n\n".format(*d))
2284N/A
2284N/A return "\n".join(ret)