summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorZac Medico <zmedico@gentoo.org>2007-03-20 09:52:15 +0000
committerZac Medico <zmedico@gentoo.org>2007-03-20 09:52:15 +0000
commitffbff569ac5769449d4f106b7b6beb37db03c0f6 (patch)
tree258e86395eaf50ddf8cc885984c2f79ae7eb7f29
parenta7a93d92e4391e9c5dfed87afc1f803a9a4a8405 (diff)
downloadportage-ffbff569ac5769449d4f106b7b6beb37db03c0f6.tar.gz
portage-ffbff569ac5769449d4f106b7b6beb37db03c0f6.tar.bz2
portage-ffbff569ac5769449d4f106b7b6beb37db03c0f6.zip
Implement license visibility filtering for GLEP 23, bug #17367, and bug #152593.
svn path=/main/trunk/; revision=6251
-rw-r--r--NEWS1
-rw-r--r--pym/emerge/__init__.py28
-rw-r--r--pym/portage/__init__.py155
-rw-r--r--pym/portage/const.py5
-rw-r--r--pym/portage/dbapi/porttree.py23
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