diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 16058d4c24..ce3d207697 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 78.1.0 +current_version = 79.0.0 commit = True tag = True diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 14f3151d34..52f11528ae 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -28,7 +28,6 @@ env: # Suppress noisy pip warnings PIP_DISABLE_PIP_VERSION_CHECK: 'true' - PIP_NO_PYTHON_VERSION_WARNING: 'true' PIP_NO_WARN_SCRIPT_LOCATION: 'true' # Ensure tests can sense settings about the environment diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index aecc11eb22..633e3648e9 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.8.0 + rev: v0.9.9 hooks: - id: ruff args: [--fix, --unsafe-fixes] diff --git a/NEWS.rst b/NEWS.rst index 554caf867c..e7f33786a3 100644 --- a/NEWS.rst +++ b/NEWS.rst @@ -1,3 +1,21 @@ +v79.0.0 +======= + +Deprecations and Removals +------------------------- + +- Removed support for 'legacy-editable' installs. (#917) + + +v78.1.1 +======= + +Bugfixes +-------- + +- More fully sanitized the filename in PackageIndex._download. (#4946) + + v78.1.0 ======= @@ -119,7 +137,7 @@ Deprecations and Removals - Deprecated ``project.license`` as a TOML table in ``pyproject.toml``\. Users are expected to move towards using ``project.license-files`` and/or SPDX expressions (as strings) in - ``pyproject.license``\. + ``project.license``\. See PEP :pep:`639 <639#deprecate-license-key-table-subkeys>`. (#4840) - Added simple validation for given glob patterns in ``license-files``\: a warning will be generated if no file is matched. diff --git a/docs/userguide/development_mode.rst b/docs/userguide/development_mode.rst index 9a79b08a93..3eabe87fcb 100644 --- a/docs/userguide/development_mode.rst +++ b/docs/userguide/development_mode.rst @@ -197,13 +197,6 @@ works (still within the context of :pep:`660`). Users are encouraged to try out the new editable installation techniques and make the necessary adaptations. -.. note:: - Newer versions of ``pip`` no longer run the fallback command - ``python setup.py develop`` when the ``pyproject.toml`` file is present. - This means that setting the environment variable - ``SETUPTOOLS_ENABLE_FEATURES="legacy-editable"`` - will have no effect when installing a package with ``pip``. - How editable installations work ------------------------------- diff --git a/pyproject.toml b/pyproject.toml index e4d0441317..3ba37aa59d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,7 @@ backend-path = ["."] [project] name = "setuptools" -version = "78.1.0" +version = "79.0.0" authors = [ { name = "Python Packaging Authority", email = "distutils-sig@python.org" }, ] @@ -14,7 +14,6 @@ readme = "README.rst" classifiers = [ "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", - "License :: OSI Approved :: MIT License", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3 :: Only", "Topic :: Software Development :: Libraries :: Python Modules", diff --git a/ruff.toml b/ruff.toml index ce0b99e1dc..349d22cebd 100644 --- a/ruff.toml +++ b/ruff.toml @@ -1,6 +1,3 @@ -# extend pyproject.toml for requires-python (workaround astral-sh/ruff#10299) -extend = "pyproject.toml" - exclude = [ "**/_vendor", "setuptools/_distutils", @@ -14,7 +11,6 @@ extend-select = [ "C901", # complex-structure "I", # isort "PERF401", # manual-list-comprehension - "W", # pycodestyle Warning # Ensure modern type annotation syntax and best practices # Not including those covered by type-checkers or exclusive to Python 3.11+ @@ -57,8 +53,6 @@ ignore = [ "Q003", "COM812", "COM819", - "ISC001", - "ISC002", # local "PERF203", # try-except-in-loop, micro-optimisation with many false-positive. Worth checking but don't block CI diff --git a/setuptools/build_meta.py b/setuptools/build_meta.py index 00fa5e1f70..8f2e930c73 100644 --- a/setuptools/build_meta.py +++ b/setuptools/build_meta.py @@ -67,9 +67,6 @@ 'SetupRequirementsError', ] -SETUPTOOLS_ENABLE_FEATURES = os.getenv("SETUPTOOLS_ENABLE_FEATURES", "").lower() -LEGACY_EDITABLE = "legacy-editable" in SETUPTOOLS_ENABLE_FEATURES.replace("_", "-") - class SetupRequirementsError(BaseException): def __init__(self, specifiers) -> None: @@ -457,37 +454,30 @@ def _get_dist_info_dir(self, metadata_directory: StrPath | None) -> str | None: assert len(dist_info_candidates) <= 1 return str(dist_info_candidates[0]) if dist_info_candidates else None - if not LEGACY_EDITABLE: - # PEP660 hooks: - # build_editable - # get_requires_for_build_editable - # prepare_metadata_for_build_editable - def build_editable( - self, - wheel_directory: StrPath, - config_settings: _ConfigSettings = None, - metadata_directory: StrPath | None = None, - ): - # XXX can or should we hide our editable_wheel command normally? - info_dir = self._get_dist_info_dir(metadata_directory) - opts = ["--dist-info-dir", info_dir] if info_dir else [] - cmd = ["editable_wheel", *opts, *self._editable_args(config_settings)] - with suppress_known_deprecation(): - return self._build_with_temp_dir( - cmd, ".whl", wheel_directory, config_settings - ) + def build_editable( + self, + wheel_directory: StrPath, + config_settings: _ConfigSettings = None, + metadata_directory: StrPath | None = None, + ): + # XXX can or should we hide our editable_wheel command normally? + info_dir = self._get_dist_info_dir(metadata_directory) + opts = ["--dist-info-dir", info_dir] if info_dir else [] + cmd = ["editable_wheel", *opts, *self._editable_args(config_settings)] + with suppress_known_deprecation(): + return self._build_with_temp_dir( + cmd, ".whl", wheel_directory, config_settings + ) - def get_requires_for_build_editable( - self, config_settings: _ConfigSettings = None - ): - return self.get_requires_for_build_wheel(config_settings) + def get_requires_for_build_editable(self, config_settings: _ConfigSettings = None): + return self.get_requires_for_build_wheel(config_settings) - def prepare_metadata_for_build_editable( - self, metadata_directory: StrPath, config_settings: _ConfigSettings = None - ): - return self.prepare_metadata_for_build_wheel( - metadata_directory, config_settings - ) + def prepare_metadata_for_build_editable( + self, metadata_directory: StrPath, config_settings: _ConfigSettings = None + ): + return self.prepare_metadata_for_build_wheel( + metadata_directory, config_settings + ) class _BuildMetaLegacyBackend(_BuildMetaBackend): @@ -549,11 +539,9 @@ class _IncompatibleBdistWheel(SetuptoolsDeprecationWarning): prepare_metadata_for_build_wheel = _BACKEND.prepare_metadata_for_build_wheel build_wheel = _BACKEND.build_wheel build_sdist = _BACKEND.build_sdist - -if not LEGACY_EDITABLE: - get_requires_for_build_editable = _BACKEND.get_requires_for_build_editable - prepare_metadata_for_build_editable = _BACKEND.prepare_metadata_for_build_editable - build_editable = _BACKEND.build_editable +get_requires_for_build_editable = _BACKEND.get_requires_for_build_editable +prepare_metadata_for_build_editable = _BACKEND.prepare_metadata_for_build_editable +build_editable = _BACKEND.build_editable # The legacy backend diff --git a/setuptools/package_index.py b/setuptools/package_index.py index 1a6abebcda..3500c2d86f 100644 --- a/setuptools/package_index.py +++ b/setuptools/package_index.py @@ -807,21 +807,63 @@ def open_url(self, url, warning=None): # noqa: C901 # is too complex (12) else: raise DistutilsError(f"Download error for {url}: {v}") from v - def _download_url(self, url, tmpdir): - # Determine download filename - # + @staticmethod + def _sanitize(name): + r""" + Replace unsafe path directives with underscores. + + >>> san = PackageIndex._sanitize + >>> san('/home/user/.ssh/authorized_keys') + '_home_user_.ssh_authorized_keys' + >>> san('..\\foo\\bing') + '__foo_bing' + >>> san('D:bar') + 'D_bar' + >>> san('C:\\bar') + 'C__bar' + >>> san('foo..bar') + 'foo..bar' + >>> san('D:../foo') + 'D___foo' + """ + pattern = '|'.join(( + # drive letters + r':', + # path separators + r'[/\\]', + # parent dirs + r'(?:(?<=([/\\]|:))\.\.(?=[/\\]|$))|(?:^\.\.(?=[/\\]|$))', + )) + return re.sub(pattern, r'_', name) + + @classmethod + def _resolve_download_filename(cls, url, tmpdir): + """ + >>> import pathlib + >>> du = PackageIndex._resolve_download_filename + >>> root = getfixture('tmp_path') + >>> url = 'https://files.pythonhosted.org/packages/a9/5a/0db.../setuptools-78.1.0.tar.gz' + >>> str(pathlib.Path(du(url, root)).relative_to(root)) + 'setuptools-78.1.0.tar.gz' + """ name, _fragment = egg_info_for_url(url) - if name: - while '..' in name: - name = name.replace('..', '.').replace('\\', '_') - else: - name = "__downloaded__" # default if URL has no path contents + name = cls._sanitize( + name + or + # default if URL has no path contents + '__downloaded__' + ) - if name.endswith('.egg.zip'): - name = name[:-4] # strip the extra .zip before download + # strip any extra .zip before download + name = re.sub(r'\.egg\.zip$', '.egg', name) - filename = os.path.join(tmpdir, name) + return os.path.join(tmpdir, name) + def _download_url(self, url, tmpdir): + """ + Determine the download filename. + """ + filename = self._resolve_download_filename(url, tmpdir) return self._download_vcs(url, filename) or self._download_other(url, filename) @staticmethod diff --git a/setuptools/tests/test_build_meta.py b/setuptools/tests/test_build_meta.py index 624bba862e..57162fd6af 100644 --- a/setuptools/tests/test_build_meta.py +++ b/setuptools/tests/test_build_meta.py @@ -936,30 +936,6 @@ def test_sys_argv_passthrough(self, tmpdir_cwd): build_backend.build_sdist("temp") -def test_legacy_editable_install(venv, tmpdir, tmpdir_cwd): - pyproject = """ - [build-system] - requires = ["setuptools"] - build-backend = "setuptools.build_meta" - [project] - name = "myproj" - version = "42" - """ - path.build({"pyproject.toml": DALS(pyproject), "mymod.py": ""}) - - # First: sanity check - cmd = ["pip", "install", "--no-build-isolation", "-e", "."] - output = venv.run(cmd, cwd=tmpdir).lower() - assert "running setup.py develop for myproj" not in output - assert "created wheel for myproj" in output - - # Then: real test - env = {**os.environ, "SETUPTOOLS_ENABLE_FEATURES": "legacy-editable"} - cmd = ["pip", "install", "--no-build-isolation", "-e", "."] - output = venv.run(cmd, cwd=tmpdir, env=env).lower() - assert "running setup.py develop for myproj" in output - - @pytest.mark.filterwarnings("ignore::setuptools.SetuptoolsDeprecationWarning") def test_sys_exit_0_in_setuppy(monkeypatch, tmp_path): """Setuptools should be resilient to setup.py with ``sys.exit(0)`` (#3973)."""