15654935 pkg facet/variant should also display those implicitly set
15713232 pkg change-facet/change-variant could be faster
16425698 pkg variant subcommand should not require "variant." prefix
16689413 facet/variant subcommands should support wildcards
16689420 pkg facet/variant should offer parsable output format
16689425 pkg facet/variant should have a full test suite
16694970 manifest facets/variants properties can raise misleading exception
16803226 no updates messaging omits linked image name
16808780 pkg variant subcommand could show available variants
--- a/doc/client_api_versions.txt Wed Jun 12 15:44:50 2013 -0700
+++ b/doc/client_api_versions.txt Wed Jun 19 15:54:08 2013 -0700
@@ -1,5 +1,15 @@
+Version 75:
+Compatible with clients using versions 72-74.
+
+ pkg.client.api.ImageInterface has changed as follows:
+
+ * New generator functions 'gen_variants()' and 'gen_facets()' to
+ retrieve the current list of facets or variants currently
+ (implicitly or explicitly) set in the image. See 'pydoc
+ pkg.client.api' for details.
+
Version 74:
-Compatible with clients using version 73, 72.
+Compatible with clients using versions 72-74.
The PlanDescription now has interfaces to
determine whether or not release notes were generated
for this operation, whether or not they must be displayed,
--- a/exception_lists/keywords Wed Jun 12 15:44:50 2013 -0700
+++ b/exception_lists/keywords Wed Jun 19 15:54:08 2013 -0700
@@ -20,9 +20,9 @@
#
#
-# Copyright 2009 Sun Microsystems, Inc. All rights reserved.
-# Use is subject to license terms.
+# Copyright (c) 2009, 2013, Oracle and/or its affiliates. All rights reserved.
#
+src/modules/client/api.py
src/modules/client/image.py
src/client.py
--- a/src/client.py Wed Jun 12 15:44:50 2013 -0700
+++ b/src/client.py Wed Jun 19 15:54:08 2013 -0700
@@ -62,7 +62,6 @@
import textwrap
import time
import traceback
- import tempfile
import pkg
import pkg.actions as actions
@@ -92,7 +91,7 @@
import sys
sys.exit(1)
-CLIENT_API_VERSION = 74
+CLIENT_API_VERSION = 75
PKG_CLIENT_NAME = "pkg"
JUST_UNKNOWN = 0
@@ -272,8 +271,8 @@
" [--deny-new-be | --require-new-be] [--be-name name]\n"
" <mediator> ...")
- adv_usage["variant"] = _("[-H] [<variant_spec>]")
- adv_usage["facet"] = ("[-H] [<facet_spec>]")
+ adv_usage["variant"] = _("[-Haiv] [-F format] [<variant_pattern> ...]")
+ adv_usage["facet"] = ("[-Hai] [-F format] [<facet_pattern> ...]")
adv_usage["avoid"] = _("[pkg_fmri_pattern] ...")
adv_usage["unavoid"] = _("[pkg_fmri_pattern] ...")
adv_usage["freeze"] = _("[-n] [-c reason] [pkg_fmri_pattern] ...")
@@ -346,10 +345,10 @@
raise ValueError(
"Unable to find usage str for %s" %
cmd)
- usage = cmd_dic[cmd]
- if usage is not "":
+ use_txt = cmd_dic[cmd]
+ if use_txt is not "":
logger.error(
- " pkg %(cmd)s %(usage)s" %
+ " pkg %(cmd)s %(use_txt)s" %
locals())
else:
logger.error(" pkg %s" % cmd)
@@ -996,7 +995,9 @@
elif src != dest:
c.append((src, dest))
else:
- a.append((src, dest))
+ # Changing or repairing package content (e.g. fix,
+ # change-facet, etc.)
+ a.append((dest, dest))
def bool_str(val):
if val:
@@ -1014,8 +1015,15 @@
cond_show(_("Packages to remove:"), "%d", len(r))
cond_show(_("Packages to install:"), "%d", len(i))
cond_show(_("Packages to update:"), "%d", len(c))
+ if varcets or mediators:
+ cond_show(_("Packages to change:"), "%d", len(a))
+ else:
+ cond_show(_("Packages to fix:"), "%d", len(a))
cond_show(_("Mediators to change:"), "%d", len(mediators))
cond_show(_("Variants/Facets to change:"), "%d", len(varcets))
+ if not plan.new_be:
+ cond_show(_("Services to change:"), "%d",
+ len(plan.services))
if verbose:
# Only show space information in verbose mode.
@@ -1027,11 +1035,6 @@
_("Estimated space to be consumed:"),
misc.bytes_to_str(plan.bytes_added)))
- if varcets or mediators:
- cond_show(_("Packages to change:"), "%d", len(a))
- else:
- cond_show(_("Packages to fix:"), "%d", len(a))
-
# only display BE information if we're operating on the
# liveroot environment (since otherwise we'll never be
# manipulating BEs).
@@ -1048,10 +1051,6 @@
status.append((_("Create backup boot environment:"),
bool_str(plan.backup_be)))
- if not plan.new_be:
- cond_show(_("Services to change:"), "%d",
- len(plan.services))
-
if "boot-archive" in disp:
status.append((_("Rebuild boot archive:"),
bool_str(plan.update_boot_archive)))
@@ -1087,7 +1086,7 @@
if "fmris" in disp:
changed = collections.defaultdict(list)
- for src, dest in itertools.chain(r, i, c):
+ for src, dest in itertools.chain(r, i, c, a):
if src and dest:
if src.publisher != dest.publisher:
pparent = "%s -> %s" % (src.publisher,
@@ -1095,8 +1094,9 @@
else:
pparent = dest.publisher
pname = dest.pkg_stem
- pver = "%s -> %s" % (src.fmri.version,
- dest.fmri.version)
+ pver = str(src.fmri.version)
+ if src != dest:
+ pver += " -> %s" % dest.fmri.version
elif dest:
pparent = dest.publisher
pname = dest.pkg_stem
@@ -1123,11 +1123,6 @@
logger.info(" %s" % pver)
last_parent = pparent
- if len(a):
- logger.info(_("Affected fmris:"))
- for src, dest in a:
- logger.info(" %s", src)
-
if "services" in disp and not plan.new_be:
last_action = None
for action, smf_fmri in plan.services:
@@ -1324,7 +1319,7 @@
else:
s = _("No updates necessary for this image.")
if api_inst.ischild():
- s + " (%s)" % api_inst.get_linked_name()
+ s += " (%s)" % api_inst.get_linked_name()
msg(s)
return
@@ -1475,13 +1470,6 @@
def __api_alloc(imgdir, exact_match, pkg_image_used):
- def qv(val):
- # Escape shell metacharacters; '\' must be escaped first to
- # prevent escaping escapes.
- for c in "\\ \t\n'`;&()|^<>?*":
- val = val.replace(c, "\\" + c)
- return val
-
progresstracker = get_tracker()
try:
return api.ImageInterface(imgdir, CLIENT_API_VERSION,
@@ -3938,8 +3926,8 @@
duplicate=True)
dest_repo = dest_pub.repository
if dest_repo.origins and \
- not dest_repo.has_origin(repo_uri):
- add_origins = [repo_uri]
+ not dest_repo.has_origin(repo_uri):
+ add_origins = [repo_uri]
if not src_repo and not add_origins:
# The repository doesn't have to provide origin
@@ -4746,72 +4734,141 @@
return EXIT_OK
-def variant_list(api_inst, args):
- """pkg variant [-H] [<variant_spec>]"""
-
- omit_headers = False
-
- opts, pargs = getopt.getopt(args, "H")
-
- for opt, arg in opts:
- if opt == "-H":
- omit_headers = True
-
- # XXX image variants should be accessible through pkg.client.api
- variants = img.get_variants()
-
- for p in pargs:
- if p not in variants:
- error(_("no such variant: %s") % p, cmd="variant")
- return EXIT_OOPS
-
- if not pargs:
- pargs = variants.keys()
-
- width = max(max([len(p) for p in pargs]), 8)
- fmt = "%%-%ss %%s" % width
- if not omit_headers:
- msg(fmt % ("VARIANT", "VALUE"))
-
- for p in pargs:
- msg(fmt % (p, variants[p]))
-
+def list_variant(op, api_inst, pargs, omit_headers, output_format,
+ list_all_items, list_installed, verbose):
+ """pkg variant [-Haiv] [-F format] [<variant_pattern> ...]"""
+
+ subcommand = "variant"
+ if output_format is None:
+ output_format = "default"
+
+ # To work around Python 2.x's scoping limits, a list is used.
+ found = [False]
+ req_variants = set(pargs)
+
+ def gen_current():
+ for (name, val, pvals) in api_inst.gen_variants(variant_list,
+ patterns=req_variants):
+ found[0] = True
+ yield {
+ "variant": name,
+ "value": val
+ }
+
+ def gen_possible():
+ for (name, val, pvals) in api_inst.gen_variants(variant_list,
+ patterns=req_variants):
+ found[0] = True
+ for pval in pvals:
+ yield {
+ "variant": name,
+ "value": pval
+ }
+
+ if verbose:
+ gen_listing = gen_possible
+ else:
+ gen_listing = gen_current
+
+ if list_all_items:
+ if verbose:
+ variant_list = api_inst.VARIANT_ALL_POSSIBLE
+ else:
+ variant_list = api_inst.VARIANT_ALL
+ elif list_installed:
+ if verbose:
+ variant_list = api_inst.VARIANT_INSTALLED_POSSIBLE
+ else:
+ variant_list = api_inst.VARIANT_INSTALLED
+ else:
+ if verbose:
+ variant_list = api_inst.VARIANT_IMAGE_POSSIBLE
+ else:
+ variant_list = api_inst.VARIANT_IMAGE
+
+ # VARIANT VALUE
+ # <variant> <value>
+ # <variant_2> <value_2>
+ # ...
+ field_data = {
+ "variant" : [("default", "json", "tsv"), _("VARIANT"), ""],
+ "value" : [("default", "json", "tsv"), _("VALUE"), ""],
+ }
+ desired_field_order = (_("VARIANT"), _("VALUE"))
+
+ # Default output formatting.
+ def_fmt = "%-70s %s"
+
+ # print without trailing newline.
+ sys.stdout.write(misc.get_listing(desired_field_order,
+ field_data, gen_listing(), output_format, def_fmt,
+ omit_headers))
+
+ if not found[0] and req_variants:
+ if output_format == "default":
+ # Don't pollute other output formats.
+ error(_("no matching variants found"),
+ cmd=subcommand)
+ return EXIT_OOPS
+
+ # Successful if no variants exist or if at least one matched.
return EXIT_OK
-def facet_list(api_inst, args):
- """pkg facet [-H] [<facet_spec>]"""
-
- omit_headers = False
-
- opts, pargs = getopt.getopt(args, "H")
-
- for opt, arg in opts:
- if opt == "-H":
- omit_headers = True
-
- # XXX image facets should be accessible through pkg.client.api
- facets = img.get_facets()
-
- for i, p in enumerate(pargs[:]):
- if not p.startswith("facet."):
- pargs[i] = "facet." + p
-
- if not pargs:
- pargs = facets.keys()
-
- if pargs:
- width = max(max([len(p) for p in pargs]), 8)
- else:
- width = 8
-
- fmt = "%%-%ss %%s" % width
-
- if not omit_headers:
- msg(fmt % ("FACETS", "VALUE"))
-
- for p in pargs:
- msg(fmt % (p, facets[p]))
-
+def list_facet(op, api_inst, pargs, omit_headers, output_format, list_all_items,
+ list_installed):
+ """pkg facet [-Hai] [-F format] [<facet_pattern> ...]"""
+
+ subcommand = "facet"
+ if output_format is None:
+ output_format = "default"
+
+ # To work around Python 2.x's scoping limits, a list is used.
+ found = [False]
+ req_facets = set(pargs)
+
+ facet_list = api_inst.FACET_IMAGE
+ if list_all_items:
+ facet_list = api_inst.FACET_ALL
+ elif list_installed:
+ facet_list = api_inst.FACET_INSTALLED
+
+ def gen_listing():
+ for (name, val) in api_inst.gen_facets(facet_list,
+ patterns=req_facets):
+ found[0] = True
+
+ # Values here are intentionally not _().
+ yield {
+ "facet": name,
+ "value": val and "True" or "False"
+ }
+
+ # FACET VALUE
+ # <facet> <value>
+ # <facet_2> <value_2>
+ # ...
+ field_data = {
+ "facet" : [("default", "json", "tsv"), _("FACET"), ""],
+ "value" : [("default", "json", "tsv"), _("VALUE"), ""],
+ }
+ desired_field_order = (_("FACET"), _("VALUE"))
+
+ # Default output formatting.
+ def_fmt = "%-70s %s"
+
+ # print without trailing newline.
+ sys.stdout.write(misc.get_listing(desired_field_order,
+ field_data, gen_listing(), output_format, def_fmt,
+ omit_headers))
+
+ if not found[0] and req_facets:
+ if output_format == "default":
+ # Don't pollute other output formats.
+ error(_("no matching facets found"),
+ cmd=subcommand)
+ return EXIT_OOPS
+
+ # Successful if no facets exist or if at least one matched.
return EXIT_OK
def list_linked(op, api_inst, pargs,
@@ -5765,6 +5822,7 @@
"attach_parent" : ("p", ""),
"list_available" : ("a", ""),
+ "list_all_items" : ("a", ""),
"output_format" : ("F", "output-format"),
"tagged" : ("", "tagged"),
@@ -5784,6 +5842,8 @@
"ctlfd" : ("", "ctlfd"),
"progfd" : ("", "progfd"),
+
+ "list_installed" : ("i", ""),
}
#
@@ -5807,7 +5867,7 @@
"change-variant" : [change_variant],
"contents" : [list_contents],
"detach-linked" : [detach_linked, 0],
- "facet" : [facet_list],
+ "facet" : [list_facet],
"fix" : [fix_image],
"freeze" : [freeze],
"help" : [None],
@@ -5840,12 +5900,11 @@
"uninstall" : [uninstall],
"unset-authority" : [publisher_unset],
"unset-property" : [property_unset],
- "update-format" : [update_format],
"unset-mediator" : [unset_mediator],
"unset-publisher" : [publisher_unset],
"update" : [update],
"update-format" : [update_format],
- "variant" : [variant_list],
+ "variant" : [list_variant],
"verify" : [verify_image],
"version" : [None],
}
@@ -5870,6 +5929,31 @@
("progfd", None),
]
+def opts_cb_varcet(api_inst, opts, opts_new):
+ if opts_new["list_all_items"] and opts_new["list_installed"]:
+ raise api_errors.InvalidOptionError(
+ api_errors.InvalidOptionError.INCOMPAT,
+ ["list_all_items", "list_installed"])
+
+opts_list_varcet = \
+ options.opts_table_no_headers + \
+ [
+ opts_cb_varcet,
+ ("list_all_items", False),
+ ("list_installed", False),
+ ("output_format", None)
+]
+
+opts_list_facet = \
+ [opts_cb_varcet] + \
+ opts_list_varcet
+
+opts_list_variant = \
+ opts_list_varcet + \
+ [
+ ("verbose", False)
+]
+
opts_list_mediator = \
options.opts_table_no_headers + \
[
@@ -5887,9 +5971,11 @@
]
cmd_opts = {
- "mediator" : opts_list_mediator,
- "unset-mediator" : opts_unset_mediator,
- "remote" : opts_remote,
+ "facet" : opts_list_facet,
+ "mediator" : opts_list_mediator,
+ "unset-mediator" : opts_unset_mediator,
+ "remote" : opts_remote,
+ "variant" : opts_list_variant,
}
@@ -6067,7 +6153,6 @@
# when there is a short and a long version for the same option
# we print both to avoid confusion.
def get_cli_opt(option):
- out = ""
try:
s, l = opts_mapping[option]
if l and not s:
@@ -6075,7 +6160,7 @@
elif s and not l:
return "-%s" % s
else:
- return("-%s/--%s" % (s,l))
+ return "-%s/--%s" % (s, l)
except KeyError:
# ignore if we can't find a match
# (happens for repeated arguments)
--- a/src/man/pkg.1 Wed Jun 12 15:44:50 2013 -0700
+++ b/src/man/pkg.1 Wed Jun 19 15:54:08 2013 -0700
@@ -1,6 +1,6 @@
'\" te
.\" Copyright (c) 2007, 2013, Oracle and/or its affiliates. All rights reserved.
-.TH pkg 1 "26 Feb 2013" "SunOS 5.12" "User Commands"
+.TH pkg 1 "19 Apr 2013" "SunOS 5.12" "User Commands"
.SH NAME
pkg \- Image Packaging System retrieval client
.SH SYNOPSIS
@@ -115,7 +115,7 @@
.LP
.nf
-/usr/bin/pkg variant [-H] [variant.\fIvariant_name\fR ...]
+/usr/bin/pkg variant [-Haiv] [-F \fIformat\fR] [\fIvariant_pattern\fR ...]
.fi
.LP
@@ -130,7 +130,7 @@
.LP
.nf
-/usr/bin/pkg facet [-H] [\fIfacet_name\fR ...]
+/usr/bin/pkg facet [-Hai] [-F \fIformat\fR] [\fIfacet_pattern\fR ...]
.fi
.LP
@@ -1576,7 +1576,7 @@
.ne 2
.mk
.na
-\fB\fBpkg variant\fR [\fB-H\fR] [\fBvariant.\fR\fIvariant_name\fR ...]\fR
+\fB\fBpkg variant\fR [\fB-Haiv\fR] [\fB-F\fR \fIformat\fR] [\fIvariant_pattern\fR ...]\fR
.ad
.sp .6
.RS 4n
@@ -1585,7 +1585,7 @@
.ne 2
.mk
.na
-\fB\fBvariant.\fR\fIvariant_name\fR\fR
+\fB\fIvariant_pattern\fR\fR
.ad
.sp .6
.RS 4n
@@ -1596,6 +1596,17 @@
.ne 2
.mk
.na
+\fB\fB-F\fR\fR
+.ad
+.sp .6
+.RS 4n
+Specify an alternative output format. Currently, only \fBtsv\fR (Tab Separated Values) is valid.
+.RE
+
+.sp
+.ne 2
+.mk
+.na
\fB\fB-H\fR\fR
.ad
.sp .6
@@ -1603,6 +1614,39 @@
Omit the headers from the listing.
.RE
+.sp
+.ne 2
+.mk
+.na
+\fB\fB-a\fR\fR
+.ad
+.sp .6
+.RS 4n
+Display all variants explicitly set in the image and all variants that are listed in installed packages. This option cannot be combined with \fB-i\fR.
+.RE
+
+.sp
+.ne 2
+.mk
+.na
+\fB\fB-i\fR\fR
+.ad
+.sp .6
+.RS 4n
+Display all variants that are listed in installed packages. This option cannot be combined with \fB-a\fR.
+.RE
+
+.sp
+.ne 2
+.mk
+.na
+\fB\fB-v\fR\fR
+.ad
+.sp .6
+.RS 4n
+Display the possible variant values that can be set for installed packages. This option can be combined with \fB-a\fR and \fB-i\fR.
+.RE
+
.RE
.sp
@@ -1624,7 +1668,7 @@
.ne 2
.mk
.na
-\fB\fBpkg facet\fR [\fB-H\fR] [\fIfacet_name\fR ...]\fR
+\fB\fBpkg facet\fR [\fB-Hai\fR] [\fB-F\fR \fIformat\fR] [\fIfacet_pattern\fR ...]\fR
.ad
.sp .6
.RS 4n
@@ -1633,7 +1677,7 @@
.ne 2
.mk
.na
-\fB\fIfacet_name\fR\fR
+\fB\fIfacet_pattern\fR\fR
.ad
.sp .6
.RS 4n
@@ -1644,6 +1688,17 @@
.ne 2
.mk
.na
+\fB\fB-F\fR\fR
+.ad
+.sp .6
+.RS 4n
+Specify an alternative output format. Currently, only \fBtsv\fR (Tab Separated Values) is valid.
+.RE
+
+.sp
+.ne 2
+.mk
+.na
\fB\fB-H\fR\fR
.ad
.sp .6
@@ -1651,6 +1706,28 @@
Omit the headers from the listing.
.RE
+.sp
+.ne 2
+.mk
+.na
+\fB\fB-a\fR\fR
+.ad
+.sp .6
+.RS 4n
+Display all facets explicitly set in the image and all facets that are listed in installed packages. This option cannot be combined with \fB-i\fR.
+.RE
+
+.sp
+.ne 2
+.mk
+.na
+\fB\fB-i\fR\fR
+.ad
+.sp .6
+.RS 4n
+Display all facets that are listed in installed packages. This option cannot be combined with \fB-a\fR.
+.RE
+
.RE
.sp
--- a/src/modules/api_common.py Wed Jun 12 15:44:50 2013 -0700
+++ b/src/modules/api_common.py Wed Jun 19 15:54:08 2013 -0700
@@ -133,8 +133,9 @@
def __init__(self, pfmri, pkg_stem=None, summary=None,
category_info_list=None, states=None, publisher=None,
version=None, build_release=None, branch=None, packaging_date=None,
- size=None, licenses=None, links=None, hardlinks=None, files=None,
- dirs=None, dependencies=None, description=None, attrs=None):
+ size=None, csize=None, licenses=None, links=None, hardlinks=None,
+ files=None, dirs=None, dependencies=None, description=None,
+ attrs=None):
self.pkg_stem = pkg_stem
self.summary = summary
@@ -148,6 +149,7 @@
self.branch = branch
self.packaging_date = packaging_date
self.size = size
+ self.csize = csize
self.fmri = pfmri
self.licenses = licenses
self.links = links
--- a/src/modules/client/api.py Wed Jun 12 15:44:50 2013 -0700
+++ b/src/modules/client/api.py Wed Jun 19 15:54:08 2013 -0700
@@ -103,8 +103,8 @@
# things like help(pkg.client.api.PlanDescription)
from pkg.client.plandesc import PlanDescription # pylint: disable=W0611
-CURRENT_API_VERSION = 74
-COMPATIBLE_API_VERSIONS = frozenset([72, 73, CURRENT_API_VERSION])
+CURRENT_API_VERSION = 75
+COMPATIBLE_API_VERSIONS = frozenset([72, 73, 74, CURRENT_API_VERSION])
CURRENT_P5I_VERSION = 1
# Image type constants.
@@ -253,6 +253,10 @@
needed. Cancel may only be invoked while a cancelable method is
running."""
+ FACET_ALL = 0
+ FACET_IMAGE = 1
+ FACET_INSTALLED = 2
+
# Constants used to reference specific values that info can return.
INFO_FOUND = 0
INFO_MISSING = 1
@@ -268,6 +272,13 @@
MATCH_FMRI = 1
MATCH_GLOB = 2
+ VARIANT_ALL = 0
+ VARIANT_ALL_POSSIBLE = 1
+ VARIANT_IMAGE = 2
+ VARIANT_IMAGE_POSSIBLE = 3
+ VARIANT_INSTALLED = 4
+ VARIANT_INSTALLED_POSSIBLE = 5
+
def __init__(self, img_path, version_id, progresstracker,
cancel_state_callable, pkg_client_name, exact_match=True,
cmdpath=None):
@@ -817,6 +828,173 @@
dependencies on this) """
return [a for a in self._img.get_avoid_dict().iteritems()]
+ def gen_facets(self, facet_list, patterns=misc.EmptyI):
+ """A generator function that produces tuples of the form:
+
+ (
+ name, - (string) facet name (e.g. facet.doc)
+ value - (boolean) current facet value
+ )
+
+ Results are always sorted by facet name.
+
+ 'facet_list' is one of the following constant values indicating
+ which facets should be returned based on how they were set:
+
+ FACET_ALL
+ Return all facets set in the image and all
+ facets listed in installed packages.
+
+ FACET_IMAGE
+ Return only the facets set in the image.
+
+ FACET_INSTALLED
+ Return only the facets listed in installed
+ packages.
+
+ 'patterns' is an optional list of facet wildcard strings to
+ filter results by."""
+
+ facets = self._img.cfg.facets
+ if facet_list != self.FACET_INSTALLED:
+ # Include all facets set in image.
+ fimg = set(facets.keys())
+ else:
+ # Don't include any set only in image.
+ fimg = set()
+
+ # Get all facets found in packages and determine state.
+ fpkg = set()
+ excludes = self._img.list_excludes()
+ if facet_list != self.FACET_IMAGE:
+ for f in self._img.gen_installed_pkgs():
+ # The manifest must be loaded without
+ # pre-applying excludes so that gen_facets() can
+ # choose how to filter the actions.
+ mfst = self._img.get_manifest(f,
+ ignore_excludes=True)
+ for facet in mfst.gen_facets(excludes=excludes):
+ # Use Facets object to determine
+ # effective facet state.
+ fpkg.add(facet)
+
+ # Generate the results.
+ for name in misc.yield_matching("facet.", sorted(fimg | fpkg),
+ patterns):
+ # The image's Facets dictionary will return the
+ # effective value for any facets not explicitly set in
+ # the image (wildcards or implicit).
+ yield (name, facets[name])
+
+ def gen_variants(self, variant_list, patterns=misc.EmptyI):
+ """A generator function that produces tuples of the form:
+
+ (
+ name, - (string) variant name (e.g. variant.arch)
+ value - (string) current variant value,
+ possible - (list) list of possible variant values based
+ on installed packages; empty unless using
+ *_POSSIBLE variant_list.
+ )
+
+ Results are always sorted by variant name.
+
+ 'variant_list' is one of the following constant values indicating
+ which variants should be returned based on how they were set:
+
+ VARIANT_ALL
+ Return all variants set in the image and all
+ variants listed in installed packages.
+
+ VARIANT_ALL_POSSIBLE
+ Return possible variant values (those found in
+ any installed package) for all variants set in
+ the image and all variants listed in installed
+ packages.
+
+ VARIANT_IMAGE
+ Return only the variants set in the image.
+
+ VARIANT_IMAGE_POSSIBLE
+ Return possible variant values (those found in
+ any installed package) for only the variants set
+ in the image.
+
+ VARIANT_INSTALLED
+ Return only the variants listed in installed
+ packages.
+
+ VARIANT_INSTALLED_POSSIBLE
+ Return possible variant values (those found in
+ any installed package) for only the variants
+ listed in installed packages.
+
+ 'patterns' is an optional list of variant wildcard strings to
+ filter results by."""
+
+ variants = self._img.cfg.variants
+ if variant_list != self.VARIANT_INSTALLED and \
+ variant_list != self.VARIANT_INSTALLED_POSSIBLE:
+ # Include all variants set in image.
+ vimg = set(variants.keys())
+ else:
+ # Don't include any set only in image.
+ vimg = set()
+
+ # Get all variants found in packages and determine state.
+ vpkg = {}
+ excludes = self._img.list_excludes()
+ vposs = collections.defaultdict(set)
+ if variant_list != self.VARIANT_IMAGE:
+ # Only incur the overhead of reading through all
+ # installed packages if not just listing variants set in
+ # image or listing possible values for them.
+ for f in self._img.gen_installed_pkgs():
+ # The manifest must be loaded without
+ # pre-applying excludes so that gen_variants()
+ # can choose how to filter the actions.
+ mfst = self._img.get_manifest(f,
+ ignore_excludes=True)
+ for variant, vals in mfst.gen_variants(
+ excludes=excludes):
+ # Unlike facets, Variants class doesn't
+ # handle implicitly set values.
+ if variant[:14] == "variant.debug.":
+ # Debug variants are implicitly
+ # false and are not required
+ # to be set explicitly in the
+ # image.
+ vpkg[variant] = variants.get(
+ variant, "false")
+ elif variant not in vimg:
+ # Although rare, packages with
+ # unknown variants (those not
+ # set in the image) can be
+ # installed as long as content
+ # does not conflict. For those
+ # variants, return None.
+ vpkg[variant] = \
+ variants.get(variant)
+
+ if (variant_list == \
+ self.VARIANT_ALL_POSSIBLE or
+ variant_list == \
+ self.VARIANT_IMAGE_POSSIBLE or
+ variant_list == \
+ self.VARIANT_INSTALLED_POSSIBLE):
+ # Build possible list of variant
+ # values.
+ vposs[variant].update(set(vals))
+
+ # Generate the results.
+ for name in misc.yield_matching("variant.",
+ sorted(vimg | set(vpkg.keys())), patterns):
+ try:
+ yield (name, vpkg[name], sorted(vposs[name]))
+ except KeyError:
+ yield (name, variants[name],
+ sorted(vposs[name]))
+
def freeze_pkgs(self, fmri_strings, dry_run=False, comment=None,
unfreeze=False):
"""Freeze/Unfreeze one or more packages."""
@@ -3915,7 +4093,7 @@
pub = name = version = None
links = hardlinks = files = dirs = \
- size = licenses = cat_info = \
+ csize = size = licenses = cat_info = \
description = None
if PackageInfo.CATEGORIES in info_needed:
@@ -3966,7 +4144,7 @@
mfst, alt_pub=alt_pub)
if PackageInfo.SIZE in info_needed:
- size = mfst.get_size(
+ size, csize = mfst.get_size(
excludes=excludes)
if act_opts & info_needed:
@@ -3987,7 +4165,7 @@
mfst.gen_key_attribute_value_by_type(
"dir", excludes))
elif PackageInfo.SIZE in info_needed:
- size = 0
+ size = csize = 0
# Trim response set.
if PackageInfo.STATE in info_needed:
@@ -4016,7 +4194,7 @@
states=states, publisher=pub, version=release,
build_release=build_release, branch=branch,
packaging_date=packaging_date, size=size,
- pfmri=pfmri, licenses=licenses,
+ csize=csize, pfmri=pfmri, licenses=licenses,
links=links, hardlinks=hardlinks, files=files,
dirs=dirs, dependencies=dependencies,
description=description, attrs=attrs))
--- a/src/modules/client/imageplan.py Wed Jun 12 15:44:50 2013 -0700
+++ b/src/modules/client/imageplan.py Wed Jun 19 15:54:08 2013 -0700
@@ -32,7 +32,6 @@
import mmap
import operator
import os
-import simplejson as json
import stat
import sys
import tempfile
@@ -45,7 +44,6 @@
import pkg.actions
import pkg.actions.driver as driver
import pkg.catalog
-import pkg.client.actuator as actuator
import pkg.client.api_errors as api_errors
import pkg.client.indexer as indexer
import pkg.client.pkg_solver as pkg_solver
@@ -234,7 +232,8 @@
return self.pd._cbytes_avail
def __vector_2_fmri_changes(self, installed_dict, vector,
- li_pkg_updates=True, new_variants=None, new_facets=None):
+ li_pkg_updates=True, new_variants=None, new_facets=None,
+ fmri_changes=None):
"""Given an installed set of packages, and a proposed vector
of package changes determine what, if any, changes should be
made to the image. This takes into account different
@@ -242,37 +241,26 @@
where the only packages being updated are linked image
constraints, etc."""
- cat = self.image.get_catalog(self.image.IMG_CATALOG_KNOWN)
-
fmri_updates = []
+ if fmri_changes is not None:
+ affected = [f[0] for f in fmri_changes]
+ else:
+ affected = None
+
for a, b in ImagePlan.__dicts2fmrichanges(installed_dict,
ImagePlan.__fmris2dict(vector)):
if a != b:
fmri_updates.append((a, b))
continue
- if new_facets is not None or new_variants:
- #
- # In the case of a facet change we reinstall
- # packages since any action in a package could
- # have a facet attached to it.
- #
- # In the case of variants packages should
- # declare what variants they contain. Hence,
- # theoretically, we should be able to reduce
- # the number of package reinstalls by removing
- # re-installs of packages that don't declare
- # variants. But unfortunately we've never
- # enforced this requirement that packages with
- # action variant tags declare their variants.
- # So now we're stuck just re-installing every
- # package. sigh.
- #
- fmri_updates.append((a, b))
- continue
-
- if not fmri_updates:
- # no planned fmri changes
- return []
+
+ if (new_facets is not None or new_variants):
+ if affected is None or a in affected:
+ # If affected list of packages has not
+ # been predetermined for package fmris
+ # that are unchanged, or if the fmri
+ # exists in the list of affected
+ # packages, add it to the list.
+ fmri_updates.append((a, a))
if fmri_updates and not li_pkg_updates:
# oops. the caller requested no package updates and
@@ -288,18 +276,9 @@
self.pd._image_lm = self.image.get_last_modified(string=True)
- def __plan_install_solver(self, li_pkg_updates=True, li_sync_op=False,
- new_facets=None, new_variants=None, pkgs_inst=None,
- reject_list=misc.EmptyI):
- """Use the solver to determine the fmri changes needed to
- install the specified pkgs, sync the specified image, and/or
- change facets/variants within the current image."""
-
- if not (new_variants or pkgs_inst or li_sync_op or
- new_facets is not None):
- # nothing to do
- self.pd._fmri_changes = []
- return
+ def __evaluate_varcets(self, new_variants, new_facets):
+ """Private helper function used to determine new facet and
+ variant state for image."""
old_facets = self.image.cfg.facets
if new_variants or \
@@ -319,6 +298,18 @@
if new_facets == old_facets:
new_facets = None
+ self.__new_excludes = self.image.list_excludes(new_variants,
+ new_facets)
+
+ return new_variants, new_facets
+
+ def __plan_install_solver(self, li_pkg_updates=True, li_sync_op=False,
+ new_facets=None, new_variants=None, pkgs_inst=None,
+ reject_list=misc.EmptyI, fmri_changes=None):
+ """Use the solver to determine the fmri changes needed to
+ install the specified pkgs, sync the specified image, and/or
+ change facets/variants within the current image."""
+
# get ranking of publishers
pub_ranks = self.image.get_publisher_ranks()
@@ -341,9 +332,6 @@
else:
inst_dict = {}
- self.__new_excludes = self.image.list_excludes(new_variants,
- new_facets)
-
if new_variants:
variants = new_variants
else:
@@ -374,7 +362,8 @@
self.pd._fmri_changes = self.__vector_2_fmri_changes(
installed_dict, new_vector,
li_pkg_updates=li_pkg_updates,
- new_variants=new_variants, new_facets=new_facets)
+ new_variants=new_variants, new_facets=new_facets,
+ fmri_changes=fmri_changes)
self.pd._solver_summary = str(solver)
if DebugValues["plan"]:
@@ -388,6 +377,20 @@
current image."""
self.__plan_op()
+
+ new_variants, new_facets = self.__evaluate_varcets(new_variants,
+ new_facets)
+
+ if not (new_variants or pkgs_inst or li_sync_op or
+ new_facets is not None):
+ # nothing to do
+ self.pd._fmri_changes = []
+ self.pd.state = plandesc.EVALUATED_PKGS
+ return
+
+ # If we ever actually support changing facets and variants at
+ # the same time as performing an install, the optimizations done
+ # for plan_change_varacets should be applied here (shared).
self.__plan_install_solver(
li_pkg_updates=li_pkg_updates,
li_sync_op=li_sync_op,
@@ -420,13 +423,144 @@
self.__plan_install(pkgs_inst=pkgs_inst,
reject_list=reject_list)
+ def __get_attr_fmri_changes(self, get_mattrs):
+ # Attempt to optimize package planning by determining which
+ # packages are actually affected by changing attributes (e.g.,
+ # facets, variants). This also provides an accurate list of
+ # affected packages as a side effect (normally, all installed
+ # packages are seen as changed). This assumes that facets and
+ # variants are not both changing at the same time.
+ use_solver = False
+ cat = self.image.get_catalog(
+ self.image.IMG_CATALOG_INSTALLED)
+ cat_info = frozenset([cat.DEPENDENCY])
+
+ fmri_changes = []
+ pt = self.__progtrack
+ rem_pkgs = self.image.count_installed_pkgs()
+
+ pt.plan_start(pt.PLAN_PKGPLAN, goal=rem_pkgs)
+ for f in self.image.gen_installed_pkgs():
+ m = self.image.get_manifest(f,
+ ignore_excludes=True)
+
+ # Get the list of attributes involved in this operation
+ # that the package uses and that have changed.
+ use_solver, mattrs = get_mattrs(m, use_solver)
+ if not mattrs:
+ # Changed attributes unused.
+ pt.plan_add_progress(pt.PLAN_PKGPLAN)
+ rem_pkgs -= 1
+ continue
+
+ # Changed attributes are used in this package.
+ fmri_changes.append((f, f))
+
+ # If any dependency actions are tagged with one
+ # of the changed attributes, assume the solver
+ # must be used.
+ for act in cat.get_entry_actions(f, cat_info):
+ for attr in mattrs:
+ if use_solver:
+ break
+ if (act.name == "depend" and
+ attr in act.attrs):
+ use_solver = True
+ break
+ if use_solver:
+ break
+
+ rem_pkgs -= 1
+ pt.plan_add_progress(pt.PLAN_PKGPLAN)
+
+ pt.plan_done(pt.PLAN_PKGPLAN)
+ pt.plan_all_done()
+
+ return use_solver, fmri_changes
+
def plan_change_varcets(self, new_facets=None, new_variants=None,
reject_list=misc.EmptyI):
"""Determine the fmri changes needed to change the specified
facets/variants."""
- self.__plan_install(new_facets=new_facets,
- new_variants=new_variants, reject_list=reject_list)
+ self.__plan_op()
+ new_variants, new_facets = self.__evaluate_varcets(new_variants,
+ new_facets)
+
+ if not new_variants and new_facets is None:
+ # nothing to do
+ self.pd._fmri_changes = []
+ self.pd.state = plandesc.EVALUATED_PKGS
+ return
+
+ # By default, we assume the solver must be used. If any of the
+ # optimizations below can be applied, they'll determine whether
+ # the solver can be used.
+ use_solver = True
+ fmri_changes = None
+
+ # The following use_solver, fmri_changes checks are only known
+ # to work correctly if only facets or only variants are
+ # changing; not both. Changing both is not currently supported
+ # anyway so this shouldn't be a problem.
+ if new_facets is not None and not new_variants:
+ old_facets = self.image.cfg.facets
+
+ def get_fattrs(m, use_solver):
+ # Get the list of facets involved in this
+ # operation that the package uses. To
+ # accurately determine which packages are
+ # actually being changed, we must compare the
+ # old effective value for each facet that is
+ # changing with its new effective value.
+ return use_solver, list(
+ f
+ for f in m.gen_facets(
+ excludes=self.__new_excludes,
+ patterns=self.pd._changed_facets)
+ if new_facets[f] != old_facets[f]
+ )
+
+ use_solver, fmri_changes = \
+ self.__get_attr_fmri_changes(get_fattrs)
+
+ if new_variants and new_facets is None:
+ nvariants = self.pd._new_variants
+
+ def get_vattrs(m, use_solver):
+ # Get the list of variants involved in this
+ # operation that the package uses.
+ mvars = []
+ for (variant, pvals) in m.gen_variants(
+ excludes=self.__new_excludes,
+ patterns=nvariants
+ ):
+ if nvariants[variant] not in pvals:
+ # If the new value for the
+ # variant is unsupported by this
+ # package, then the solver
+ # should be triggered so the
+ # package can be removed.
+ use_solver = True
+ mvars.append(variant)
+ return use_solver, mvars
+
+ use_solver, fmri_changes = \
+ self.__get_attr_fmri_changes(get_vattrs)
+
+ if use_solver:
+ self.__plan_install_solver(
+ fmri_changes=fmri_changes,
+ new_facets=new_facets,
+ new_variants=new_variants,
+ reject_list=reject_list)
+ else:
+ # If solver isn't involved, assume the list of packages
+ # has been determined.
+ self.pd._fmri_changes = fmri_changes and \
+ fmri_changes or []
+
+ self.pd.state = plandesc.EVALUATED_PKGS
def plan_set_mediators(self, new_mediators):
"""Determine the changes needed to set the specified mediators.
@@ -880,7 +1014,7 @@
# disallow mount points for safety's sake.
if my_dev != os.stat(os.path.dirname(dir_loc)).st_dev:
- return [], []
+ return [], []
# Any explicit or implicitly packaged directories are
# ignored; checking all directory entries is cheap.
@@ -2272,7 +2406,7 @@
if str(pfmri.version) == "0,5.11" \
and containing_fmri.pkg_name \
not in installed_dict:
- return True
+ return True
else:
pfmri.pkg_name = \
containing_fmri.pkg_name
@@ -2326,7 +2460,7 @@
for note in self.pd.release_notes[1]:
if isinstance(note, unicode):
note = note.encode("utf-8")
- print >>tmpfile, note
+ print >> tmpfile, note
tmpfile.close()
self.pd.release_notes_name = os.path.basename(path)
--- a/src/modules/client/plandesc.py Wed Jun 12 15:44:50 2013 -0700
+++ b/src/modules/client/plandesc.py Wed Jun 19 15:54:08 2013 -0700
@@ -481,10 +481,13 @@
"""Returns a formatted list of strings representing the
variant/facet changes in this plan"""
vs, fs = self.varcets
- ret = []
- ret.extend(["variant %s: %s" % a for a in vs])
- ret.extend([" facet %s: %s" % a for a in fs])
- return ret
+ return list(itertools.chain((
+ "variant %s: %s" % (name[8:], val)
+ for (name, val) in vs
+ ), (
+ " facet %s: %s" % (name[6:], val)
+ for (name, val) in fs
+ )))
def get_changes(self):
"""A generation function that yields tuples of PackageInfo
@@ -501,10 +504,17 @@
and 'dest_pi' is the new version of the package it is
being upgraded to."""
- for pp in sorted(self.pkg_plans,
- key=operator.attrgetter("origin_fmri", "destination_fmri")):
- yield (PackageInfo.build_from_fmri(pp.origin_fmri),
- PackageInfo.build_from_fmri(pp.destination_fmri))
+ key = operator.attrgetter("origin_fmri", "destination_fmri")
+ for pp in sorted(self.pkg_plans, key=key):
+ sfmri = pp.origin_fmri
+ dfmri = pp.destination_fmri
+ if sfmri == dfmri:
+ sinfo = dinfo = PackageInfo.build_from_fmri(
+ sfmri)
+ else:
+ sinfo = PackageInfo.build_from_fmri(sfmri)
+ dinfo = PackageInfo.build_from_fmri(dfmri)
+ yield (sinfo, dinfo)
def get_actions(self):
"""A generator function that yields action change descriptions
--- a/src/modules/gui/misc_non_gui.py Wed Jun 12 15:44:50 2013 -0700
+++ b/src/modules/gui/misc_non_gui.py Wed Jun 19 15:54:08 2013 -0700
@@ -41,7 +41,7 @@
# The current version of the Client API the PM, UM and
# WebInstall GUIs have been tested against and are known to work with.
-CLIENT_API_VERSION = 73
+CLIENT_API_VERSION = 75
LOG_DIR = "/var/tmp"
LOG_ERROR_EXT = "_error.log"
LOG_INFO_EXT = "_info.log"
--- a/src/modules/gui/preferences.py Wed Jun 12 15:44:50 2013 -0700
+++ b/src/modules/gui/preferences.py Wed Jun 19 15:54:08 2013 -0700
@@ -19,7 +19,7 @@
#
# CDDL HEADER END
#
-# Copyright (c) 2010, 2011, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
#
import g11nsvc as g11nsvc
@@ -32,7 +32,7 @@
import pygtk
pygtk.require("2.0")
except ImportError:
- sys.exit(1)
+ sys.exit(1)
import pkg.client.api_errors as api_errors
import pkg.client.api as api
import pkg.gui.misc as gui_misc
@@ -536,16 +536,15 @@
break
facets = None
if manifest != None:
- manifest.gen_actions(())
- facets = manifest.facets
+ facets = list(manifest.gen_facets())
if debug and facets != None:
print "DEBUG facets from system/locale:", facets
facetlocales = []
if facets == None:
return facetlocales
-
- for facet_key in facets.keys():
+
+ for facet_key in facets:
if not facet_key.startswith(LOCALE_PREFIX):
continue
m = re.match(LOCALE_MATCH, facet_key)
@@ -629,7 +628,7 @@
selected = row[enumerations.LOCALE_SELECTED]
locale = row[enumerations.LOCALE]
lang = locale.split("_")[0]
-
+
if not lang_locale_dict.has_key(lang):
lang_locale_dict[lang] = {}
lang_locale_dict[lang][locale] = selected
@@ -766,7 +765,7 @@
def __on_preferencescancel_clicked(self, widget):
self.w_preferencesdialog.hide()
-
+
def __on_preferencesclose_clicked(self, widget):
error_dialog_title = _("Preferences")
text = self.w_gsig_name_entry.get_text()
--- a/src/modules/lint/engine.py Wed Jun 12 15:44:50 2013 -0700
+++ b/src/modules/lint/engine.py Wed Jun 19 15:54:08 2013 -0700
@@ -40,7 +40,7 @@
import urllib2
PKG_CLIENT_NAME = "pkglint"
-CLIENT_API_VERSION = 73
+CLIENT_API_VERSION = 75
pkg.client.global_settings.client_name = PKG_CLIENT_NAME
class LintEngineException(Exception):
--- a/src/modules/manifest.py Wed Jun 12 15:44:50 2013 -0700
+++ b/src/modules/manifest.py Wed Jun 19 15:54:08 2013 -0700
@@ -29,11 +29,12 @@
import hashlib
import os
import tempfile
-from itertools import groupby, chain, repeat, izip
+from itertools import groupby, chain, product, repeat, izip
from operator import itemgetter
import pkg.actions as actions
import pkg.client.api_errors as apx
+import pkg.facet as facet
import pkg.misc as misc
import pkg.portable as portable
import pkg.variant as variant
@@ -123,8 +124,7 @@
self.fmri = pfmri
self._cache = {}
- self._facets = None # facets seen in package
- self._variants = None # variants seen in package
+ self._absent_cache = []
self.actions = []
self.actions_bytype = {}
self.attributes = {} # package-wide attributes
@@ -367,13 +367,267 @@
)
yield a
+ def _gen_attrs_to_str(self):
+ """Generate set action supplemental data containing all facets
+ and variants from self.actions and size information. Each
+ returned line must be newline-terminated."""
+
+ emit_variants = "pkg.variant" not in self
+ emit_facets = "pkg.facet" not in self
+ emit_sizes = "pkg.size" not in self and "pkg.csize" not in self
+
+ if not any((emit_variants, emit_facets, emit_sizes)):
+ # Package already has these attributes.
+ return
+
+ # List of possible variants and possible values for them.
+ variants = defaultdict(set)
+
+ # Seed with declared set of variants as actions may be common to
+ # both and so will not be tagged with variant.
+ for name in self.attributes:
+ if name[:8] == "variant.":
+ variants[name] = set(self.attributes[name])
+
+ # List of possible facets and under what variant combinations
+ # they were seen.
+ facets = defaultdict(set)
+
+ # Unique (facet, value) (variant, value) combinations.
+ refs = defaultdict(lambda: defaultdict(int))
+
+ for a in self.gen_actions():
+ name = a.name
+ attrs = a.attrs
+ if name == "set":
+ if attrs["name"][:12] == "pkg.variant":
+ emit_variants = False
+ elif attrs["name"][:9] == "pkg.facet":
+ emit_facets = False
+
+ afacets = []
+ avariants = []
+ for attr, val in attrs.iteritems():
+ if attr[:8] == "variant.":
+ variants[attr].add(val)
+ avariants.append((attr, val))
+ elif attr[:6] == "facet.":
+ afacets.append((attr, val))
+
+ for name, val in afacets:
+ # Facet applicable to this particular variant
+ # combination.
+ varkey = tuple(sorted(avariants))
+ facets[varkey].add(name)
+
+ # This *must* be sorted to ensure reproducible set
+ # action generation for sizes and to ensure each
+ # combination is actually unique.
+ varcetkeys = tuple(sorted(chain(afacets, avariants)))
+ refs[varcetkeys]["csize"] += misc.get_pkg_otw_size(a)
+ if name == "signature":
+ refs[varcetkeys]["csize"] += \
+ a.get_action_chain_csize()
+ refs[varcetkeys]["size"] += a.get_size()
+
+ # Prevent scope leak.
+ afacets = avariants = attrs = varcetkeys = None
+
+ if emit_variants:
+ # Unnecessary if we can guarantee all variants will be
+ # declared at package level. Omit the "variant." prefix
+ # from attribute values since that's implicit and can be
+ # added back when the action is parsed.
+ yield "%s\n" % AttributeAction(None, name="pkg.variant",
+ value=sorted(v[8:] for v in variants))
+
+ # Emit a set action for every variant used with possible values
+ # if one does not already exist.
+ for name in variants:
+ # merge_facets needs the variant values sorted and this
+ # is desirable when generating the variant attr anyway.
+ variants[name] = sorted(variants[name])
+ if name not in self.attributes:
+ yield "%s\n" % AttributeAction(None, name=name,
+ value=variants[name])
+
+ if emit_facets:
+ # Get unvarianted facet set.
+ cfacets = facets.pop((), set())
+
+ # For each variant combination, remove unvarianted
+ # facets since they are common to all variants.
+ for varkey, fnames in facets.items():
+ fnames.difference_update(cfacets)
+ if not fnames:
+ # No facets unique to this combo;
+ # discard.
+ del facets[varkey]
+
+ # If all possible variant combinations supported by the
+ # package have at least one facet, then the intersection
+ # of facets for all variants can be merged with the
+ # common set.
+ merge_facets = len(facets) > 0
+ if merge_facets:
+ # Determine unique set of variant combinations
+ # seen for faceted actions.
+ vcombos = set((
+ tuple(
+ vpair[0]
+ for vpair in varkey
+ )
+ for varkey in facets
+ ))
+
+ # For each unique variant combination, determine
+ # if the cartesian product of all variant values
+ # supported by the package for the combination
+ # has been seen. In other words, if the
+ # combination is ((variant.arch,)) and the
+ # package supports (i386, sparc), then both
+ # (variant.arch, i386) and (variant.arch, sparc)
+ # must exist. This code assumes variant values
+ # for each variant are already sorted.
+ for pair in chain.from_iterable(
+ product(*(
+ tuple((name, val)
+ for val in variants[name])
+ for name in vcombo)
+ )
+ for vcombo in vcombos
+ ):
+ if pair not in facets:
+ # If any combination the package
+ # supports has not been seen for
+ # one or more facets, then some
+ # facets are unique to one or
+ # more combinations.
+ merge_facets = False
+ break
+
+ if merge_facets:
+ # Merge the facets common to all variants if safe;
+ # if we always merged them, then facets only
+ # used by a single variant (think i386-only or
+ # sparc-only content) would be seen unvarianted
+ # (that's bad).
+ vfacets = facets.values()
+ vcfacets = vfacets[0].intersection(*vfacets[1:])
+
+ if vcfacets:
+ # At least one facet is shared between
+ # all variant combinations; move the
+ # common ones to the unvarianted set.
+ cfacets.update(vcfacets)
+
+ # Remove facets common to all combos.
+ for varkey, fnames in facets.items():
+ fnames.difference_update(vcfacets)
+ if not fnames:
+ # No facets unique to
+ # this combo; discard.
+ del facets[varkey]
+
+ # Omit the "facet." prefix from attribute values since
+ # that's implicit and can be added back when the action
+ # is parsed.
+ val = sorted(f[6:] for f in cfacets)
+ if not val:
+ # If we don't do this, action stringify will
+ # emit this as "set name=pkg.facet" which is
+ # then transformed to "set name=name
+ # value=pkg.facet". Not what we wanted, but is
+ # expected for historical reasons.
+ val = ""
+
+ # Always emit an action enumerating the list of facets
+ # common to all variants, even if there aren't any.
+ # That way if there are also no variant-specific facets,
+ # package operations will know that no facets are used
+ # by the package instead of having to scan the whole
+ # manifest.
+ yield "%s\n" % AttributeAction(None,
+ name="pkg.facet.common", value=val)
+
+ # Now emit a pkg.facet action for each variant
+ # combination containing the list of facets unique to
+ # that combination.
+ for varkey, fnames in facets.iteritems():
+ # A unique key for each combination is needed,
+ # and using a hash obfuscates that interface
+ # while giving us a reliable way to generate
+ # a reproducible, unique identifier. The key
+ # string below looks like this before hashing:
+ # variant.archi386variant.debug.osnetTrue...
+ key = hashlib.sha1(
+ "".join("%s%s" % v for v in varkey)
+ ).hexdigest()
+
+ # Omit the "facet." prefix from attribute values
+ # since that's implicit and can be added back
+ # when the action is parsed.
+ act = AttributeAction(None,
+ name="pkg.facet.%s" % key,
+ value=sorted(f[6:] for f in fnames))
+ attrs = act.attrs
+ # Tag action with variants.
+ for v in varkey:
+ attrs[v[0]] = v[1]
+ yield "%s\n" % act
+
+ # Emit pkg.[c]size attribute for [compressed] size of package
+ # for each facet/variant combination.
+ csize = 0
+ size = 0
+ for varcetkeys in refs:
+ rcsize = refs[varcetkeys]["csize"]
+ rsize = refs[varcetkeys]["size"]
+
+ if not varcetkeys:
+ # For unfaceted/unvarianted actions, keep a
+ # running total so a single [c]size action can
+ # be generated.
+ csize += rcsize
+ size += rsize
+ continue
+
+ if emit_sizes and (rcsize > 0 or rsize > 0):
+ # Only emit if > 0; actions may be
+ # faceted/variant without payload.
+
+ # A unique key for each combination is needed,
+ # and using a hash obfuscates that interface
+ # while giving us a reliable way to generate
+ # a reproducible, unique identifier. The key
+ # string below looks like this before hashing:
+ # facet.docTruevariant.archi386...
+ key = hashlib.sha1(
+ "".join("%s%s" % v for v in varcetkeys)
+ ).hexdigest()
+
+ # The sizes are abbreviated in the name of byte
+ # conservation.
+ act = AttributeAction(None,
+ name="pkg.sizes.%s" % key,
+ value=["csz=%s" % rcsize, "sz=%s" % rsize])
+ attrs = act.attrs
+ for v in varcetkeys:
+ attrs[v[0]] = v[1]
+ yield "%s\n" % act
+
+ if emit_sizes:
+ act = AttributeAction(None, name="pkg.sizes.common",
+ value=["csz=%s" % csize, "sz=%s" % size])
+ yield "%s\n" % act
+
def _actions_to_dict(self, references):
"""create dictionary of all actions referenced explicitly or
implicitly from self.actions... include variants as values;
collapse variants where possible"""
refs = {}
- # build a dictionary containing all directories tagged w/
+ # build a dictionary containing all actions tagged w/
# variants
for a in self.actions:
v, f = a.get_varcet_keys()
@@ -419,6 +673,117 @@
return list(s)
+ def gen_facets(self, excludes=EmptyI, patterns=EmptyI):
+ """A generator function that returns the supported facet
+ attributes (strings) for this package based on the specified (or
+ current) excludes that also match at least one of the patterns
+ provided. Facets must be true or false so a list of possible
+ facet values is not returned."""
+
+ if self.excludes == excludes:
+ excludes = EmptyI
+ assert excludes == EmptyI or self.excludes == EmptyI
+
+ try:
+ facets = self["pkg.facet"]
+ except KeyError:
+ facets = None
+
+ if facets is not None and excludes == EmptyI:
+ # No excludes? Then use the pre-determined set of
+ # facets.
+ for f in misc.yield_matching("facet.", facets, patterns):
+ yield f
+ return
+
+ # If different excludes were specified, then look for pkg.facet
+ # actions containing the list of facets.
+ found = False
+ seen = set()
+ for a in self.gen_actions_by_type("set", excludes=excludes):
+ if a.attrs["name"][:10] == "pkg.facet.":
+ # Either a pkg.facet.common action or a
+ # pkg.facet.X variant-specific action.
+ found = True
+ val = a.attrlist("value")
+ if len(val) == 1 and val[0] == "":
+ # No facets.
+ continue
+
+ for f in misc.yield_matching("facet.", (
+ "facet.%s" % n
+ for n in val
+ ), patterns):
+ if f in seen:
+ # Prevent duplicates; it's
+ # possible a given facet may be
+ # valid for more than one unique
+ # variant combination that's
+ # allowed by current excludes.
+ continue
+
+ seen.add(f)
+ yield f
+
+ if not found:
+ # Fallback to sifting actions to yield possible.
+ facets = self._get_varcets(excludes=excludes)[1]
+ for f in misc.yield_matching("facet.", facets, patterns):
+ yield f
+
+ def gen_variants(self, excludes=EmptyI, patterns=EmptyI):
+ """A generator function that yields a list of tuples of the form
+ (variant, [values]). Where 'variant' is the variant attribute
+ name (e.g. 'variant.arch') and '[values]' is a list of the
+ variant values supported by this package. Variants returned are
+ those allowed by the specified (or current) excludes that also
+ match at least one of the patterns provided."""
+
+ if self.excludes == excludes:
+ excludes = EmptyI
+ assert excludes == EmptyI or self.excludes == EmptyI
+
+ try:
+ variants = self["pkg.variant"]
+ except KeyError:
+ variants = None
+
+ if variants is not None and excludes == EmptyI:
+ # No excludes? Then use the pre-determined set of
+ # variants.
+ for v in misc.yield_matching("variant.", variants,
+ patterns):
+ yield v, self.attributes.get(v, [])
+ return
+
+ # If different excludes were specified, then look for
+ # pkg.variant action containing the list of variants.
+ found = False
+ variants = defaultdict(set)
+ for a in self.gen_actions_by_type("set", excludes=excludes):
+ aname = a.attrs["name"]
+ if aname == "pkg.variant":
+ val = a.attrlist("value")
+ if len(val) == 1 and val[0] == "":
+ # No variants.
+ return
+ for v in val:
+ found = True
+ # Ensure variant entries exist (debug
+ # variants may not) via defaultdict.
+ variants["variant.%s" % v]
+ elif aname[:8] == "variant.":
+ for v in a.attrlist("value"):
+ found = True
+ variants[aname].add(v)
+
+ if not found:
+ # Fallback to sifting actions to get possible.
+ variants = self._get_varcets(excludes=excludes)[0]
+
+ for v in misc.yield_matching("variant.", variants, patterns):
+ yield v, variants[v]
+
def gen_mediators(self, excludes=EmptyI):
"""A generator function that yields tuples of the form (mediator,
mediations) expressing the set of possible mediations for this
@@ -592,10 +957,9 @@
self.actions = []
self.actions_bytype = {}
- self._variants = None
- self._facets = None
self.attributes = {}
self._cache = {}
+ self._absent_cache = []
# So we could build up here the type/key_attr dictionaries like
# sdict and odict in difference() above, and have that be our
@@ -661,12 +1025,6 @@
if excludes and not action.include_this(excludes):
return
- if self._variants:
- # Reset facet/variant cache if needed (if one is set,
- # then both are set, so only need to check for one).
- self._facets = None
- self._variants = None
-
self.actions.append(action)
try:
self.actions_bytype[aname].append(action)
@@ -681,14 +1039,66 @@
"""Fill attribute array w/ set action contents."""
try:
keyvalue = action.attrs["name"]
- if keyvalue == "fmri":
- keyvalue = "pkg.fmri"
- if keyvalue not in self.attributes:
- self.attributes[keyvalue] = \
- action.attrs["value"]
- except KeyError: # ignore broken set actions
+ if keyvalue[:10] == "pkg.sizes.":
+ # To reduce manifest bloat, size and csize
+ # are set on a single action so need splitting
+ # into separate attributes.
+ attrval = action.attrlist("value")
+ for entry in attrval:
+ szname, szval = entry.split("=", 1)
+ if szname == "sz":
+ szname = "pkg.size"
+ elif szname == "csz":
+ szname = "pkg.csize"
+ else:
+ # Skip unknowns.
+ continue
+
+ self.attributes.setdefault(szname, 0)
+ self.attributes[szname] += int(szval)
+ return
+ except (KeyError, TypeError, ValueError):
+ # ignore broken set actions
pass
+ # Ensure facet and variant attributes are always lists.
+ if keyvalue[:10] == "pkg.facet.":
+ # Possible facets list is spread over multiple actions.
+ val = action.attrlist("value")
+ if len(val) == 1 and val[0] == "":
+ # No facets.
+ val = []
+
+ seen = self.attributes.setdefault("pkg.facet", [])
+ for f in val:
+ entry = "facet.%s" % f
+ if entry not in seen:
+ # Prevent duplicates; it's possible a
+ # given facet may be valid for more than
+ # one unique variant combination that's
+ # allowed by current excludes.
+ seen.append(f)
+ return
+ elif keyvalue == "pkg.variant":
+ val = action.attrlist("value")
+ if len(val) == 1 and val[0] == "":
+ # No variants.
+ val = []
+
+ self.attributes[keyvalue] = [
+ "variant.%s" % v
+ for v in val
+ ]
+ return
+ elif keyvalue[:8] == "variant.":
+ self.attributes[keyvalue] = action.attrlist("value")
+ return
+
+ if keyvalue == "fmri":
+ # Ancient manifest compatibility.
+ keyvalue = "pkg.fmri"
+ self.attributes[keyvalue] = action.attrs["value"]
+
@staticmethod
def search_dict(file_path, excludes, return_line=False,
log=None):
@@ -896,24 +1306,64 @@
"'false'" % ret))
def get_size(self, excludes=EmptyI):
- """Returns an integer representing the total size, in bytes, of
- the Manifest's data payload.
+ """Returns an integer tuple of the form (size, csize), where
+ 'size' represents the total uncompressed size, in bytes, of the
+ Manifest's data payload, and 'csize' represents the compressed
+ version of that.
- 'excludes' is a list of variants which should be allowed when
- calculating the total.
- """
+ 'excludes' is a list of a list of variants and facets which
+ should be allowed when calculating the total."""
+ if self.excludes == excludes:
+ excludes = EmptyI
+ assert excludes == EmptyI or self.excludes == EmptyI
+
+ csize = 0
size = 0
- for a in self.gen_actions(excludes):
+
+ attrs = self.attributes
+ if ("pkg.size" in attrs and "pkg.csize" in attrs) and \
+ (excludes == EmptyI or self.excludes == excludes):
+ # If specified excludes match loaded excludes, then use
+ # cached attributes; this is safe as manifest attributes
+ # are reset or updated every time exclude_content,
+ # set_content, or add_action is called.
+ return (attrs["pkg.size"], attrs["pkg.csize"])
+
+ for a in self.gen_actions(excludes=excludes):
size += a.get_size()
- return size
+ csize += misc.get_pkg_otw_size(a)
+
+ if excludes == EmptyI:
+ # Cache for future calls.
+ attrs["pkg.size"] = size
+ attrs["pkg.csize"] = csize
+
+ return (size, csize)
- def __load_varcets(self):
- """Private helper function to populate list of facets and
- variants on-demand."""
+ def _get_varcets(self, excludes=EmptyI):
+ """Private helper function to get list of facets/variants."""
+
+ variants = defaultdict(set)
+ facets = defaultdict(set)
- self._facets = {}
- self._variants = {}
+ nexcludes = excludes
+ if nexcludes:
+ # Facet filtering should never be applied when excluding
+ # actions; only variant filtering. This is ugly, but
+ # our current variant/facet filtering system doesn't
+ # allow you to be selective and various bits in
+ # pkg.manifest assume you always filter on both so we
+ # have to fake up a filter for facets.
+ nexcludes = [
+ x for x in excludes
+ if x.__func__ != facet._allow_facet
+ ]
+ # Excludes list must always have zero or two items; so
+ # fake second entry.
+ nexcludes.append(lambda x: True)
+ assert len(nexcludes) == 2
+
for action in self.gen_actions():
# append any variants and facets to manifest dict
attrs = action.attrs
@@ -923,29 +1373,27 @@
continue
try:
- for v, d in chain(
- izip(v_list, repeat(self._variants)),
- izip(f_list, repeat(self._facets))):
- try:
+ for v, d in izip(v_list, repeat(variants)):
+ d[v].add(attrs[v])
+
+ if not excludes or action.include_this(
+ nexcludes):
+ # While variants are package level (you
+ # can't install a package without
+ # setting the variant first), facets
+ # from the current action should only be
+ # included if the action is not
+ # excluded.
+ for v, d in izip(f_list, repeat(facets)):
d[v].add(attrs[v])
- except KeyError:
- d[v] = set([attrs[v]])
except TypeError:
# Lists can't be set elements.
raise actions.InvalidActionError(action,
_("%(forv)s '%(v)s' specified multiple times") %
{"forv": v.split(".", 1)[0], "v": v})
- def __get_facets(self):
- if self._facets is None:
- self.__load_varcets()
- return self._facets
-
- def __get_variants(self):
- if self._variants is None:
- self.__load_varcets()
- return self._variants
-
+ return (variants, facets)
+
def __getitem__(self, key):
"""Return the value for the package attribute 'key'."""
return self.attributes[key]
@@ -965,9 +1413,6 @@
def __contains__(self, key):
return key in self.attributes
- facets = property(lambda self: self.__get_facets())
- variants = property(lambda self: self.__get_variants())
-
null = Manifest()
class FactoredManifest(Manifest):
@@ -1056,8 +1501,6 @@
when downloading new manifests"""
self.actions = []
self.actions_bytype = {}
- self._variants = None
- self._facets = None
self.attributes = {}
self.loaded = False
@@ -1091,24 +1534,47 @@
# Ensure target cache directory and intermediates exist.
misc.makedirs(t_dir)
- # create per-action type cache; use rename to avoid
- # corrupt files if ^C'd in the middle
- for n in self.actions_bytype.keys():
+ # create per-action type cache; use rename to avoid corrupt
+ # files if ^C'd in the middle. All action types are considered
+ # so that empty cache files are created if no action of that
+ # type exists for the package (avoids full manifest loads
+ # later).
+ for n, acts in self.actions_bytype.iteritems():
t_prefix = "manifest.%s." % n
- fd, fn = tempfile.mkstemp(dir=t_dir, prefix=t_prefix)
- f = os.fdopen(fd, "wb")
+ try:
+ fd, fn = tempfile.mkstemp(dir=t_dir,
+ prefix=t_prefix)
+ except EnvironmentError, e:
+ raise apx._convert_error(e)
- for a in self.actions_bytype[n]:
- f.write("%s\n" % a)
- f.close()
- os.chmod(fn, PKG_FILE_MODE)
- portable.rename(fn, self.__cache_path("manifest.%s" % n))
+ f = os.fdopen(fd, "wb")
+ try:
+ for a in acts:
+ f.write("%s\n" % a)
+ if n == "set":
+ # Add supplemental action data; yes this
+ # does mean the cache is not the same as
+ # retrieved manifest, but that's ok.
+ # Signature verification is done using
+ # the raw manifest.
+ f.writelines(self._gen_attrs_to_str())
+ except EnvironmentError, e:
+ raise apx._convert_error(e)
+ finally:
+ f.close()
+
+ try:
+ os.chmod(fn, PKG_FILE_MODE)
+ portable.rename(fn,
+ self.__cache_path("manifest.%s" % n))
+ except EnvironmentError, e:
+ raise apx._convert_error(e)
def create_cache(name, refs):
try:
fd, fn = tempfile.mkstemp(dir=t_dir,
- prefix="manifest.dircache.")
+ prefix=name + ".")
with os.fdopen(fd, "wb") as f:
f.writelines(refs())
os.chmod(fn, PKG_FILE_MODE)
@@ -1215,30 +1681,66 @@
for a in Manifest.gen_actions_by_type(self, atype,
excludes):
yield a
- else:
- if excludes == EmptyI:
- excludes = self.excludes
- assert excludes == self.excludes or \
- self.excludes == EmptyI
- # we have a cached copy - use it
- mpath = self.__cache_path("manifest.%s" % atype)
+ return
+
+ if excludes == EmptyI:
+ excludes = self.excludes
+ assert excludes == self.excludes or self.excludes == EmptyI
- if not os.path.exists(mpath):
- return # no such action in this manifest
+ if atype in self._absent_cache:
+ # No such action in the manifest; must be done *after*
+ # asserting excludes are correct to avoid hiding
+ # failures.
+ return
+ # Assume a cached copy exists; if not, tag the action type to
+ # avoid pointless I/O later.
+ mpath = self.__cache_path("manifest.%s" % atype)
+
+ try:
with open(mpath, "rb") as f:
for l in f:
a = actions.fromstr(l.rstrip())
if not excludes or \
a.include_this(excludes):
yield a
+ except EnvironmentError, e:
+ if e.errno == errno.ENOENT:
+ self._absent_cache.append(atype)
+ return # no such action in this manifest
+ raise apx._convert_error(e)
- def gen_mediators(self, excludes):
+ def gen_facets(self, excludes=EmptyI, patterns=EmptyI):
+ """A generator function that returns the supported facet
+ attributes (strings) for this package based on the specified (or
+ current) excludes that also match at least one of the patterns
+ provided. Facets must be true or false so a list of possible
+ facet values is not returned."""
+
+ if not self.loaded and not self.__load_attributes():
+ self.__load()
+ return Manifest.gen_facets(self, excludes=excludes,
+ patterns=patterns)
+
+ def gen_variants(self, excludes=EmptyI, patterns=EmptyI):
+ """A generator function that yields a list of tuples of the form
+ (variant, [values]). Where 'variant' is the variant attribute
+ name (e.g. 'variant.arch') and '[values]' is a list of the
+ variant values supported by this package. Variants returned are
+ those allowed by the specified (or current) excludes that also
+ match at least one of the patterns provided."""
+
+ if not self.loaded and not self.__load_attributes():
+ self.__load()
+ return Manifest.gen_variants(self, excludes=excludes,
+ patterns=patterns)
+
+ def gen_mediators(self, excludes=EmptyI):
"""A generator function that yields set actions expressing the
set of possible mediations for this package.
"""
self.__load_cached_data("manifest.mediatorcache")
- return Manifest.gen_mediators(self, excludes)
+ return Manifest.gen_mediators(self, excludes=excludes)
def __load_attributes(self):
"""Load attributes dictionary from cached set actions;
@@ -1253,8 +1755,21 @@
if not self.excludes or \
a.include_this(self.excludes):
self.fill_attributes(a)
+
return True
+ def get_size(self, excludes=EmptyI):
+ """Returns an integer tuple of the form (size, csize), where
+ 'size' represents the total uncompressed size, in bytes, of the
+ Manifest's data payload, and 'csize' represents the compressed
+ version of that.
+
+ 'excludes' is a list of a list of variants and facets which
+ should be allowed when calculating the total."""
+ if not self.loaded and not self.__load_attributes():
+ self.__load()
+ return Manifest.get_size(self, excludes=excludes)
+
def __getitem__(self, key):
if not self.loaded and not self.__load_attributes():
self.__load()
--- a/src/modules/misc.py Wed Jun 12 15:44:50 2013 -0700
+++ b/src/modules/misc.py Wed Jun 19 15:54:08 2013 -0700
@@ -32,6 +32,7 @@
import collections
import datetime
import errno
+import fnmatch
import getopt
import hashlib
import itertools
@@ -497,8 +498,8 @@
pkg.csize. If that value isn't available, it returns pkg.size.
If pkg.size isn't available, return zero."""
- size = action.attrs.get("pkg.csize", 0)
- if size == 0:
+ size = action.attrs.get("pkg.csize")
+ if size is None:
size = action.attrs.get("pkg.size", 0)
return int(size)
@@ -2428,6 +2429,42 @@
s = s.decode("utf-8", "replace")
return s
+def yield_matching(pat_prefix, items, patterns):
+ """Helper function for yielding items that match one of the provided
+ patterns."""
+
+ if patterns:
+ # Normalize patterns and determine whether to glob.
+ npatterns = []
+ for p in patterns:
+ if pat_prefix:
+ pat = p.startswith(pat_prefix) and \
+ p or (pat_prefix + p)
+ else:
+ pat = p
+ if "*" in p or "?" in p:
+ pat = re.compile(fnmatch.translate(pat)).match
+ glob_match = True
+ else:
+ glob_match = False
+
+ npatterns.append((pat, glob_match))
+ patterns = npatterns
+ npatterns = None
+
+ for item in items:
+ for (pat, glob_match) in patterns:
+ if glob_match:
+ if pat(item):
+ break
+ elif item == pat:
+ break
+ else:
+ if patterns:
+ continue
+ # No patterns or matched at least one.
+ yield item
+
sigdict = defaultdict(list)
--- a/src/modules/server/api.py Wed Jun 12 15:44:50 2013 -0700
+++ b/src/modules/server/api.py Wed Jun 19 15:54:08 2013 -0700
@@ -327,8 +327,8 @@
states = None
links = hardlinks = files = dirs = dependencies = None
- summary = size = licenses = cat_info = description = \
- None
+ summary = csize = size = licenses = cat_info = \
+ description = None
if cat_opts & info_needed:
summary, description, cat_info, dependencies = \
@@ -359,7 +359,8 @@
licenses = self.__licenses(mfst)
if PackageInfo.SIZE in info_needed:
- size = mfst.get_size(excludes=excludes)
+ size, csize = mfst.get_size(
+ excludes=excludes)
if act_opts & info_needed:
if PackageInfo.LINKS in info_needed:
@@ -384,9 +385,10 @@
publisher=pub, version=release,
build_release=build_release, branch=branch,
packaging_date=packaging_date, size=size,
- pfmri=f, licenses=licenses, links=links,
- hardlinks=hardlinks, files=files, dirs=dirs,
- dependencies=dependencies, description=description))
+ csize=csize, pfmri=f, licenses=licenses,
+ links=links, hardlinks=hardlinks, files=files,
+ dirs=dirs, dependencies=dependencies,
+ description=description))
return {
self.INFO_FOUND: pis,
self.INFO_MISSING: notfound,
--- a/src/modules/server/depot.py Wed Jun 12 15:44:50 2013 -0700
+++ b/src/modules/server/depot.py Wed Jun 19 15:54:08 2013 -0700
@@ -70,7 +70,6 @@
import pkg
import pkg.actions as actions
-import pkg.catalog as catalog
import pkg.config as cfg
import pkg.fmri as fmri
import pkg.indexer as indexer
@@ -1325,22 +1324,24 @@
lsummary.seek(0)
self.__set_response_expires("info", 86400*365, 86400*365)
+ size, csize = m.get_size()
return """\
- Name: %s
- Summary: %s
- Publisher: %s
- Version: %s
- Build Release: %s
- Branch: %s
-Packaging Date: %s
- Size: %s
- FMRI: %s
+ Name: %s
+ Summary: %s
+ Publisher: %s
+ Version: %s
+ Build Release: %s
+ Branch: %s
+ Packaging Date: %s
+ Size: %s
+Compressed Size: %s
+ FMRI: %s
License:
%s
""" % (name, summary, pub, ver.release, ver.build_release,
- ver.branch, ver.get_timestamp().strftime("%c"),
- misc.bytes_to_str(m.get_size()), pfmri, lsummary.read())
+ ver.branch, ver.get_timestamp().strftime("%c"), misc.bytes_to_str(size),
+ misc.bytes_to_str(csize), pfmri, lsummary.read())
@cherrypy.tools.response_headers(headers=[(
"Content-Type", p5i.MIME_TYPE)])
--- a/src/pkgdep.py Wed Jun 12 15:44:50 2013 -0700
+++ b/src/pkgdep.py Wed Jun 19 15:54:08 2013 -0700
@@ -43,7 +43,7 @@
import pkg.publish.dependencies as dependencies
from pkg.misc import msg, emsg, PipeError
-CLIENT_API_VERSION = 73
+CLIENT_API_VERSION = 75
PKG_CLIENT_NAME = "pkgdepend"
DEFAULT_SUFFIX = ".res"
--- a/src/sysrepo.py Wed Jun 12 15:44:50 2013 -0700
+++ b/src/sysrepo.py Wed Jun 19 15:54:08 2013 -0700
@@ -59,7 +59,7 @@
orig_cwd = None
PKG_CLIENT_NAME = "pkg.sysrepo"
-CLIENT_API_VERSION = 73
+CLIENT_API_VERSION = 75
pkg.client.global_settings.client_name = PKG_CLIENT_NAME
# exit codes
--- a/src/tests/cli/t_change_facet.py Wed Jun 12 15:44:50 2013 -0700
+++ b/src/tests/cli/t_change_facet.py Wed Jun 19 15:54:08 2013 -0700
@@ -20,7 +20,7 @@
# CDDL HEADER END
#
-# Copyright (c) 2009, 2012, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2009, 2013, Oracle and/or its affiliates. All rights reserved.
import testutils
if __name__ == "__main__":
@@ -122,7 +122,7 @@
self.pkg_image_create(self.rurl, additional_args=ic_args)
self.pkg("facet")
- self.pkg("facet -H 'facet.locale*' | egrep False")
+ self.pkg("facet -H -F tsv 'facet.locale*' | egrep False")
# install a package and verify
alist = [self.plist[0]]
@@ -145,7 +145,7 @@
# are in effect
self.pkg("change-facet -n --parsable=0 wombat=false")
self.assertEqualParsable(self.output,
- affect_packages=alist,
+ affect_packages=[],
change_facets=[["facet.wombat", False]])
# Again, but this time after removing the publisher cache data
@@ -157,7 +157,7 @@
self.pkg("change-facet --no-refresh -n --parsable=0 "
"wombat=false", su_wrap=True)
self.assertEqualParsable(self.output,
- affect_packages=alist,
+ affect_packages=[],
change_facets=[["facet.wombat", False]])
# Again, but this time after removing the cache directory
@@ -168,7 +168,7 @@
self.pkg("change-facet --no-refresh -n --parsable=0 "
"wombat=false", su_wrap=True)
self.assertEqualParsable(self.output,
- affect_packages=alist,
+ affect_packages=[],
change_facets=[["facet.wombat", False]])
# change to pick up another file w/ two tags and test the
@@ -252,8 +252,8 @@
# Test that setting a non-existent facet to True then removing
# it works.
self.pkg("change-facet -v foo=True")
- self.pkg("facet -H")
- self.assertEqual("facet.foo True\n", self.output)
+ self.pkg("facet -H -F tsv")
+ self.assertEqual("facet.foo\tTrue\n", self.output)
self.pkg("change-facet --parsable=0 foo=None")
self.assertEqualParsable(self.output, change_facets=[
["facet.foo", None]])
@@ -291,12 +291,11 @@
# install a random package and make sure we don't accidentally
# change facets.
self.pkg("install [email protected]")
- self.pkg("facet -H")
- output = self.reduceSpaces(self.output)
+ self.pkg("facet -H -F tsv")
expected = (
- "facet.locale.fr_FR False\n"
- "facet.locale.fr False\n")
- self.assertEqualDiff(expected, output)
+ "facet.locale.fr\tFalse\n"
+ "facet.locale.fr_FR\tFalse\n")
+ self.assertEqualDiff(expected, self.output)
for i in [ 0, 3, 4, 5, 6, 7 ]:
self.assert_file_is_there(str(i))
for i in [ 1, 2 ]:
@@ -306,12 +305,11 @@
# update an image and make sure we don't accidentally change
# facets.
self.pkg("update")
- self.pkg("facet -H")
- output = self.reduceSpaces(self.output)
+ self.pkg("facet -H -F tsv")
expected = (
- "facet.locale.fr_FR False\n"
- "facet.locale.fr False\n")
- self.assertEqualDiff(expected, output)
+ "facet.locale.fr\tFalse\n"
+ "facet.locale.fr_FR\tFalse\n")
+ self.assertEqualDiff(expected, self.output)
for i in [ 0, 3, 4, 5, 6, 7 ]:
self.assert_file_is_there(str(i))
for i in [ 1, 2 ]:
@@ -333,10 +331,10 @@
# set a facet on an image with no facets
self.pkg("change-facet -v locale.fr=False")
- self.pkg("facet -H")
+ self.pkg("facet -H -F tsv")
output = self.reduceSpaces(self.output)
expected = (
- "facet.locale.fr False\n")
+ "facet.locale.fr\tFalse\n")
self.assertEqualDiff(expected, output)
for i in [ 0, 2, 3, 4, 5, 6, 7 ]:
self.assert_file_is_there(str(i))
@@ -346,12 +344,11 @@
# set a facet on an image with existing facets
self.pkg("change-facet -v locale.fr_FR=False")
- self.pkg("facet -H")
- output = self.reduceSpaces(self.output)
+ self.pkg("facet -H -F tsv")
expected = (
- "facet.locale.fr_FR False\n"
- "facet.locale.fr False\n")
- self.assertEqualDiff(expected, output)
+ "facet.locale.fr\tFalse\n"
+ "facet.locale.fr_FR\tFalse\n")
+ self.assertEqualDiff(expected, self.output)
for i in [ 0, 3, 4, 5, 6, 7 ]:
self.assert_file_is_there(str(i))
for i in [ 1, 2 ]:
@@ -361,11 +358,11 @@
# clear a facet while setting a facet on an image with other
# facets that aren't being changed
self.pkg("change-facet -v locale.fr=None locale.nl=False")
- self.pkg("facet -H")
+ self.pkg("facet -H -F tsv")
output = self.reduceSpaces(self.output)
expected = (
- "facet.locale.fr_FR False\n"
- "facet.locale.nl False\n")
+ "facet.locale.fr_FR\tFalse\n"
+ "facet.locale.nl\tFalse\n")
self.assertEqualDiff(expected, output)
for i in [ 0, 1, 3, 4, 6, 7 ]:
self.assert_file_is_there(str(i))
@@ -376,10 +373,10 @@
# clear a facet on an image with other facets that aren't
# being changed
self.pkg("change-facet -v locale.nl=None")
- self.pkg("facet -H")
+ self.pkg("facet -H -F tsv")
output = self.reduceSpaces(self.output)
expected = (
- "facet.locale.fr_FR False\n")
+ "facet.locale.fr_FR\tFalse\n")
self.assertEqualDiff(expected, output)
for i in [ 0, 1, 3, 4, 5, 6, 7 ]:
self.assert_file_is_there(str(i))
@@ -389,7 +386,7 @@
# clear the only facet on an image
self.pkg("change-facet -v locale.fr_FR=None")
- self.pkg("facet -H")
+ self.pkg("facet -H -F tsv")
self.assertEqualDiff("", self.output)
for i in range(8):
self.assert_file_is_there(str(i))
--- a/src/tests/cli/t_change_variant.py Wed Jun 12 15:44:50 2013 -0700
+++ b/src/tests/cli/t_change_variant.py Wed Jun 19 15:54:08 2013 -0700
@@ -20,7 +20,7 @@
# CDDL HEADER END
#
-# Copyright (c) 2009, 2012, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2009, 2013, Oracle and/or its affiliates. All rights reserved.
import testutils
if __name__ == "__main__":
@@ -32,6 +32,7 @@
import re
import unittest
+import pkg.misc as misc
from pkg.client.pkgdefs import *
class TestPkgChangeVariant(pkg5unittest.SingleDepotTestCase):
@@ -69,8 +70,19 @@
add file tmp/pkg_shared/shared/nonglobal_motd mode=0555 owner=root group=bin path=shared/zone_motd variant.opensolaris.zone=nonglobal
add file tmp/pkg_shared/unique/global mode=0555 owner=root group=bin path=unique/global variant.opensolaris.zone=global
add file tmp/pkg_shared/unique/nonglobal mode=0555 owner=root group=bin path=unique/nonglobal variant.opensolaris.zone=nonglobal
+ close"""
- close"""
+ pkg_unknown = """
+ open [email protected]
+ add set name=variant.unknown value=bar value=foo
+ add file tmp/bar path=usr/bin/bar mode=0755 owner=root group=root variant.unknown=bar
+ add file tmp/foo path=usr/bin/foo mode=0755 owner=root group=root variant.unknown=foo
+ close
+ open [email protected]
+ add set name=variant.unknown value=bar value=foo
+ add file tmp/bar path=usr/bin/foobar mode=0755 owner=root group=root variant.unknown=bar
+ add file tmp/foo path=usr/bin/foobar mode=0755 owner=root group=root variant.unknown=foo
+ close """
# this package intentionally has no variant.arch specification.
pkg_inc = """
@@ -109,7 +121,10 @@
"tmp/pkg_shared/shared/global_motd",
"tmp/pkg_shared/shared/nonglobal_motd",
"tmp/pkg_shared/unique/global",
- "tmp/pkg_shared/unique/nonglobal"
+ "tmp/pkg_shared/unique/nonglobal",
+
+ "tmp/bar",
+ "tmp/foo"
]
def setUp(self):
@@ -117,7 +132,8 @@
self.make_misc_files(self.misc_files)
self.pkgsend_bulk(self.rurl, (self.pkg_i386, self.pkg_sparc,
- self.pkg_shared, self.pkg_inc, self.pkg_cluster))
+ self.pkg_shared, self.pkg_inc, self.pkg_cluster,
+ self.pkg_unknown))
# verify pkg search indexes
self.verify_search = True
@@ -125,6 +141,16 @@
# verify installed images before changing variants
self.verify_install = False
+ def __assert_variant_matches_tsv(self, expected, errout=None,
+ exit=0, opts=misc.EmptyI, names=misc.EmptyI, su_wrap=False):
+ self.pkg("variant %s -H -F tsv %s" % (" ".join(opts),
+ " ".join(names)), exit=exit, su_wrap=su_wrap)
+ self.assertEqualDiff(expected, self.output)
+ if errout:
+ self.assert_(self.errout != "")
+ else:
+ self.assertEqualDiff("", self.errout)
+
def f_verify(self, path, token=None, negate=False):
"""Verify that the specified path exists and contains
the specified token. If negate is true, then make sure
@@ -303,8 +329,12 @@
"variant.opensolaris.zone": v_zone
}
self.image_create(self.rurl, variants=variants)
- self.pkg("variant -H| egrep %s" % ("'variant.arch[ ]*%s'" % v_arch))
- self.pkg("variant -H| egrep %s" % ("'variant.opensolaris.zone[ ]*%s'" % v_zone))
+
+ exp_tsv = """\
+variant.arch\t%s
+variant.opensolaris.zone\t%s
+""" % (v_arch, v_zone)
+ self.__assert_variant_matches_tsv(exp_tsv)
# install the specified packages into the image
ii_args = ""
@@ -325,8 +355,11 @@
# verify the updated image
self.i_verify(v_arch2, v_zone2, pl2)
- self.pkg("variant -H| egrep %s" % ("'variant.arch[ ]*%s'" % v_arch2))
- self.pkg("variant -H| egrep %s" % ("'variant.opensolaris.zone[ ]*%s'" % v_zone2))
+ exp_tsv = """\
+variant.arch\t%s
+variant.opensolaris.zone\t%s
+""" % (v_arch2, v_zone2)
+ self.__assert_variant_matches_tsv(exp_tsv)
self.image_destroy()
@@ -428,6 +461,44 @@
self.cv_test("sparc", "global", ["pkg_cluster"],
"i386", "nonglobal", ["pkg_cluster"])
+ def test_cv_12_unknown(self):
+ """Ensure that packages with an unknown variant and
+ non-conflicting content can be installed and subsequently
+ altered using change-variant."""
+
+ self.image_create(self.rurl)
+
+ # Install package with unknown variant and verify both files are
+ # present.
+ self.pkg("install -v [email protected]")
+ for fname in ("bar", "foo"):
+ self.f_verify("usr/bin/%s" % fname, fname)
+
+ # Next, verify upgrade to version of package with unknown
+ # variant fails if new version delivers conflicting content and
+ # variant has not been set.
+ self.pkg("update -vvv [email protected]", exit=1)
+
+ # Next, set unknown variant explicitly and verify content
+ # changes as expected.
+ self.pkg("change-variant unknown=foo")
+
+ # Verify bar no longer exists...
+ self.f_verify("usr/bin/bar", "bar", negate=True)
+ # ...and foo still does.
+ self.f_verify("usr/bin/foo", "foo")
+
+ # Next, upgrade to version of package with conflicting content
+ # and verify content changes as expected.
+ self.pkg("update -vvv [email protected]")
+
+ # Verify bar and foo no longer exist...
+ for fname in ("bar", "foo"):
+ self.f_verify("usr/bin/%s" % fname, fname, negate=True)
+
+ # ...and that foo variant of foobar is now installed.
+ self.f_verify("usr/bin/foobar", "foo")
+
def test_cv_parsable(self):
"""Test the parsable output of change-variant."""
--- a/src/tests/cli/t_pkg_install.py Wed Jun 12 15:44:50 2013 -0700
+++ b/src/tests/cli/t_pkg_install.py Wed Jun 19 15:54:08 2013 -0700
@@ -8472,8 +8472,8 @@
"tripledupfilec")
self.pkg("-D broken-conflicting-action-handling=1 install "
"tripledupfilea")
- self.pkg("change-variant variant.foo=two")
- self.pkg("change-variant variant.foo=one", exit=1)
+ self.pkg("change-variant -vvv variant.foo=two")
+ self.pkg("change-variant -vvv variant.foo=one", exit=1)
def dir_exists(self, path, mode=None, owner=None, group=None):
dir_path = os.path.join(self.get_img_path(), path)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/tests/cli/t_pkg_varcet.py Wed Jun 19 15:54:08 2013 -0700
@@ -0,0 +1,591 @@
+#!/usr/bin/python
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License (the "License").
+# You may not use this file except in compliance with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or http://www.opensolaris.org/os/licensing.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+
+# Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.
+
+import testutils
+if __name__ == "__main__":
+ testutils.setup_environment("../../../proto")
+import pkg5unittest
+
+import os
+import pkg.fmri as fmri
+import pkg.portable as portable
+import pkg.misc as misc
+import pkg.p5p
+import shutil
+import stat
+import tempfile
+import unittest
+
+
+class TestPkgVarcet(pkg5unittest.SingleDepotTestCase):
+
+ # Don't discard repository or setUp() every test.
+ persistent_setup = True
+
+ pkg_foo = """
+ open [email protected]
+ add file tmp/non-debug path=usr/bin/foo mode=0755 owner=root group=root
+ add file tmp/man path=usr/man/man1/foo.1 mode=0444 owner=root group=root
+ close
+ open [email protected]
+ add set name=variant.icecream value=neapolitan value=strawberry
+ add file tmp/debug path=usr/bin/foo mode=0755 owner=root group=root variant.debug.foo=true
+ add file tmp/non-debug path=usr/bin/foo mode=0755 owner=root group=root variant.debug.foo=false
+ add file tmp/neapolitan path=etc/icecream mode=0644 owner=root group=root variant.icecream=neapolitan
+ add file tmp/strawberry path=etc/icecream mode=0644 owner=root group=root variant.icecream=strawberry
+ add file tmp/man path=usr/man/man1/foo.1 mode=0444 owner=root group=root facet.doc.man=true
+ add file tmp/doc path=usr/share/foo/README mode=0444 owner=root group=root facet.doc.txt=true
+ add file tmp/pdf path=usr/share/foo/README.pdf mode=0444 owner=root group=root facet.doc.pdf=true
+ open [email protected]
+ add set name=pkg.facet value=doc.man value=doc.txt value=doc.pdf
+ add set name=pkg.variant value=icecream value=debug.foo
+ add set name=variant.icecream value=neapolitan value=strawberry
+ add file tmp/debug path=usr/bin/foo mode=0755 owner=root group=root variant.debug.foo=true
+ add file tmp/non-debug path=usr/bin/foo mode=0755 owner=root group=root variant.debug.foo=false
+ add file tmp/neapolitan path=etc/icecream mode=0644 owner=root group=root variant.icecream=neapolitan
+ add file tmp/strawberry path=etc/icecream mode=0644 owner=root group=root variant.icecream=strawberry
+ add file tmp/man path=usr/man/man1/foo.1 mode=0444 owner=root group=root facet.doc.man=true
+ add file tmp/doc path=usr/share/foo/README mode=0444 owner=root group=root facet.doc.txt=true
+ add file tmp/pdf path=usr/share/foo/README.pdf mode=0444 owner=root group=root facet.doc.pdf=true
+ close """
+
+ pkg_unknown = """
+ open [email protected]
+ add set name=variant.unknown value=bar value=foo
+ add file tmp/non-debug path=usr/bin/bar mode=0755 owner=root group=root variant.unknown=bar
+ add file tmp/non-debug path=usr/bin/foo mode=0755 owner=root group=root variant.unknown=foo
+ close """
+
+ misc_files = ["tmp/debug", "tmp/non-debug", "tmp/neapolitan",
+ "tmp/strawberry", "tmp/doc", "tmp/man", "tmp/pdf"]
+
+ def setUp(self):
+ pkg5unittest.SingleDepotTestCase.setUp(self)
+ self.make_misc_files(self.misc_files)
+ self.pkgsend_bulk(self.rurl, [
+ getattr(self, p)
+ for p in dir(self)
+ if p.startswith("pkg_") and isinstance(getattr(self, p),
+ basestring)
+ ])
+
+ def __assert_varcet_matches_default(self, cmd, expected, errout=None,
+ exit=0, opts=misc.EmptyI, names=misc.EmptyI, su_wrap=False):
+ if errout is None and exit != 0:
+ # Assume there should be error output for non-zero exit
+ # if not explicitly indicated.
+ errout = True
+
+ self.pkg("%s %s -H %s" % (cmd, " ".join(opts), " ".join(names)),
+ exit=exit, su_wrap=su_wrap)
+ self.assertEqualDiff(expected, self.reduceSpaces(self.output))
+ if errout:
+ self.assert_(self.errout != "")
+ else:
+ self.assertEqualDiff("", self.errout)
+
+ def __assert_varcet_matches_tsv(self, cmd, expected, errout=None,
+ exit=0, opts=misc.EmptyI, names=misc.EmptyI, su_wrap=False):
+ self.pkg("%s %s -H -F tsv %s" % (cmd, " ".join(opts),
+ " ".join(names)), exit=exit, su_wrap=su_wrap)
+ self.assertEqualDiff(expected, self.output)
+ if errout:
+ self.assert_(self.errout != "")
+ else:
+ self.assertEqualDiff("", self.errout)
+
+ def __assert_varcet_fails(self, cmd, operands, errout=True, exit=1,
+ su_wrap=False):
+ self.pkg("%s %s" % (cmd, operands), exit=exit, su_wrap=su_wrap)
+ if errout:
+ self.assert_(self.errout != "")
+ else:
+ self.assertEqualDiff("", self.errout)
+
+ def __assert_facet_matches_default(self, *args, **kwargs):
+ self.__assert_varcet_matches_default("facet", *args,
+ **kwargs)
+
+ def __assert_facet_matches_tsv(self, *args, **kwargs):
+ self.__assert_varcet_matches_tsv("facet", *args,
+ **kwargs)
+
+ def __assert_facet_matches(self, exp_def, **kwargs):
+ exp_tsv = exp_def.replace(" ", "\t")
+ self.__assert_varcet_matches_default("facet", exp_def,
+ **kwargs)
+ self.__assert_varcet_matches_tsv("facet", exp_tsv,
+ **kwargs)
+
+ def __assert_facet_fails(self, *args, **kwargs):
+ self.__assert_varcet_fails("facet", *args, **kwargs)
+
+ def __assert_variant_matches_default(self, *args, **kwargs):
+ self.__assert_varcet_matches_default("variant", *args,
+ **kwargs)
+
+ def __assert_variant_matches_tsv(self, *args, **kwargs):
+ self.__assert_varcet_matches_tsv("variant", *args,
+ **kwargs)
+
+ def __assert_variant_matches(self, exp_def, **kwargs):
+ exp_tsv = exp_def.replace(" ", "\t")
+ self.__assert_varcet_matches_default("variant", exp_def,
+ **kwargs)
+ self.__assert_varcet_matches_tsv("variant", exp_tsv,
+ **kwargs)
+
+ def __assert_variant_fails(self, *args, **kwargs):
+ self.__assert_varcet_fails("variant", *args, **kwargs)
+
+ def __test_foo_facet_upgrade(self, pkg):
+ #
+ # Next, verify output after upgrading package to faceted
+ # version.
+ #
+ self.pkg("update %s" % pkg)
+
+ # Verify output for no options and no patterns.
+ exp_def = """\
+facet.doc.* False
+facet.doc.html False
+facet.doc.man False
+facet.doc.txt True
+"""
+ self.__assert_facet_matches(exp_def)
+
+ # Unmatched because facet is not explicitly set.
+ self.__assert_facet_fails("doc.pdf")
+ self.__assert_facet_fails("'*pdf'")
+
+ # Matched case for explicitly set.
+ exp_def = """\
+facet.doc.* False
+facet.doc.txt True
+"""
+ names = ("'facet.doc.[*]'", "doc.txt")
+ self.__assert_facet_matches(exp_def, names=names)
+
+ # Verify -a output.
+ exp_def = """\
+facet.doc.* False
+facet.doc.html False
+facet.doc.man False
+facet.doc.pdf False
+facet.doc.txt True
+"""
+ opts = ("-a",)
+ self.__assert_facet_matches(exp_def, opts=opts)
+
+ # Matched case for explicitly set and those in packages.
+ exp_def = """\
+facet.doc.* False
+facet.doc.pdf False
+facet.doc.txt True
+"""
+ names = ("'facet.doc.[*]'", "*pdf", "facet.doc.txt")
+ opts = ("-a",)
+ self.__assert_facet_matches(exp_def, opts=opts, names=names)
+
+ # Verify -i output.
+ exp_def = """\
+facet.doc.man False
+facet.doc.pdf False
+facet.doc.txt True
+"""
+ opts = ("-i",)
+ self.__assert_facet_matches(exp_def, opts=opts)
+
+ # Unmatched because facet is not used in package.
+ self.__assert_facet_fails("-i doc.html")
+ self.__assert_facet_fails("-i '*html'")
+
+ # Matched case in packages.
+ exp_def = """\
+facet.doc.man False
+facet.doc.pdf False
+"""
+ names = ("'facet.*[!t]'",)
+ opts = ("-i",)
+ self.__assert_facet_matches(exp_def, opts=opts, names=names)
+
+ exp_def = """\
+facet.doc.pdf False
+"""
+ names = ("'*pdf'",)
+ opts = ("-i",)
+ self.__assert_facet_matches(exp_def, opts=opts, names=names)
+
+ # Now uninstall package and verify output (to ensure any
+ # potentially cached information has been updated).
+ self.pkg("uninstall foo")
+
+ exp_def = """\
+facet.doc.* False
+facet.doc.html False
+facet.doc.man False
+facet.doc.txt True
+"""
+
+ # Output should be the same for both -a and default cases with
+ # no packages installed.
+ for opts in ((), ("-a",)):
+ self.__assert_facet_matches(exp_def, opts=opts)
+
+ # No output expected for -i.
+ opts = ("-i",)
+ self.__assert_facet_matches("", opts=opts)
+
+ def test_00_facet(self):
+ """Verify facet subcommand works as expected."""
+
+ # create an image
+ variants = { "variant.icecream": "strawberry" }
+ self.image_create(self.rurl, variants=variants)
+
+ # Verify invalid options handled gracefully.
+ self.__assert_facet_fails("-z", exit=2)
+ self.__assert_facet_fails("-fi", exit=2)
+
+ #
+ # First, verify output before setting any facets or installing
+ # any packages.
+ #
+
+ # Output should be the same for all cases with no facets set and
+ # no packages installed.
+ for opts in ((), ("-i",), ("-a",)):
+ # No operands specified case.
+ self.__assert_facet_matches("", opts=opts)
+
+ # Unprivileged user case.
+ self.__assert_facet_matches("", opts=opts, su_wrap=True)
+
+ # Unmatched case.
+ self.__assert_facet_matches_default("", opts=opts,
+ names=("bogus",), exit=1)
+
+ # Unmatched case tsv; subtly different as no error
+ # output is expected.
+ self.__assert_facet_matches_tsv("", opts=opts,
+ names=("bogus",), exit=1, errout=False)
+
+ #
+ # Next, verify output after setting facets.
+ #
+
+ # Set some facets.
+ self.pkg("change-facet 'doc.*=False' doc.man=False "
+ "facet.doc.html=False facet.doc.txt=True")
+
+ exp_def = """\
+facet.doc.* False
+facet.doc.html False
+facet.doc.man False
+facet.doc.txt True
+"""
+
+ # Output should be the same for both -a and default cases with
+ # no packages installed.
+ for opts in ((), ("-a",)):
+ self.__assert_facet_matches(exp_def, opts=opts)
+
+ #
+ # Next, verify output after installing unfaceted package.
+ #
+ self.pkg("install [email protected]")
+
+ # Verify output for no options and no patterns.
+ exp_def = """\
+facet.doc.* False
+facet.doc.html False
+facet.doc.man False
+facet.doc.txt True
+"""
+ self.__assert_facet_matches(exp_def)
+
+ # Verify -a output.
+ opts = ("-a",)
+ self.__assert_facet_matches(exp_def, opts=opts)
+
+ # Verify -i output.
+ opts = ("-i",)
+ self.__assert_facet_matches("", opts=opts)
+
+ # Test upgraded package that does not declare all
+ # facets/variants.
+ self.__test_foo_facet_upgrade("[email protected]")
+
+ # Reinstall and then retest with upgraded package that declares
+ # all facets/variants.
+ self.pkg("install [email protected]")
+ self.__test_foo_facet_upgrade("[email protected]")
+
+ def __test_foo_variant_upgrade(self, pkg, variants):
+ #
+ # Next, verify output after upgrading package to varianted
+ # version.
+ #
+ self.pkg("update %s" % pkg)
+
+ # Verify output for no options and no patterns.
+ exp_def = """\
+variant.arch %(variant.arch)s
+variant.icecream strawberry
+variant.opensolaris.zone global
+""" % variants
+ self.__assert_variant_matches(exp_def)
+
+ # Unmatched because variant is not explicitly set.
+ self.__assert_variant_fails("debug.foo")
+ self.__assert_variant_fails("'*foo'")
+
+ # Matched case for explicitly set.
+ exp_def = """\
+variant.arch %(variant.arch)s
+variant.opensolaris.zone global
+""" % variants
+ names = ("arch", "'variant.*zone'")
+ self.__assert_variant_matches(exp_def, names=names)
+
+ # Verify -a output.
+ exp_def = """\
+variant.arch %(variant.arch)s
+variant.debug.foo false
+variant.icecream strawberry
+variant.opensolaris.zone global
+""" % variants
+ opts = ("-a",)
+ self.__assert_variant_matches(exp_def, opts=opts)
+
+ # Matched case for explicitly set and those in packages.
+ exp_def = """\
+variant.arch %(variant.arch)s
+variant.debug.foo false
+variant.opensolaris.zone global
+""" % variants
+ names = ("'variant.debug.*'", "arch", "'*zone'")
+ opts = ("-a",)
+ self.__assert_variant_matches(exp_def, opts=opts, names=names)
+
+ # Verify -i output.
+ exp_def = """\
+variant.debug.foo false
+variant.icecream strawberry
+""" % variants
+ opts = ("-i",)
+ self.__assert_variant_matches(exp_def, opts=opts)
+
+ # Unmatched because variant is not used in package.
+ self.__assert_variant_fails("-i opensolaris.zone")
+ self.__assert_variant_fails("-i '*arch'")
+
+ # Verify -v and -av output.
+ exp_def = """\
+variant.debug.foo false
+variant.debug.foo true
+variant.icecream neapolitan
+variant.icecream strawberry
+"""
+ for opts in (("-v",), ("-av",)):
+ self.__assert_variant_matches(exp_def, opts=opts)
+
+ exp_def = """\
+variant.icecream neapolitan
+variant.icecream strawberry
+""" % variants
+ names = ("'ice*'",)
+ opts = ("-av",)
+ self.__assert_variant_matches(exp_def, opts=opts, names=names)
+
+ # Matched case in packages.
+ exp_def = """\
+variant.icecream strawberry
+""" % variants
+ names = ("'variant.*[!o]'",)
+ opts = ("-i",)
+ self.__assert_variant_matches(exp_def, opts=opts, names=names)
+
+ exp_def = """\
+variant.debug.foo false
+""" % variants
+ names = ("*foo",)
+ opts = ("-i",)
+ self.__assert_variant_matches(exp_def, opts=opts, names=names)
+
+ # Now uninstall package and verify output (to ensure any
+ # potentially cached information has been updated).
+ self.pkg("uninstall foo")
+
+ exp_def = """\
+variant.arch %(variant.arch)s
+variant.icecream strawberry
+variant.opensolaris.zone global
+""" % variants
+
+ # Output should be the same for both -a and default cases with
+ # no packages installed.
+ for opts in ((), ("-a",)):
+ self.__assert_variant_matches(exp_def, opts=opts)
+
+ # No output expected for -v, -av, -i, or -iv.
+ for opts in (("-v",), ("-av",), ("-i",), ("-iv",)):
+ self.__assert_variant_matches("", opts=opts)
+
+ def test_01_variant(self):
+ """Verify variant subcommand works as expected."""
+
+ # create an image
+ self.image_create(self.rurl)
+
+ # Get variant data.
+ api_obj = self.get_img_api_obj()
+ variants = dict(v[:-1] for v in api_obj.gen_variants(
+ api_obj.VARIANT_IMAGE))
+
+ # Verify invalid options handled gracefully.
+ self.__assert_variant_fails("-z", exit=2)
+ self.__assert_variant_fails("-ai", exit=2)
+ self.__assert_variant_fails("-aiv", exit=2)
+
+ #
+ # First, verify output before setting any variants or installing
+ # any packages.
+ #
+
+ # Output should be the same for -a and default cases with no
+ # variants set and no packages installed.
+ exp_def = """\
+variant.arch %(variant.arch)s
+variant.opensolaris.zone global
+""" % variants
+
+ for opts in ((), ("-a",)):
+ # No operands specified case.
+ self.__assert_variant_matches(exp_def, opts=opts)
+
+ # Unprivileged user case.
+ self.__assert_variant_matches(exp_def, opts=opts,
+ su_wrap=True)
+
+ # Unmatched case.
+ self.__assert_variant_matches_default("", opts=opts,
+ names=("bogus",), exit=1)
+
+ # Unmatched case tsv; subtly different as no error
+ # output is expected.
+ self.__assert_variant_matches_tsv("", opts=opts,
+ names=("bogus",), exit=1, errout=False)
+
+ # No output expected for with no variants set and no packages
+ # installed for -v, -av, -i, and -iv.
+ for opts in (("-v",), ("-av",), ("-i",), ("-iv",)):
+ self.__assert_variant_matches("", opts=opts)
+
+ #
+ # Next, verify output after setting variants.
+ #
+
+ # Set some variants.
+ self.pkg("change-variant variant.icecream=strawberry")
+
+ exp_def = """\
+variant.arch %(variant.arch)s
+variant.icecream strawberry
+variant.opensolaris.zone global
+""" % variants
+
+ # Output should be the same for both -a and default cases with
+ # no packages installed.
+ for opts in ((), ("-a",)):
+ self.__assert_variant_matches(exp_def, opts=opts)
+
+ #
+ # Next, verify output after installing unvarianted package.
+ #
+ self.pkg("install [email protected]")
+
+ # Verify output for no options and no patterns.
+ exp_def = """\
+variant.arch %(variant.arch)s
+variant.icecream strawberry
+variant.opensolaris.zone global
+""" % variants
+ self.__assert_variant_matches(exp_def)
+
+ # Verify -a output.
+ opts = ("-a",)
+ self.__assert_variant_matches(exp_def, opts=opts)
+
+ # Verify -v, -av, -i, and -iv output.
+ for opts in (("-v",), ("-av",), ("-i",), ("-iv",)):
+ self.__assert_variant_matches("", opts=opts)
+
+ # Test upgraded package that does not declare all
+ # facets/variants.
+ self.__test_foo_variant_upgrade("[email protected]", variants)
+
+ # Reinstall and then retest with upgraded package that declares
+ # all facets/variants.
+ self.pkg("install [email protected]")
+ self.__test_foo_variant_upgrade("[email protected]", variants)
+
+ # Next, verify output after installing package with unknown
+ # variant.
+ self.pkg("install [email protected]")
+
+ # Verify output for no options and no patterns.
+ exp_def = """\
+variant.arch %(variant.arch)s
+variant.icecream strawberry
+variant.opensolaris.zone global
+""" % variants
+ self.__assert_variant_matches(exp_def)
+
+ # Verify -a output.
+ exp_def = """\
+variant.arch %(variant.arch)s
+variant.icecream strawberry
+variant.opensolaris.zone global
+variant.unknown
+""" % variants
+ self.__assert_variant_matches(exp_def, opts=("-a",))
+
+ # Verify -i output.
+ exp_def = """\
+variant.unknown
+""" % variants
+ self.__assert_variant_matches(exp_def, opts=("-i",))
+
+ # Verify -v, -av, and -iv output.
+ for opts in (("-v",), ("-av",), ("-iv",)):
+ exp_def = """\
+variant.unknown bar
+variant.unknown foo
+""" % variants
+ self.__assert_variant_matches(exp_def, opts=opts)
+
+
+if __name__ == "__main__":
+ unittest.main()
--- a/src/tests/cli/t_pkgdep_resolve.py Wed Jun 12 15:44:50 2013 -0700
+++ b/src/tests/cli/t_pkgdep_resolve.py Wed Jun 19 15:54:08 2013 -0700
@@ -1116,7 +1116,9 @@
"\n".join(
[str(d) for d in pkg_deps[col_path]]))
d = pkg_deps[one_dep][0]
- self.assertEqual(d.attrs["fmri"], exp_pkg)
+ self.assertEqual(d.attrs["fmri"], exp_pkg,
+ "Expected dependency %s; found %s." % (exp_pkg,
+ d.attrs["fmri"]))
col_path = self.make_manifest(self.collision_manf)
col_path_num_var = self.make_manifest(
--- a/src/tests/pkg5unittest.py Wed Jun 12 15:44:50 2013 -0700
+++ b/src/tests/pkg5unittest.py Wed Jun 19 15:54:08 2013 -0700
@@ -132,7 +132,7 @@
# Version test suite is known to work with.
PKG_CLIENT_NAME = "pkg"
-CLIENT_API_VERSION = 73
+CLIENT_API_VERSION = 75
ELIDABLE_ERRORS = [ TestSkippedException, depotcontroller.DepotStateException ]