From f81fd19a7bbfe742f1b6a05efd8694a2bd40be69 Mon Sep 17 00:00:00 2001 From: Tzu-ping Chung Date: Thu, 28 May 2020 16:08:17 +0800 Subject: [PATCH 1/4] Ensure entry points are read as UTF-8 Like the wheel metadata, this is, strictly speaking, unspecified. But UTF-8 is the de-facto standard, and we should support that. --- src/pip/_internal/operations/install/wheel.py | 16 +++++--------- tests/functional/test_install_wheel.py | 22 +++++++++++++++++++ tests/unit/test_wheel.py | 16 +++++++++----- 3 files changed, 38 insertions(+), 16 deletions(-) diff --git a/src/pip/_internal/operations/install/wheel.py b/src/pip/_internal/operations/install/wheel.py index 36877ca5e76..b2b074bea21 100644 --- a/src/pip/_internal/operations/install/wheel.py +++ b/src/pip/_internal/operations/install/wheel.py @@ -7,6 +7,7 @@ import compileall import contextlib import csv +import io import logging import os.path import re @@ -21,14 +22,7 @@ from pip._vendor import pkg_resources from pip._vendor.distlib.scripts import ScriptMaker from pip._vendor.distlib.util import get_export_entry -from pip._vendor.six import ( - PY2, - StringIO, - ensure_str, - ensure_text, - itervalues, - text_type, -) +from pip._vendor.six import PY2, ensure_str, ensure_text, itervalues, text_type from pip._internal.exceptions import InstallationError from pip._internal.locations import get_major_minor_version @@ -131,11 +125,11 @@ def get_entrypoints(filename): # means that they may or may not be valid INI files. The attempt here is to # strip leading and trailing whitespace in order to make them valid INI # files. - with open(filename) as fp: - data = StringIO() + with io.open(filename, encoding="utf-8") as fp: + data = io.StringIO() for line in fp: data.write(line.strip()) - data.write("\n") + data.write(u"\n") data.seek(0) # get the entry points and then the script names diff --git a/tests/functional/test_install_wheel.py b/tests/functional/test_install_wheel.py index 660ee667045..6d94622458a 100644 --- a/tests/functional/test_install_wheel.py +++ b/tests/functional/test_install_wheel.py @@ -348,6 +348,28 @@ def test_install_from_wheel_gen_uppercase_entrypoint( assert bool(os.access(script.base_path / wrapper_file, os.X_OK)) +# pkg_resources.EntryPoint() does not parse unicode correctly on Python 2. +@skip_if_python2 +def test_install_from_wheel_gen_unicode_entrypoint(script): + make_wheel( + "script_wheel_unicode", + "1.0", + console_scripts=["進入點 = 模組:函式"], + ).save_to_dir(script.scratch_path) + + result = script.pip( + "install", + "--no-index", + "--find-links", + script.scratch_path, + "script_wheel_unicode", + ) + if os.name == "nt": + result.did_create(script.bin.joinpath("進入點.exe")) + else: + result.did_create(script.bin.joinpath("進入點")) + + def test_install_from_wheel_with_legacy(script, shared_data, tmpdir): """ Test installing scripts (legacy scripts are preserved) diff --git a/tests/unit/test_wheel.py b/tests/unit/test_wheel.py index 2834b18f087..7335e1ff8aa 100644 --- a/tests/unit/test_wheel.py +++ b/tests/unit/test_wheel.py @@ -2,6 +2,7 @@ """Tests for wheel binary packages and .dist-info.""" import csv +import io import logging import os import textwrap @@ -29,7 +30,7 @@ from pip._internal.utils.compat import WINDOWS from pip._internal.utils.misc import hash_file from pip._internal.utils.unpacking import unpack_file -from tests.lib import DATA_DIR, assert_paths_equal +from tests.lib import DATA_DIR, assert_paths_equal, skip_if_python2 def call_get_legacy_build_wheel_path(caplog, names): @@ -81,12 +82,17 @@ def test_get_legacy_build_wheel_path__multiple_names(caplog): ] -@pytest.mark.parametrize("console_scripts", - ["pip = pip._internal.main:pip", - "pip:pip = pip._internal.main:pip"]) +@pytest.mark.parametrize( + "console_scripts", + [ + "pip = pip._internal.main:pip", + "pip:pip = pip._internal.main:pip", + pytest.param("進入點 = 套件.模組:函式", marks=skip_if_python2), + ], +) def test_get_entrypoints(tmpdir, console_scripts): entry_points = tmpdir.joinpath("entry_points.txt") - with open(str(entry_points), "w") as fp: + with io.open(str(entry_points), "w", encoding="utf-8") as fp: fp.write(""" [console_scripts] {} From 216328ce102d43ba5ac9cfbc12396645db14e15c Mon Sep 17 00:00:00 2001 From: Tzu-ping Chung Date: Thu, 28 May 2020 16:12:48 +0800 Subject: [PATCH 2/4] News --- news/8342.bugfix | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 news/8342.bugfix diff --git a/news/8342.bugfix b/news/8342.bugfix new file mode 100644 index 00000000000..fd6b9b8257b --- /dev/null +++ b/news/8342.bugfix @@ -0,0 +1,2 @@ +Correctly treat non-ASCII entry point declarations in wheels so they can be +installed on Windows. From dc82ac2e0c382dbd17b3b6898faf12a2d4174948 Mon Sep 17 00:00:00 2001 From: Tzu-ping Chung Date: Thu, 28 May 2020 16:13:02 +0800 Subject: [PATCH 3/4] Fix typo in NEWS.rst --- NEWS.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NEWS.rst b/NEWS.rst index 46d322a4216..ac3c8615375 100644 --- a/NEWS.rst +++ b/NEWS.rst @@ -9,7 +9,7 @@ Deprecations and Removals Bug Fixes --------- -- Correctly treat wheels contenting non-ASCII file contents so they can be +- Correctly treat wheels containing non-ASCII file contents so they can be installed on Windows. (`#5712 `_) - Revert building of local directories in place, restoring the pre-20.1 behaviour of copying to a temporary directory. (`#7555 `_) From b0c1308d64878c26948bcde6cfc047b1fcd021c2 Mon Sep 17 00:00:00 2001 From: Tzu-ping Chung Date: Thu, 28 May 2020 16:15:40 +0800 Subject: [PATCH 4/4] Always Unicode --- tests/unit/test_wheel.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/unit/test_wheel.py b/tests/unit/test_wheel.py index 7335e1ff8aa..f734e0ae647 100644 --- a/tests/unit/test_wheel.py +++ b/tests/unit/test_wheel.py @@ -85,15 +85,15 @@ def test_get_legacy_build_wheel_path__multiple_names(caplog): @pytest.mark.parametrize( "console_scripts", [ - "pip = pip._internal.main:pip", - "pip:pip = pip._internal.main:pip", - pytest.param("進入點 = 套件.模組:函式", marks=skip_if_python2), + u"pip = pip._internal.main:pip", + u"pip:pip = pip._internal.main:pip", + pytest.param(u"進入點 = 套件.模組:函式", marks=skip_if_python2), ], ) def test_get_entrypoints(tmpdir, console_scripts): entry_points = tmpdir.joinpath("entry_points.txt") with io.open(str(entry_points), "w", encoding="utf-8") as fp: - fp.write(""" + fp.write(u""" [console_scripts] {} [section]