EAPI="5-progress": Add automatic unpack dependencies.
authorArfrever Frehtes Taifersar Arahesis <Arfrever@Apache.Org>
Sat, 24 Nov 2012 12:28:44 +0000 (13:28 +0100)
committerArfrever Frehtes Taifersar Arahesis <Arfrever@Apache.Org>
Sat, 24 Nov 2012 12:28:44 +0000 (13:28 +0100)
doc/package/ebuild/eapi/5-progress.docbook
pym/_emerge/EbuildMetadataPhase.py
pym/portage/__init__.py
pym/portage/dep/__init__.py
pym/portage/eapi.py
pym/portage/package/ebuild/_config/unpack_dependencies.py [new file with mode: 0644]
pym/portage/package/ebuild/config.py
pym/portage/tests/resolver/ResolverPlayground.py
pym/portage/tests/resolver/test_unpack_dependencies.py [new file with mode: 0644]

index 33464644fd6823228e213d2086cf670d89bfdbe0..93ce6dcdeb76f4ec9bf383539820dec304fd5ee0 100644 (file)
                                </tgroup>
                        </table>
                </section>
+               <section id='package-ebuild-eapi-5-progress-metadata-automatic-unpack-dependencies'>
+                       <title>Automatic Unpack Dependencies</title>
+                       <para>
+                               Dependencies on packages required to unpack archives specified in SRC_URI are automatically appended to DEPEND. These dependencies are calculated from filename extensions of archives specified in SRC_URI. Dependencies (for ebuilds using given EAPI) corresponding to given filename extensions are configured in ${repository_path}/profiles/unpack_dependencies/${EAPI} files.
+                       </para>
+                       <table><title>Unpack Dependencies Configuration Examples</title>
+                               <tgroup cols='1' align='left'>
+                                       <tbody>
+                                               <row>
+                                                       <entry>bz2 app-arch/bzip2</entry>
+                                               </row>
+                                               <row>
+                                                       <entry>gz app-arch/gzip</entry>
+                                               </row>
+                                               <row>
+                                                       <entry>tar app-arch/tar</entry>
+                                               </row>
+                                               <row>
+                                                       <entry>tar.bz2 app-arch/tar app-arch/bzip2</entry>
+                                               </row>
+                                               <row>
+                                                       <entry>tar.gz app-arch/tar app-arch/gzip</entry>
+                                               </row>
+                                               <row>
+                                                       <entry>zip app-arch/unzip</entry>
+                                               </row>
+                                       </tbody>
+                               </tgroup>
+                       </table>
+               </section>
        </section>
        <section id='package-ebuild-eapi-5-progress-globstar'>
                <title>globstar shell option enabled by default</title>
index a6c515a4cc7f1a555a4fc96b59e5b74bcfc86083..f351b1650e6af18bc3a0fd7ca7830eb1df9b8967 100644 (file)
@@ -12,6 +12,8 @@ from portage import os
 from portage import _encodings
 from portage import _unicode_decode
 from portage import _unicode_encode
+from portage.dep import extract_unpack_dependencies
+from portage.eapi import eapi_has_automatic_unpack_dependencies
 
 import errno
 import fcntl
@@ -180,6 +182,13 @@ class EbuildMetadataPhase(SubProcess):
                                                metadata["_eclasses_"] = {}
                                        metadata.pop("INHERITED", None)
 
+                                       if eapi_has_automatic_unpack_dependencies(metadata["EAPI"]):
+                                               repo = self.portdb.repositories.get_name_for_location(self.repo_path)
+                                               unpackers = self.settings.unpack_dependencies.get(repo, {}).get(metadata["EAPI"], {})
+                                               unpack_dependencies = extract_unpack_dependencies(metadata["SRC_URI"], unpackers)
+                                               if unpack_dependencies:
+                                                       metadata["DEPEND"] += (" " if metadata["DEPEND"] else "") + unpack_dependencies
+
                                        # If called by egencache, this cache write is
                                        # undesirable when metadata-transfer is disabled.
                                        if self.write_auxdb is not False:
