Parse EAPI with pattern from PMS section 7.3.1.
authorZac Medico <zmedico@gentoo.org>
Wed, 9 May 2012 06:38:47 +0000 (23:38 -0700)
committerZac Medico <zmedico@gentoo.org>
Wed, 9 May 2012 07:09:22 +0000 (00:09 -0700)
This implements the specification that was approved in Gentoo's council
meeting on May 8, 2012 (see bug #402167). The parse-eapi-ebuild-head
FEATURES setting is now enabled by default, and causes non-conformant
ebuilds to be treated as invalid. This behavior will soon become
enabled unconditionally.

12 files changed:
bin/ebuild.sh
bin/repoman
cnf/make.globals
man/make.conf.5
man/repoman.1
pym/_emerge/EbuildMetadataPhase.py
pym/_emerge/actions.py
pym/portage/__init__.py
pym/portage/dbapi/porttree.py
pym/portage/package/ebuild/config.py
pym/portage/package/ebuild/doebuild.py
pym/repoman/checks.py

index 32dc64f0c6f4fe79fcefbd1406c250c4e2dde6a2..d161bc65dcbfe392333e143e7684642fecafec11 100755 (executable)
@@ -534,7 +534,7 @@ if ! has "$EBUILD_PHASE" clean cleanrm ; then
                # In order to ensure correct interaction between ebuilds and
                # eclasses, they need to be unset before this process of
                # interaction begins.
-               unset DEPEND RDEPEND PDEPEND INHERITED IUSE REQUIRED_USE \
+               unset EAPI DEPEND RDEPEND PDEPEND INHERITED IUSE REQUIRED_USE \
                        ECLASS E_IUSE E_REQUIRED_USE E_DEPEND E_RDEPEND E_PDEPEND
 
                if [[ $PORTAGE_DEBUG != 1 || ${-/x/} != $- ]] ; then
@@ -551,7 +551,7 @@ if ! has "$EBUILD_PHASE" clean cleanrm ; then
                        rm "$PORTAGE_BUILDDIR/.ebuild_changed"
                fi
 
-               [[ -n $EAPI ]] || EAPI=0
+               [ "${EAPI+set}" = set ] || EAPI=0
 
                if has "$EAPI" 0 1 2 3 3_pre2 ; then
                        export RDEPEND=${RDEPEND-${DEPEND}}
@@ -670,7 +670,7 @@ if [[ $EBUILD_PHASE = depend ]] ; then
                PROPERTIES DEFINED_PHASES UNUSED_05 UNUSED_04
                UNUSED_03 UNUSED_02 UNUSED_01"
 
-       [ -n "${EAPI}" ] || EAPI=0
+       [ "${EAPI+set}" = set ] || EAPI=0
 
        # The extra $(echo) commands remove newlines.
        if [ -n "${dbkey}" ] ; then
index cfe4b8e01c6ae7688694ad078ff6e8ec91659c9d..3d1a4b0c160b2d04b1f194bc4e0bc1b28583a16e 100755 (executable)
@@ -322,7 +322,7 @@ qahelp={
        "LICENSE.virtual":"Virtuals that have a non-empty LICENSE variable",
        "DESCRIPTION.missing":"Ebuilds that have a missing or empty DESCRIPTION variable",
        "DESCRIPTION.toolong":"DESCRIPTION is over %d characters" % max_desc_len,
-       "EAPI.definition":"EAPI definition does not conform to PMS section 8.3.1 (first non-comment, non-blank line)",
+       "EAPI.definition":"EAPI definition does not conform to PMS section 7.3.1 (first non-comment, non-blank line)",
        "EAPI.deprecated":"Ebuilds that use features that are deprecated in the current EAPI",
        "EAPI.incompatible":"Ebuilds that use features that are only available with a different EAPI",
        "EAPI.unsupported":"Ebuilds that have an unsupported EAPI version (you must upgrade portage)",
index 975726ab6dfc1de465e36e4fdfae47db0d8825ed..1717baf681e38e4187fef7328f4b4d6d2573400e 100644 (file)
@@ -52,7 +52,7 @@ FETCHCOMMAND_SFTP="bash -c \"x=\\\${2#sftp://} ; host=\\\${x%%/*} ; port=\\\${ho
 
 # Default user options
 FEATURES="assume-digests binpkg-logs distlocks ebuild-locks
-          fixlafiles news parallel-fetch protect-owned
+          fixlafiles news parallel-fetch parse-eapi-ebuild-head protect-owned
           sandbox sfperms strict unknown-features-warn unmerge-logs
           unmerge-orphans userfetch"
 
index b7108945c573066baa4f12135afb81f66bf3e9ee..9897073c61af8ece4c91c18f92a1c44ffffee228 100644 (file)
@@ -407,9 +407,9 @@ parallelization. For additional parallelization, disable
 \fIebuild\-locks\fR.
 .TP
 .B parse\-eapi\-ebuild\-head
-Parse \fBEAPI\fR from the head of the ebuild (first 30 lines). This feature
-is only intended for experimental purposes and should not be enabled under
-normal circumstances.
+Parse \fBEAPI\fR from the head of the ebuild as specified in PMS section
+7.3.1, and treat non\-conformant ebuilds as invalid. This feature is
+enabled by default, and will soon become enabled unconditionally.
 .TP
 .B prelink\-checksums
 If \fBprelink\fR(8) is installed then use it to undo any prelinks on files
index 0e0715c9d2cd5b4ba01af3d9a3463392ef53fb11..4305ce7d7dedff240f06043129c0b281805bcc17 100644 (file)
@@ -146,7 +146,7 @@ Syntax error in DEPEND (usually an extra/missing space/parenthesis)
 Ebuilds that have a missing or empty DESCRIPTION variable
 .TP
 .B EAPI.definition
-EAPI definition does not conform to PMS section 8.3.1 (first
+EAPI definition does not conform to PMS section 7.3.1 (first
 non\-comment, non\-blank line)
 .TP
 .B EAPI.deprecated
index a34542d4374252716a8e7da307b568ed99044a39..f89077ed145ad2769505159291a75fe5aaec7a9c 100644 (file)
@@ -9,9 +9,13 @@ from portage import os
 from portage import _encodings
 from portage import _unicode_decode
 from portage import _unicode_encode
+from portage.dep import _repo_separator
+from portage.elog import elog_process
+from portage.elog.messages import eerror
 import errno
 import fcntl
 import io
+import textwrap
 
 class EbuildMetadataPhase(SubProcess):
 
@@ -20,9 +24,9 @@ class EbuildMetadataPhase(SubProcess):
        used to extract metadata from the ebuild.
        """
 
-       __slots__ = ("cpv", "eapi", "ebuild_hash", "fd_pipes", "metadata_callback",
-               "metadata", "portdb", "repo_path", "settings") + \
-               ("_raw_metadata",)
+       __slots__ = ("cpv", "ebuild_hash", "fd_pipes",
+               "metadata_callback", "metadata", "portdb", "repo_path", "settings") + \
+               ("_eapi", "_eapi_lineno", "_raw_metadata",)
 
        _file_names = ("ebuild",)
        _files_dict = slot_dict_class(_file_names, prefix="")
@@ -33,26 +37,31 @@ class EbuildMetadataPhase(SubProcess):
                settings.setcpv(self.cpv)
                ebuild_path = self.ebuild_hash.location
 
-               # the caller can pass in eapi in order to avoid
-               # redundant _parse_eapi_ebuild_head calls
-               eapi = self.eapi
-               if eapi is None and \
-                       'parse-eapi-ebuild-head' in settings.features:
-                       with io.open(_unicode_encode(ebuild_path,
-                               encoding=_encodings['fs'], errors='strict'),
-                               mode='r', encoding=_encodings['repo.content'],
-                               errors='replace') as f:
-                               eapi = portage._parse_eapi_ebuild_head(f)
-
-               if eapi is not None:
-                       if not portage.eapi_is_supported(eapi):
-                               self.metadata = self.metadata_callback(self.cpv,
-                                       self.repo_path, {'EAPI' : eapi}, self.ebuild_hash)
-                               self._set_returncode((self.pid, os.EX_OK << 8))
-                               self.wait()
-                               return
-
-                       settings.configdict['pkg']['EAPI'] = eapi
+               with io.open(_unicode_encode(ebuild_path,
+                       encoding=_encodings['fs'], errors='strict'),
+                       mode='r', encoding=_encodings['repo.content'],
+                       errors='replace') as f:
+                       self._eapi, self._eapi_lineno = portage._parse_eapi_ebuild_head(f)
+
+               parsed_eapi = self._eapi
+               if parsed_eapi is None:
+                       parsed_eapi = "0"
+
+               if not parsed_eapi:
+                       # An empty EAPI setting is invalid.
+                       self._eapi_invalid(None)
+                       self._set_returncode((self.pid, 1 << 8))
+                       self.wait()
+                       return
+
+               if not portage.eapi_is_supported(parsed_eapi):
+                       self.metadata = self.metadata_callback(self.cpv,
+                               self.repo_path, {'EAPI' : parsed_eapi}, self.ebuild_hash)
+                       self._set_returncode((self.pid, os.EX_OK << 8))
+                       self.wait()
+                       return
+
+               settings.configdict['pkg']['EAPI'] = parsed_eapi
 
                debug = settings.get("PORTAGE_DEBUG") == "1"
                master_fd = None
@@ -144,7 +153,58 @@ class EbuildMetadataPhase(SubProcess):
                                # number of lines is incorrect.
                                self.returncode = 1
                        else:
-                               metadata = zip(portage.auxdbkeys, metadata_lines)
-                               self.metadata = self.metadata_callback(self.cpv,
-                                       self.repo_path, metadata, self.ebuild_hash)
+                               metadata_valid = True
+                               metadata = dict(zip(portage.auxdbkeys, metadata_lines))
+                               parsed_eapi = self._eapi
+                               if parsed_eapi is None:
+                                       parsed_eapi = "0"
+                               if portage.eapi_is_supported(metadata["EAPI"]) and \
+                                       metadata["EAPI"] != parsed_eapi:
+                                       self._eapi_invalid(metadata)
+                                       if 'parse-eapi-ebuild-head' in self.settings.features:
+                                               metadata_valid = False
+
+                               if metadata_valid:
+                                       self.metadata = self.metadata_callback(self.cpv,
+                                               self.repo_path, metadata, self.ebuild_hash)
+                               else:
+                                       self.returncode = 1
+
+       def _eapi_invalid(self, metadata):
+
+               repo_name = self.portdb.getRepositoryName(self.repo_path)
 
+               msg = []
+               msg.extend(textwrap.wrap(("EAPI assignment in ebuild '%s%s%s' does not"
+                       " conform with PMS section 7.3.1:") %
+                       (self.cpv, _repo_separator, repo_name), 70))
+
+               if not self._eapi:
+                       # None means the assignment was not found, while an
+                       # empty string indicates an (invalid) empty assingment.
+                       msg.append(
+                               "\tvalid EAPI assignment must"
+                               " occur on or before line: %s" %
+                               self._eapi_lineno)
+               else:
+                       msg.append(("\tbash returned EAPI '%s' which does not match "
+                               "assignment on line: %s") %
+                               (metadata["EAPI"], self._eapi_lineno))
+
+               if 'parse-eapi-ebuild-head' in self.settings.features:
+                       msg.extend(textwrap.wrap(("NOTE: This error will soon"
+                               " become unconditionally fatal in a future version of Portage,"
+                               " but at this time, it can by made non-fatal by setting"
+                               " FEATURES=-parse-eapi-ebuild-head in"
+                               " make.conf."), 70))
+               else:
+                       msg.extend(textwrap.wrap(("NOTE: This error will soon"
+                               " become unconditionally fatal in a future version of Portage."
+                               " At the earliest opportunity, please enable"
+                               " FEATURES=parse-eapi-ebuild-head in make.conf in order to"
+                               " make this error fatal."), 70))
+
+               for line in msg:
+                       eerror(line, phase="other", key=self.cpv)
+               elog_process(self.cpv, self.settings,
+                       phasefilter=("other",))
index 9c87120500b91fe35c8f65b5553a668a3d9e7cf6..62f3ff79d1f7d639a771e012e5cba132b9cf1d53 100644 (file)
@@ -28,7 +28,7 @@ portage.proxy.lazyimport.lazyimport(globals(),
 from portage.localization import _
 from portage import os
 from portage import shutil
-from portage import _unicode_decode
+from portage import eapi_is_supported, _unicode_decode
 from portage.cache.cache_errors import CacheError
 from portage.const import GLOBAL_CONFIG_PATH
 from portage.const import _ENABLE_DYN_LINK_MAP, _ENABLE_SET_CONFIG
@@ -1716,9 +1716,6 @@ def action_metadata(settings, portdb, myopts, porttrees=None):
        if onProgress is not None:
                onProgress(maxval, curval)
 
-       from portage import eapi_is_supported, \
-               _validate_cache_for_unsupported_eapis
-
        # TODO: Display error messages, but do not interfere with the progress bar.
        # Here's how:
        #  1) erase the progress bar
@@ -1758,8 +1755,7 @@ def action_metadata(settings, portdb, myopts, porttrees=None):
                                eapi = eapi.lstrip('-')
                                eapi_supported = eapi_is_supported(eapi)
                                if not eapi_supported:
-                                       if not _validate_cache_for_unsupported_eapis:
-                                               continue
+                                       continue
 
                                dest = None
                                try:
index 3495b96eca7d8c2b94b91ddd5d53e5f0b9bf3f5a..31d580742b5bac7cd0c45dcd459e2f6fe93c370d 100644 (file)
@@ -435,29 +435,25 @@ def eapi_is_supported(eapi):
                return False
        return eapi <= portage.const.EAPI
 
-# Generally, it's best not to assume that cache entries for unsupported EAPIs
-# can be validated. However, the current package manager specification does not
-# guarantee that the EAPI can be parsed without sourcing the ebuild, so
-# it's too costly to discard existing cache entries for unsupported EAPIs.
-# Therefore, by default, assume that cache entries for unsupported EAPIs can be
-# validated. If FEATURES=parse-eapi-* is enabled, this assumption is discarded
-# since the EAPI can be determined without the incurring the cost of sourcing
-# the ebuild.
-_validate_cache_for_unsupported_eapis = True
-
-_parse_eapi_ebuild_head_re = re.compile(r'^EAPI=[\'"]?([^\'"#]*)')
-_parse_eapi_ebuild_head_max_lines = 30
+# This pattern is specified by PMS section 7.3.1.
+_pms_eapi_re = re.compile(r"^[ \t]*EAPI=(['\"]?)([A-Za-z0-9+_.-]*)\1[ \t]*(#.*)?$")
+_comment_or_blank_line = re.compile(r"^\s*(#.*)?$")
 
 def _parse_eapi_ebuild_head(f):
-       count = 0
+       eapi = None
+       eapi_lineno = None
+       lineno = 0
        for line in f:
-               m = _parse_eapi_ebuild_head_re.match(line)
-               if m is not None:
-                       return m.group(1).strip()
-               count += 1
-               if count >= _parse_eapi_ebuild_head_max_lines:
+               lineno += 1
+               m = _comment_or_blank_line.match(line)
+               if m is None:
+                       eapi_lineno = lineno
+                       m = _pms_eapi_re.match(line)
+                       if m is not None:
+                               eapi = m.group(2)
                        break
-       return '0'
+
+       return (eapi, eapi_lineno)
 
 def _movefile(src, dest, **kwargs):
        """Calls movefile and raises a PortageException if an error occurs."""
index bdb87f13e770764a7ad013c1346ba186a61d5bba..16962b5668566c6a3a7f9089d90e63319ce14bc3 100644 (file)
@@ -485,38 +485,22 @@ class portdbapi(dbapi):
                                raise KeyError(mycpv)
 
                        self.doebuild_settings.setcpv(mycpv)
-                       eapi = None
-
-                       if eapi is None and \
-                               'parse-eapi-ebuild-head' in self.doebuild_settings.features:
-                               with io.open(_unicode_encode(myebuild,
-                                       encoding=_encodings['fs'], errors='strict'),
-                                       mode='r', encoding=_encodings['repo.content'],
-                                       errors='replace') as f:
-                                       eapi = portage._parse_eapi_ebuild_head(f)
-
-                       if eapi is not None:
-                               self.doebuild_settings.configdict['pkg']['EAPI'] = eapi
-
-                       if eapi is not None and not portage.eapi_is_supported(eapi):
-                               mydata = self._metadata_callback(
-                                       mycpv, mylocation, {'EAPI':eapi}, ebuild_hash)
-                       else:
-                               proc = EbuildMetadataPhase(cpv=mycpv, eapi=eapi,
-                                       ebuild_hash=ebuild_hash,
-                                       metadata_callback=self._metadata_callback, portdb=self,
-                                       repo_path=mylocation,
-                                       scheduler=PollScheduler().sched_iface,
-                                       settings=self.doebuild_settings)
 
-                               proc.start()
-                               proc.wait()
+                       proc = EbuildMetadataPhase(cpv=mycpv,
+                               ebuild_hash=ebuild_hash,
+                               metadata_callback=self._metadata_callback, portdb=self,
+                               repo_path=mylocation,
+                               scheduler=PollScheduler().sched_iface,
+                               settings=self.doebuild_settings)
+
+                       proc.start()
+                       proc.wait()
 
-                               if proc.returncode != os.EX_OK:
-                                       self._broken_ebuilds.add(myebuild)
-                                       raise KeyError(mycpv)
+                       if proc.returncode != os.EX_OK:
+                               self._broken_ebuilds.add(myebuild)
+                               raise KeyError(mycpv)
 
-                               mydata = proc.metadata
+                       mydata = proc.metadata
 
                mydata["repository"] = self.repositories.get_name_for_location(mylocation)
                mydata["_mtime_"] = ebuild_hash.mtime
index 38f15c081e0d691424084dac0909db05b6edfbc4..32d65367ef89c562614fc6af84f3b62a3f089ef1 100644 (file)
@@ -776,9 +776,6 @@ class config(object):
                        if bsd_chflags:
                                self.features.add('chflags')
 
-                       if 'parse-eapi-ebuild-head' in self.features:
-                               portage._validate_cache_for_unsupported_eapis = False
-
                        self._iuse_implicit_match = _iuse_implicit_match_cache(self)
 
                        self._validate_commands()
index da30bda38532b5d189de8af7a2ba7d9597480ad0..4f7d4a8a19aa257296d8b213e426198ef3539b31 100644 (file)
@@ -337,12 +337,14 @@ def doebuild_environment(myebuild, mydo, myroot=None, settings=None,
        # when uninstalling a package that has corrupt EAPI metadata.
        eapi = None
        if mydo == 'depend' and 'EAPI' not in mysettings.configdict['pkg']:
-               if eapi is None and 'parse-eapi-ebuild-head' in mysettings.features:
+               if eapi is None:
                        with io.open(_unicode_encode(ebuild_path,
                                encoding=_encodings['fs'], errors='strict'),
                                mode='r', encoding=_encodings['content'],
                                errors='replace') as f:
-                               eapi = _parse_eapi_ebuild_head(f)
+                               eapi, eapi_lineno = _parse_eapi_ebuild_head(f)
+                               if eapi is None:
+                                       eapi = "0"
 
                if eapi is not None:
                        if not eapi_is_supported(eapi):
index 733bbc3c1e4338dbaed2e862c1d4d86682a54869..77df603a27a18f9f8f766bd4537f800515bd8587 100644 (file)
@@ -8,6 +8,7 @@ and correctness of an ebuild."""
 import re
 import time
 import repoman.errors as errors
+import portage
 from portage.eapi import eapi_supports_prefix, eapi_has_implicit_rdepend, \
        eapi_has_src_prepare_and_src_configure, eapi_has_dosed_dohard, \
        eapi_exports_AA, eapi_exports_KV
@@ -284,14 +285,12 @@ class EbuildUselessCdS(LineCheck):
 
 class EapiDefinition(LineCheck):
        """
-       Check that EAPI assignment conforms to PMS section 8.3.1
+       Check that EAPI assignment conforms to PMS section 7.3.1
        (first non-comment, non-blank line).
        """
        repoman_check_name = 'EAPI.definition'
        ignore_comment = True
-
-       # This pattern is specified by PMS section 8.3.1.
-       _eapi_re = re.compile(r"^[ \t]*EAPI=(['\"]?)([A-Za-z0-9+_.-]*)\1[ \t]*(#.*)?$")
+       _eapi_re = portage._pms_eapi_re
 
        def new(self, pkg):
                self._cached_eapi = pkg.metadata['EAPI']