Beispiel #1
0
    def load_control_field_overrides(self, control_fields):
        """
        Apply user defined control field overrides.

        :param control_fields:

            The control field defaults constructed by py2deb (a
            :class:`deb_pkg_tools.deb822.Deb822` object).

        :returns:

            The merged defaults and overrides (a
            :class:`deb_pkg_tools.deb822.Deb822` object).

        Looks for an ``stdeb.cfg`` file inside the Python package's source
        distribution and if found it merges the overrides into the control
        fields that will be embedded in the generated Debian binary package.

        This method first applies any overrides defined in the ``DEFAULT``
        section and then it applies any overrides defined in the section whose
        normalized name (see :func:`~py2deb.utils.package_names_match()`)
        matches that of the Python package.
        """
        py2deb_cfg = os.path.join(self.requirement.source_directory,
                                  'stdeb.cfg')
        if not os.path.isfile(py2deb_cfg):
            logger.debug("Control field overrides file not found (%s).",
                         py2deb_cfg)
        else:
            logger.debug("Loading control field overrides from %s ..",
                         py2deb_cfg)
            parser = configparser.RawConfigParser()
            parser.read(py2deb_cfg)
            # Prepare to load the overrides from the DEFAULT section and
            # the section whose name matches that of the Python package.
            # DEFAULT is processed first on purpose.
            section_names = ['DEFAULT']
            # Match the normalized package name instead of the raw package
            # name because `python setup.py egg_info' normalizes
            # underscores in package names to dashes which can bite
            # unsuspecting users. For what it's worth, PEP-8 discourages
            # underscores in package names but doesn't forbid them:
            # https://www.python.org/dev/peps/pep-0008/#package-and-module-names
            section_names.extend(
                section_name for section_name in parser.sections()
                if package_names_match(section_name, self.python_name))
            for section_name in section_names:
                if parser.has_section(section_name):
                    overrides = dict(parser.items(section_name))
                    logger.debug(
                        "Found %i control file field override(s) in section %s of %s: %r",
                        len(overrides), section_name, py2deb_cfg, overrides)
                    control_fields = merge_control_fields(
                        control_fields, overrides)
        return control_fields
Beispiel #2
0
    def load_control_field_overrides(self, control_fields):
        """
        Apply user defined control field overrides.

        Looks for an ``stdeb.cfg`` file inside the Python package's source
        distribution and if found it merges the overrides into the control
        fields that will be embedded in the generated Debian binary package.

        This method first applies any overrides defined in the ``DEFAULT``
        section and then it applies any overrides defined in the section whose
        normalized name (see :py:func:`~py2deb.utils.package_names_match()`)
        matches that of the Python package.

        :param control_fields: The control field defaults constructed by py2deb
                               (a :py:class:`debian.deb822.Deb822` object).
        :returns: The merged defaults and overrides (a
                  :py:class:`debian.deb822.Deb822` object).
        """
        py2deb_cfg = os.path.join(self.requirement.source_directory, 'stdeb.cfg')
        if not os.path.isfile(py2deb_cfg):
            logger.debug("Control field overrides file not found (%s).", py2deb_cfg)
        else:
            logger.debug("Loading control field overrides from %s ..", py2deb_cfg)
            parser = configparser.RawConfigParser()
            parser.read(py2deb_cfg)
            # Prepare to load the overrides from the DEFAULT section and
            # the section whose name matches that of the Python package.
            # DEFAULT is processed first on purpose.
            section_names = ['DEFAULT']
            # Match the normalized package name instead of the raw package
            # name because `python setup.py egg_info' normalizes
            # underscores in package names to dashes which can bite
            # unsuspecting users. For what it's worth, PEP-8 discourages
            # underscores in package names but doesn't forbid them:
            # https://www.python.org/dev/peps/pep-0008/#package-and-module-names
            section_names.extend(section_name for section_name in parser.sections()
                                 if package_names_match(section_name, self.python_name))
            for section_name in section_names:
                if parser.has_section(section_name):
                    overrides = dict(parser.items(section_name))
                    logger.debug("Found %i control file field override(s) in section %s of %s: %r",
                                 len(overrides), section_name, py2deb_cfg, overrides)
                    control_fields = merge_control_fields(control_fields, overrides)
        return control_fields
