7034764 dangling link check fails when links and targets are in different packages
authorDanek Duvall <danek.duvall@oracle.com>
Thu, 07 Apr 2011 16:25:05 -0700
changeset 186 3adedf0f9f4d
parent 185 773dda89f186
child 187 80d8cb349fea
7034764 dangling link check fails when links and targets are in different packages
components/python/python26/python-26.p5m
components/tomcat/tomcat-examples.p5m
components/top/top.p5m
components/zsh/zsh.p5m
make-rules/shared-macros.mk
tools/python/pkglint/userland.py
--- 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),