index 08d9e5d86bc4f05bcb010d81266960ae8e6c16e8..3e634b53b9fa644b56bbca4e9c0dccf66bb1c77b 100644 (file)
@@ -427,6 +427,7 @@ _doebuild_manifest_exempt_depend = 0
 
 _testing_eapis = frozenset(["4-python", "4-slot-abi", "5-progress", "5-hdepend"])
 _deprecated_eapis = frozenset(["4_pre1", "3_pre2", "3_pre1", "5_pre1", "5_pre2"])
+_supported_eapis = frozenset([str(x) for x in range(portage.const.EAPI)] + list(_testing_eapis) + list(_deprecated_eapis))
 
 def _eapi_is_deprecated(eapi):
        return eapi in _deprecated_eapis
index 60d1cc96ae9e54943db429bad16f5d7d13415bfb..e2aa00dfee62e998568aa01a7673a6f96c94084e 100644 (file)
@@ -2764,3 +2764,48 @@ def extract_affecting_use(mystr, atom, eapi=None):
                        _("malformed syntax: '%s'") % mystr)
 
        return affecting_use
+
+def extract_unpack_dependencies(src_uri, unpackers):
+       """
+       Return unpack dependencies string for given SRC_URI string.
+
+       @param src_uri: SRC_URI string
+       @type src_uri: String
+       @param unpackers: Dictionary mapping archive suffixes to dependency strings
+       @type unpackers: Dictionary
+       @rtype: String
+       @return: Dependency string specifying packages required to unpack archives.
+       """
+       src_uri = src_uri.split()
+
+       depend = []
+       for i in range(len(src_uri)):
+               if src_uri[i][-1] == "?" or src_uri[i] in ("(", ")"):
+                       depend.append(src_uri[i])
+               elif (i+1 < len(src_uri) and src_uri[i+1] == "->") or src_uri[i] == "->":
+                       continue
+               else:
+                       for suffix in sorted(unpackers, key=lambda x: len(x), reverse=True):
+                               suffix = suffix.lower()
+                               if src_uri[i].lower().endswith(suffix):
+                                       depend.append(unpackers[suffix])
+                                       break
+
+       while True:
+               cleaned_depend = depend[:]
+               for i in range(len(cleaned_depend)):
+                       if cleaned_depend[i] is None:
+                               continue
+                       elif cleaned_depend[i] == "(" and cleaned_depend[i+1] == ")":
+                               cleaned_depend[i] = None
+                               cleaned_depend[i+1] = None
+                       elif cleaned_depend[i][-1] == "?" and cleaned_depend[i+1] == "(" and cleaned_depend[i+2] == ")":
+                               cleaned_depend[i] = None
+                               cleaned_depend[i+1] = None
+                               cleaned_depend[i+2] = None
+               if depend == cleaned_depend:
+                       break
+               else:
+                       depend = [x for x in cleaned_depend if x is not None]
+
+       return " ".join(depend)
index bc1240c90392457f1482c7f2cb3dbb27fd340008..4f77910c528d37c73999dfc681e0ebdb4833e605 100644 (file)
@@ -86,6 +86,9 @@ def eapi_allows_directories_on_profile_level_and_repository_level(eapi):
 def eapi_has_use_aliases(eapi):
        return eapi in ("4-python", "5-progress")
 
+def eapi_has_automatic_unpack_dependencies(eapi):
+       return eapi in ("5-progress",)
+
 def eapi_has_hdepend(eapi):
        return eapi in ("5-hdepend",)
 