Beispiel #3
0
    def transform_version(self, package_to_convert, python_requirement_name, python_requirement_version):
        """
        Transform a Python requirement version to a Debian version number.

        :param package_to_convert: The :class:`.PackageToConvert` whose
                                   requirement is being transformed.
        :param python_requirement_name: The name of a Python package
                                        as found on PyPI (a string).
        :param python_requirement_version: The required version of the
                                           Python package (a string).
        :returns: The transformed version (a string).

        This method is a wrapper for :func:`.normalize_package_version()` that
        takes care of one additional quirk to ensure compatibility with
        :pypi:`pip`. Explaining this quirk requires a bit of context:

        - When package A requires package B (via ``install_requires``) and
          package A absolutely pins the required version of package B using one
          or more trailing zeros (e.g. ``B==1.0.0``) but the actual version
          number of package B (embedded in the metadata of package B) contains
          less trailing zeros (e.g. ``1.0``) then :pypi:`pip` will not complain
          but silently fetch version ``1.0`` of package B to satisfy the
          requirement.

        - However this doesn't change the absolutely pinned version in the
          ``install_requires`` metadata of package A.

        - When py2deb converts the resulting requirement set, the dependency of
          package A is converted as ``B (= 1.0.0)``. The resulting packages
          will not be installable because :man:`apt` considers ``1.0`` to be
          different from ``1.0.0``.

        This method analyzes the requirement set to identify occurrences of
        this quirk and strip trailing zeros in ``install_requires`` metadata
        that would otherwise result in converted packages that cannot be
        installed.
        """
        matching_packages = [
            pkg for pkg in self.packages_to_convert
            if package_names_match(pkg.python_name, python_requirement_name)
        ]
        if len(matching_packages) > 1:
            # My assumption while writing this code is that this should never
            # happen. This check is to make sure that if it does happen it will
            # be noticed because the last thing I want is for this `hack' to
            # result in packages that are silently wrongly converted.
            normalized_name = normalize_package_name(python_requirement_name)
            num_matches = len(matching_packages)
            raise Exception(compact("""
                Expected requirement set to contain exactly one Python package
                whose name can be normalized to {name} but encountered {count}
                packages instead! (matching packages: {matches})
            """, name=normalized_name, count=num_matches, matches=matching_packages))
        elif matching_packages:
            # Check whether the version number included in the requirement set
            # matches the version number in a package's requirements.
            requirement_to_convert = matching_packages[0]
            if python_requirement_version != requirement_to_convert.python_version:
                logger.debug("Checking whether to strip trailing zeros from required version ..")
                # Check whether the version numbers share the same prefix.
                required_version = tokenize_version(python_requirement_version)
                included_version = tokenize_version(requirement_to_convert.python_version)
                common_length = min(len(required_version), len(included_version))
                required_prefix = required_version[:common_length]
                included_prefix = included_version[:common_length]
                prefixes_match = (required_prefix == included_prefix)
                logger.debug("Prefix of required version: %s", required_prefix)
                logger.debug("Prefix of included version: %s", included_prefix)
                logger.debug("Prefixes match? %s", prefixes_match)
                # Check if 1) only the required version has a suffix and 2) this
                # suffix consists only of trailing zeros.
                required_suffix = required_version[common_length:]
                included_suffix = included_version[common_length:]
                logger.debug("Suffix of required version: %s", required_suffix)
                logger.debug("Suffix of included version: %s", included_suffix)
                if prefixes_match and required_suffix and not included_suffix:
                    # Check whether the suffix of the required version contains
                    # only zeros, i.e. pip considers the version numbers the same
                    # although apt would not agree.
                    if all(re.match('^0+$', t) for t in required_suffix if t.isdigit()):
                        modified_version = ''.join(required_prefix)
                        logger.warning("Stripping superfluous trailing zeros from required"
                                       " version of %s required by %s! (%s -> %s)",
                                       python_requirement_name, package_to_convert.python_name,
                                       python_requirement_version, modified_version)
                        python_requirement_version = modified_version
        return normalize_package_version(python_requirement_version, prerelease_workaround=self.prerelease_workaround)
