diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 51b8c05c6a..f2d1cc76da 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -73,3 +73,4 @@ repos: - id: insert-license files: \.py$ args: [--license-filepath, .github/disclaimer.txt, --no-extra-eol] + exclude: ^conda_build/version.py diff --git a/conda_build/skeletons/cpan.py b/conda_build/skeletons/cpan.py index bbde883050..a1c53735dc 100644 --- a/conda_build/skeletons/cpan.py +++ b/conda_build/skeletons/cpan.py @@ -7,7 +7,6 @@ import codecs import hashlib -from pkg_resources import parse_version from glob import glob import gzip import json @@ -28,6 +27,7 @@ from conda_build.config import get_or_merge_config from conda_build.utils import on_win, check_call_env from conda_build.variants import get_default_variant +from conda_build.version import _parse as parse_version import requests from conda_build import environ diff --git a/conda_build/skeletons/pypi.py b/conda_build/skeletons/pypi.py index 765751ef19..152be0b164 100644 --- a/conda_build/skeletons/pypi.py +++ b/conda_build/skeletons/pypi.py @@ -12,7 +12,7 @@ from os import makedirs, listdir, getcwd, chdir from os.path import join, isdir, exists, isfile, abspath -from pkg_resources import parse_version +from conda_build.version import _parse as parse_version import re from shutil import copy2 import subprocess diff --git a/conda_build/variants.py b/conda_build/variants.py index 19c1e87a5f..6e1295459c 100644 --- a/conda_build/variants.py +++ b/conda_build/variants.py @@ -8,7 +8,6 @@ from functools import lru_cache from itertools import product import os.path -from pkg_resources import parse_version import re import sys @@ -16,6 +15,7 @@ from conda_build.conda_interface import subdir from conda_build.conda_interface import cc_conda_build +from conda_build.version import _parse as parse_version from conda_build.utils import ensure_list, get_logger, islist, on_win, trim_empty_keys DEFAULT_VARIANTS = { diff --git a/conda_build/version.py b/conda_build/version.py new file mode 100644 index 0000000000..880d0d2c8a --- /dev/null +++ b/conda_build/version.py @@ -0,0 +1,160 @@ +# Copyright (C) Donald Stufft and individual contributors +# SPDX-License-Identifier: BSD-2-Clause +""" +This file was partially copied from the packaging.version module before the +LegacyVersion class was removed to continue to support version parsing in +a backward-compatible way where PEP 440 support can't be used. + +Copyright (c) Donald Stufft and individual contributors. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +""" +import re +from typing import Iterator, List, Tuple, Union +from packaging.version import _BaseVersion, Version, InvalidVersion + +LegacyCmpKey = Tuple[int, Tuple[str, ...]] + + +def _parse(version: str) -> Union["_LegacyVersion", "Version"]: + """ + Parse the given version string and return either a :class:`Version` object + or a :class:`_LegacyVersion` object depending on if the given version is + a valid PEP 440 version or a legacy version. + """ + try: + return Version(version) + except InvalidVersion: + return _LegacyVersion(version) + + +class _LegacyVersion(_BaseVersion): + + def __init__(self, version: str) -> None: + self._version = str(version) + self._key = _legacy_cmpkey(self._version) + + def __str__(self) -> str: + return self._version + + def __repr__(self) -> str: + return f"<_LegacyVersion('{self}')>" + + @property + def public(self) -> str: + return self._version + + @property + def base_version(self) -> str: + return self._version + + @property + def epoch(self) -> int: + return -1 + + @property + def release(self) -> None: + return None + + @property + def pre(self) -> None: + return None + + @property + def post(self) -> None: + return None + + @property + def dev(self) -> None: + return None + + @property + def local(self) -> None: + return None + + @property + def is_prerelease(self) -> bool: + return False + + @property + def is_postrelease(self) -> bool: + return False + + @property + def is_devrelease(self) -> bool: + return False + + +_legacy_version_component_re = re.compile(r"(\d+ | [a-z]+ | \.| -)", re.VERBOSE) + +_legacy_version_replacement_map = { + "pre": "c", + "preview": "c", + "-": "final-", + "rc": "c", + "dev": "@", +} + + +def _parse_version_parts(s: str) -> Iterator[str]: + for part in _legacy_version_component_re.split(s): + part = _legacy_version_replacement_map.get(part, part) + + if not part or part == ".": + continue + + if part[:1] in "0123456789": + # pad for numeric comparison + yield part.zfill(8) + else: + yield "*" + part + + # ensure that alpha/beta/candidate are before final + yield "*final" + + +def _legacy_cmpkey(version: str) -> LegacyCmpKey: + + # We hardcode an epoch of -1 here. A PEP 440 version can only have a epoch + # greater than or equal to 0. This will effectively put the LegacyVersion, + # which uses the defacto standard originally implemented by setuptools, + # as before all PEP 440 versions. + epoch = -1 + + # This scheme is taken from pkg_resources.parse_version setuptools prior to + # it's adoption of the packaging library. + parts: List[str] = [] + for part in _parse_version_parts(version.lower()): + if part.startswith("*"): + # remove "-" before a prerelease tag + if part < "*final": + while parts and parts[-1] == "*final-": + parts.pop() + + # remove trailing zeros from each series of numeric parts + while parts and parts[-1] == "00000000": + parts.pop() + + parts.append(part) + + return epoch, tuple(parts) diff --git a/recipe/meta.yaml b/recipe/meta.yaml index 8868ca77a3..67bab3e894 100644 --- a/recipe/meta.yaml +++ b/recipe/meta.yaml @@ -34,6 +34,7 @@ requirements: - filelock - futures # [py<3] - jinja2 + - packaging - patchelf # [linux] - patch >=2.6 # [not win] - m2-patch >=2.6 # [win] @@ -44,7 +45,6 @@ requirements: - pyyaml - requests - scandir # [py<34] - - setuptools - six - glob2 >=0.6 - pytz diff --git a/setup.py b/setup.py index 5ee01e6cf1..2cb1d8ea56 100755 --- a/setup.py +++ b/setup.py @@ -26,7 +26,7 @@ "psutil", "six", "libarchive-c", - "setuptools", + "packaging", # "conda-package-handling", # remove comment once released on PyPI "glob2", ] diff --git a/tests/test_api_skeleton.py b/tests/test_api_skeleton.py index ab54d16278..645d01039a 100644 --- a/tests/test_api_skeleton.py +++ b/tests/test_api_skeleton.py @@ -7,7 +7,7 @@ import subprocess import sys -from pkg_resources import parse_version +from conda_build.version import _parse as parse_version import pytest import ruamel.yaml