From 09c1975eda61b2662bf1d1bfd40d6170670c8954 Mon Sep 17 00:00:00 2001 From: Stefano Rivera Date: Sun, 16 Oct 2022 13:01:21 +0200 Subject: [PATCH] Search wheels for .dist-info directories Some wheels don't use normalized names for their .dist-info directories, so search the wheel for them. Fixes: #134 Bug-Upstream: https://github.com/pypa/installer/issues/134 Bug-Debian: https://bugs.debian.org/1008606 Forwarded: https://github.com/pypa/installer/pull/137 --- ...ch-wheels-for-.dist-info-directories.patch | 186 ++++++++++++++++++ debian/patches/series | 1 + 2 files changed, 187 insertions(+) create mode 100644 debian/patches/0001-Search-wheels-for-.dist-info-directories.patch create mode 100644 debian/patches/series diff --git a/debian/patches/0001-Search-wheels-for-.dist-info-directories.patch b/debian/patches/0001-Search-wheels-for-.dist-info-directories.patch new file mode 100644 index 0000000..3b32ad3 --- /dev/null +++ b/debian/patches/0001-Search-wheels-for-.dist-info-directories.patch @@ -0,0 +1,186 @@ +From: Stefano Rivera +Date: Sun, 16 Oct 2022 13:01:21 +0200 +Subject: Search wheels for .dist-info directories + +Some wheels don't use normalized names for their .dist-info directories, +so search the wheel for them. + +Fixes: #134 +Bug-Upstream: https://github.com/pypa/installer/issues/134 +Bug-Debian: https://bugs.debian.org/1008606 +Forwarded: https://github.com/pypa/installer/pull/137 +--- + docs/conf.py | 2 +- + src/installer/sources.py | 29 ++++++++++++++++++++++++++++- + src/installer/utils.py | 8 ++++++++ + tests/test_scripts.py | 4 ++++ + tests/test_sources.py | 17 +++++++++++++++++ + tests/test_utils.py | 22 ++++++++++++++++++++++ + 6 files changed, 80 insertions(+), 2 deletions(-) + +diff --git a/docs/conf.py b/docs/conf.py +index 22bbe11..3ff6d4d 100644 +--- a/docs/conf.py ++++ b/docs/conf.py +@@ -24,7 +24,7 @@ extensions = [ + # -- Options for HTML output ----------------------------------------------------------- + # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output + +-html_theme = "furo" ++#html_theme = "furo" + html_title = project + + # -- Options for Autodoc -------------------------------------------------------------- +diff --git a/src/installer/sources.py b/src/installer/sources.py +index fa0bc34..e3a7c45 100644 +--- a/src/installer/sources.py ++++ b/src/installer/sources.py +@@ -8,7 +8,7 @@ from contextlib import contextmanager + from typing import BinaryIO, Iterator, List, Tuple, cast + + from installer.records import parse_record_file +-from installer.utils import parse_wheel_filename ++from installer.utils import canonicalize_name, parse_wheel_filename + + WheelContentElement = Tuple[Tuple[str, str, str], BinaryIO, bool] + +@@ -122,6 +122,33 @@ class WheelFile(WheelSource): + with zipfile.ZipFile(path) as f: + yield cls(f) + ++ @property ++ def dist_info_dir(self) -> str: ++ """Name of the dist-info directory.""" ++ if not hasattr(self, "_dist_info_dir"): ++ top_level_directories = { ++ path.split("/", 1)[0] for path in self._zipfile.namelist() ++ } ++ dist_infos = [ ++ name for name in top_level_directories if name.endswith(".dist-info") ++ ] ++ ++ assert ( ++ len(dist_infos) == 1 ++ ), "Wheel doesn't contain exactly one .dist-info directory" ++ dist_info_dir = dist_infos[0] ++ ++ # NAME-VER.dist-info ++ di_dname = dist_info_dir.rsplit("-", 2)[0] ++ norm_di_dname = canonicalize_name(di_dname) ++ norm_file_dname = canonicalize_name(self.distribution) ++ assert ( ++ norm_di_dname == norm_file_dname ++ ), "Wheel .dist-info directory doesn't match wheel filename" ++ ++ self._dist_info_dir = dist_info_dir ++ return self._dist_info_dir ++ + @property + def dist_info_filenames(self) -> List[str]: + """Get names of all files in the dist-info directory.""" +diff --git a/src/installer/utils.py b/src/installer/utils.py +index 7b1404d..cef2bd8 100644 +--- a/src/installer/utils.py ++++ b/src/installer/utils.py +@@ -94,6 +94,14 @@ def parse_metadata_file(contents: str) -> Message: + return feed_parser.close() + + ++def canonicalize_name(name: str) -> str: ++ """Canonicalize a project name according to PEP-503. ++ ++ :param name: The project name to canonicalize ++ """ ++ return re.sub(r"[-_.]+", "-", name).lower() ++ ++ + def parse_wheel_filename(filename: str) -> WheelFilename: + """Parse a wheel filename, into it's various components. + +diff --git a/tests/test_scripts.py b/tests/test_scripts.py +index 2da6577..6442e19 100644 +--- a/tests/test_scripts.py ++++ b/tests/test_scripts.py +@@ -40,6 +40,8 @@ def _read_launcher_data(section, kind): + return f.read() + + ++@pytest.mark.skip( ++ "Skipped on Debian, we don't include the simple_launcher .exes") + @pytest.mark.parametrize("section", ["console", "gui"]) + @pytest.mark.parametrize("kind", ["win-ia32", "win-amd64", "win-arm"]) + def test_script_generate_launcher(section, kind): +@@ -60,6 +62,8 @@ def test_script_generate_launcher(section, kind): + assert b"baz.qux()" in code + + ++@pytest.mark.skip( ++ "Skipped on Debian, we don't include the simple_launcher .exes") + @pytest.mark.parametrize( + "section, kind", + [("nonexist", "win-ia32"), ("console", "nonexist"), ("nonexist", "nonexist")], +diff --git a/tests/test_sources.py b/tests/test_sources.py +index a79cc24..8d71496 100644 +--- a/tests/test_sources.py ++++ b/tests/test_sources.py +@@ -92,3 +92,20 @@ class TestWheelFile: + + assert sorted(got_records) == sorted(expected_records) + assert got_files == files ++ ++ def test_finds_dist_info(self, fancy_wheel): ++ denorm = fancy_wheel.rename(fancy_wheel.parent / "Fancy-1.0.0-py3-none-any.whl") ++ # Python 3.7: rename doesn't return the new name: ++ denorm = fancy_wheel.parent / "Fancy-1.0.0-py3-none-any.whl" ++ with WheelFile.open(denorm) as source: ++ assert source.dist_info_filenames ++ ++ def test_requires_dist_info_name_match(self, fancy_wheel): ++ misnamed = fancy_wheel.rename( ++ fancy_wheel.parent / "misnamed-1.0.0-py3-none-any.whl" ++ ) ++ # Python 3.7: rename doesn't return the new name: ++ misnamed = fancy_wheel.parent / "misnamed-1.0.0-py3-none-any.whl" ++ with pytest.raises(AssertionError): ++ with WheelFile.open(misnamed) as source: ++ source.dist_info_filenames +diff --git a/tests/test_utils.py b/tests/test_utils.py +index bfcc089..e4bfb6a 100644 +--- a/tests/test_utils.py ++++ b/tests/test_utils.py +@@ -16,6 +16,7 @@ from installer.utils import ( + construct_record_file, + copyfileobj_with_hashing, + fix_shebang, ++ canonicalize_name, + parse_entrypoints, + parse_metadata_file, + parse_wheel_filename, +@@ -41,6 +42,27 @@ class TestParseMetadata: + assert result.get_all("MULTI-USE-FIELD") == ["1", "2", "3"] + + ++class TestCanonicalizeDistributionName: ++ @pytest.mark.parametrize( ++ "string, expected", ++ [ ++ # Noop ++ ( ++ "package-1", ++ "package-1", ++ ), ++ # PEP 508 canonicalization ++ ( ++ "ABC..12", ++ "abc-12", ++ ), ++ ], ++ ) ++ def test_valid_cases(self, string, expected): ++ got = canonicalize_name(string) ++ assert expected == got, (expected, got) ++ ++ + class TestParseWheelFilename: + @pytest.mark.parametrize( + "string, expected", diff --git a/debian/patches/series b/debian/patches/series new file mode 100644 index 0000000..9ef642e --- /dev/null +++ b/debian/patches/series @@ -0,0 +1 @@ +0001-Search-wheels-for-.dist-info-directories.patch