From ffbff569ac5769449d4f106b7b6beb37db03c0f6 Mon Sep 17 00:00:00 2001 From: Zac Medico Date: Tue, 20 Mar 2007 09:52:15 +0000 Subject: Implement license visibility filtering for GLEP 23, bug #17367, and bug #152593. svn path=/main/trunk/; revision=6251 --- NEWS | 1 + pym/emerge/__init__.py | 28 ++++++-- pym/portage/__init__.py | 155 +++++++++++++++++++++++++++++++++++++++++- pym/portage/const.py | 5 +- pym/portage/dbapi/porttree.py | 23 +++++-- 5 files changed, 199 insertions(+), 13 deletions(-) diff --git a/NEWS b/NEWS index 08f2669b7..c73f3acb5 100644 --- a/NEWS +++ b/NEWS @@ -9,6 +9,7 @@ portage-2.1.3 * Add ** as new token for package.keywords to bypass the keyword visibility layer * Namespace sanitizing: move all portage related code into portage.* namespace, rename portage_foo modules to portage.foo (but keep symlinks for compability) +* Add license visibility filtering (GLEP 23) portage-2.1.2 ------------- diff --git a/pym/emerge/__init__.py b/pym/emerge/__init__.py index 2c64acada..5b38f95af 100644 --- a/pym/emerge/__init__.py +++ b/pym/emerge/__init__.py @@ -1678,16 +1678,32 @@ class depgraph: print "\n!!! "+red("All ebuilds that could satisfy ")+green(xinfo)+red(" have been masked.") print "!!! One of the following masked packages is required to complete your request:" oldcomment = "" + shown_licenses = [] for p in alleb: mreasons = portage.getmaskingstatus(p, settings=pkgsettings, portdb=portdb) print "- "+p+" (masked by: "+", ".join(mreasons)+")" - comment, filename = portage.getmaskingreason(p, - settings=pkgsettings, portdb=portdb, return_location=True) - if comment and comment != oldcomment: - print filename+":" - print comment - oldcomment = comment + if "package.mask" in mreasons: + comment, filename = \ + portage.getmaskingreason(p, + settings=pkgsettings, portdb=portdb, + return_location=True) + if comment and comment != oldcomment: + print filename+":" + print comment + oldcomment = comment + licenses = portdb.aux_get(p, ["LICENSE"])[0] + missing_licenses = [] + for l in pkgsettings.getMissingLicenses( + licenses, p): + l_path = portdb.findLicensePath(l) + if l in shown_licenses: + continue + msg = ("A copy of the '%s' license" + \ + " is located at '%s'.") % (l, l_path) + print msg + print + shown_licenses.append(l) print print "For more information, see MASKED PACKAGES section in the emerge man page or " print "refer to the Gentoo Handbook." diff --git a/pym/portage/__init__.py b/pym/portage/__init__.py index 129804632..660e114e0 100644 --- a/pym/portage/__init__.py +++ b/pym/portage/__init__.py @@ -907,6 +907,9 @@ class config: self.dirVirtuals = copy.deepcopy(clone.dirVirtuals) self.treeVirtuals = copy.deepcopy(clone.treeVirtuals) self.features = copy.deepcopy(clone.features) + + self._accept_license = copy.deepcopy(clone._accept_license) + self._plicensedict = copy.deepcopy(clone._plicensedict) else: # backupenv is for calculated incremental variables. @@ -1208,6 +1211,7 @@ class config: self.pusedict = {} self.pkeywordsdict = {} + self._plicensedict = {} self.punmaskdict = {} abs_user_config = os.path.join(config_root, USER_CONFIG_PATH.lstrip(os.path.sep)) @@ -1260,6 +1264,17 @@ class config: if not self.pkeywordsdict.has_key(cp): self.pkeywordsdict[cp] = {} self.pkeywordsdict[cp][key] = pkgdict[key] + + #package.license + licdict = grabdict_package(os.path.join( + abs_user_config, "package.license"), recursive=1) + for k, v in licdict.iteritems(): + cp = dep_getkey(k) + cp_dict = self._plicensedict.get(cp) + if not cp_dict: + cp_dict = {} + self._plicensedict[cp] = cp_dict + cp_dict[k] = self.expandLicenseTokens(v) #package.unmask pkgunmasklines = grabfile_package( @@ -1334,6 +1349,12 @@ class config: else: self.pprovideddict[mycatpkg]=[x] + # parse licensegroups + self._license_groups = {} + for x in locations: + self._license_groups.update( + grabdict(os.path.join(x, "license_groups"))) + # reasonable defaults; this is important as without USE_ORDER, # USE will always be "" (nothing set)! if "USE_ORDER" not in self: @@ -1369,6 +1390,18 @@ class config: self["PORTAGE_PYM_PATH"] = PORTAGE_PYM_PATH self.backup_changes("PORTAGE_PYM_PATH") + # Expand license groups + # This has to do be done for each config layer before regenerate() + # in order for incremental negation to work properly. + if local_config: + for c in self.configdict.itervalues(): + v = c.get("ACCEPT_LICENSE") + if not v: + continue + v = " ".join(self.expandLicenseTokens(v.split())) + c["ACCEPT_LICENSE"] = v + del c, v + for var in ("PORTAGE_INST_UID", "PORTAGE_INST_GID"): try: self[var] = str(int(self.get(var, "0"))) @@ -1382,6 +1415,20 @@ class config: self.regenerate() self.features = portage.util.unique_array(self["FEATURES"].split()) + if local_config: + self._accept_license = \ + set(self.get("ACCEPT_LICENSE", "").split()) + # In order to enforce explicit acceptance for restrictive + # licenses that require it, "*" will not be allowed in the + # user config. Don't enforce this until license groups are + # fully implemented in the tree. + #self._accept_license.discard("*") + if not self._accept_license: + self._accept_license = set(["*"]) + else: + # repoman will accept any license + self._accept_license = set(["*"]) + if "gpg" in self.features: if not os.path.exists(self["PORTAGE_GPG_DIR"]) or \ not os.path.isdir(self["PORTAGE_GPG_DIR"]): @@ -1437,6 +1484,51 @@ class config: writemsg("!!! %s\n" % str(e), noiselevel=-1) + def expandLicenseTokens(self, tokens): + """ Take a token from ACCEPT_LICENSE or package.license and expand it + if it's a group token (indicated by @) or just return it if it's not a + group. If a group is negated then negate all group elements.""" + expanded_tokens = [] + for x in tokens: + expanded_tokens.extend(self._expandLicenseToken(x, None)) + return expanded_tokens + + def _expandLicenseToken(self, token, traversed_groups): + negate = False + rValue = [] + if token.startswith("-"): + negate = True + license_name = token[1:] + else: + license_name = token + if not license_name.startswith("@"): + rValue.append(token) + return rValue + group_name = license_name[1:] + if not traversed_groups: + traversed_groups = set() + license_group = self._license_groups.get(group_name) + if group_name in traversed_groups: + writemsg(("Circular license group reference" + \ + " detected in '%s'\n") % group_name, noiselevel=-1) + rValue.append("@"+group_name) + elif license_group: + traversed_groups.add(group_name) + for l in license_group: + if l.startswith("-"): + writemsg(("Skipping invalid element %s" + \ + " in license group '%s'\n") % (l, group_name), + noiselevel=-1) + else: + rValue.extend(self._expandLicenseToken(l, traversed_groups)) + else: + writemsg("Undefined license group '%s'\n" % group_name, + noiselevel=-1) + rValue.append("@"+group_name) + if negate: + rvalue = ["-" + token for token in rValue] + return rValue + def validate(self): """Validate miscellaneous settings and display warnings if necessary. (This code was previously in the global scope of portage.py)""" @@ -1656,6 +1748,49 @@ class config: if has_changed: self.reset(keeping_pkg=1,use_cache=use_cache) + def getMissingLicenses(self, licenses, cpv): + cpdict = self._plicensedict.get(dep_getkey(cpv), None) + acceptable_licenses = self._accept_license.copy() + if cpdict: + for atom in match_to_list(cpv, cpdict.keys()): + acceptable_licenses.update(cpdict[atom]) + if "*" in acceptable_licenses: + return [] + if "?" in licenses: + self.setcpv(cpv) + license_struct = portage.dep.paren_reduce(licenses) + license_struct = portage.dep.use_reduce( + license_struct, uselist=self["USE"].split()) + license_struct = portage.dep.dep_opconvert(license_struct) + return self._getMissingLicenses(license_struct, acceptable_licenses) + + def _getMissingLicenses(self, license_struct, acceptable_licenses): + if not license_struct: + return [] + if license_struct[0] == "||": + ret = [] + for element in license_struct[1:]: + if isinstance(element, list): + if element: + ret.append(self._getMissingLicenses(element)) + else: + if element in acceptable_licenses: + return [] + ret.append(element) + # Return all masked licenses, since we don't know which combination + # (if any) the user will decide to unmask. + return flatten(ret) + + ret = [] + for element in license_struct: + if isinstance(element, list): + if element: + ret.extend(self._getMissingLicenses(element)) + else: + if element not in acceptable_licenses: + ret.append(element) + return ret + def setinst(self,mycpv,mydbapi): self.modifying() if len(self.virtuals) == 0: @@ -4479,7 +4614,7 @@ def getmaskingreason(mycpv, settings=None, portdb=None, return_location=False): def getmaskingstatus(mycpv, settings=None, portdb=None): if settings is None: - settings = globals()["settings"] + settings = config(clone=globals()["settings"]) if portdb is None: portdb = globals()["portdb"] mysplit = catpkgsplit(mycpv) @@ -4508,7 +4643,8 @@ def getmaskingstatus(mycpv, settings=None, portdb=None): # keywords checking try: - mygroups, eapi = portdb.aux_get(mycpv, ["KEYWORDS", "EAPI"]) + mygroups, licenses, eapi = portdb.aux_get( + mycpv, ["KEYWORDS", "LICENSE", "EAPI"]) except KeyError: # The "depend" phase apparently failed for some reason. An associated # error message will have already been printed to stderr. @@ -4563,6 +4699,21 @@ def getmaskingstatus(mycpv, settings=None, portdb=None): if kmask: rValue.append(kmask+" keyword") + + try: + missing_licenses = settings.getMissingLicenses(licenses, mycpv) + if missing_licenses: + allowed_tokens = set(["||", "(", ")"]) + allowed_tokens.update(missing_licenses) + license_split = licenses.split() + license_split = [x for x in license_split \ + if x in allowed_tokens] + msg = license_split[:] + msg.append("license(s)") + rValue.append(" ".join(msg)) + except portage.exception.InvalidDependString, e: + rValue.append("LICENSE: "+str(e)) + return rValue diff --git a/pym/portage/const.py b/pym/portage/const.py index fa1187fa7..28e6f36ff 100644 --- a/pym/portage/const.py +++ b/pym/portage/const.py @@ -48,7 +48,10 @@ COLOR_MAP_FILE = USER_CONFIG_PATH + "/color.map" REPO_NAME_FILE = "repo_name" REPO_NAME_LOC = "profiles" + "/" + REPO_NAME_FILE -INCREMENTALS=["USE","USE_EXPAND","USE_EXPAND_HIDDEN","FEATURES","ACCEPT_KEYWORDS","ACCEPT_LICENSE","CONFIG_PROTECT_MASK","CONFIG_PROTECT","PRELINK_PATH","PRELINK_PATH_MASK"] +INCREMENTALS = ["USE", "USE_EXPAND", "USE_EXPAND_HIDDEN", "FEATURES", + "ACCEPT_KEYWORDS", "ACCEPT_LICENSE", + "CONFIG_PROTECT_MASK", "CONFIG_PROTECT", + "PRELINK_PATH", "PRELINK_PATH_MASK"] EBUILD_PHASES = ["setup", "unpack", "compile", "test", "install", "preinst", "postinst", "prerm", "postrm", "other"] diff --git a/pym/portage/dbapi/porttree.py b/pym/portage/dbapi/porttree.py index 08e696149..4118a4da4 100644 --- a/pym/portage/dbapi/porttree.py +++ b/pym/portage/dbapi/porttree.py @@ -6,7 +6,7 @@ from portage.dep import use_reduce, paren_reduce, dep_getslot, dep_getkey, \ match_from_list, match_to_list from portage.exception import OperationNotPermitted, PortageException, \ UntrustedSignature, SecurityViolation, InvalidSignature, MissingSignature, \ - FileNotFound + FileNotFound, InvalidDependString from portage.manifest import Manifest from portage.output import red from portage.util import ensure_dirs, writemsg, apply_recursive_permissions @@ -109,7 +109,7 @@ class portdbapi(dbapi): self.auxdb[x] = self.auxdbmodule( self.depcachedir, x, filtered_auxdbkeys, gid=portage_gid) # Selectively cache metadata in order to optimize dep matching. - self._aux_cache_keys = set(["EAPI", "KEYWORDS", "SLOT"]) + self._aux_cache_keys = set(["EAPI", "KEYWORDS", "LICENSE", "SLOT"]) self._aux_cache = {} def _init_cache_dirs(self): @@ -156,6 +156,15 @@ class portdbapi(dbapi): return "" return mydig+"/files/digest-"+mysplit[-1] + def findLicensePath(self, license_name): + mytrees = self.porttrees[:] + mytrees.reverse() + for x in mytrees: + license_path = os.path.join(x, "licenses", license_name) + if os.access(license_path, os.R_OK): + return license_path + return None + def findname(self,mycpv): return self.findname2(mycpv)[0] @@ -624,13 +633,14 @@ class portdbapi(dbapi): accept_keywords = self.mysettings["ACCEPT_KEYWORDS"].split() pkgdict = self.mysettings.pkeywordsdict + aux_keys = ["KEYWORDS", "LICENSE", "EAPI"] for mycpv in mylist: try: - keys, eapi = self.aux_get(mycpv, ["KEYWORDS", "EAPI"]) + keys, licenses, eapi = self.aux_get(mycpv, aux_keys) except KeyError: continue except PortageException, e: - writemsg("!!! Error: aux_get('%s', ['KEYWORDS', 'EAPI'])\n" % \ + writemsg("!!! Error: aux_get('%s', %s)\n" % (mycpv, aux_keys), mycpv, noiselevel=-1) writemsg("!!! %s\n" % str(e), noiselevel=-1) del e @@ -675,6 +685,11 @@ class portdbapi(dbapi): hasstable = True if not match and ((hastesting and "~*" in pgroups) or (hasstable and "*" in pgroups) or "**" in pgroups): match=1 + try: + if self.mysettings.getMissingLicenses(licenses, mycpv): + match = 0 + except InvalidDependString: + match = 0 if match and eapi_is_supported(eapi): newlist.append(mycpv) return newlist -- cgit v1.2.3-1-g7c22