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
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
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)
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)