From 1c7ccce1c315ebab3973635975bb2fd823af7119 Mon Sep 17 00:00:00 2001 From: JinWang An Date: Mon, 27 Mar 2023 17:02:43 +0900 Subject: [PATCH] Imported Upstream version 60.4.0 --- .bumpversion.cfg | 2 +- .github/workflows/main.yml | 39 +- .gitignore | 1 - CHANGES.rst | 42 ++ _distutils_hack/__init__.py | 62 +- conftest.py | 20 + docs/_ext/_custom_icons.py | 58 -- docs/{images/README.rst => artwork.rst} | 6 +- docs/conf.py | 16 +- docs/images/banner-640x320.svg | 131 +--- docs/images/banner-negative-640x320.svg | 140 +--- docs/images/favicon.svg | 64 +- docs/images/logo-demo.svg | 689 ++++-------------- docs/images/logo-inline-negative.svg | 136 +--- docs/images/logo-inline.svg | 127 +--- docs/images/logo-negative.svg | 137 +--- docs/images/logo-over-white.svg | 137 +--- docs/images/logo-symbol-only.svg | 62 +- docs/images/logo-text-only.svg | 111 +-- docs/images/logo.svg | 128 +--- docs/index.rst | 2 +- docs/userguide/quickstart.rst | 5 +- exercises.py | 6 + pavement.py | 32 + pkg_resources/__init__.py | 4 +- pkg_resources/_vendor/packaging/LICENSE | 3 + .../_vendor/packaging/LICENSE.APACHE | 177 +++++ pkg_resources/_vendor/packaging/LICENSE.BSD | 23 + pkg_resources/_vendor/pyparsing.LICENSE.txt | 18 + pytest.ini | 1 + setup.cfg | 20 +- setuptools/_vendor/more_itertools/LICENSE | 19 + setuptools/_vendor/ordered_set.MIT-LICENSE | 19 + setuptools/_vendor/packaging/LICENSE | 3 + setuptools/_vendor/packaging/LICENSE.APACHE | 177 +++++ setuptools/_vendor/packaging/LICENSE.BSD | 23 + setuptools/_vendor/pyparsing.LICENSE.txt | 18 + setuptools/command/easy_install.py | 4 +- setuptools/tests/contexts.py | 13 + setuptools/tests/environment.py | 18 +- setuptools/tests/fixtures.py | 81 +- setuptools/tests/integration/__init__.py | 0 setuptools/tests/integration/helpers.py | 61 ++ .../integration/test_pip_install_sdist.py | 218 ++++++ setuptools/tests/test_bdist_deprecations.py | 2 +- setuptools/tests/test_distutils_adoption.py | 24 - setuptools/tests/test_easy_install.py | 49 ++ setuptools/tests/test_setuptools.py | 5 + setuptools/tests/test_virtualenv.py | 96 +-- setuptools/tests/test_wheel.py | 33 + setuptools/wheel.py | 6 +- tox.ini | 17 +- 52 files changed, 1642 insertions(+), 1643 deletions(-) delete mode 100644 docs/_ext/_custom_icons.py rename docs/{images/README.rst => artwork.rst} (97%) create mode 100644 exercises.py create mode 100644 pkg_resources/_vendor/packaging/LICENSE create mode 100644 pkg_resources/_vendor/packaging/LICENSE.APACHE create mode 100644 pkg_resources/_vendor/packaging/LICENSE.BSD create mode 100644 pkg_resources/_vendor/pyparsing.LICENSE.txt create mode 100644 setuptools/_vendor/more_itertools/LICENSE create mode 100644 setuptools/_vendor/ordered_set.MIT-LICENSE create mode 100644 setuptools/_vendor/packaging/LICENSE create mode 100644 setuptools/_vendor/packaging/LICENSE.APACHE create mode 100644 setuptools/_vendor/packaging/LICENSE.BSD create mode 100644 setuptools/_vendor/pyparsing.LICENSE.txt create mode 100644 setuptools/tests/integration/__init__.py create mode 100644 setuptools/tests/integration/helpers.py create mode 100644 setuptools/tests/integration/test_pip_install_sdist.py diff --git a/.bumpversion.cfg b/.bumpversion.cfg index ca91c30..57c3823 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 60.3.1 +current_version = 60.4.0 commit = True tag = True diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 94d0a08..dd6cef7 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,6 +1,6 @@ name: tests -on: [push, pull_request] +on: [push, pull_request, workflow_dispatch] jobs: test: @@ -60,8 +60,43 @@ jobs: run: | C:\\tools\\cygwin\\bin\\bash -l -x -c 'cd $(cygpath -u "$GITHUB_WORKSPACE") && tox -- --cov-report xml' - release: + integration-test: + strategy: + matrix: + distutils: + - stdlib + - local needs: test + if: github.event_name == 'workflow_dispatch' || (github.event_name == 'push' && contains(github.ref, 'refs/tags/')) + # To avoid long times and high resource usage, we assume that: + # 1. The setuptools APIs used by packages don't vary too much with OS or + # Python implementation + # 2. Any circumstance for which the previous assumption is not valid is + # already tested via unit tests (or other tests not classified here as + # "integration") + # With that in mind, the integration tests can run for a single setup + runs-on: ubuntu-latest + env: + SETUPTOOLS_USE_DISTUTILS: ${{ matrix.distutils }} + steps: + - uses: actions/checkout@v2 + - name: Install OS-level dependencies + run: | + sudo apt-get update + sudo apt-get install build-essential gfortran libopenblas-dev + - name: Setup Python + uses: actions/setup-python@v2 + with: + # Use a release that is not very new but still have a long life: + python-version: "3.8" + - name: Install tox + run: | + python -m pip install tox + - name: Run integration tests + run: tox -e integration + + release: + needs: [test, test_cygwin, integration-test] if: github.event_name == 'push' && contains(github.ref, 'refs/tags/') runs-on: ubuntu-latest diff --git a/.gitignore b/.gitignore index dc14826..90ae805 100644 --- a/.gitignore +++ b/.gitignore @@ -6,7 +6,6 @@ docs/build include lib distribute.egg-info -foo.egg-info setuptools.egg-info .coverage .eggs diff --git a/CHANGES.rst b/CHANGES.rst index 2c52ecf..2f36767 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,3 +1,45 @@ +v60.4.0 +------- + + +Changes +^^^^^^^ +* #2839: Removed `requires` sorting when installing wheels as an egg dir. +* #2953: Fixed a bug that easy install incorrectly parsed Python 3.10 version string. +* #3006: Fixed startup performance issue of Python interpreter due to imports of + costly modules in ``_distutils_hack`` -- by :user:`tiran` + +Documentation changes +^^^^^^^^^^^^^^^^^^^^^ +* #2674: Added link to additional resources on packaging in Quickstart guide +* #3008: "In-tree" Sphinx extension for "favicons" replaced with ``sphinx-favicon``. +* #3008: SVG images (logo, banners, ...) optimised with the help of the ``scour`` + package. + +Misc +^^^^ +* #2862: Added integration tests that focus on building and installing some packages in + the Python ecosystem via ``pip`` -- by :user:`abravalheri` +* #2952: Modified "vendoring" logic to keep license files. +* #2968: Improved isolation for some tests that where inadvertently using the project + root for builds, and therefore creating directories (e.g. ``build``, ``dist``, + ``*.egg-info``) that could interfere with the outcome of other tests + -- by :user:`abravalheri`. +* #2968: Introduced new test fixtures ``venv``, ``venv_without_setuptools``, + ``bare_venv`` that rely on the ``jaraco.envs`` package. + These new test fixtures were also used to remove the (currently problematic) + dependency on the ``pytest_virtualenv`` plugin. +* #2968: Removed ``tmp_src`` test fixture. Previously this fixture was copying all the + files and folders under the project root, including the ``.git`` directory, + which is error prone and increases testing time. + + Since ``tmp_src`` was used to populate virtual environments (installing the + version of ``setuptools`` under test via the source tree), it was replaced by + the new ``setuptools_sdist`` and ``setuptools_wheel`` fixtures (that are build + only once per session testing and can be shared between all the workers for + read-only usage). + + v60.3.1 ------- diff --git a/_distutils_hack/__init__.py b/_distutils_hack/__init__.py index 75bc446..0307734 100644 --- a/_distutils_hack/__init__.py +++ b/_distutils_hack/__init__.py @@ -1,19 +1,11 @@ +# don't import any costly modules import sys import os -import re -import importlib -import warnings -import contextlib is_pypy = '__pypy__' in sys.builtin_module_names -warnings.filterwarnings('ignore', - r'.+ distutils\b.+ deprecated', - DeprecationWarning) - - def warn_distutils_present(): if 'distutils' not in sys.modules: return @@ -21,6 +13,7 @@ def warn_distutils_present(): # PyPy for 3.6 unconditionally imports distutils, so bypass the warning # https://foss.heptapod.net/pypy/pypy/-/blob/be829135bc0d758997b3566062999ee8b23872b4/lib-python/3/site.py#L250 return + import warnings warnings.warn( "Distutils was imported before Setuptools, but importing Setuptools " "also replaces the `distutils` module in `sys.modules`. This may lead " @@ -33,8 +26,12 @@ def warn_distutils_present(): def clear_distutils(): if 'distutils' not in sys.modules: return + import warnings warnings.warn("Setuptools is replacing distutils.") - mods = [name for name in sys.modules if re.match(r'distutils\b', name)] + mods = [ + name for name in sys.modules + if name == "distutils" or name.startswith("distutils.") + ] for name in mods: del sys.modules[name] @@ -48,6 +45,7 @@ def enabled(): def ensure_local_distutils(): + import importlib clear_distutils() # With the DistutilsMetaFinder in place, @@ -73,15 +71,12 @@ def do_override(): ensure_local_distutils() -class suppress(contextlib.suppress, contextlib.ContextDecorator): - """ - A version of contextlib.suppress with decorator support. +class _TrivialRe: + def __init__(self, *patterns): + self._patterns = patterns - >>> @suppress(KeyError) - ... def key_error(): - ... {}[''] - >>> key_error() - """ + def match(self, string): + return all(pat in string for pat in self._patterns) class DistutilsMetaFinder: @@ -94,8 +89,20 @@ class DistutilsMetaFinder: return method() def spec_for_distutils(self): + import importlib import importlib.abc import importlib.util + import warnings + + # warnings.filterwarnings() imports the re module + warnings._add_filter( + 'ignore', + _TrivialRe("distutils", "deprecated"), + DeprecationWarning, + None, + 0, + append=True + ) try: mod = importlib.import_module('setuptools._distutils') @@ -144,13 +151,15 @@ class DistutilsMetaFinder: ) @classmethod - @suppress(AttributeError) def is_get_pip(cls): """ Detect if get-pip is being invoked. Ref #2993. """ - import __main__ - return os.path.basename(__main__.__file__) == 'get-pip.py' + try: + import __main__ + return os.path.basename(__main__.__file__) == 'get-pip.py' + except AttributeError: + pass @staticmethod def frame_file_is_setup(frame): @@ -168,12 +177,11 @@ def add_shim(): DISTUTILS_FINDER in sys.meta_path or insert_shim() -@contextlib.contextmanager -def shim(): - insert_shim() - try: - yield - finally: +class shim: + def __enter__(self): + insert_shim() + + def __exit__(self, exc, value, tb): remove_shim() diff --git a/conftest.py b/conftest.py index d5e851f..43f33ba 100644 --- a/conftest.py +++ b/conftest.py @@ -1,5 +1,7 @@ import sys +import pytest + pytest_plugins = 'setuptools.tests.fixtures' @@ -9,6 +11,14 @@ def pytest_addoption(parser): "--package_name", action="append", default=[], help="list of package_name to pass to test functions", ) + parser.addoption( + "--integration", action="store_true", default=False, + help="run integration tests (only)" + ) + + +def pytest_configure(config): + config.addinivalue_line("markers", "integration: integration tests") collect_ignore = [ @@ -27,3 +37,13 @@ collect_ignore = [ if sys.version_info < (3, 6): collect_ignore.append('docs/conf.py') # uses f-strings collect_ignore.append('pavement.py') + + +@pytest.fixture(autouse=True) +def _skip_integration(request): + running_integration_tests = request.config.getoption("--integration") + is_integration_test = request.node.get_closest_marker("integration") + if running_integration_tests and not is_integration_test: + pytest.skip("running integration tests only") + if not running_integration_tests and is_integration_test: + pytest.skip("skipping integration tests") diff --git a/docs/_ext/_custom_icons.py b/docs/_ext/_custom_icons.py deleted file mode 100644 index 245162c..0000000 --- a/docs/_ext/_custom_icons.py +++ /dev/null @@ -1,58 +0,0 @@ -"""'In-tree' sphinx extension to add icons/favicons to documentation""" -import os -from sphinx.util.fileutil import copy_asset_file - - -IMAGES_DIR = "_images" # same used by .. image:: and .. picture:: - - -def _prepare_image(pathto, confdir, outdir, icon_attrs): - """Copy icon files to the ``IMAGES_DIR`` and return a modified version of - the icon attributes dict replacing ``file`` with the correct ``href``. - """ - icon = icon_attrs.copy() - src = os.path.join(confdir, icon.pop("file")) - if not os.path.exists(src): - raise FileNotFoundError(f"icon {src!r} not found") - - dest = os.path.join(outdir, IMAGES_DIR) - copy_asset_file(src, dest) # already compares if dest exists and is uptodate - - asset_name = os.path.basename(src) - icon["href"] = pathto(f"{IMAGES_DIR}/{asset_name}", resource=True) - return icon - - -def _link_tag(attrs): - return "" - - -def _add_icons(app, _pagename, _templatename, context, doctree): - """Add multiple "favicons", not limited to PNG/ICO files""" - # https://evilmartians.com/chronicles/how-to-favicon-in-2021-six-files-that-fit-most-needs - # https://caniuse.com/link-icon-svg - try: - pathto = context['pathto'] - except KeyError as ex: - msg = f"{__name__} extension is supposed to be call in HTML contexts" - raise ValueError(msg) from ex - - if doctree and "icons" in app.config: - icons = [ - _prepare_image(pathto, app.confdir, app.outdir, icon) - for icon in app.config["icons"] - ] - context["metatags"] += "\n".join(_link_tag(attrs) for attrs in icons) - - -def setup(app): - images_dir = os.path.join(app.outdir, IMAGES_DIR) - os.makedirs(images_dir, exist_ok=True) - - app.add_config_value("icons", None, "html") - app.connect("html-page-context", _add_icons) - - return { - 'parallel_read_safe': True, - 'parallel_write_safe': True, - } diff --git a/docs/images/README.rst b/docs/artwork.rst similarity index 97% rename from docs/images/README.rst rename to docs/artwork.rst index 55a5a60..907e62a 100644 --- a/docs/images/README.rst +++ b/docs/artwork.rst @@ -2,7 +2,7 @@ Artwork ======= -.. figure:: logo-over-white.svg +.. figure:: images/logo-over-white.svg :align: center Setuptools logo, designed in 2021 by `Anderson Bravalheri`_ @@ -35,7 +35,7 @@ on the circumstances: The following image illustrate these alternatives: -.. image:: logo-demo.svg +.. image:: images/logo-demo.svg :align: center Please refer to the SVG files in the `setuptools repository`_ for the specific @@ -107,7 +107,7 @@ used by the setuptools software (MIT): SETUPTOOLS PROJECT. Whenever possible, please make the image a link to -https://github.com/pypa/setuptools. +https://github.com/pypa/setuptools or https://setuptools.pypa.io. .. _Anderson Bravalheri: https://github.com/abravalheri diff --git a/docs/conf.py b/docs/conf.py index f6ccff0..1fb2771 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,6 +1,3 @@ -import os -import sys - extensions = ['sphinx.ext.autodoc', 'jaraco.packaging.sphinx', 'rst.linker'] master_doc = "index" @@ -179,23 +176,22 @@ towncrier_draft_include_empty = False extensions += ['jaraco.tidelift'] # Add icons (aka "favicons") to documentation -sys.path.append(os.path.join(os.path.dirname(__file__), '_ext')) -extensions += ['_custom_icons'] +extensions += ['sphinx-favicon'] +html_static_path = ['images'] # should contain the folder with icons # List of dicts with HTML attributes -# as defined in https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link -# except that ``file`` gets replaced with the correct ``href`` -icons = [ +# static-file points to files in the html_static_path (href is computed) +favicons = [ { # "Catch-all" goes first, otherwise some browsers will overwrite "rel": "icon", "type": "image/svg+xml", - "file": "images/logo-symbol-only.svg", + "static-file": "logo-symbol-only.svg", "sizes": "any" }, { # Version with thicker strokes for better visibility at smaller sizes "rel": "icon", "type": "image/svg+xml", - "file": "images/favicon.svg", + "static-file": "favicon.svg", "sizes": "16x16 24x24 32x32 48x48" }, # rel="apple-touch-icon" does not support SVG yet diff --git a/docs/images/banner-640x320.svg b/docs/images/banner-640x320.svg index 8222f64..4e908ea 100644 --- a/docs/images/banner-640x320.svg +++ b/docs/images/banner-640x320.svg @@ -1,101 +1,36 @@ - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/images/banner-negative-640x320.svg b/docs/images/banner-negative-640x320.svg index fd5535f..d45698e 100644 --- a/docs/images/banner-negative-640x320.svg +++ b/docs/images/banner-negative-640x320.svg @@ -1,109 +1,37 @@ - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/images/favicon.svg b/docs/images/favicon.svg index 3ac5daf..a1d3191 100644 --- a/docs/images/favicon.svg +++ b/docs/images/favicon.svg @@ -1,55 +1,23 @@ - - - + + + + image/svg+xml + + + + + + + + diff --git a/docs/images/logo-demo.svg b/docs/images/logo-demo.svg index 279b908..6b78ebc 100644 --- a/docs/images/logo-demo.svg +++ b/docs/images/logo-demo.svg @@ -1,543 +1,150 @@ - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/images/logo-inline-negative.svg b/docs/images/logo-inline-negative.svg index deed96e..4bf63cf 100644 --- a/docs/images/logo-inline-negative.svg +++ b/docs/images/logo-inline-negative.svg @@ -1,105 +1,35 @@ - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/images/logo-inline.svg b/docs/images/logo-inline.svg index 11ab7df..6e45103 100644 --- a/docs/images/logo-inline.svg +++ b/docs/images/logo-inline.svg @@ -1,97 +1,34 @@ - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/images/logo-negative.svg b/docs/images/logo-negative.svg index 23a553d..d214204 100644 --- a/docs/images/logo-negative.svg +++ b/docs/images/logo-negative.svg @@ -1,106 +1,37 @@ - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/images/logo-over-white.svg b/docs/images/logo-over-white.svg index 3ae3968..1ed0138 100644 --- a/docs/images/logo-over-white.svg +++ b/docs/images/logo-over-white.svg @@ -1,106 +1,37 @@ - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/images/logo-symbol-only.svg b/docs/images/logo-symbol-only.svg index 7d839c6..2bbf2d5 100644 --- a/docs/images/logo-symbol-only.svg +++ b/docs/images/logo-symbol-only.svg @@ -1,46 +1,20 @@ - - - - - - - image/svg+xml - - - - - - - - - - - - + + + + + + image/svg+xml + + + + + + + + + + + + diff --git a/docs/images/logo-text-only.svg b/docs/images/logo-text-only.svg index a59731d..2e92580 100644 --- a/docs/images/logo-text-only.svg +++ b/docs/images/logo-text-only.svg @@ -1,85 +1,30 @@ - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/images/logo.svg b/docs/images/logo.svg index 103d294..7c793a0 100644 --- a/docs/images/logo.svg +++ b/docs/images/logo.svg @@ -1,98 +1,36 @@ - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/index.rst b/docs/index.rst index b886c8f..0f52c36 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -26,6 +26,6 @@ designed to facilitate packaging Python projects. Development guide Backward compatibility & deprecated practice Changelog - Artwork + artwork .. tidelift-referral-banner:: diff --git a/docs/userguide/quickstart.rst b/docs/userguide/quickstart.rst index da904ba..4c62c6d 100644 --- a/docs/userguide/quickstart.rst +++ b/docs/userguide/quickstart.rst @@ -225,5 +225,6 @@ parsed by ``setuptool`` to ease the pain of transition. Resources on Python packaging ============================= -Packaging in Python is hard. Here we provide a list of links for those that -want to learn more. +Packaging in Python can be hard and is constantly evolving. +`Python Packaging User Guide `_ has tutorials and +up-to-date references that can help you when it is time to distribute your work. diff --git a/exercises.py b/exercises.py new file mode 100644 index 0000000..76176be --- /dev/null +++ b/exercises.py @@ -0,0 +1,6 @@ +def measure_startup_perf(): + # run by pytest_perf + import subprocess + import sys # end warmup + + subprocess.check_call([sys.executable, '-c', 'pass']) diff --git a/pavement.py b/pavement.py index 81ff6f1..6d5d519 100644 --- a/pavement.py +++ b/pavement.py @@ -1,6 +1,7 @@ import re import sys import subprocess +from fnmatch import fnmatch from paver.easy import task, path as Path @@ -52,12 +53,43 @@ def install(vendor): '-t', str(vendor), ] subprocess.check_call(install_args) + move_licenses(vendor) remove_all(vendor.glob('*.dist-info')) remove_all(vendor.glob('*.egg-info')) remove_all(vendor.glob('six.py')) (vendor / '__init__.py').write_text('') +def move_licenses(vendor): + license_patterns = ("*LICEN[CS]E*", "COPYING*", "NOTICE*", "AUTHORS*") + licenses = ( + entry + for path in vendor.glob("*.dist-info") + for entry in path.glob("*") + if any(fnmatch(str(entry), p) for p in license_patterns) + ) + for file in licenses: + file.move(_find_license_dest(file, vendor)) + + +def _find_license_dest(license_file, vendor): + basename = license_file.basename() + pkg = license_file.dirname().basename().replace(".dist-info", "") + parts = pkg.split("-") + acc = [] + for part in parts: + # Find actual name from normalized name + version + acc.append(part) + for option in ("_".join(acc), "-".join(acc), ".".join(acc)): + candidate = vendor / option + if candidate.isdir(): + return candidate / basename + if Path(f"{candidate}.py").isfile(): + return Path(f"{candidate}.{basename}") + + raise FileNotFoundError(f"No destination found for {license_file}") + + def update_pkg_resources(): vendor = Path('pkg_resources/_vendor') install(vendor) diff --git a/pkg_resources/__init__.py b/pkg_resources/__init__.py index 955fdc4..f98516d 100644 --- a/pkg_resources/__init__.py +++ b/pkg_resources/__init__.py @@ -3047,12 +3047,12 @@ class DistInfoDistribution(Distribution): if not req.marker or req.marker.evaluate({'extra': extra}): yield req - common = frozenset(reqs_for_extra(None)) + common = types.MappingProxyType(dict.fromkeys(reqs_for_extra(None))) dm[None].extend(common) for extra in self._parsed_pkg_info.get_all('Provides-Extra') or []: s_extra = safe_extra(extra.strip()) - dm[s_extra] = list(frozenset(reqs_for_extra(extra)) - common) + dm[s_extra] = [r for r in reqs_for_extra(extra) if r not in common] return dm diff --git a/pkg_resources/_vendor/packaging/LICENSE b/pkg_resources/_vendor/packaging/LICENSE new file mode 100644 index 0000000..6f62d44 --- /dev/null +++ b/pkg_resources/_vendor/packaging/LICENSE @@ -0,0 +1,3 @@ +This software is made available under the terms of *either* of the licenses +found in LICENSE.APACHE or LICENSE.BSD. Contributions to this software is made +under the terms of *both* these licenses. diff --git a/pkg_resources/_vendor/packaging/LICENSE.APACHE b/pkg_resources/_vendor/packaging/LICENSE.APACHE new file mode 100644 index 0000000..f433b1a --- /dev/null +++ b/pkg_resources/_vendor/packaging/LICENSE.APACHE @@ -0,0 +1,177 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS diff --git a/pkg_resources/_vendor/packaging/LICENSE.BSD b/pkg_resources/_vendor/packaging/LICENSE.BSD new file mode 100644 index 0000000..42ce7b7 --- /dev/null +++ b/pkg_resources/_vendor/packaging/LICENSE.BSD @@ -0,0 +1,23 @@ +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. diff --git a/pkg_resources/_vendor/pyparsing.LICENSE.txt b/pkg_resources/_vendor/pyparsing.LICENSE.txt new file mode 100644 index 0000000..1bf9852 --- /dev/null +++ b/pkg_resources/_vendor/pyparsing.LICENSE.txt @@ -0,0 +1,18 @@ +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/pytest.ini b/pytest.ini index 5c0ad03..f522a45 100644 --- a/pytest.ini +++ b/pytest.ini @@ -38,6 +38,7 @@ filterwarnings= # SETUPTOOLS_USE_DISTUTILS=stdlib but for # https://github.com/pytest-dev/pytest/discussions/9296 ignore:The distutils.sysconfig module is deprecated, use sysconfig instead + ignore:The distutils package is deprecated.* # Workaround for pypa/setuptools#2868 # ideally would apply to PyPy only but for diff --git a/setup.cfg b/setup.cfg index a726d2f..2095916 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = setuptools -version = 60.3.1 +version = 60.4.0 author = Python Packaging Authority author_email = distutils-sig@python.org description = Easily download, build, install, upgrade, and uninstall Python packages @@ -50,12 +50,12 @@ testing = # workaround for jaraco/skeleton#22 python_implementation != "PyPy" pytest-enabler >= 1.0.1 + pytest-perf # local mock flake8-2020 virtualenv>=13.0.0 - pytest-virtualenv>=1.2.7 # TODO: Update once man-group/pytest-plugins#188 is solved wheel paver pip>=19.1 # For proper file:// URLs support. @@ -63,6 +63,21 @@ testing = pytest-xdist sphinx>=4.3.2 jaraco.path>=3.2.0 + build[virtualenv] + filelock>=3.4.0 + +testing-integration = + pytest + pytest-xdist + pytest-enabler + virtualenv>=13.0.0 + tomli + wheel + jaraco.path>=3.2.0 + jaraco.envs>=2.2 + build[virtualenv] + filelock>=3.4.0 + docs = # upstream @@ -73,6 +88,7 @@ docs = # local pygments-github-lexers==0.0.5 + sphinx-favicon sphinx-inline-tabs sphinxcontrib-towncrier furo diff --git a/setuptools/_vendor/more_itertools/LICENSE b/setuptools/_vendor/more_itertools/LICENSE new file mode 100644 index 0000000..0a523be --- /dev/null +++ b/setuptools/_vendor/more_itertools/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2012 Erik Rose + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/setuptools/_vendor/ordered_set.MIT-LICENSE b/setuptools/_vendor/ordered_set.MIT-LICENSE new file mode 100644 index 0000000..25117ef --- /dev/null +++ b/setuptools/_vendor/ordered_set.MIT-LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2018 Luminoso Technologies, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/setuptools/_vendor/packaging/LICENSE b/setuptools/_vendor/packaging/LICENSE new file mode 100644 index 0000000..6f62d44 --- /dev/null +++ b/setuptools/_vendor/packaging/LICENSE @@ -0,0 +1,3 @@ +This software is made available under the terms of *either* of the licenses +found in LICENSE.APACHE or LICENSE.BSD. Contributions to this software is made +under the terms of *both* these licenses. diff --git a/setuptools/_vendor/packaging/LICENSE.APACHE b/setuptools/_vendor/packaging/LICENSE.APACHE new file mode 100644 index 0000000..f433b1a --- /dev/null +++ b/setuptools/_vendor/packaging/LICENSE.APACHE @@ -0,0 +1,177 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS diff --git a/setuptools/_vendor/packaging/LICENSE.BSD b/setuptools/_vendor/packaging/LICENSE.BSD new file mode 100644 index 0000000..42ce7b7 --- /dev/null +++ b/setuptools/_vendor/packaging/LICENSE.BSD @@ -0,0 +1,23 @@ +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. diff --git a/setuptools/_vendor/pyparsing.LICENSE.txt b/setuptools/_vendor/pyparsing.LICENSE.txt new file mode 100644 index 0000000..1bf9852 --- /dev/null +++ b/setuptools/_vendor/pyparsing.LICENSE.txt @@ -0,0 +1,18 @@ +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/setuptools/command/easy_install.py b/setuptools/command/easy_install.py index aad5794..a2962a7 100644 --- a/setuptools/command/easy_install.py +++ b/setuptools/command/easy_install.py @@ -243,8 +243,8 @@ class easy_install(Command): 'dist_version': self.distribution.get_version(), 'dist_fullname': self.distribution.get_fullname(), 'py_version': py_version, - 'py_version_short': py_version[0:3], - 'py_version_nodot': py_version[0] + py_version[2], + 'py_version_short': f'{sys.version_info.major}.{sys.version_info.minor}', + 'py_version_nodot': f'{sys.version_info.major}{sys.version_info.minor}', 'sys_prefix': prefix, 'prefix': prefix, 'sys_exec_prefix': exec_prefix, diff --git a/setuptools/tests/contexts.py b/setuptools/tests/contexts.py index 51ce898..5316e59 100644 --- a/setuptools/tests/contexts.py +++ b/setuptools/tests/contexts.py @@ -7,6 +7,7 @@ import site import io import pkg_resources +from filelock import FileLock @contextlib.contextmanager @@ -96,3 +97,15 @@ def suppress_exceptions(*excs): yield except excs: pass + + +@contextlib.contextmanager +def session_locked_tmp_dir(tmp_path_factory, name): + """Uses a file lock to guarantee only one worker can access a temp dir""" + root_tmp_dir = tmp_path_factory.getbasetemp().parent + # ^-- get the temp directory shared by all workers + locked_dir = root_tmp_dir / name + with FileLock(locked_dir.with_suffix(".lock")): + # ^-- prevent multiple workers to access the directory at once + locked_dir.mkdir(exist_ok=True, parents=True) + yield locked_dir diff --git a/setuptools/tests/environment.py b/setuptools/tests/environment.py index c0274c3..a0c0ec6 100644 --- a/setuptools/tests/environment.py +++ b/setuptools/tests/environment.py @@ -1,9 +1,25 @@ import os import sys +import subprocess import unicodedata - from subprocess import Popen as _Popen, PIPE as _PIPE +import jaraco.envs + + +class VirtualEnv(jaraco.envs.VirtualEnv): + name = '.env' + # Some version of PyPy will import distutils on startup, implicitly + # importing setuptools, and thus leading to BackendInvalid errors + # when upgrading Setuptools. Bypass this behavior by avoiding the + # early availability and need to upgrade. + create_opts = ['--no-setuptools'] + + def run(self, cmd, *args, **kwargs): + cmd = [self.exe(cmd[0])] + cmd[1:] + kwargs = {"cwd": self.root, **kwargs} # Allow overriding + return subprocess.check_output(cmd, *args, **kwargs) + def _which_dirs(cmd): result = set() diff --git a/setuptools/tests/fixtures.py b/setuptools/tests/fixtures.py index a5a172e..9b91d7d 100644 --- a/setuptools/tests/fixtures.py +++ b/setuptools/tests/fixtures.py @@ -1,11 +1,11 @@ import contextlib import sys -import shutil import subprocess import pytest +import path -from . import contexts +from . import contexts, environment @pytest.fixture @@ -28,22 +28,6 @@ def tmpdir_cwd(tmpdir): yield orig -@pytest.fixture -def tmp_src(request, tmp_path): - """Make a copy of the source dir under `$tmp/src`. - - This fixture is useful whenever it's necessary to run `setup.py` - or `pip install` against the source directory when there's no - control over the number of simultaneous invocations. Such - concurrent runs create and delete directories with the same names - under the target directory and so they influence each other's runs - when they are not being executed sequentially. - """ - tmp_src_path = tmp_path / 'src' - shutil.copytree(request.config.rootdir, tmp_src_path) - return tmp_src_path - - @pytest.fixture(autouse=True, scope="session") def workaround_xdist_376(request): """ @@ -72,3 +56,64 @@ def sample_project(tmp_path): except Exception: pytest.skip("Unable to clone sampleproject") return tmp_path / 'sampleproject' + + +# sdist and wheel artifacts should be stable across a round of tests +# so we can build them once per session and use the files as "readonly" + + +@pytest.fixture(scope="session") +def setuptools_sdist(tmp_path_factory, request): + with contexts.session_locked_tmp_dir(tmp_path_factory, "sdist_build") as tmp: + dist = next(tmp.glob("*.tar.gz"), None) + if dist: + return dist + + subprocess.check_call([ + sys.executable, "-m", "build", "--sdist", + "--outdir", str(tmp), str(request.config.rootdir) + ]) + return next(tmp.glob("*.tar.gz")) + + +@pytest.fixture(scope="session") +def setuptools_wheel(tmp_path_factory, request): + with contexts.session_locked_tmp_dir(tmp_path_factory, "wheel_build") as tmp: + dist = next(tmp.glob("*.whl"), None) + if dist: + return dist + + subprocess.check_call([ + sys.executable, "-m", "build", "--wheel", + "--outdir", str(tmp) , str(request.config.rootdir) + ]) + return next(tmp.glob("*.whl")) + + +@pytest.fixture +def venv(tmp_path, setuptools_wheel): + """Virtual env with the version of setuptools under test installed""" + env = environment.VirtualEnv() + env.root = path.Path(tmp_path / 'venv') + env.req = str(setuptools_wheel) + return env.create() + + +@pytest.fixture +def venv_without_setuptools(tmp_path): + """Virtual env without any version of setuptools installed""" + env = environment.VirtualEnv() + env.root = path.Path(tmp_path / 'venv_without_setuptools') + env.create_opts = ['--no-setuptools'] + env.ensure_env() + return env + + +@pytest.fixture +def bare_venv(tmp_path): + """Virtual env without any common packages installed""" + env = environment.VirtualEnv() + env.root = path.Path(tmp_path / 'bare_venv') + env.create_opts = ['--no-setuptools', '--no-pip', '--no-wheel', '--no-seed'] + env.ensure_env() + return env diff --git a/setuptools/tests/integration/__init__.py b/setuptools/tests/integration/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/setuptools/tests/integration/helpers.py b/setuptools/tests/integration/helpers.py new file mode 100644 index 0000000..43f4390 --- /dev/null +++ b/setuptools/tests/integration/helpers.py @@ -0,0 +1,61 @@ +"""Reusable functions and classes for different types of integration tests. + +For example ``Archive`` can be used to check the contents of distribution built +with setuptools, and ``run`` will always try to be as verbose as possible to +facilitate debugging. +""" +import os +import subprocess +import tarfile +from zipfile import ZipFile + + +def run(cmd, env=None): + r = subprocess.run( + cmd, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + universal_newlines=True, + env={**os.environ, **(env or {})} + # ^-- allow overwriting instead of discarding the current env + ) + + out = r.stdout + "\n" + r.stderr + # pytest omits stdout/err by default, if the test fails they help debugging + print("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~") + print(f"Command: {cmd}\nreturn code: {r.returncode}\n\n{out}") + + if r.returncode == 0: + return out + raise subprocess.CalledProcessError(r.returncode, cmd, r.stdout, r.stderr) + + +class Archive: + """Compatibility layer for ZipFile/Info and TarFile/Info""" + def __init__(self, filename): + self._filename = filename + if filename.endswith("tar.gz"): + self._obj = tarfile.open(filename, "r:gz") + elif filename.endswith("zip"): + self._obj = ZipFile(filename) + else: + raise ValueError(f"{filename} doesn't seem to be a zip or tar.gz") + + def __iter__(self): + if hasattr(self._obj, "infolist"): + return iter(self._obj.infolist()) + return iter(self._obj) + + def get_name(self, zip_or_tar_info): + if hasattr(zip_or_tar_info, "filename"): + return zip_or_tar_info.filename + return zip_or_tar_info.name + + def get_content(self, zip_or_tar_info): + if hasattr(self._obj, "extractfile"): + content = self._obj.extractfile(zip_or_tar_info) + if content is None: + msg = f"Invalid {zip_or_tar_info.name} in {self._filename}" + raise ValueError(msg) + return str(content.read(), "utf-8") + return str(self._obj.read(zip_or_tar_info), "utf-8") diff --git a/setuptools/tests/integration/test_pip_install_sdist.py b/setuptools/tests/integration/test_pip_install_sdist.py new file mode 100644 index 0000000..86cc423 --- /dev/null +++ b/setuptools/tests/integration/test_pip_install_sdist.py @@ -0,0 +1,218 @@ +"""Integration tests for setuptools that focus on building packages via pip. + +The idea behind these tests is not to exhaustively check all the possible +combinations of packages, operating systems, supporting libraries, etc, but +rather check a limited number of popular packages and how they interact with +the exposed public API. This way if any change in API is introduced, we hope to +identify backward compatibility problems before publishing a release. + +The number of tested packages is purposefully kept small, to minimise duration +and the associated maintenance cost (changes in the way these packages define +their build process may require changes in the tests). +""" +import json +import os +import shutil +import sys +from enum import Enum +from glob import glob +from hashlib import md5 +from urllib.request import urlopen + +import pytest +from packaging.requirements import Requirement + +from .helpers import Archive, run + + +pytestmark = pytest.mark.integration + +LATEST, = list(Enum("v", "LATEST")) +"""Default version to be checked""" +# There are positive and negative aspects of checking the latest version of the +# packages. +# The main positive aspect is that the latest version might have already +# removed the use of APIs deprecated in previous releases of setuptools. + + +# Packages to be tested: +# (Please notice the test environment cannot support EVERY library required for +# compiling binary extensions. In Ubuntu/Debian nomenclature, we only assume +# that `build-essential`, `gfortran` and `libopenblas-dev` are installed, +# due to their relevance to the numerical/scientific programming ecosystem) +EXAMPLES = [ + ("pandas", LATEST), # cython + custom build_ext + ("sphinx", LATEST), # custom setup.py + ("pip", LATEST), # just in case... + ("pytest", LATEST), # uses setuptools_scm + ("mypy", LATEST), # custom build_py + ext_modules + + # --- Popular packages: https://hugovk.github.io/top-pypi-packages/ --- + ("botocore", LATEST), + ("kiwisolver", "1.3.2"), # build_ext, version pinned due to setup_requires + ("brotli", LATEST), # not in the list but used by urllib3 + + # When adding packages to this list, make sure they expose a `__version__` + # attribute, or modify the tests bellow +] + + +# Some packages have "optional" dependencies that modify their build behaviour +# and are not listed in pyproject.toml, others still use `setup_requires` +EXTRA_BUILD_DEPS = { + "sphinx": ("babel>=1.3",), + "kiwisolver": ("cppy>=1.1.0",) +} + + +VIRTUALENV = (sys.executable, "-m", "virtualenv") + + +# By default, pip will try to build packages in isolation (PEP 517), which +# means it will download the previous stable version of setuptools. +# `pip` flags can avoid that (the version of setuptools under test +# should be the one to be used) +SDIST_OPTIONS = ( + "--ignore-installed", + "--no-build-isolation", + # We don't need "--no-binary :all:" since we specify the path to the sdist. + # It also helps with performance, since dependencies can come from wheels. +) +# The downside of `--no-build-isolation` is that pip will not download build +# dependencies. The test script will have to also handle that. + + +@pytest.fixture +def venv_python(tmp_path): + run([*VIRTUALENV, str(tmp_path / ".venv")]) + possible_path = (str(p.parent) for p in tmp_path.glob(".venv/*/python*")) + return shutil.which("python", path=os.pathsep.join(possible_path)) + + +@pytest.fixture(autouse=True) +def _prepare(tmp_path, venv_python, monkeypatch, request): + download_path = os.getenv("DOWNLOAD_PATH", str(tmp_path)) + os.makedirs(download_path, exist_ok=True) + + # Environment vars used for building some of the packages + monkeypatch.setenv("USE_MYPYC", "1") + + def _debug_info(): + # Let's provide the maximum amount of information possible in the case + # it is necessary to debug the tests directly from the CI logs. + print("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~") + print("Temporary directory:") + map(print, tmp_path.glob("*")) + print("Virtual environment:") + run([venv_python, "-m", "pip", "freeze"]) + request.addfinalizer(_debug_info) + + +ALREADY_LOADED = ("pytest", "mypy") # loaded by pytest/pytest-enabler + + +@pytest.mark.parametrize('package, version', EXAMPLES) +def test_install_sdist(package, version, tmp_path, venv_python, setuptools_wheel): + venv_pip = (venv_python, "-m", "pip") + sdist = retrieve_sdist(package, version, tmp_path) + deps = build_deps(package, sdist) + if deps: + print("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~") + print("Dependencies:", deps) + run([*venv_pip, "install", *deps]) + + # Use a virtualenv to simulate PEP 517 isolation + # but install fresh setuptools wheel to ensure the version under development + run([*venv_pip, "install", "-I", setuptools_wheel]) + run([*venv_pip, "install", *SDIST_OPTIONS, sdist]) + + # Execute a simple script to make sure the package was installed correctly + script = f"import {package}; print(getattr({package}, '__version__', 0))" + run([venv_python, "-c", script]) + + +# ---- Helper Functions ---- + + +def retrieve_sdist(package, version, tmp_path): + """Either use cached sdist file or download it from PyPI""" + # `pip download` cannot be used due to + # https://github.com/pypa/pip/issues/1884 + # https://discuss.python.org/t/pep-625-file-name-of-a-source-distribution/4686 + # We have to find the correct distribution file and download it + download_path = os.getenv("DOWNLOAD_PATH", str(tmp_path)) + dist = retrieve_pypi_sdist_metadata(package, version) + + # Remove old files to prevent cache to grow indefinitely + for file in glob(os.path.join(download_path, f"{package}*")): + if dist["filename"] != file: + os.unlink(file) + + dist_file = os.path.join(download_path, dist["filename"]) + if not os.path.exists(dist_file): + download(dist["url"], dist_file, dist["md5_digest"]) + return dist_file + + +def retrieve_pypi_sdist_metadata(package, version): + # https://warehouse.pypa.io/api-reference/json.html + id_ = package if version is LATEST else f"{package}/{version}" + with urlopen(f"https://pypi.org/pypi/{id_}/json") as f: + metadata = json.load(f) + + if metadata["info"]["yanked"]: + raise ValueError(f"Release for {package} {version} was yanked") + + version = metadata["info"]["version"] + release = metadata["releases"][version] + dists = [d for d in release if d["packagetype"] == "sdist"] + if len(dists) == 0: + raise ValueError(f"No sdist found for {package} {version}") + + for dist in dists: + if dist["filename"].endswith(".tar.gz"): + return dist + + # Not all packages are publishing tar.gz + return dist + + +def download(url, dest, md5_digest): + with urlopen(url) as f: + data = f.read() + + assert md5(data).hexdigest() == md5_digest + + with open(dest, "wb") as f: + f.write(data) + + assert os.path.exists(dest) + + +def build_deps(package, sdist_file): + """Find out what are the build dependencies for a package. + + We need to "manually" install them, since pip will not install build + deps with `--no-build-isolation`. + """ + import tomli as toml + + # delay importing, since pytest discovery phase may hit this file from a + # testenv without tomli + + archive = Archive(sdist_file) + pyproject = _read_pyproject(archive) + + info = toml.loads(pyproject) + deps = info.get("build-system", {}).get("requires", []) + deps += EXTRA_BUILD_DEPS.get(package, []) + # Remove setuptools from requirements (and deduplicate) + requirements = {Requirement(d).name: d for d in deps} + return [v for k, v in requirements.items() if k != "setuptools"] + + +def _read_pyproject(archive): + for member in archive: + if os.path.basename(archive.get_name(member)) == "pyproject.toml": + return archive.get_content(member) + return "" diff --git a/setuptools/tests/test_bdist_deprecations.py b/setuptools/tests/test_bdist_deprecations.py index 28482fd..1a900c6 100644 --- a/setuptools/tests/test_bdist_deprecations.py +++ b/setuptools/tests/test_bdist_deprecations.py @@ -11,7 +11,7 @@ from setuptools import SetuptoolsDeprecationWarning @pytest.mark.skipif(sys.platform == 'win32', reason='non-Windows only') @mock.patch('distutils.command.bdist_rpm.bdist_rpm') -def test_bdist_rpm_warning(distutils_cmd): +def test_bdist_rpm_warning(distutils_cmd, tmpdir_cwd): dist = Distribution( dict( script_name='setup.py', diff --git a/setuptools/tests/test_distutils_adoption.py b/setuptools/tests/test_distutils_adoption.py index 1e73f9a..7007548 100644 --- a/setuptools/tests/test_distutils_adoption.py +++ b/setuptools/tests/test_distutils_adoption.py @@ -1,39 +1,15 @@ import os import sys import functools -import subprocess import platform import textwrap import pytest -import jaraco.envs -import path IS_PYPY = '__pypy__' in sys.builtin_module_names -class VirtualEnv(jaraco.envs.VirtualEnv): - name = '.env' - # Some version of PyPy will import distutils on startup, implicitly - # importing setuptools, and thus leading to BackendInvalid errors - # when upgrading Setuptools. Bypass this behavior by avoiding the - # early availability and need to upgrade. - create_opts = ['--no-setuptools'] - - def run(self, cmd, *args, **kwargs): - cmd = [self.exe(cmd[0])] + cmd[1:] - return subprocess.check_output(cmd, *args, cwd=self.root, **kwargs) - - -@pytest.fixture -def venv(tmp_path, tmp_src): - env = VirtualEnv() - env.root = path.Path(tmp_path / 'venv') - env.req = str(tmp_src) - return env.create() - - def popen_text(call): """ Augment the Popen call with the parameters to ensure unicode text. diff --git a/setuptools/tests/test_easy_install.py b/setuptools/tests/test_easy_install.py index 6840d03..4a2c253 100644 --- a/setuptools/tests/test_easy_install.py +++ b/setuptools/tests/test_easy_install.py @@ -17,6 +17,8 @@ import time import re import subprocess import pathlib +import warnings +from collections import namedtuple import pytest from jaraco import path @@ -1058,3 +1060,50 @@ class TestWindowsScriptWriter: hdr = hdr.rstrip('\n') # header should not start with an escaped quote assert not hdr.startswith('\\"') + + +VersionStub = namedtuple("VersionStub", "major, minor, micro, releaselevel, serial") + + +@pytest.mark.skipif( + os.name == 'nt', + reason='Installation schemes for Windows may use values for interpolation ' + 'that come directly from sysconfig and are difficult to patch/mock' +) +def test_use_correct_python_version_string(tmpdir, tmpdir_cwd, monkeypatch): + # In issue #3001, easy_install wrongly uses the `python3.1` directory + # when the interpreter is `python3.10` and the `--user` option is given. + # See pypa/setuptools#3001. + dist = Distribution() + cmd = dist.get_command_obj('easy_install') + cmd.args = ['ok'] + cmd.optimize = 0 + cmd.user = True + cmd.install_userbase = str(tmpdir) + cmd.install_usersite = None + install_cmd = dist.get_command_obj('install') + install_cmd.install_userbase = str(tmpdir) + install_cmd.install_usersite = None + + with monkeypatch.context() as patch, warnings.catch_warnings(): + warnings.simplefilter("ignore") + version = '3.10.1 (main, Dec 21 2021, 09:17:12) [GCC 10.2.1 20210110]' + info = VersionStub(3, 10, 1, "final", 0) + patch.setattr('site.ENABLE_USER_SITE', True) + patch.setattr('sys.version', version) + patch.setattr('sys.version_info', info) + patch.setattr(cmd, 'create_home_path', mock.Mock()) + cmd.finalize_options() + + if os.getenv('SETUPTOOLS_USE_DISTUTILS', 'local') == 'local': + # Installation schemes in stdlib distutils might be outdated/bugged + name = "pypy" if hasattr(sys, 'pypy_version_info') else "python" + install_dir = cmd.install_dir.lower() + assert f"{name}3.10" in install_dir or f"{name}310" in install_dir + + # The following "variables" are used for interpolation in distutils + # installation schemes, so it should be fair to treat them as "semi-public", + # or at least public enough so we can have a test to make sure they are correct + assert cmd.config_vars['py_version'] == '3.10.1' + assert cmd.config_vars['py_version_short'] == '3.10' + assert cmd.config_vars['py_version_nodot'] == '310' diff --git a/setuptools/tests/test_setuptools.py b/setuptools/tests/test_setuptools.py index 3609ab5..3c42926 100644 --- a/setuptools/tests/test_setuptools.py +++ b/setuptools/tests/test_setuptools.py @@ -18,6 +18,11 @@ import setuptools.depends as dep from setuptools.depends import Require +@pytest.fixture(autouse=True) +def isolated_dir(tmpdir_cwd): + yield + + def makeSetup(**args): """Return distribution from 'setup(**args)', without executing commands""" diff --git a/setuptools/tests/test_virtualenv.py b/setuptools/tests/test_virtualenv.py index 61d239a..0ba8964 100644 --- a/setuptools/tests/test_virtualenv.py +++ b/setuptools/tests/test_virtualenv.py @@ -1,52 +1,34 @@ -import glob import os import sys import itertools +import subprocess import pathlib import pytest -from pytest_fixture_config import yield_requires_config - -import pytest_virtualenv +from . import contexts from .textwrap import DALS from .test_easy_install import make_nspkg_sdist @pytest.fixture(autouse=True) -def pytest_virtualenv_works(virtualenv): +def pytest_virtualenv_works(venv): """ pytest_virtualenv may not work. if it doesn't, skip these tests. See #1284. """ - venv_prefix = virtualenv.run( - 'python -c "import sys; print(sys.prefix)"', - capture=True, - ).strip() + venv_prefix = venv.run(["python" , "-c", "import sys; print(sys.prefix)"]).strip() if venv_prefix == sys.prefix: pytest.skip("virtualenv is broken (see pypa/setuptools#1284)") -@yield_requires_config(pytest_virtualenv.CONFIG, ['virtualenv_executable']) -@pytest.fixture(scope='function') -def bare_virtualenv(): - """ Bare virtualenv (no pip/setuptools/wheel). - """ - with pytest_virtualenv.VirtualEnv(args=( - '--no-wheel', - '--no-pip', - '--no-setuptools', - )) as venv: - yield venv - - -def test_clean_env_install(bare_virtualenv, tmp_src): +def test_clean_env_install(venv_without_setuptools, setuptools_wheel): """ Check setuptools can be installed in a clean environment. """ - cmd = [bare_virtualenv.python, 'setup.py', 'install'] - bare_virtualenv.run(cmd, cd=tmp_src) + cmd = ["python", "-m", "pip", "install", str(setuptools_wheel)] + venv_without_setuptools.run(cmd) def _get_pip_versions(): @@ -99,41 +81,31 @@ def _get_pip_versions(): reason="https://github.com/pypa/setuptools/pull/2865#issuecomment-965834995", ) @pytest.mark.parametrize('pip_version', _get_pip_versions()) -def test_pip_upgrade_from_source(pip_version, tmp_src, virtualenv): +def test_pip_upgrade_from_source(pip_version, venv_without_setuptools, + setuptools_wheel, setuptools_sdist): """ Check pip can upgrade setuptools from source. """ - # Install pip/wheel, and remove setuptools (as it + # Install pip/wheel, in a venv without setuptools (as it # should not be needed for bootstraping from source) - if pip_version is None: - upgrade_pip = () - else: - upgrade_pip = ('python -m pip install -U "{pip_version}" --retries=1',) - virtualenv.run(' && '.join(( - 'pip uninstall -y setuptools', - 'pip install -U wheel', - ) + upgrade_pip).format(pip_version=pip_version)) - dist_dir = virtualenv.workspace - # Generate source distribution / wheel. - virtualenv.run(' && '.join(( - 'python setup.py -q sdist -d {dist}', - 'python setup.py -q bdist_wheel -d {dist}', - )).format(dist=dist_dir), cd=tmp_src) - sdist = glob.glob(os.path.join(dist_dir, '*.zip'))[0] - wheel = glob.glob(os.path.join(dist_dir, '*.whl'))[0] - # Then update from wheel. - virtualenv.run('pip install ' + wheel) + venv = venv_without_setuptools + venv.run(["pip", "install", "-U", "wheel"]) + if pip_version is not None: + venv.run(["python", "-m", "pip", "install", "-U", pip_version, "--retries=1"]) + with pytest.raises(subprocess.CalledProcessError): + # Meta-test to make sure setuptools is not installed + venv.run(["python", "-c", "import setuptools"]) + + # Then install from wheel. + venv.run(["pip", "install", str(setuptools_wheel)]) # And finally try to upgrade from source. - virtualenv.run('pip install --no-cache-dir --upgrade ' + sdist) + venv.run(["pip", "install", "--no-cache-dir", "--upgrade", str(setuptools_sdist)]) -def _check_test_command_install_requirements(virtualenv, tmpdir, cwd): +def _check_test_command_install_requirements(venv, tmpdir): """ Check the test command will install all required dependencies. """ - # Install setuptools. - virtualenv.run('python setup.py develop', cd=cwd) - def sdist(distname, version): dist_path = tmpdir.join('%s-%s.tar.gz' % (distname, version)) make_nspkg_sdist(str(dist_path), distname, version) @@ -182,28 +154,24 @@ def _check_test_command_install_requirements(virtualenv, tmpdir, cwd): open('success', 'w').close() ''')) - # Run test command for test package. - # use 'virtualenv.python' as workaround for man-group/pytest-plugins#166 - cmd = [virtualenv.python, 'setup.py', 'test', '-s', 'test'] - virtualenv.run(cmd, cd=str(tmpdir)) + + cmd = ["python", 'setup.py', 'test', '-s', 'test'] + venv.run(cmd, cwd=str(tmpdir)) assert tmpdir.join('success').check() -def test_test_command_install_requirements(virtualenv, tmpdir, request): +def test_test_command_install_requirements(venv, tmpdir, tmpdir_cwd): # Ensure pip/wheel packages are installed. - virtualenv.run( - "python -c \"__import__('pkg_resources').require(['pip', 'wheel'])\"") - # uninstall setuptools so that 'setup.py develop' works - virtualenv.run("python -m pip uninstall -y setuptools") + venv.run(["python", "-c", "__import__('pkg_resources').require(['pip', 'wheel'])"]) # disable index URL so bits and bobs aren't requested from PyPI - virtualenv.env['PIP_NO_INDEX'] = '1' - _check_test_command_install_requirements(virtualenv, tmpdir, request.config.rootdir) + with contexts.environment(PYTHONPATH=None, PIP_NO_INDEX="1"): + _check_test_command_install_requirements(venv, tmpdir) -def test_no_missing_dependencies(bare_virtualenv, request): +def test_no_missing_dependencies(bare_venv, request): """ Quick and dirty test to ensure all external dependencies are vendored. """ + setuptools_dir = request.config.rootdir for command in ('upload',): # sorted(distutils.command.__all__): - cmd = [bare_virtualenv.python, 'setup.py', command, '-h'] - bare_virtualenv.run(cmd, cd=request.config.rootdir) + bare_venv.run(['python', 'setup.py', command, '-h'], cwd=setuptools_dir) diff --git a/setuptools/tests/test_wheel.py b/setuptools/tests/test_wheel.py index 7345b13..a15c3a4 100644 --- a/setuptools/tests/test_wheel.py +++ b/setuptools/tests/test_wheel.py @@ -148,6 +148,7 @@ def _check_wheel_install(filename, install_dir, install_tree_includes, if requires_txt is None: assert not dist.has_metadata('requires.txt') else: + # Order must match to ensure reproducibility. assert requires_txt == dist.get_metadata('requires.txt').lstrip() @@ -419,6 +420,38 @@ WHEEL_INSTALL_TESTS = ( ), ), + dict( + id='requires_ensure_order', + install_requires=''' + foo + bar + baz + qux + ''', + extras_require={ + 'extra': ''' + foobar>3 + barbaz>4 + bazqux>5 + quxzap>6 + ''', + }, + requires_txt=DALS( + ''' + foo + bar + baz + qux + + [extra] + foobar>3 + barbaz>4 + bazqux>5 + quxzap>6 + ''' + ), + ), + dict( id='namespace_package', file_defs={ diff --git a/setuptools/wheel.py b/setuptools/wheel.py index 0be811a..9819e8b 100644 --- a/setuptools/wheel.py +++ b/setuptools/wheel.py @@ -136,13 +136,13 @@ class Wheel: def raw_req(req): req.marker = None return str(req) - install_requires = list(sorted(map(raw_req, dist.requires()))) + install_requires = list(map(raw_req, dist.requires())) extras_require = { - extra: sorted( + extra: [ req for req in map(raw_req, dist.requires((extra,))) if req not in install_requires - ) + ] for extra in dist.extras } os.rename(dist_info, egg_info) diff --git a/tox.ini b/tox.ini index 145c220..d6cc3f5 100644 --- a/tox.ini +++ b/tox.ini @@ -7,8 +7,7 @@ toxworkdir={env:TOX_WORK_DIR:.tox} [testenv] deps = - # TODO: remove after man-group/pytest-plugins#188 is solved - pytest-virtualenv @ git+https://github.com/jaraco/pytest-plugins@distutils-deprecated#subdirectory=pytest-virtualenv + # Ideally all the dependencies should be set as "extras" commands = pytest {posargs} usedevelop = True @@ -16,6 +15,20 @@ extras = testing passenv = SETUPTOOLS_USE_DISTUTILS windir # required for test_pkg_resources + # honor git config in pytest-perf + HOME + +[testenv:integration] +deps = {[testenv]deps} +extras = testing-integration +passenv = + {[testenv]passenv} + DOWNLOAD_PATH +setenv = + PROJECT_ROOT = {toxinidir} +commands = + pytest --integration {posargs:-vv --durations=10 setuptools/tests/integration} + # use verbose mode by default to facilitate debugging from CI logs [testenv:docs] extras = -- 2.34.1