diff --git a/newsfragments/1825.bugfix.rst b/newsfragments/1825.bugfix.rst new file mode 100644 index 0000000000..ff55d18725 --- /dev/null +++ b/newsfragments/1825.bugfix.rst @@ -0,0 +1 @@ +Re-use pre-existing ``.dist-info`` dir when creating wheels via the build backend APIs (PEP 517) and the ``metadata_directory`` argument is passed -- by :user:`pelson`. diff --git a/setuptools/build_meta.py b/setuptools/build_meta.py index a6b85afc42..ecf434bbf3 100644 --- a/setuptools/build_meta.py +++ b/setuptools/build_meta.py @@ -417,9 +417,12 @@ def build_wheel( config_settings: _ConfigSettings = None, metadata_directory: StrPath | None = None, ): + cmd = ['bdist_wheel'] + if metadata_directory: + cmd.extend(['--dist-info-dir', metadata_directory]) with suppress_known_deprecation(): return self._build_with_temp_dir( - ['bdist_wheel'], + cmd, '.whl', wheel_directory, config_settings, diff --git a/setuptools/command/bdist_wheel.py b/setuptools/command/bdist_wheel.py index fa97976fef..aeade98f6f 100644 --- a/setuptools/command/bdist_wheel.py +++ b/setuptools/command/bdist_wheel.py @@ -231,6 +231,13 @@ class bdist_wheel(Command): None, "Python tag (cp32|cp33|cpNN) for abi3 wheel tag [default: false]", ), + ( + "dist-info-dir=", + None, + "directory where a pre-generated dist-info can be found (e.g. as a " + "result of calling the PEP517 'prepare_metadata_for_build_wheel' " + "method)", + ), ] boolean_options = ["keep-temp", "skip-build", "relative", "universal"] @@ -243,6 +250,7 @@ def initialize_options(self) -> None: self.format = "zip" self.keep_temp = False self.dist_dir: str | None = None + self.dist_info_dir = None self.egginfo_dir: str | None = None self.root_is_pure: bool | None = None self.skip_build = False @@ -261,8 +269,9 @@ def finalize_options(self) -> None: bdist_base = self.get_finalized_command("bdist").bdist_base self.bdist_dir = os.path.join(bdist_base, "wheel") - egg_info = cast(egg_info_cls, self.distribution.get_command_obj("egg_info")) - egg_info.ensure_finalized() # needed for correct `wheel_dist_name` + if self.dist_info_dir is None: + egg_info = cast(egg_info_cls, self.distribution.get_command_obj("egg_info")) + egg_info.ensure_finalized() # needed for correct `wheel_dist_name` self.data_dir = self.wheel_dist_name + ".data" self.plat_name_supplied = bool(self.plat_name) @@ -447,7 +456,16 @@ def run(self): f"{safer_version(self.distribution.get_version())}.dist-info" ) distinfo_dir = os.path.join(self.bdist_dir, distinfo_dirname) - self.egg2dist(self.egginfo_dir, distinfo_dir) + if self.dist_info_dir: + # Use the given dist-info directly. + log.debug(f"reusing {self.dist_info_dir}") + shutil.copytree(self.dist_info_dir, distinfo_dir) + # Egg info is still generated, so remove it now to avoid it getting + # copied into the wheel. + shutil.rmtree(self.egginfo_dir) + else: + # Convert the generated egg-info into dist-info. + self.egg2dist(self.egginfo_dir, distinfo_dir) self.write_wheelfile(distinfo_dir) diff --git a/setuptools/tests/test_bdist_wheel.py b/setuptools/tests/test_bdist_wheel.py index 8b64e90f72..47200d0a26 100644 --- a/setuptools/tests/test_bdist_wheel.py +++ b/setuptools/tests/test_bdist_wheel.py @@ -619,3 +619,28 @@ def _fake_import(name: str, *args, **kwargs): monkeypatch.delitem(sys.modules, "setuptools.command.bdist_wheel") import setuptools.command.bdist_wheel # noqa: F401 + + +def test_dist_info_provided(dummy_dist, monkeypatch, tmp_path): + monkeypatch.chdir(dummy_dist) + distinfo = tmp_path / "dummy_dist.dist-info" + + distinfo.mkdir() + (distinfo / "METADATA").write_text("name: helloworld", encoding="utf-8") + + # We don't control the metadata. According to PEP-517, "The hook MAY also + # create other files inside this directory, and a build frontend MUST + # preserve". + (distinfo / "FOO").write_text("bar", encoding="utf-8") + + bdist_wheel_cmd(bdist_dir=str(tmp_path), dist_info_dir=str(distinfo)).run() + expected = { + "dummy_dist-1.0.dist-info/FOO", + "dummy_dist-1.0.dist-info/RECORD", + } + with ZipFile("dist/dummy_dist-1.0-py3-none-any.whl") as wf: + files_found = set(wf.namelist()) + # Check that all expected files are there. + assert expected - files_found == set() + # Make sure there is no accidental egg-info bleeding into the wheel. + assert not [path for path in files_found if 'egg-info' in str(path)]