diff --git a/pym/portage/package/ebuild/_config/unpack_dependencies.py b/pym/portage/package/ebuild/_config/unpack_dependencies.py
new file mode 100644 (file)
index 0000000..1375189
--- /dev/null
@@ -0,0 +1,38 @@
+# Copyright 2012 Gentoo Foundation
+# Distributed under the terms of the GNU General Public License v2
+
+from portage import os, _supported_eapis
+from portage.dep import use_reduce
+from portage.eapi import eapi_has_automatic_unpack_dependencies
+from portage.exception import InvalidDependString
+from portage.localization import _
+from portage.util import grabfile, writemsg
+
+def load_unpack_dependencies_configuration(repositories):
+       repo_dict = {}
+       for repo in repositories.repos_with_profiles():
+               for eapi in _supported_eapis:
+                       if eapi_has_automatic_unpack_dependencies(eapi):
+                               file_name = os.path.join(repo.location, "profiles", "unpack_dependencies", eapi)
+                               lines = grabfile(file_name, recursive=True)
+                               for line in lines:
+                                       elements = line.split()
+                                       suffix = elements[0].lower()
+                                       if len(elements) == 1:
+                                               writemsg(_("--- Missing unpack dependencies for '%s' suffix in '%s'\n") % (suffix, file_name))
+                                       depend = " ".join(elements[1:])
+                                       try:
+                                               use_reduce(depend, eapi=eapi)
+                                       except InvalidDependString as e:
+                                               writemsg(_("--- Invalid unpack dependencies for '%s' suffix in '%s': '%s'\n" % (suffix, file_name, e)))
+                                       else:
+                                               repo_dict.setdefault(repo.name, {}).setdefault(eapi, {})[suffix] = depend
+
+       ret = {}
+       for repo in repositories.repos_with_profiles():
+               for repo_name in [x.name for x in repo.masters] + [repo.name]:
+                       for eapi in repo_dict.get(repo_name, {}):
+                               for suffix, depend in repo_dict.get(repo_name, {}).get(eapi, {}).items():
+                                       ret.setdefault(repo.name, {}).setdefault(eapi, {})[suffix] = depend
+
+       return ret
index 45a3351a921d050f3421cf0b3735ae7b745b74fc..86eac8a5269eee088a58df31ba5d25f9f221f9d2 100644 (file)
@@ -56,6 +56,7 @@ from portage.package.ebuild._config.LocationsManager import LocationsManager
 from portage.package.ebuild._config.MaskManager import MaskManager
 from portage.package.ebuild._config.VirtualsManager import VirtualsManager
 from portage.package.ebuild._config.helper import ordered_by_atom_specificity, prune_incremental
+from portage.package.ebuild._config.unpack_dependencies import load_unpack_dependencies_configuration
 
 if sys.hexversion >= 0x3000000:
        basestring = str
@@ -237,6 +238,7 @@ class config(object):
                        self.profiles = clone.profiles
                        self.packages = clone.packages
                        self.repositories = clone.repositories
+                       self.unpack_dependencies = clone.unpack_dependencies
                        self._iuse_effective = clone._iuse_effective
                        self._iuse_implicit_match = clone._iuse_implicit_match
                        self._non_user_variables = clone._non_user_variables
@@ -489,6 +491,7 @@ class config(object):
                                        x = Atom(x.lstrip('*'))
                                self.prevmaskdict.setdefault(x.cp, []).append(x)
 
+                       self.unpack_dependencies = load_unpack_dependencies_configuration(self.repositories)
 
                        mygcfg = {}
                        if self.profiles:
index 9b30edbee8cd23da4dfbf54ed55cee0eb567be6b..f81e046c1c8e49a6c3eb51b26d9902ecd425543b 100644 (file)
@@ -2,6 +2,7 @@
 # Distributed under the terms of the GNU General Public License v2
 
 from itertools import permutations
+import fnmatch
 import sys
 import tempfile
 import portage
