[bumpversion]
-current_version = 64.0.2
+current_version = 64.0.3
commit = True
tag = True
+v64.0.3
+-------
+
+
+Misc
+^^^^
+* #3515: Fixed "inline" file copying for editable installations and
+ optional extensions.
+* #3517: Fixed ``editable_wheel`` to ensure other commands are finalized before using
+ them. This should prevent errors with plugins trying to use different commands
+ or reinitializing them.
+* #3517: Augmented filter to prevent transient/temporary source files from being
+ considered ``package_data`` or ``data_files``.
+
+
v64.0.2
-------
installation command: ``python setup.py develop`` [#installer]_.
-How editable installations work?
---------------------------------
+How editable installations work
+-------------------------------
*Advanced topic*
[metadata]
name = setuptools
-version = 64.0.2
+version = 64.0.3
author = Python Packaging Authority
author_email = distutils-sig@python.org
description = Easily download, build, install, upgrade, and uninstall Python packages
# Always copy, even if source is older than destination, to ensure
# that the right extensions for the current Python/platform are
# used.
- self.copy_file(regular_file, inplace_file, level=self.verbose)
+ if os.path.exists(regular_file) or not ext.optional:
+ self.copy_file(regular_file, inplace_file, level=self.verbose)
if ext._needs_stub:
inplace_stub = self._get_equivalent_stub(ext, inplace_file)
files = ei_cmd.filelist.files
check = _IncludePackageDataAbuse()
- for path in _filter_absolute_egg_info(files, egg_info_dir):
+ for path in self._filter_build_files(files, egg_info_dir):
d, f = os.path.split(assert_relative(path))
prev = None
oldf = f
check.warn(importable)
mf.setdefault(src_dirs[d], []).append(path)
+ def _filter_build_files(self, files: Iterable[str], egg_info: str) -> Iterator[str]:
+ """
+ ``build_meta`` may try to create egg_info outside of the project directory,
+ and this can be problematic for certain plugins (reported in issue #3500).
+
+ Extensions might also include between their sources files created on the
+ ``build_lib`` and ``build_temp`` directories.
+
+ This function should filter this case of invalid files out.
+ """
+ build = self.get_finalized_command("build")
+ build_dirs = (egg_info, self.build_lib, build.build_temp, build.build_base)
+ norm_dirs = [os.path.normpath(p) for p in build_dirs if p]
+
+ for file in files:
+ norm_path = os.path.normpath(file)
+ if not os.path.isabs(file) or all(d not in norm_path for d in norm_dirs):
+ yield file
+
def get_data_files(self):
pass # Lazily compute data files in _get_data_files() function.
msg = textwrap.dedent(self.MESSAGE).format(importable=importable)
warnings.warn(msg, SetuptoolsDeprecationWarning, stacklevel=2)
self._already_warned.add(importable)
-
-
-def _filter_absolute_egg_info(files: Iterable[str], egg_info: str) -> Iterator[str]:
- """
- ``build_meta`` may try to create egg_info outside of the project directory,
- and this can be problematic for certain plugins (reported in issue #3500).
- This function should filter this case of invalid files out.
- """
- egg_info_name = Path(egg_info).name
- for file in files:
- if not (egg_info_name in file and os.path.isabs(file)):
- yield file
self._ensure_dist_info()
# Add missing dist_info files
- bdist_wheel = self.reinitialize_command("bdist_wheel")
+ self.reinitialize_command("bdist_wheel")
+ bdist_wheel = self.get_finalized_command("bdist_wheel")
bdist_wheel.write_wheelfile(self.dist_info_dir)
self._create_wheel_file(bdist_wheel)
if self.dist_info_dir is None:
dist_info = self.reinitialize_command("dist_info")
dist_info.output_dir = self.dist_dir
- dist_info.finalize_options()
+ dist_info.ensure_finalized()
dist_info.run()
self.dist_info_dir = dist_info.dist_info_dir
else:
# Also remove _safely_run, TestCustomBuildPy. Suggested date: Aug/2023.
build: Command = self.get_finalized_command("build")
for name in build.get_sub_commands():
- cmd = self.distribution.get_command_obj(name)
+ cmd = self.get_finalized_command(name)
if name == "build_py" and type(cmd) != build_py_cls:
self._safely_run(name)
else:
from setuptools.command.build_ext import build_ext, get_abi3_suffix
from setuptools.dist import Distribution
from setuptools.extension import Extension
+from setuptools.errors import CompileError
from . import environment
from .textwrap import DALS
+import pytest
+
IS_PYPY = '__pypy__' in sys.builtin_module_names
assert example_stub.endswith(".pyc")
+class TestBuildExtInplace:
+ def get_build_ext_cmd(self, optional: bool, **opts):
+ files = {
+ "eggs.c": "#include missingheader.h\n",
+ ".build": {"lib": {}, "tmp": {}},
+ }
+ path.build(files)
+ extension = Extension('spam.eggs', ['eggs.c'], optional=optional)
+ dist = Distribution(dict(ext_modules=[extension]))
+ dist.script_name = 'setup.py'
+ cmd = build_ext(dist)
+ vars(cmd).update(build_lib=".build/lib", build_temp=".build/tmp", **opts)
+ cmd.ensure_finalized()
+ return cmd
+
+ def test_optional(self, tmpdir_cwd, capsys):
+ """
+ If optional extensions fail to build, setuptools should show the error
+ in the logs but not fail to build
+ """
+ cmd = self.get_build_ext_cmd(optional=True, inplace=True)
+ cmd.run()
+ logs = capsys.readouterr()
+ messages = (logs.out + logs.err)
+ assert 'build_ext: building extension "spam.eggs" failed' in messages
+ # No compile error exception should be raised
+
+ def test_non_optional(self, tmpdir_cwd):
+ # Non-optional extensions should raise an exception
+ cmd = self.get_build_ext_cmd(optional=False, inplace=True)
+ with pytest.raises(CompileError):
+ cmd.run()
+
+
def test_build_ext_config_handling(tmpdir_cwd):
files = {
'setup.py': DALS(
_find_namespaces,
_find_package_roots,
_finder_template,
+ editable_wheel,
)
from setuptools.dist import Distribution
assert b"42" in out
+class TestCustomBuildWheel:
+ def install_custom_build_wheel(self, dist):
+ bdist_wheel_cls = dist.get_command_class("bdist_wheel")
+
+ class MyBdistWheel(bdist_wheel_cls):
+ def get_tag(self):
+ # In issue #3513, we can see that some extensions may try to access
+ # the `plat_name` property in bdist_wheel
+ if self.plat_name.startswith("macosx-"):
+ _ = "macOS platform"
+ return super().get_tag()
+
+ dist.cmdclass["bdist_wheel"] = MyBdistWheel
+
+ def test_access_plat_name(self, tmpdir_cwd):
+ # Even when a custom bdist_wheel tries to access plat_name the build should
+ # be successful
+ jaraco.path.build({"module.py": "x = 42"})
+ dist = Distribution()
+ dist.script_name = "setup.py"
+ dist.set_defaults()
+ self.install_custom_build_wheel(dist)
+ cmd = editable_wheel(dist)
+ cmd.ensure_finalized()
+ cmd.run()
+ wheel_file = str(next(Path().glob('dist/*')))
+ assert "editable" in wheel_file
+ assert wheel_file.endswith(".whl")
+
+
def install_project(name, venv, tmp_path, files, *opts):
project = tmp_path / name
project.mkdir()