| # Copyright 2010-2015 Gentoo Foundation |
| # Distributed under the terms of the GNU General Public License v2 |
| |
| __all__ = ( |
| 'LicenseManager', |
| ) |
| |
| from portage import os |
| from portage.dep import ExtendedAtomDict, use_reduce |
| from portage.exception import InvalidDependString |
| from portage.localization import _ |
| from portage.util import grabdict, grabdict_package, writemsg |
| from portage.versions import cpv_getkey, _pkg_str |
| |
| from portage.package.ebuild._config.helper import ordered_by_atom_specificity |
| |
| |
| class LicenseManager(object): |
| |
| def __init__(self, license_group_locations, abs_user_config, user_config=True): |
| |
| self._accept_license_str = None |
| self._accept_license = None |
| self._license_groups = {} |
| self._plicensedict = ExtendedAtomDict(dict) |
| self._undef_lic_groups = set() |
| |
| if user_config: |
| license_group_locations = list(license_group_locations) + [abs_user_config] |
| |
| self._read_license_groups(license_group_locations) |
| |
| if user_config: |
| self._read_user_config(abs_user_config) |
| |
| def _read_user_config(self, abs_user_config): |
| licdict = grabdict_package(os.path.join( |
| abs_user_config, "package.license"), recursive=1, allow_wildcard=True, allow_repo=True, verify_eapi=False) |
| for k, v in licdict.items(): |
| self._plicensedict.setdefault(k.cp, {})[k] = \ |
| self.expandLicenseTokens(v) |
| |
| def _read_license_groups(self, locations): |
| for loc in locations: |
| for k, v in grabdict( |
| os.path.join(loc, "license_groups")).items(): |
| self._license_groups.setdefault(k, []).extend(v) |
| |
| for k, v in self._license_groups.items(): |
| self._license_groups[k] = frozenset(v) |
| |
| def extract_global_changes(self, old=""): |
| ret = old |
| atom_license_map = self._plicensedict.get("*/*") |
| if atom_license_map is not None: |
| v = atom_license_map.pop("*/*", None) |
| if v is not None: |
| ret = " ".join(v) |
| if old: |
| ret = old + " " + ret |
| if not atom_license_map: |
| #No tokens left in atom_license_map, remove it. |
| del self._plicensedict["*/*"] |
| return ret |
| |
| 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 traversed_groups is None: |
| 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: |
| if self._license_groups and \ |
| group_name not in self._undef_lic_groups: |
| self._undef_lic_groups.add(group_name) |
| 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 _getPkgAcceptLicense(self, cpv, slot, repo): |
| """ |
| Get an ACCEPT_LICENSE list, accounting for package.license. |
| """ |
| accept_license = self._accept_license |
| cp = cpv_getkey(cpv) |
| cpdict = self._plicensedict.get(cp) |
| if cpdict: |
| if not hasattr(cpv, "slot"): |
| cpv = _pkg_str(cpv, slot=slot, repo=repo) |
| plicence_list = ordered_by_atom_specificity(cpdict, cpv) |
| if plicence_list: |
| accept_license = list(self._accept_license) |
| for x in plicence_list: |
| accept_license.extend(x) |
| return accept_license |
| |
| def get_prunned_accept_license(self, cpv, use, lic, slot, repo): |
| """ |
| Generate a pruned version of ACCEPT_LICENSE, by intersection with |
| LICENSE. This is required since otherwise ACCEPT_LICENSE might be |
| too big (bigger than ARG_MAX), causing execve() calls to fail with |
| E2BIG errors as in bug #262647. |
| """ |
| try: |
| licenses = set(use_reduce(lic, uselist=use, flat=True)) |
| except InvalidDependString: |
| licenses = set() |
| licenses.discard('||') |
| |
| accept_license = self._getPkgAcceptLicense(cpv, slot, repo) |
| |
| if accept_license: |
| acceptable_licenses = set() |
| for x in accept_license: |
| if x == '*': |
| acceptable_licenses.update(licenses) |
| elif x == '-*': |
| acceptable_licenses.clear() |
| elif x[:1] == '-': |
| acceptable_licenses.discard(x[1:]) |
| elif x in licenses: |
| acceptable_licenses.add(x) |
| |
| licenses = acceptable_licenses |
| return ' '.join(sorted(licenses)) |
| |
| def getMissingLicenses(self, cpv, use, lic, slot, repo): |
| """ |
| Take a LICENSE string and return a list of any licenses that the user |
| may need to accept for the given package. The returned list will not |
| contain any licenses that have already been accepted. This method |
| can throw an InvalidDependString exception. |
| |
| @param cpv: The package name (for package.license support) |
| @type cpv: String |
| @param use: "USE" from the cpv's metadata |
| @type use: String |
| @param lic: "LICENSE" from the cpv's metadata |
| @type lic: String |
| @param slot: "SLOT" from the cpv's metadata |
| @type slot: String |
| @rtype: List |
| @return: A list of licenses that have not been accepted. |
| """ |
| |
| licenses = set(use_reduce(lic, matchall=1, flat=True)) |
| licenses.discard('||') |
| |
| acceptable_licenses = set() |
| for x in self._getPkgAcceptLicense(cpv, slot, repo): |
| if x == '*': |
| acceptable_licenses.update(licenses) |
| elif x == '-*': |
| acceptable_licenses.clear() |
| elif x[:1] == '-': |
| acceptable_licenses.discard(x[1:]) |
| else: |
| acceptable_licenses.add(x) |
| |
| license_str = lic |
| if "?" in license_str: |
| use = use.split() |
| else: |
| use = [] |
| |
| license_struct = use_reduce(license_str, uselist=use, opconvert=True) |
| return self._getMaskedLicenses(license_struct, acceptable_licenses) |
| |
| def _getMaskedLicenses(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: |
| tmp = self._getMaskedLicenses(element, acceptable_licenses) |
| if not tmp: |
| return [] |
| ret.extend(tmp) |
| 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 ret |
| |
| ret = [] |
| for element in license_struct: |
| if isinstance(element, list): |
| if element: |
| ret.extend(self._getMaskedLicenses(element, |
| acceptable_licenses)) |
| else: |
| if element not in acceptable_licenses: |
| ret.append(element) |
| return ret |
| |
| def set_accept_license_str(self, accept_license_str): |
| if accept_license_str != self._accept_license_str: |
| self._accept_license_str = accept_license_str |
| self._accept_license = tuple(self.expandLicenseTokens(accept_license_str.split())) |