@@ -37,7 +38,7 @@ class ResolverPlayground(object):
        config_files = frozenset(("eapi", "layout.conf", "make.conf", "package.accept_keywords",
                "package.keywords", "package.license", "package.mask", "package.properties",
                "package.unmask", "package.use", "package.use.aliases", "package.use.stable.mask",
-               "use.aliases", "use.force", "use.mask", "layout.conf"))
+               "unpack_dependencies", "use.aliases", "use.force", "use.mask", "layout.conf"))
 
        metadata_xml_template = """<?xml version="1.0" encoding="UTF-8"?>
 <!DOCTYPE pkgmetadata SYSTEM "http://www.gentoo.org/dtd/metadata.dtd">
@@ -282,13 +283,15 @@ class ResolverPlayground(object):
                        repo_config = repo_configs.get(repo) 
                        if repo_config:
                                for config_file, lines in repo_config.items():
-                                       if config_file not in self.config_files:
+                                       if config_file not in self.config_files and not any(fnmatch.fnmatch(config_file, os.path.join(x, "*")) for x in self.config_files): 
                                                raise ValueError("Unknown config file: '%s'" % config_file)
 
                                        if config_file in ("layout.conf",):
                                                file_name = os.path.join(repo_dir, "metadata", config_file)
                                        else:
                                                file_name = os.path.join(profile_dir, config_file)
+                                               if "/" in config_file and not os.path.isdir(os.path.dirname(file_name)):
+                                                       os.makedirs(os.path.dirname(file_name))
                                        f = open(file_name, "w")
                                        for line in lines:
                                                f.write("%s\n" % line)
diff --git a/pym/portage/tests/resolver/test_unpack_dependencies.py b/pym/portage/tests/resolver/test_unpack_dependencies.py
new file mode 100644 (file)
index 0000000..cfceff4
--- /dev/null
@@ -0,0 +1,65 @@
+# Copyright 2012 Gentoo Foundation
+# Distributed under the terms of the GNU General Public License v2
+
+from portage.tests import TestCase
+from portage.tests.resolver.ResolverPlayground import ResolverPlayground, ResolverPlaygroundTestCase
+
+class UnpackDependenciesTestCase(TestCase):
+       def testUnpackDependencies(self):
+               distfiles = {
+                       "A-1.tar.gz": b"binary\0content",
+                       "B-1.TAR.XZ": b"binary\0content",
+                       "B-docs-1.tar.bz2": b"binary\0content",
+                       "C-1.TAR.XZ": b"binary\0content",
+                       "C-docs-1.tar.bz2": b"binary\0content",
+               }
+
+               ebuilds = {
+                       "dev-libs/A-1": {"SRC_URI": "A-1.tar.gz", "EAPI": "5-progress"},
+                       "dev-libs/B-1": {"IUSE": "doc", "SRC_URI": "B-1.TAR.XZ doc? ( B-docs-1.tar.bz2 )", "EAPI": "5-progress"},
+                       "dev-libs/C-1": {"IUSE": "doc", "SRC_URI": "C-1.TAR.XZ doc? ( C-docs-1.tar.bz2 )", "EAPI": "5-progress"},
+                       "app-arch/bzip2-1": {},
+                       "app-arch/gzip-1": {},
+                       "app-arch/tar-1": {},
+                       "app-arch/xz-utils-1": {},
+               }
+
+               repo_configs = {
+                       "test_repo": {
+                               "unpack_dependencies/5-progress": (
+                                       "tar.bz2 app-arch/tar app-arch/bzip2",
+                                       "tar.gz app-arch/tar app-arch/gzip",
+                                       "tar.xz app-arch/tar app-arch/xz-utils",
+                               ),
+                       },
+               }
+
+               test_cases = (
+                       ResolverPlaygroundTestCase(
+                               ["dev-libs/A"],
+                               success = True,
+                               ignore_mergelist_order = True,
+                               mergelist = ["app-arch/tar-1", "app-arch/gzip-1", "dev-libs/A-1"]),
+                       ResolverPlaygroundTestCase(
+                               ["dev-libs/B"],
+                               success = True,
+                               ignore_mergelist_order = True,
+                               mergelist = ["app-arch/tar-1", "app-arch/xz-utils-1", "dev-libs/B-1"]),
+                       ResolverPlaygroundTestCase(
+                               ["dev-libs/C"],
+                               success = True,
+                               ignore_mergelist_order = True,
+                               mergelist = ["app-arch/tar-1", "app-arch/xz-utils-1", "app-arch/bzip2-1", "dev-libs/C-1"]),
+               )
+
+               user_config = {
+                       "package.use": ("dev-libs/C doc",)
+               }
+
+               playground = ResolverPlayground(distfiles=distfiles, ebuilds=ebuilds, repo_configs=repo_configs, user_config=user_config)
+               try:
+                       for test_case in test_cases:
+                               playground.run_TestCase(test_case)
+                               self.assertEqual(test_case.test_success, True, test_case.fail_msg)
+               finally:
+                       playground.cleanup()