Beispiel #4
0
    def transform_version(self, package_to_convert, python_requirement_name, python_requirement_version):
        """
        Transform a Python requirement version to a Debian version number.

        :param package_to_convert: The :class:`.PackageToConvert` whose
                                   requirement is being transformed.
        :param python_requirement_name: The name of a Python package
                                        as found on PyPI (a string).
        :param python_requirement_version: The required version of the
                                           Python package (a string).
        :returns: The transformed version (a string).

        This method is a wrapper for :func:`.normalize_package_version()` that
        takes care of one additional quirk to ensure compatibility with pip.
        Explaining this quirk requires a bit of context:

        - When package A requires package B (via ``install_requires``) and
          package A absolutely pins the required version of package B using one
          or more trailing zeros (e.g. ``B==1.0.0``) but the actual version
          number of package B (embedded in the metadata of package B) contains
          less trailing zeros (e.g. ``1.0``) then pip will not complain but
          silently fetch version ``1.0`` of package B to satisfy the
          requirement.

        - However this doesn't change the absolutely pinned version in the
          ``install_requires`` metadata of package A.

        - When py2deb converts the resulting requirement set, the dependency of
          package A is converted as ``B (= 1.0.0)``. The resulting packages
          will not be installable because ``apt`` considers ``1.0`` to be
          different from ``1.0.0``.

        This method analyzes the requirement set to identify occurrences of
        this quirk and strip trailing zeros in ``install_requires`` metadata
        that would otherwise result in converted packages that cannot be
        installed.
        """
        matching_packages = [p for p in self.packages_to_convert
                             if package_names_match(p.python_name, python_requirement_name)]
        if len(matching_packages) != 1:
            # My assumption while writing this code is that this should never
            # happen. This check is to make sure that if it does happen it will
            # be noticed because the last thing I want is for this `hack' to
            # result in packages that are silently wrongly converted.
            normalized_name = normalize_package_name(python_requirement_name)
            num_matches = len(matching_packages)
            raise Exception(compact("""
                Expected requirement set to contain exactly one Python package
                whose name can be normalized to {name} but encountered {count}
                packages instead! (matching packages: {matches})
            """, name=normalized_name, count=num_matches, matches=matching_packages))
        # Check whether the version number included in the requirement set
        # matches the version number in a package's requirements.
        requirement_to_convert = matching_packages[0]
        if python_requirement_version != requirement_to_convert.python_version:
            logger.debug("Checking whether to strip trailing zeros from required version ..")
            # Check whether the version numbers share the same prefix.
            required_version = tokenize_version(python_requirement_version)
            included_version = tokenize_version(requirement_to_convert.python_version)
            common_length = min(len(required_version), len(included_version))
            required_prefix = required_version[:common_length]
            included_prefix = included_version[:common_length]
            prefixes_match = (required_prefix == included_prefix)
            logger.debug("Prefix of required version: %s", required_prefix)
            logger.debug("Prefix of included version: %s", included_prefix)
            logger.debug("Prefixes match? %s", prefixes_match)
            # Check if 1) only the required version has a suffix and 2) this
            # suffix consists only of trailing zeros.
            required_suffix = required_version[common_length:]
            included_suffix = included_version[common_length:]
            logger.debug("Suffix of required version: %s", required_suffix)
            logger.debug("Suffix of included version: %s", included_suffix)
            if prefixes_match and required_suffix and not included_suffix:
                # Check whether the suffix of the required version contains
                # only zeros, i.e. pip considers the version numbers the same
                # although apt would not agree.
                if all(re.match('^0+$', t) for t in required_suffix if t.isdigit()):
                    modified_version = ''.join(required_prefix)
                    logger.warning("Stripping superfluous trailing zeros from required"
                                   " version of %s required by %s! (%s -> %s)",
                                   python_requirement_name, package_to_convert.python_name,
                                   python_requirement_version, modified_version)
                    python_requirement_version = modified_version
        return normalize_package_version(python_requirement_version)