Repository: pypa/pip

Issue:
 UnicodeDecodeError when using utf-8 in entry_points
**Environment**

* pip version: 20.1.1
* Python version: 3.7.6
* OS: win7 pro

<!-- Feel free to add more information about your environment here -->

**Description**
I've a demo package having utf-8 in entry_points:
https://github.com/program-in-chinese/study/blob/e08bc8d9cb7e4c6c5f493ef9d43a331fa8f6f766/1-%E5%9F%BA%E7%A1%80/%E5%B0%8F%E6%B8%B8%E6%88%8F/setup.py#L12

It published successfully and installed fine in Mac, but got error when installed in Windows.

Not sure if related to https://github.com/pypa/pip/issues/2042

**Expected behavior**
Installed successfully without error, just like in Mac.

**How to Reproduce**
<!-- Describe the steps to reproduce this bug. -->

1. Get package from https://pypi.org/project/demo-game-guess-number/
2. An error occurs.

**Output**

```
>pip install demo-game-guess-number
Collecting demo-game-guess-number
  Using cached demo_game_guess_number-0.0.5-py3-none-any.whl (2.5 kB)
Installing collected packages: demo-game-guess-number
ERROR: Exception:
Traceback (most recent call last):
  File "c:\users\win7_laptop\appdata\local\programs\python\python37\lib\site-pac
kages\pip\_internal\cli\base_command.py", line 188, in _main
    status = self.run(options, args)
  File "c:\users\win7_laptop\appdata\local\programs\python\python37\lib\site-pac
kages\pip\_internal\cli\req_command.py", line 185, in wrapper
    return func(self, options, args)
  File "c:\users\win7_laptop\appdata\local\programs\python\python37\lib\site-pac
kages\pip\_internal\commands\install.py", line 407, in run
    use_user_site=options.use_user_site,
  File "c:\users\win7_laptop\appdata\local\programs\python\python37\lib\site-pac
kages\pip\_internal\req\__init__.py", line 71, in install_given_reqs
    **kwargs
  File "c:\users\win7_laptop\appdata\local\programs\python\python37\lib\site-pac
kages\pip\_internal\req\req_install.py", line 811, in install
    direct_url=direct_url,
  File "c:\users\win7_laptop\appdata\local\programs\python\python37\lib\site-pac
kages\pip\_internal\operations\install\wheel.py", line 630, in install_wheel
    direct_url=direct_url,
  File "c:\users\win7_laptop\appdata\local\programs\python\python37\lib\site-pac
kages\pip\_internal\operations\install\wheel.py", line 426, in install_unpacked_
wheel
    console, gui = get_entrypoints(ep_file)
  File "c:\users\win7_laptop\appdata\local\programs\python\python37\lib\site-pac
kages\pip\_internal\operations\install\wheel.py", line 119, in get_entrypoints
    for line in fp:
UnicodeDecodeError: 'gbk' codec can't decode byte 0x97 in position 26: illegal m
ultibyte sequence
```



Discussion:

Resolving patch:
diff --git a/src/pip/_internal/operations/install/wheel.py b/src/pip/_internal/operations/install/wheel.py
--- 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


Test patch:
diff --git a/tests/functional/test_install_wheel.py b/tests/functional/test_install_wheel.py
--- 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
--- 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,13 +82,18 @@ 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",
+    [
+        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 open(str(entry_points), "w") as fp:
-        fp.write("""
+    with io.open(str(entry_points), "w", encoding="utf-8") as fp:
+        fp.write(u"""
             [console_scripts]
             {}
             [section]