7034764 dangling link check fails when links and targets are in different packages
--- a/components/python/python26/python-26.p5m Thu Apr 07 15:37:10 2011 -0700
+++ b/components/python/python26/python-26.p5m Thu Apr 07 16:25:05 2011 -0700
@@ -2921,7 +2921,7 @@
file path=usr/lib/python2.6/zipfile.py
file path=usr/lib/python2.6/zipfile.pyc
file usr/share/man/man1/python.1 path=usr/share/man/man1/python2.6.1
-hardlink path=usr/bin/isapython2.6 pkg.linted=true target=../lib/isaexec
+hardlink path=usr/bin/isapython2.6 target=../lib/isaexec
legacy pkg=SUNWPython26 category=GNOME2,application,JDS4 \
desc="The Python interpreter, libraries and utilities" \
name="The Python interpreter, libraries and utilities" vendor=Python.org
--- a/components/tomcat/tomcat-examples.p5m Thu Apr 07 15:37:10 2011 -0700
+++ b/components/tomcat/tomcat-examples.p5m Thu Apr 07 16:25:05 2011 -0700
@@ -460,8 +460,6 @@
legacy pkg=SUNWtcat-examples \
desc="Tomcat Servlet/JSP Container - example applications" \
name="Tomcat Servlet/JSP Container - example applications"
-
+link path=var/tomcat6/webapps/docs target=../../../usr/tomcat6/docs
license tomcat.license license="Apache v2.0"
-link path=var/tomcat6/webapps/docs pkg.linted=true \
- target=../../../usr/tomcat6/docs
--- a/components/top/top.p5m Thu Apr 07 15:37:10 2011 -0700
+++ b/components/top/top.p5m Thu Apr 07 16:25:05 2011 -0700
@@ -43,7 +43,7 @@
file usr/bin/top path=usr/bin/$(MACH32)/top
file path=usr/bin/$(MACH64)/top
file path=usr/share/man/man1/top.1
-hardlink path=usr/bin/top pkg.linted=true target=../lib/isaexec
+hardlink path=usr/bin/top target=../lib/isaexec
legacy pkg=SUNWtop desc="top - 3.8beta1" \
name="top - provides a rolling display of top cpu using processes"
license top.license license="BSD, Apachev2.0"
--- a/components/zsh/zsh.p5m Thu Apr 07 15:37:10 2011 -0700
+++ b/components/zsh/zsh.p5m Thu Apr 07 16:25:05 2011 -0700
@@ -1315,6 +1315,5 @@
file path=usr/share/zsh/$(COMPONENT_VERSION)/functions/Zle/zed-set-file-name
file path=usr/share/zsh/$(COMPONENT_VERSION)/scripts/newuser
hardlink path=usr/bin/zsh target=zsh-$(COMPONENT_VERSION)
+link path=etc/zprofile target=profile
license license license="Zsh License"
-# This link dangles deliberately, and userland.action002.0 doesn't like that.
-link path=etc/zprofile pkg.linted=true target=profile
--- a/make-rules/shared-macros.mk Thu Apr 07 15:37:10 2011 -0700
+++ b/make-rules/shared-macros.mk Thu Apr 07 16:25:05 2011 -0700
@@ -69,6 +69,10 @@
PKG_REPO = file:$(WS_REPO)
+# Set a default reference repository against which pkglint is run, in case it
+# hasn't been set in the environment.
+CANONICAL_REPO ?= http://ipkg.us.oracle.com/solaris11/dev/
+
COMPONENT_DIR = $(shell pwd)
SOURCE_DIR = $(COMPONENT_DIR)/$(COMPONENT_SRC)
BUILD_DIR = $(COMPONENT_DIR)/build
--- a/tools/python/pkglint/userland.py Thu Apr 07 15:37:10 2011 -0700
+++ b/tools/python/pkglint/userland.py Thu Apr 07 16:25:05 2011 -0700
@@ -27,11 +27,11 @@
# Some userland consolidation specific lint checks
import pkg.lint.base as base
+from pkg.lint.engine import lint_fmri_successor
import pkg.elf as elf
import re
import os.path
-
class UserlandActionChecker(base.ActionChecker):
"""An opensolaris.org-specific class to check actions."""
@@ -51,10 +51,134 @@
re.compile('^\$ORIGIN/')
]
self.initscript_re = re.compile("^etc/(rc.|init)\.d")
+
+ self.lint_paths = {}
+ self.ref_paths = {}
+
super(UserlandActionChecker, self).__init__(config)
- def startup(self, engine):
- pass
+ def startup(self, engine):
+ """Initialize the checker with a dictionary of paths, so that we
+ can do link resolution.
+
+ This is copied from the core pkglint code, but should eventually
+ be made common.
+ """
+
+ def seed_dict(mf, attr, dic, atype=None, verbose=False):
+ """Updates a dictionary of { attr: [(fmri, action), ..]}
+ where attr is the value of that attribute from
+ actions of a given type atype, in the given
+ manifest."""
+
+ pkg_vars = mf.get_all_variants()
+
+ if atype:
+ mfg = (a for a in mf.gen_actions_by_type(atype))
+ else:
+ mfg = (a for a in mf.gen_actions())
+
+ for action in mfg:
+ if atype and action.name != atype:
+ continue
+ if attr not in action.attrs:
+ continue
+
+ variants = action.get_variant_template()
+ variants.merge_unknown(pkg_vars)
+ action.attrs.update(variants)
+
+ p = action.attrs[attr]
+ dic.setdefault(p, []).append((mf.fmri, action))
+
+ # construct a set of FMRIs being presented for linting, and
+ # avoid seeding the reference dictionary with any for which
+ # we're delivering new packages.
+ lint_fmris = {}
+ for m in engine.gen_manifests(engine.lint_api_inst,
+ release=engine.release, pattern=engine.pattern):
+ lint_fmris.setdefault(m.fmri.get_name(), []).append(m.fmri)
+ for m in engine.lint_manifests:
+ lint_fmris.setdefault(m.fmri.get_name(), []).append(m.fmri)
+
+ engine.logger.debug(
+ _("Seeding reference action path dictionaries."))
+
+ for manifest in engine.gen_manifests(engine.ref_api_inst,
+ release=engine.release):
+ # Only put this manifest into the reference dictionary
+ # if it's not an older version of the same package.
+ if not any(
+ lint_fmri_successor(fmri, manifest.fmri)
+ for fmri
+ in lint_fmris.get(manifest.fmri.get_name(), [])
+ ):
+ seed_dict(manifest, "path", self.ref_paths)
+
+ engine.logger.debug(
+ _("Seeding lint action path dictionaries."))
+
+ # we provide a search pattern, to allow users to lint a
+ # subset of the packages in the lint_repository
+ for manifest in engine.gen_manifests(engine.lint_api_inst,
+ release=engine.release, pattern=engine.pattern):
+ seed_dict(manifest, "path", self.lint_paths)
+
+ engine.logger.debug(
+ _("Seeding local action path dictionaries."))
+
+ for manifest in engine.lint_manifests:
+ seed_dict(manifest, "path", self.lint_paths)
+
+ self.__merge_dict(self.lint_paths, self.ref_paths,
+ ignore_pubs=engine.ignore_pubs)
+
+ def __merge_dict(self, src, target, ignore_pubs=True):
+ """Merges the given src dictionary into the target
+ dictionary, giving us the target content as it would appear,
+ were the packages in src to get published to the
+ repositories that made up target.
+
+ We need to only merge packages at the same or successive
+ version from the src dictionary into the target dictionary.
+ If the src dictionary contains a package with no version
+ information, it is assumed to be more recent than the same
+ package with no version in the target."""
+
+ for p in src:
+ if p not in target:
+ target[p] = src[p]
+ continue
+
+ def build_dic(arr):
+ """Builds a dictionary of fmri:action entries"""
+ dic = {}
+ for (pfmri, action) in arr:
+ if pfmri in dic:
+ dic[pfmri].append(action)
+ else:
+ dic[pfmri] = [action]
+ return dic
+
+ src_dic = build_dic(src[p])
+ targ_dic = build_dic(target[p])
+
+ for src_pfmri in src_dic:
+ # we want to remove entries deemed older than
+ # src_pfmri from targ_dic.
+ for targ_pfmri in targ_dic.copy():
+ sname = src_pfmri.get_name()
+ tname = targ_pfmri.get_name()
+ if lint_fmri_successor(src_pfmri,
+ targ_pfmri,
+ ignore_pubs=ignore_pubs):
+ targ_dic.pop(targ_pfmri)
+ targ_dic.update(src_dic)
+ l = []
+ for pfmri in targ_dic:
+ for action in targ_dic[pfmri]:
+ l.append((pfmri, action))
+ target[p] = l
def __realpath(self, path, target):
"""Combine path and target to get the real path."""
@@ -179,19 +303,11 @@
path = action.attrs["path"]
target = action.attrs["target"]
realtarget = self.__realpath(path, target)
-
- resolved = False
- for maction in manifest.gen_actions():
- mpath = None
- if maction.name in ["dir", "file", "link",
- "hardlink"]:
- mpath = maction.attrs["path"]
- if mpath and mpath == realtarget:
- resolved = True
- break
-
- if resolved != True:
+ # Check against the target image (ref_paths), since links might
+ # resolve outside the packages delivering a particular
+ # component.
+ if not self.ref_paths.get(realtarget, None):
engine.error(
_("%s %s has unresolvable target '%s'") %
(action.name, path, target),