manifest.py revision 1066
bcb4e51a409d94ae670de96afb8483a4f7855294Stephan Bosch#!/usr/bin/python2.4
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen#
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen# CDDL HEADER START
785d9cca224d33ca3938e9166784f6483e8a27d7Timo Sirainen#
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen# The contents of this file are subject to the terms of the
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen# Common Development and Distribution License (the "License").
32ee977e189266744ef69ac4e832fd3111d6f949Timo Sirainen# You may not use this file except in compliance with the License.
0b4e1043e596bfb36d999dacbf1d4d63ee96d75fTimo Sirainen#
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen# or http://www.opensolaris.org/os/licensing.
785d9cca224d33ca3938e9166784f6483e8a27d7Timo Sirainen# See the License for the specific language governing permissions
6fe91298731abf0b70dfd796ecc30d3be81fa5d1Timo Sirainen# and limitations under the License.
5fb3bff645380804c9db2510940c41db6b8fdb01Timo Sirainen#
436adac819e7cbeef04af08dcc6a4f3ecd4a1d9eMartti Rannanjärvi# When distributing Covered Code, include this CDDL HEADER in each
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
f6f021c133f680cf3d559187524fd9abcbaae9b9Timo Sirainen# If applicable, add the following below this CDDL HEADER, with the
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen# fields enclosed by brackets "[]" replaced with your own identifying
73a552a9ed06cd6017ad4ee4b252a8b38c8ac42dTimo Sirainen# information: Portions Copyright [yyyy] [name of copyright owner]
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen#
73a552a9ed06cd6017ad4ee4b252a8b38c8ac42dTimo Sirainen# CDDL HEADER END
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen#
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen
7ccdf78cd45aea9d14e048a5b9f077515c71978fTimo Sirainen#
7ccdf78cd45aea9d14e048a5b9f077515c71978fTimo Sirainen# Copyright 2009 Sun Microsystems, Inc. All rights reserved.
785d9cca224d33ca3938e9166784f6483e8a27d7Timo Sirainen# Use is subject to license terms.
785d9cca224d33ca3938e9166784f6483e8a27d7Timo Sirainen#
e0cc94d52184944c5da76b509da184e8449b3a5cTimo Sirainen
e0cc94d52184944c5da76b509da184e8449b3a5cTimo Sirainenimport os
e0cc94d52184944c5da76b509da184e8449b3a5cTimo Sirainenimport errno
785d9cca224d33ca3938e9166784f6483e8a27d7Timo Sirainenfrom itertools import groupby, chain, repeat
785d9cca224d33ca3938e9166784f6483e8a27d7Timo Sirainenfrom pkg.misc import EmptyI, expanddirs
785d9cca224d33ca3938e9166784f6483e8a27d7Timo Sirainen
785d9cca224d33ca3938e9166784f6483e8a27d7Timo Sirainenimport pkg.actions as actions
785d9cca224d33ca3938e9166784f6483e8a27d7Timo Sirainenimport pkg.portable as portable
785d9cca224d33ca3938e9166784f6483e8a27d7Timo Sirainenfrom pkg.actions.attribute import AttributeAction
e0cc94d52184944c5da76b509da184e8449b3a5cTimo Sirainen
785d9cca224d33ca3938e9166784f6483e8a27d7Timo Sirainenclass Manifest(object):
785d9cca224d33ca3938e9166784f6483e8a27d7Timo Sirainen """A Manifest is the representation of the actions composing a specific
785d9cca224d33ca3938e9166784f6483e8a27d7Timo Sirainen package version on both the client and the repository. Both purposes
785d9cca224d33ca3938e9166784f6483e8a27d7Timo Sirainen utilize the same storage format.
785d9cca224d33ca3938e9166784f6483e8a27d7Timo Sirainen
785d9cca224d33ca3938e9166784f6483e8a27d7Timo Sirainen The serialized structure of a manifest is an unordered list of actions.
785d9cca224d33ca3938e9166784f6483e8a27d7Timo Sirainen
785d9cca224d33ca3938e9166784f6483e8a27d7Timo Sirainen The special action, "set", represents a package attribute.
785d9cca224d33ca3938e9166784f6483e8a27d7Timo Sirainen
785d9cca224d33ca3938e9166784f6483e8a27d7Timo Sirainen The reserved attribute, "fmri", represents the package and version
785d9cca224d33ca3938e9166784f6483e8a27d7Timo Sirainen described by this manifest. It is available as a string via the
a10ed8c47534b4c6b6bf2711ccfe577e720a47b4Timo Sirainen attributes dictionary, and as an FMRI object from the fmri member.
785d9cca224d33ca3938e9166784f6483e8a27d7Timo Sirainen
785d9cca224d33ca3938e9166784f6483e8a27d7Timo Sirainen The list of manifest-wide reserved attributes is
785d9cca224d33ca3938e9166784f6483e8a27d7Timo Sirainen
d22301419109ed4a38351715e6760011421dadecTimo Sirainen base_directory Default base directory, for non-user images.
a10ed8c47534b4c6b6bf2711ccfe577e720a47b4Timo Sirainen fmri Package FMRI.
a10ed8c47534b4c6b6bf2711ccfe577e720a47b4Timo Sirainen isa Package is intended for a list of ISAs.
785d9cca224d33ca3938e9166784f6483e8a27d7Timo Sirainen platform Package is intended for a list of platforms.
785d9cca224d33ca3938e9166784f6483e8a27d7Timo Sirainen relocatable Suitable for User Image.
e0cc94d52184944c5da76b509da184e8449b3a5cTimo Sirainen
e0cc94d52184944c5da76b509da184e8449b3a5cTimo Sirainen All non-prefixed attributes are reserved to the framework. Third
e0cc94d52184944c5da76b509da184e8449b3a5cTimo Sirainen parties may prefix their attributes with a reversed domain name, domain
e0cc94d52184944c5da76b509da184e8449b3a5cTimo Sirainen name, or stock symbol. An example might be
e0cc94d52184944c5da76b509da184e8449b3a5cTimo Sirainen
e0cc94d52184944c5da76b509da184e8449b3a5cTimo Sirainen com.example,supported
817d027593510c3ba70ad542ce0011f5f6916d1eTimo Sirainen
e0cc94d52184944c5da76b509da184e8449b3a5cTimo Sirainen as an indicator that a specific package version is supported by the
e0cc94d52184944c5da76b509da184e8449b3a5cTimo Sirainen vendor, example.com.
e0cc94d52184944c5da76b509da184e8449b3a5cTimo Sirainen
e0cc94d52184944c5da76b509da184e8449b3a5cTimo Sirainen manifest.null is provided as the null manifest. Differences against the
e0cc94d52184944c5da76b509da184e8449b3a5cTimo Sirainen null manifest result in the complete set of attributes and actions of
e0cc94d52184944c5da76b509da184e8449b3a5cTimo Sirainen the non-null manifest, meaning that all operations can be viewed as
e0cc94d52184944c5da76b509da184e8449b3a5cTimo Sirainen tranitions between the manifest being installed and the manifest already
785d9cca224d33ca3938e9166784f6483e8a27d7Timo Sirainen present in the image (which may be the null manifest).
785d9cca224d33ca3938e9166784f6483e8a27d7Timo Sirainen """
785d9cca224d33ca3938e9166784f6483e8a27d7Timo Sirainen
b5e6f6f27c1461f0f9f202615eeb738a645188c3Timo Sirainen def __init__(self):
00efa7d99981e18e286c02b18c1163dde18ee521Timo Sirainen self.img = None
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen self.fmri = None
785d9cca224d33ca3938e9166784f6483e8a27d7Timo Sirainen
785d9cca224d33ca3938e9166784f6483e8a27d7Timo Sirainen self.actions = []
785d9cca224d33ca3938e9166784f6483e8a27d7Timo Sirainen self.actions_bytype = {}
641f0c0900ee6e7cf9667f4b40ed95cec7d0cdcaTimo Sirainen self.variants = {} # variants seen in package
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen self.facets = {} # facets seen in package
6013fbad6638795a00e6c2a2dd2cdbee19612494Timo Sirainen self.attributes = {} # package-wide attributes
7ede6554e451ec039a67beec7d6ee4aff61d386eTimo Sirainen
7ede6554e451ec039a67beec7d6ee4aff61d386eTimo Sirainen def __str__(self):
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen r = ""
785d9cca224d33ca3938e9166784f6483e8a27d7Timo Sirainen if "fmri" not in self.attributes and self.fmri != None:
785d9cca224d33ca3938e9166784f6483e8a27d7Timo Sirainen r += "set name=fmri value=%s\n" % self.fmri
e0cc94d52184944c5da76b509da184e8449b3a5cTimo Sirainen
e0cc94d52184944c5da76b509da184e8449b3a5cTimo Sirainen for act in sorted(self.actions):
785d9cca224d33ca3938e9166784f6483e8a27d7Timo Sirainen r += "%s\n" % act
1235bbd139a1ff97f641fa0e77205eb9adbb0400Timo Sirainen return r
1235bbd139a1ff97f641fa0e77205eb9adbb0400Timo Sirainen
1235bbd139a1ff97f641fa0e77205eb9adbb0400Timo Sirainen def tostr_unsorted(self):
785d9cca224d33ca3938e9166784f6483e8a27d7Timo Sirainen r = ""
2bde8972f2dcec46d96543407cc5b56954054359Timo Sirainen if "fmri" not in self.attributes and self.fmri != None:
641f0c0900ee6e7cf9667f4b40ed95cec7d0cdcaTimo Sirainen r += "set name=fmri value=%s\n" % self.fmri
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen
ca98892a6b8a30ffc1fe26fcf02c7d59e3204e7eTimo Sirainen for act in self.actions:
ca98892a6b8a30ffc1fe26fcf02c7d59e3204e7eTimo Sirainen r += "%s\n" % act
785d9cca224d33ca3938e9166784f6483e8a27d7Timo Sirainen return r
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen
2bde8972f2dcec46d96543407cc5b56954054359Timo Sirainen
2bde8972f2dcec46d96543407cc5b56954054359Timo Sirainen def difference(self, origin, origin_exclude=EmptyI,
2bde8972f2dcec46d96543407cc5b56954054359Timo Sirainen self_exclude=EmptyI):
2bde8972f2dcec46d96543407cc5b56954054359Timo Sirainen """Return three lists of action pairs representing origin and
2bde8972f2dcec46d96543407cc5b56954054359Timo Sirainen destination actions. The first list contains the pairs
2bde8972f2dcec46d96543407cc5b56954054359Timo Sirainen representing additions, the second list contains the pairs
2bde8972f2dcec46d96543407cc5b56954054359Timo Sirainen representing updates, and the third list contains the pairs
2bde8972f2dcec46d96543407cc5b56954054359Timo Sirainen representing removals. All three lists are in the order in
a8c1d873ebe624cf65893d79e1a509203116cb9aTimo Sirainen which they should be executed."""
a8c1d873ebe624cf65893d79e1a509203116cb9aTimo Sirainen # XXX Do we need to find some way to assert that the keys are
a8c1d873ebe624cf65893d79e1a509203116cb9aTimo Sirainen # all unique?
a8c1d873ebe624cf65893d79e1a509203116cb9aTimo Sirainen
a8c1d873ebe624cf65893d79e1a509203116cb9aTimo Sirainen if isinstance(origin, EmptyCachedManifest):
a8c1d873ebe624cf65893d79e1a509203116cb9aTimo Sirainen # No origin was provided, so nothing has been changed or
2bde8972f2dcec46d96543407cc5b56954054359Timo Sirainen # removed; only added. In addition, this doesn't need
2f2823ad8928654ed405467c6c1f4fd4c6f5cf7cTimo Sirainen # to be sorted since the caller likely already does
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen # (such as pkgplan/imageplan).
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen return (
825f6569a5276488133796c2f529c65128a09ba0Timo Sirainen [(None, a) for a in self.gen_actions(self_exclude)],
b0ab23e6fd425bf864cdc9fdab0631573d4d1b5fTimo Sirainen [], [])
825f6569a5276488133796c2f529c65128a09ba0Timo Sirainen
b0ab23e6fd425bf864cdc9fdab0631573d4d1b5fTimo Sirainen sdict = dict(
825f6569a5276488133796c2f529c65128a09ba0Timo Sirainen ((a.name, a.attrs.get(a.key_attr, id(a))), a)
b0ab23e6fd425bf864cdc9fdab0631573d4d1b5fTimo Sirainen for a in self.gen_actions(self_exclude)
825f6569a5276488133796c2f529c65128a09ba0Timo Sirainen )
b0ab23e6fd425bf864cdc9fdab0631573d4d1b5fTimo Sirainen odict = dict(
b0ab23e6fd425bf864cdc9fdab0631573d4d1b5fTimo Sirainen ((a.name, a.attrs.get(a.key_attr, id(a))), a)
7fc0f80480063a9d4cb9e8c07b50db2a5627799eTimo Sirainen for a in origin.gen_actions(origin_exclude)
d4002fe1f64d25a792f76fb102ef7dc519cd4e24Martti Rannanjärvi )
b0ab23e6fd425bf864cdc9fdab0631573d4d1b5fTimo Sirainen
825f6569a5276488133796c2f529c65128a09ba0Timo Sirainen sset = set(sdict.keys())
b0ab23e6fd425bf864cdc9fdab0631573d4d1b5fTimo Sirainen oset = set(odict.keys())
d4002fe1f64d25a792f76fb102ef7dc519cd4e24Martti Rannanjärvi
b0ab23e6fd425bf864cdc9fdab0631573d4d1b5fTimo Sirainen added = [(None, sdict[i]) for i in sset - oset]
825f6569a5276488133796c2f529c65128a09ba0Timo Sirainen removed = [(odict[i], None) for i in oset - sset]
b0ab23e6fd425bf864cdc9fdab0631573d4d1b5fTimo Sirainen # XXX for now, we force license actions to always be
d4002fe1f64d25a792f76fb102ef7dc519cd4e24Martti Rannanjärvi # different to insure that existing license files for
b0ab23e6fd425bf864cdc9fdab0631573d4d1b5fTimo Sirainen # new versions are always installed
825f6569a5276488133796c2f529c65128a09ba0Timo Sirainen changed = [
825f6569a5276488133796c2f529c65128a09ba0Timo Sirainen (odict[i], sdict[i])
825f6569a5276488133796c2f529c65128a09ba0Timo Sirainen for i in oset & sset
825f6569a5276488133796c2f529c65128a09ba0Timo Sirainen if odict[i].different(sdict[i]) or i[0] == "license"
59151b71059df1190acd75d8717ed04a7920c862Timo Sirainen ]
b5e6f6f27c1461f0f9f202615eeb738a645188c3Timo Sirainen
00efa7d99981e18e286c02b18c1163dde18ee521Timo Sirainen # XXX Do changed actions need to be sorted at all? This is
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen # likely to be the largest list, so we might save significant
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen # time by not sorting. Should we sort above? Insert into a
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen # sorted list?
785d9cca224d33ca3938e9166784f6483e8a27d7Timo Sirainen
785d9cca224d33ca3938e9166784f6483e8a27d7Timo Sirainen # singlesort = lambda x: x[0] or x[1]
785d9cca224d33ca3938e9166784f6483e8a27d7Timo Sirainen addsort = lambda x: x[1]
a8c1d873ebe624cf65893d79e1a509203116cb9aTimo Sirainen remsort = lambda x: x[0]
a8c1d873ebe624cf65893d79e1a509203116cb9aTimo Sirainen removed.sort(key = remsort, reverse = True)
a8c1d873ebe624cf65893d79e1a509203116cb9aTimo Sirainen added.sort(key = addsort)
a8c1d873ebe624cf65893d79e1a509203116cb9aTimo Sirainen changed.sort(key = addsort)
a8c1d873ebe624cf65893d79e1a509203116cb9aTimo Sirainen
7ccdf78cd45aea9d14e048a5b9f077515c71978fTimo Sirainen return (added, changed, removed)
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen @staticmethod
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen def comm(*compare_m):
f7ad1162969feff6b08f0e640a928db1783daae9Timo Sirainen """Like the unix utility comm, except that this function
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen takes an arbitrary number of manifests and compares them,
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen returning a tuple consisting of each manifest's actions
785d9cca224d33ca3938e9166784f6483e8a27d7Timo Sirainen that are not the same for all manifests, followed by a
785d9cca224d33ca3938e9166784f6483e8a27d7Timo Sirainen list of actions that are the same in each manifest."""
785d9cca224d33ca3938e9166784f6483e8a27d7Timo Sirainen
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen # construct list of dictionaries of actions in each
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen # manifest, indexed by unique keys
785d9cca224d33ca3938e9166784f6483e8a27d7Timo Sirainen m_dicts = [
b0ab23e6fd425bf864cdc9fdab0631573d4d1b5fTimo Sirainen dict(
785d9cca224d33ca3938e9166784f6483e8a27d7Timo Sirainen ((a.name, a.attrs.get(a.key_attr, id(a))), a)
039e42997fe5e0d1c5ad9306dc0ae69bf0e1ca10Timo Sirainen for a in m.actions)
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen for m in compare_m
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen ]
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen # construct list of key sets in each dict
490f66d6476d51cc02333d6eb398a5cd94b67f48Timo Sirainen #
0b4e1043e596bfb36d999dacbf1d4d63ee96d75fTimo Sirainen m_sets = [
490f66d6476d51cc02333d6eb398a5cd94b67f48Timo Sirainen set(m.keys())
ca98892a6b8a30ffc1fe26fcf02c7d59e3204e7eTimo Sirainen for m in m_dicts
94f84d1c3f786d1b92dd2a1507f83a2dad887c56Timo Sirainen ]
490f66d6476d51cc02333d6eb398a5cd94b67f48Timo Sirainen
ca98892a6b8a30ffc1fe26fcf02c7d59e3204e7eTimo Sirainen common_keys = reduce(lambda a, b: a & b, m_sets)
ca98892a6b8a30ffc1fe26fcf02c7d59e3204e7eTimo Sirainen
490f66d6476d51cc02333d6eb398a5cd94b67f48Timo Sirainen # determine which common_keys have common actions
490f66d6476d51cc02333d6eb398a5cd94b67f48Timo Sirainen for k in common_keys.copy():
490f66d6476d51cc02333d6eb398a5cd94b67f48Timo Sirainen for i in range(len(m_dicts) - 1):
490f66d6476d51cc02333d6eb398a5cd94b67f48Timo Sirainen if m_dicts[i][k].different(
490f66d6476d51cc02333d6eb398a5cd94b67f48Timo Sirainen m_dicts[i + 1][k]):
490f66d6476d51cc02333d6eb398a5cd94b67f48Timo Sirainen common_keys.remove(k)
490f66d6476d51cc02333d6eb398a5cd94b67f48Timo Sirainen break
0b4e1043e596bfb36d999dacbf1d4d63ee96d75fTimo Sirainen return tuple(
0b4e1043e596bfb36d999dacbf1d4d63ee96d75fTimo Sirainen [
490f66d6476d51cc02333d6eb398a5cd94b67f48Timo Sirainen [m_dicts[i][k] for k in m_sets[i] - common_keys]
490f66d6476d51cc02333d6eb398a5cd94b67f48Timo Sirainen for i in range(len(m_dicts))
490f66d6476d51cc02333d6eb398a5cd94b67f48Timo Sirainen ]
490f66d6476d51cc02333d6eb398a5cd94b67f48Timo Sirainen +
490f66d6476d51cc02333d6eb398a5cd94b67f48Timo Sirainen [
490f66d6476d51cc02333d6eb398a5cd94b67f48Timo Sirainen [ m_dicts[0][k] for k in common_keys ]
94f84d1c3f786d1b92dd2a1507f83a2dad887c56Timo Sirainen ]
94f84d1c3f786d1b92dd2a1507f83a2dad887c56Timo Sirainen )
94f84d1c3f786d1b92dd2a1507f83a2dad887c56Timo Sirainen
490f66d6476d51cc02333d6eb398a5cd94b67f48Timo Sirainen
94f84d1c3f786d1b92dd2a1507f83a2dad887c56Timo Sirainen def combined_difference(self, origin, ov=EmptyI, sv=EmptyI):
94f84d1c3f786d1b92dd2a1507f83a2dad887c56Timo Sirainen """Where difference() returns three lists, combined_difference()
490f66d6476d51cc02333d6eb398a5cd94b67f48Timo Sirainen returns a single list of the concatenation of the three."""
d4002fe1f64d25a792f76fb102ef7dc519cd4e24Martti Rannanjärvi return list(chain(*self.difference(origin, ov, sv)))
490f66d6476d51cc02333d6eb398a5cd94b67f48Timo Sirainen
490f66d6476d51cc02333d6eb398a5cd94b67f48Timo Sirainen def humanized_differences(self, other, ov=EmptyI, sv=EmptyI):
490f66d6476d51cc02333d6eb398a5cd94b67f48Timo Sirainen """Output expects that self is newer than other. Use of sets
490f66d6476d51cc02333d6eb398a5cd94b67f48Timo Sirainen requires that we convert the action objects into some marshalled
1d2b188f0eedc3cab6e27ceac5425a037f38042eTimo Sirainen form, otherwise set member identities are derived from the
32ee977e189266744ef69ac4e832fd3111d6f949Timo Sirainen object pointers, rather than the contents."""
32ee977e189266744ef69ac4e832fd3111d6f949Timo Sirainen
32ee977e189266744ef69ac4e832fd3111d6f949Timo Sirainen l = self.difference(other, ov, sv)
0b4e1043e596bfb36d999dacbf1d4d63ee96d75fTimo Sirainen out = ""
32ee977e189266744ef69ac4e832fd3111d6f949Timo Sirainen
32ee977e189266744ef69ac4e832fd3111d6f949Timo Sirainen for src, dest in chain(*l):
ce8244c05b9f89d7729a5abe6f4838c1dce46c64Timo Sirainen if not src:
32ee977e189266744ef69ac4e832fd3111d6f949Timo Sirainen out += "+ %s\n" % str(dest)
32ee977e189266744ef69ac4e832fd3111d6f949Timo Sirainen elif not dest:
0b4e1043e596bfb36d999dacbf1d4d63ee96d75fTimo Sirainen out += "- %s\n" + str(src)
0b4e1043e596bfb36d999dacbf1d4d63ee96d75fTimo Sirainen else:
ca98892a6b8a30ffc1fe26fcf02c7d59e3204e7eTimo Sirainen out += "%s -> %s\n" % (src, dest)
ca98892a6b8a30ffc1fe26fcf02c7d59e3204e7eTimo Sirainen return out
0b4e1043e596bfb36d999dacbf1d4d63ee96d75fTimo Sirainen
9fc97c8aa8190df87624d214bcc5d0b5362bec93Timo Sirainen def gen_actions(self, excludes=EmptyI):
9fc97c8aa8190df87624d214bcc5d0b5362bec93Timo Sirainen """Generate actions in manifest through ordered callable list"""
9fc97c8aa8190df87624d214bcc5d0b5362bec93Timo Sirainen for a in self.actions:
9fc97c8aa8190df87624d214bcc5d0b5362bec93Timo Sirainen for c in excludes:
9fc97c8aa8190df87624d214bcc5d0b5362bec93Timo Sirainen if not c(a):
9fc97c8aa8190df87624d214bcc5d0b5362bec93Timo Sirainen break
9fc97c8aa8190df87624d214bcc5d0b5362bec93Timo Sirainen else:
9fc97c8aa8190df87624d214bcc5d0b5362bec93Timo Sirainen yield a
ce8244c05b9f89d7729a5abe6f4838c1dce46c64Timo Sirainen
ce8244c05b9f89d7729a5abe6f4838c1dce46c64Timo Sirainen def gen_actions_by_type(self, atype, excludes=EmptyI):
32ee977e189266744ef69ac4e832fd3111d6f949Timo Sirainen """Generate actions in the manifest of type "type"
ce8244c05b9f89d7729a5abe6f4838c1dce46c64Timo Sirainen through ordered callable list"""
32ee977e189266744ef69ac4e832fd3111d6f949Timo Sirainen for a in self.actions_bytype.get(atype, []):
ce8244c05b9f89d7729a5abe6f4838c1dce46c64Timo Sirainen for c in excludes:
32ee977e189266744ef69ac4e832fd3111d6f949Timo Sirainen if not c(a):
32ee977e189266744ef69ac4e832fd3111d6f949Timo Sirainen break
d4002fe1f64d25a792f76fb102ef7dc519cd4e24Martti Rannanjärvi else:
32ee977e189266744ef69ac4e832fd3111d6f949Timo Sirainen yield a
32ee977e189266744ef69ac4e832fd3111d6f949Timo Sirainen
0b4e1043e596bfb36d999dacbf1d4d63ee96d75fTimo Sirainen def gen_key_attribute_value_by_type(self, atype, excludes=EmptyI):
32ee977e189266744ef69ac4e832fd3111d6f949Timo Sirainen """Generate the value of the key atrribute for each action
32ee977e189266744ef69ac4e832fd3111d6f949Timo Sirainen of type "type" in the manifest."""
32ee977e189266744ef69ac4e832fd3111d6f949Timo Sirainen
32ee977e189266744ef69ac4e832fd3111d6f949Timo Sirainen return (
32ee977e189266744ef69ac4e832fd3111d6f949Timo Sirainen a.attrs.get(a.key_attr)
1d2b188f0eedc3cab6e27ceac5425a037f38042eTimo Sirainen for a in self.gen_actions_by_type(atype, excludes)
32ee977e189266744ef69ac4e832fd3111d6f949Timo Sirainen )
32ee977e189266744ef69ac4e832fd3111d6f949Timo Sirainen
eddd9bf1a1369aea4a2715f6be1137da6d17d293Timo Sirainen def duplicates(self, excludes=EmptyI):
32ee977e189266744ef69ac4e832fd3111d6f949Timo Sirainen """Find actions in the manifest which are duplicates (i.e.,
ca98892a6b8a30ffc1fe26fcf02c7d59e3204e7eTimo Sirainen represent the same object) but which are not identical (i.e.,
32ee977e189266744ef69ac4e832fd3111d6f949Timo Sirainen have all the same attributes)."""
32ee977e189266744ef69ac4e832fd3111d6f949Timo Sirainen
32ee977e189266744ef69ac4e832fd3111d6f949Timo Sirainen def fun(a):
d4002fe1f64d25a792f76fb102ef7dc519cd4e24Martti Rannanjärvi """Return a key on which actions can be sorted."""
ca98892a6b8a30ffc1fe26fcf02c7d59e3204e7eTimo Sirainen return a.name, a.attrs.get(a.key_attr, id(a))
32ee977e189266744ef69ac4e832fd3111d6f949Timo Sirainen
6cfb1377b2c034cfc4bd37361cc1520f2d8acd6dTimo Sirainen alldups = []
32ee977e189266744ef69ac4e832fd3111d6f949Timo Sirainen acts = [a for a in self.gen_actions(excludes)]
32ee977e189266744ef69ac4e832fd3111d6f949Timo Sirainen
32ee977e189266744ef69ac4e832fd3111d6f949Timo Sirainen for k, g in groupby(sorted(acts, key=fun), fun):
19e8adccba16ff419f5675b1575358c2956dce83Timo Sirainen glist = list(g)
1d2b188f0eedc3cab6e27ceac5425a037f38042eTimo Sirainen dups = set()
19e8adccba16ff419f5675b1575358c2956dce83Timo Sirainen for i in range(len(glist) - 1):
6cfb1377b2c034cfc4bd37361cc1520f2d8acd6dTimo Sirainen if glist[i].different(glist[i + 1]):
32ee977e189266744ef69ac4e832fd3111d6f949Timo Sirainen dups.add(glist[i])
f6f021c133f680cf3d559187524fd9abcbaae9b9Timo Sirainen dups.add(glist[i + 1])
f6f021c133f680cf3d559187524fd9abcbaae9b9Timo Sirainen if dups:
f6f021c133f680cf3d559187524fd9abcbaae9b9Timo Sirainen alldups.append((k, dups))
f6f021c133f680cf3d559187524fd9abcbaae9b9Timo Sirainen return alldups
f6f021c133f680cf3d559187524fd9abcbaae9b9Timo Sirainen
f6f021c133f680cf3d559187524fd9abcbaae9b9Timo Sirainen def set_fmri(self, img, fmri):
f6f021c133f680cf3d559187524fd9abcbaae9b9Timo Sirainen self.img = img
f6f021c133f680cf3d559187524fd9abcbaae9b9Timo Sirainen self.fmri = fmri
f6f021c133f680cf3d559187524fd9abcbaae9b9Timo Sirainen
f6f021c133f680cf3d559187524fd9abcbaae9b9Timo Sirainen def set_content(self, content, excludes=EmptyI):
f6f021c133f680cf3d559187524fd9abcbaae9b9Timo Sirainen """content is the text representation of the manifest"""
f6f021c133f680cf3d559187524fd9abcbaae9b9Timo Sirainen self.actions = []
f6f021c133f680cf3d559187524fd9abcbaae9b9Timo Sirainen self.actions_bytype = {}
f6f021c133f680cf3d559187524fd9abcbaae9b9Timo Sirainen self.variants = {}
f6f021c133f680cf3d559187524fd9abcbaae9b9Timo Sirainen self.facets = {}
f6f021c133f680cf3d559187524fd9abcbaae9b9Timo Sirainen self.attributes = {}
f6f021c133f680cf3d559187524fd9abcbaae9b9Timo Sirainen
f6f021c133f680cf3d559187524fd9abcbaae9b9Timo Sirainen # So we could build up here the type/key_attr dictionaries like
f6f021c133f680cf3d559187524fd9abcbaae9b9Timo Sirainen # sdict and odict in difference() above, and have that be our
f6f021c133f680cf3d559187524fd9abcbaae9b9Timo Sirainen # main datastore, rather than the simple list we have now. If
f6f021c133f680cf3d559187524fd9abcbaae9b9Timo Sirainen # we do that here, we can even assert that the "same" action
f6f021c133f680cf3d559187524fd9abcbaae9b9Timo Sirainen # can't be in a manifest twice. (The problem of having the same
f6f021c133f680cf3d559187524fd9abcbaae9b9Timo Sirainen # action more than once in packages that can be installed
f6f021c133f680cf3d559187524fd9abcbaae9b9Timo Sirainen # together has to be solved somewhere else, though.)
f6f021c133f680cf3d559187524fd9abcbaae9b9Timo Sirainen for l in content.splitlines():
f6f021c133f680cf3d559187524fd9abcbaae9b9Timo Sirainen l = l.lstrip()
f6f021c133f680cf3d559187524fd9abcbaae9b9Timo Sirainen if not l or l[0] == "#":
f6f021c133f680cf3d559187524fd9abcbaae9b9Timo Sirainen continue
f6f021c133f680cf3d559187524fd9abcbaae9b9Timo Sirainen
f6f021c133f680cf3d559187524fd9abcbaae9b9Timo Sirainen try:
f6f021c133f680cf3d559187524fd9abcbaae9b9Timo Sirainen action = actions.fromstr(l)
f6f021c133f680cf3d559187524fd9abcbaae9b9Timo Sirainen except actions.ActionError, e:
f6f021c133f680cf3d559187524fd9abcbaae9b9Timo Sirainen # Add the FMRI to the exception and re-raise
f6f021c133f680cf3d559187524fd9abcbaae9b9Timo Sirainen e.fmri = self.fmri
f6f021c133f680cf3d559187524fd9abcbaae9b9Timo Sirainen raise
f6f021c133f680cf3d559187524fd9abcbaae9b9Timo Sirainen
f6f021c133f680cf3d559187524fd9abcbaae9b9Timo Sirainen if action.name == "set" and \
f6f021c133f680cf3d559187524fd9abcbaae9b9Timo Sirainen action.attrs["name"] == "authority":
f6f021c133f680cf3d559187524fd9abcbaae9b9Timo Sirainen # Translate old action to new.
f6f021c133f680cf3d559187524fd9abcbaae9b9Timo Sirainen action.attrs["name"] = "publisher"
f6f021c133f680cf3d559187524fd9abcbaae9b9Timo Sirainen
f6f021c133f680cf3d559187524fd9abcbaae9b9Timo Sirainen if action.attrs.has_key("path"):
f6f021c133f680cf3d559187524fd9abcbaae9b9Timo Sirainen np = action.attrs["path"].lstrip(os.path.sep)
f6f021c133f680cf3d559187524fd9abcbaae9b9Timo Sirainen action.attrs["path"] = np
f6f021c133f680cf3d559187524fd9abcbaae9b9Timo Sirainen
f6f021c133f680cf3d559187524fd9abcbaae9b9Timo Sirainen if not action.include_this(excludes):
f6f021c133f680cf3d559187524fd9abcbaae9b9Timo Sirainen continue
f6f021c133f680cf3d559187524fd9abcbaae9b9Timo Sirainen
f6f021c133f680cf3d559187524fd9abcbaae9b9Timo Sirainen self.actions.append(action)
f6f021c133f680cf3d559187524fd9abcbaae9b9Timo Sirainen
f6f021c133f680cf3d559187524fd9abcbaae9b9Timo Sirainen if action.name not in self.actions_bytype:
self.actions_bytype[action.name] = [ action ]
else:
self.actions_bytype[action.name].append(action)
# add any set actions to attributes
if action.name == "set":
self.fill_attributes(action)
# append any variants and facets to manifest dict
v_list, f_list = action.get_varcet_keys()
if v_list or f_list:
for v, d in zip(v_list, repeat(self.variants)) \
+ zip(f_list, repeat(self.facets)):
if v not in d:
d[v] = set([action.attrs[v]])
else:
d[v].add(action.attrs[v])
return
def fill_attributes(self, action):
"""Fill attribute array w/ set action contents."""
try:
keyvalue = action.attrs["name"]
if keyvalue not in self.attributes:
self.attributes[keyvalue] = \
action.attrs["value"]
except KeyError: # ignore broken set actions
pass
@staticmethod
def search_dict(file_path, excludes, return_line=False,
log=None):
"""Produces the search dictionary for a specific manifest.
A dictionary is constructed which maps a tuple of token,
action type, key, and the value that matched the token to
the byte offset into the manifest file. file_path is the
path to the manifest file. excludes is the variants which
should be allowed in this image. return_line is a debugging
flag which makes the function map the information to the
string of the line, rather than the byte offset to allow
easier debugging."""
if log is None:
log = lambda x: None
file_handle = file(file_path)
cur_pos = 0
line = file_handle.readline()
action_dict = {}
def __handle_list(lst, cp):
for action_name, subtype, tok, full_value in lst:
if action_name == "set":
if full_value is None:
full_value = tok
else:
if full_value is None:
full_value = subtype
if full_value is None:
full_value = action_name
if isinstance(tok, list):
__handle_list([
(action_name, subtype, t,
full_value)
for t in tok
], cp)
else:
if (tok, action_name, subtype,
full_value) in action_dict:
action_dict[(tok, action_name,
subtype, full_value)
].append(cp)
else:
action_dict[(tok, action_name,
subtype, full_value)] = [cp]
while line:
l = line.strip()
if l and l[0] != "#":
try:
action = actions.fromstr(l)
except actions.MalformedActionError, e:
log((_("%(fp)s:\n%(e)s") %
{ "fp": file_path, "e": e }))
else:
if action.include_this(excludes):
if action.attrs.has_key("path"):
np = action.attrs["path"].lstrip(os.path.sep)
action.attrs["path"] = \
np
try:
inds = action.generate_indices()
except KeyError, k:
log(_("%(fp)s contains "
"an action which is"
" missing the "
"expected attribute"
": %(at)s.\nThe "
"action is:"
"%(act)s") %
{
"fp": file_path,
"at": k.args[0],
"act":l
})
else:
arg = cur_pos
if return_line:
arg = l
__handle_list(inds, arg)
cur_pos = file_handle.tell()
line = file_handle.readline()
file_handle.close()
return action_dict
def store(self, mfst_path):
"""Store the manifest contents to disk."""
tmp_path = mfst_path + ".tmp"
try:
mfile = file(tmp_path, "w")
except IOError:
try:
os.makedirs(os.path.dirname(mfst_path))
except OSError, e:
if e.errno != errno.EEXIST:
raise
mfile = file(tmp_path, "w")
#
# We specifically avoid sorting manifests before writing
# them to disk-- there's really no point in doing so, since
# we'll sort actions globally during packaging operations.
#
mfile.write(self.tostr_unsorted())
mfile.close()
portable.rename(tmp_path, mfst_path)
def get_variants(self, name):
if name not in self.attributes:
return None
variants = self.attributes[name]
if not isinstance(variants, str):
return variants
return [variants]
def get(self, key, default):
try:
return self[key]
except KeyError:
return default
def get_size(self, excludes=EmptyI):
"""Returns an integer representing the total size, in bytes, of
the Manifest's data payload.
'excludes' is a list of variants which should be allowed when
calculating the total.
"""
size = 0
for a in self.gen_actions(excludes):
size += int(a.attrs.get("pkg.size", "0"))
return size
def __getitem__(self, key):
"""Return the value for the package attribute 'key'."""
return self.attributes[key]
def __setitem__(self, key, value):
"""Set the value for the package attribute 'key' to 'value'."""
self.attributes[key] = value
for a in self.actions:
if a.name == "set" and a.attrs["name"] == key:
a.attrs["value"] = value
return
new_attr = AttributeAction(None, name=key, value=value)
self.actions.append(new_attr)
def __contains__(self, key):
return key in self.attributes
null = Manifest()
class CachedManifest(Manifest):
"""This class handles a cache of manifests for the client;
it partitions the manifest into multiple files (one per
action type) and also builds an on-disk cache of the
directories explictly and implicitly referenced by the
manifest, tagging each one w/ the appropriate variants/facets."""
def __file_path(self, file):
return os.path.join(self.__pkgdir,
self.fmri.get_dir_path(), file)
def __init__(self, fmri, pkgdir, preferred_pub, excludes=EmptyI, contents=None):
"""Raises KeyError exception if cached manifest
is not present and contents are None; delays
reading of manifest until required if cache file
is present"""
Manifest.__init__(self)
self.__pkgdir = pkgdir
self.__pub = preferred_pub
self.loaded = False
self.set_fmri(None, fmri)
self.excludes = excludes
mpath = self.__file_path("manifest")
# Do we have a cached copy?
if not os.path.exists(mpath):
if not contents:
raise KeyError, fmri
# we have no cached copy; save one
# don't specify excludes so on-disk copy has
# all variants
self.set_content(contents)
self.__finiload()
if self.__storeback():
self.__unload()
elif excludes:
self.set_content(contents, excludes)
return
# we have a cached copy of the manifest
mdpath = self.__file_path("manifest.dircache")
# have we computed the dircache?
if not os.path.exists(mdpath): # we're adding cache
self.excludes = EmptyI # to existing manifest
self.__load()
if self.__storeback():
self.__unload()
elif excludes:
self.excludes = excludes
self.__load()
def __load(self):
"""Load all manifest contents from on-disk copy of manifest"""
f = file(self.__file_path("manifest"))
data = f.read()
f.close()
self.set_content(data, self.excludes)
self.__finiload()
def __unload(self):
"""Unload manifest; used to reduce peak memory comsumption
when downloading new manifests"""
self.actions = []
self.actions_bytype = {}
self.variants = {}
self.facets = {}
self.attributes = {}
self.loaded = False
def __finiload(self):
"""Finish loading.... this part of initialization is common
to multiple code paths"""
self.loaded = True
# this needs to change; we should not modify on-disk manifest
if "publisher" not in self.attributes:
if not self.fmri.has_publisher():
pub = self.__pub
else:
pub = self.fmri.get_publisher()
Manifest.__setitem__(self, "publisher", pub)
def __storeback(self):
""" store the current action set; also create per-type
caches. Return True if data was saved, False if not"""
assert self.loaded
try:
self.store(self.__file_path("manifest"))
self.__storebytype()
return True
except EnvironmentError, e:
# this allows us to try to cache new manifests
# when non-root w/o failures
if e.errno not in (errno.EROFS, errno.EACCES):
raise
return False
def __storebytype(self):
""" create manifest.<typename> files to accelerate partial
parsing of manifests. Separate from __storeback code to
allow upgrade to reuse existing on disk manifests"""
assert self.loaded
# create per-action type cache; use rename to avoid
# corrupt files if ^C'd in the middle
# XXX consider use of per-process tmp file names
for n in self.actions_bytype.keys():
f = file(self.__file_path("manifest.%s.tmp" % n),
"w")
for a in self.actions_bytype[n]:
f.write("%s\n" % a)
f.close()
portable.rename(self.__file_path("manifest.%s.tmp" % n),
self.__file_path("manifest.%s" % n))
# create dircache
f = file(self.__file_path("manifest.dircache.tmp"), "w")
dirs = self.__actions_to_dirs()
for s in self.__gen_dirs_to_str(dirs):
f.write(s)
f.close()
portable.rename(self.__file_path("manifest.dircache.tmp"),
self.__file_path("manifest.dircache"))
def __gen_dirs_to_str(self, dirs):
""" from a dictionary of paths, generate contents of dircache
file"""
for d in dirs:
for v in dirs[d]:
yield "dir path=%s %s\n" % \
(d, " ".join("%s=%s" % t \
for t in v.iteritems()))
def __actions_to_dirs(self):
""" create dictionary of all directories referenced
by actions explicitly or implicitly from self.actions...
include variants as values; collapse variants where possible"""
assert self.loaded
dirs = {}
# build a dictionary containing all directories tagged w/
# variants
for a in self.actions:
v, f = a.get_varcet_keys()
variants = dict((name, a.attrs[name]) for name in v + f)
for d in expanddirs(a.directory_references()):
if d not in dirs:
dirs[d] = [variants]
elif variants not in dirs[d]:
dirs[d].append(variants)
# remove any tags if any entries are always installed (NULL)
for d in dirs:
if {} in dirs[d]:
dirs[d] = [{}]
continue
# could collapse dirs where all variants are present
return dirs
def get_directories(self, excludes):
""" return a list of directories implicitly or
explicitly referenced by this object"""
mpath = self.__file_path("manifest.dircache")
if not os.path.exists(mpath):
# no cached copy
if not self.loaded:
# need to load from disk
self.__load()
# generate actions that contain directories
alist = [
actions.fromstr(s.strip())
for s in self.__gen_dirs_to_str(
self.__actions_to_dirs())
]
else:
# we have cached copy on disk; use it
f = file(mpath)
alist = [actions.fromstr(s.strip()) for s in f]
f.close()
s = set([
a.attrs["path"]
for a in alist
if a.include_this(excludes)
])
return list(s)
def gen_actions_by_type(self, atype, excludes=EmptyI):
""" generate actions of the specified type;
use already in-memory stuff if already loaded,
otherwise use per-action types files"""
if self.loaded: #if already loaded, use in-memory cached version
# invoke subclass method to generate action by action
for a in Manifest.gen_actions_by_type(self, atype,
excludes):
yield a
return
mpath = self.__file_path("manifest.dircache")
if not os.path.exists(mpath):
# no cached copy :-(
if not self.loaded:
# get manifest from disk
self.__load()
# invoke subclass method to generate action by action
for a in Manifest.gen_actions_by_type(self, atype,
excludes):
yield a
else:
# we have a cached copy - use it
mpath = self.__file_path("manifest.%s" % atype)
if not os.path.exists(mpath):
return # no such action in this manifest
f = file(mpath)
for l in f:
a = actions.fromstr(l.strip())
if a.include_this(excludes):
yield a
f.close()
def __load_attributes(self):
"""Load attributes dictionary from cached set actions;
this speeds up pkg info a lot"""
mpath = self.__file_path("manifest.set")
if not os.path.exists(mpath):
return False
f = file(mpath)
for l in f:
a = actions.fromstr(l.strip())
if a.include_this(self.excludes):
self.fill_attributes(a)
f.close()
return True
def __getitem__(self, key):
if not self.loaded and not self.__load_attributes():
self.__load()
return Manifest.__getitem__(self, key)
def __setitem__(self, key, value):
"""No assignments to cached manifests allowed."""
assert "CachedManifests are not dicts"
def __contains__(self, key):
if not self.loaded and not self.__load_attributes():
self.__load()
return Manifest.__contains__(self, key)
def get(self, key, default):
try:
return self[key]
except KeyError:
return default
def get_variants(self, name):
if not self.loaded and not self.__load_attributes():
self.__load()
return Manifest.get_variants(self, name)
@staticmethod
def search_dict(file_path, excludes, return_line=False):
if not self.loaded:
self.__load()
return Manifest.search_dict(file_path, excludes,
return_line=return_line)
def gen_actions(self, excludes=EmptyI):
if not self.loaded:
self.__load()
return Manifest.gen_actions(self, excludes=excludes)
def duplicates(self, excludes=EmptyI):
if not self.loaded:
self.__load()
return Manifest.duplicates(self, excludes=excludes)
def difference(self, origin, origin_exclude=EmptyI, self_exclude=EmptyI):
if not self.loaded:
self.__load()
return Manifest.difference(self, origin,
origin_exclude=origin_exclude,
self_exclude=self_exclude)
class EmptyCachedManifest(Manifest):
"""Special class for pkgplan's need for a empty manifest;
the regular null manifest doesn't support get_directories
and making the cached manifest code handle this case is
too ugly..."""
def __init__(self):
Manifest.__init__(self)
def difference(self, origin, origin_exclude=EmptyI,
self_exclude=EmptyI):
"""Return three lists of action pairs representing origin and
destination actions. The first list contains the pairs
representing additions, the second list contains the pairs
representing updates, and the third list contains the pairs
representing removals. All three lists are in the order in
which they should be executed."""
# The difference for this case is simply everything in the
# origin has been removed. This is an optimization for
# uninstall.
return ([], [],
[(a, None) for a in origin.gen_actions(self_exclude)])
def get_directories(self, excludes):
return []
NullCachedManifest = EmptyCachedManifest()