We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
`.dist-info.virtualenv` marker files break importlib.metadata in app-data seeded venvs
Since virtualenv 20.0.0, dist-info directories in virtual environments seeded using the `app-data` method are accompanied by empty marker files with the same name as the directory plus a `.virtualenv` suffix:

https://github.com/pypa/virtualenv/blob/c8ac632cd40bfb014515151a02d1ea55d47ff467/src/virtualenv/seed/via_app_data/pip_install/base.py#L73-L76

This breaks `importlib.metadata`, which picks these files up and attempts to create `importlib.metadata.Distribution` instances for them. As a result, something like `importlib.metadata.version("pip")` will return `None`.

The following session was run in a virtual environment created by virtualenv 20.0.2 inside a `python:3.8.1` Docker image:

```python
Python 3.8.1 (default, Feb  2 2020, 08:37:37)
[GCC 8.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import importlib.metadata
>>> for d in importlib.metadata.distributions():
...     name = d.metadata["name"]
...     version = d.metadata["version"]
...     path = d._path
...     print(f"{path=} {name=} {version=}")
...
path=PosixPath('/venv/lib/python3.8/site-packages/pip-20.0.2.dist-info.virtualenv') name=None version=None
path=PosixPath('/venv/lib/python3.8/site-packages/wheel-0.34.2.dist-info.virtualenv') name=None version=None
path=PosixPath('/venv/lib/python3.8/site-packages/pip-20.0.2.dist-info') name='pip' version='20.0.2'
path=PosixPath('/venv/lib/python3.8/site-packages/wheel-0.34.2.dist-info') name='wheel' version='0.34.2'
path=PosixPath('/venv/lib/python3.8/site-packages/setuptools-45.2.0.dist-info') name='setuptools' version='45.2.0'
path=PosixPath('/venv/lib/python3.8/site-packages/setuptools-45.2.0.dist-info.virtualenv') name=None version=None
>>> d = importlib.metadata.distribution("pip")
>>> d.metadata["name"]
>>> importlib.metadata.version("pip")
>>>
```

This may in fact be an issue in `importlib.metadata`. I'm reporting it here in the assumption that virtualenv may be able to fix or work around this issue faster, and that it would need to work around the issue anyway for Python 3.8.0 and 3.8.1.

The issue also causes Poetry to break when invoked inside such virtual environments, for example in Nox and tox: https://github.com/python-poetry/poetry/issues/1953. This is currently affecting projects that use Poetry in their CI pipelines.

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `src/virtualenv/seed/via_app_data/pip_install/base.py`
Content:
```
1 from __future__ import absolute_import, unicode_literals
2 
3 import logging
4 import os
5 import re
6 import shutil
7 import zipfile
8 from abc import ABCMeta, abstractmethod
9 from tempfile import mkdtemp
10 
11 import six
12 from six import PY3
13 
14 from virtualenv.util import ConfigParser
15 from virtualenv.util.path import Path
16 
17 
18 @six.add_metaclass(ABCMeta)
19 class PipInstall(object):
20     def __init__(self, wheel, creator, image_folder):
21         self._wheel = wheel
22         self._creator = creator
23         self._image_dir = image_folder
24         self._extracted = False
25         self.__dist_info = None
26         self._console_entry_points = None
27 
28     @abstractmethod
29     def _sync(self, src, dst):
30         raise NotImplementedError
31 
32     def install(self):
33         self._extracted = True
34         # sync image
35         for filename in self._image_dir.iterdir():
36             into = self._creator.purelib / filename.name
37             if into.exists():
38                 if into.is_dir() and not into.is_symlink():
39                     shutil.rmtree(str(into))
40                 else:
41                     into.unlink()
42             self._sync(filename, into)
43         # generate console executables
44         consoles = set()
45         script_dir = self._creator.script_dir
46         for name, module in self._console_scripts.items():
47             consoles.update(self._create_console_entry_point(name, module, script_dir))
48         logging.debug("generated console scripts %s", " ".join(i.name for i in consoles))
49 
50     def build_image(self):
51         # 1. first extract the wheel
52         logging.debug("build install image to %s of %s", self._image_dir, self._wheel.name)
53         with zipfile.ZipFile(str(self._wheel)) as zip_ref:
54             zip_ref.extractall(str(self._image_dir))
55             self._extracted = True
56         # 2. now add additional files not present in the package
57         new_files = self._generate_new_files()
58         # 3. finally fix the records file
59         self._fix_records(new_files)
60 
61     def _records_text(self, files):
62         record_data = "\n".join(
63             "{},,".format(os.path.relpath(six.ensure_text(str(rec)), six.ensure_text(str(self._image_dir))))
64             for rec in files
65         )
66         return record_data
67 
68     def _generate_new_files(self):
69         new_files = set()
70         installer = self._dist_info / "INSTALLER"
71         installer.write_text("pip\n")
72         new_files.add(installer)
73         # inject a no-op root element, as workaround for bug added
74         # by https://github.com/pypa/pip/commit/c7ae06c79#r35523722
75         marker = self._image_dir / "{}.virtualenv".format(self._dist_info.name)
76         marker.write_text("")
77         new_files.add(marker)
78         folder = mkdtemp()
79         try:
80             to_folder = Path(folder)
81             rel = os.path.relpath(
82                 six.ensure_text(str(self._creator.script_dir)), six.ensure_text(str(self._creator.purelib))
83             )
84             for name, module in self._console_scripts.items():
85                 new_files.update(
86                     Path(os.path.normpath(six.ensure_text(str(self._image_dir / rel / i.name))))
87                     for i in self._create_console_entry_point(name, module, to_folder)
88                 )
89         finally:
90             shutil.rmtree(folder, ignore_errors=True)
91         return new_files
92 
93     @property
94     def _dist_info(self):
95         if self._extracted is False:
96             return None  # pragma: no cover
97         if self.__dist_info is None:
98             for filename in self._image_dir.iterdir():
99                 if filename.suffix == ".dist-info":
100                     self.__dist_info = filename
101                     break
102             else:
103                 raise RuntimeError("no dist info")  # pragma: no cover
104         return self.__dist_info
105 
106     @abstractmethod
107     def _fix_records(self, extra_record_data):
108         raise NotImplementedError
109 
110     @property
111     def _console_scripts(self):
112         if self._extracted is False:
113             return None  # pragma: no cover
114         if self._console_entry_points is None:
115             self._console_entry_points = {}
116             entry_points = self._dist_info / "entry_points.txt"
117             if entry_points.exists():
118                 parser = ConfigParser.ConfigParser()
119                 with entry_points.open() as file_handler:
120                     reader = getattr(parser, "read_file" if PY3 else "readfp")
121                     reader(file_handler)
122                 if "console_scripts" in parser.sections():
123                     for name, value in parser.items("console_scripts"):
124                         match = re.match(r"(.*?)-?\d\.?\d*", name)
125                         if match:
126                             name = match.groups(1)[0]
127                         self._console_entry_points[name] = value
128         return self._console_entry_points
129 
130     def _create_console_entry_point(self, name, value, to_folder):
131         result = []
132         from distlib.scripts import ScriptMaker
133 
134         maker = ScriptMaker(None, str(to_folder))
135         maker.clobber = True  # overwrite
136         maker.variants = {"", "X", "X.Y"}  # create all variants
137         maker.set_mode = True  # ensure they are executable
138         maker.executable = str(self._creator.exe)
139         specification = "{} = {}".format(name, value)
140         new_files = maker.make(specification)
141         result.extend(Path(i) for i in new_files)
142         return result
143 
144     def clear(self):
145         if self._image_dir.exists():
146             shutil.rmtree(six.ensure_text(str(self._image_dir)))
147 
148     def has_image(self):
149         return self._image_dir.exists() and next(self._image_dir.iterdir()) is not None
150
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/src/virtualenv/seed/via_app_data/pip_install/base.py b/src/virtualenv/seed/via_app_data/pip_install/base.py
--- a/src/virtualenv/seed/via_app_data/pip_install/base.py
+++ b/src/virtualenv/seed/via_app_data/pip_install/base.py
@@ -70,9 +70,8 @@
         installer = self._dist_info / "INSTALLER"
         installer.write_text("pip\n")
         new_files.add(installer)
-        # inject a no-op root element, as workaround for bug added
-        # by https://github.com/pypa/pip/commit/c7ae06c79#r35523722
-        marker = self._image_dir / "{}.virtualenv".format(self._dist_info.name)
+        # inject a no-op root element, as workaround for bug in https://github.com/pypa/pip/issues/7226
+        marker = self._image_dir / "{}.virtualenv".format(self._dist_info.stem)
         marker.write_text("")
         new_files.add(marker)
         folder = mkdtemp()

{"golden_diff": "diff --git a/src/virtualenv/seed/via_app_data/pip_install/base.py b/src/virtualenv/seed/via_app_data/pip_install/base.py\n--- a/src/virtualenv/seed/via_app_data/pip_install/base.py\n+++ b/src/virtualenv/seed/via_app_data/pip_install/base.py\n@@ -70,9 +70,8 @@\n         installer = self._dist_info / \"INSTALLER\"\n         installer.write_text(\"pip\\n\")\n         new_files.add(installer)\n-        # inject a no-op root element, as workaround for bug added\n-        # by https://github.com/pypa/pip/commit/c7ae06c79#r35523722\n-        marker = self._image_dir / \"{}.virtualenv\".format(self._dist_info.name)\n+        # inject a no-op root element, as workaround for bug in https://github.com/pypa/pip/issues/7226\n+        marker = self._image_dir / \"{}.virtualenv\".format(self._dist_info.stem)\n         marker.write_text(\"\")\n         new_files.add(marker)\n         folder = mkdtemp()\n", "issue": "`.dist-info.virtualenv` marker files break importlib.metadata in app-data seeded venvs\nSince virtualenv 20.0.0, dist-info directories in virtual environments seeded using the `app-data` method are accompanied by empty marker files with the same name as the directory plus a `.virtualenv` suffix:\r\n\r\nhttps://github.com/pypa/virtualenv/blob/c8ac632cd40bfb014515151a02d1ea55d47ff467/src/virtualenv/seed/via_app_data/pip_install/base.py#L73-L76\r\n\r\nThis breaks `importlib.metadata`, which picks these files up and attempts to create `importlib.metadata.Distribution` instances for them. As a result, something like `importlib.metadata.version(\"pip\")` will return `None`.\r\n\r\nThe following session was run in a virtual environment created by virtualenv 20.0.2 inside a `python:3.8.1` Docker image:\r\n\r\n```python\r\nPython 3.8.1 (default, Feb  2 2020, 08:37:37)\r\n[GCC 8.3.0] on linux\r\nType \"help\", \"copyright\", \"credits\" or \"license\" for more information.\r\n>>> import importlib.metadata\r\n>>> for d in importlib.metadata.distributions():\r\n...     name = d.metadata[\"name\"]\r\n...     version = d.metadata[\"version\"]\r\n...     path = d._path\r\n...     print(f\"{path=} {name=} {version=}\")\r\n...\r\npath=PosixPath('/venv/lib/python3.8/site-packages/pip-20.0.2.dist-info.virtualenv') name=None version=None\r\npath=PosixPath('/venv/lib/python3.8/site-packages/wheel-0.34.2.dist-info.virtualenv') name=None version=None\r\npath=PosixPath('/venv/lib/python3.8/site-packages/pip-20.0.2.dist-info') name='pip' version='20.0.2'\r\npath=PosixPath('/venv/lib/python3.8/site-packages/wheel-0.34.2.dist-info') name='wheel' version='0.34.2'\r\npath=PosixPath('/venv/lib/python3.8/site-packages/setuptools-45.2.0.dist-info') name='setuptools' version='45.2.0'\r\npath=PosixPath('/venv/lib/python3.8/site-packages/setuptools-45.2.0.dist-info.virtualenv') name=None version=None\r\n>>> d = importlib.metadata.distribution(\"pip\")\r\n>>> d.metadata[\"name\"]\r\n>>> importlib.metadata.version(\"pip\")\r\n>>>\r\n```\r\n\r\nThis may in fact be an issue in `importlib.metadata`. I'm reporting it here in the assumption that virtualenv may be able to fix or work around this issue faster, and that it would need to work around the issue anyway for Python 3.8.0 and 3.8.1.\r\n\r\nThe issue also causes Poetry to break when invoked inside such virtual environments, for example in Nox and tox: https://github.com/python-poetry/poetry/issues/1953. This is currently affecting projects that use Poetry in their CI pipelines.\n", "before_files": [{"content": "from __future__ import absolute_import, unicode_literals\n\nimport logging\nimport os\nimport re\nimport shutil\nimport zipfile\nfrom abc import ABCMeta, abstractmethod\nfrom tempfile import mkdtemp\n\nimport six\nfrom six import PY3\n\nfrom virtualenv.util import ConfigParser\nfrom virtualenv.util.path import Path\n\n\n@six.add_metaclass(ABCMeta)\nclass PipInstall(object):\n    def __init__(self, wheel, creator, image_folder):\n        self._wheel = wheel\n        self._creator = creator\n        self._image_dir = image_folder\n        self._extracted = False\n        self.__dist_info = None\n        self._console_entry_points = None\n\n    @abstractmethod\n    def _sync(self, src, dst):\n        raise NotImplementedError\n\n    def install(self):\n        self._extracted = True\n        # sync image\n        for filename in self._image_dir.iterdir():\n            into = self._creator.purelib / filename.name\n            if into.exists():\n                if into.is_dir() and not into.is_symlink():\n                    shutil.rmtree(str(into))\n                else:\n                    into.unlink()\n            self._sync(filename, into)\n        # generate console executables\n        consoles = set()\n        script_dir = self._creator.script_dir\n        for name, module in self._console_scripts.items():\n            consoles.update(self._create_console_entry_point(name, module, script_dir))\n        logging.debug(\"generated console scripts %s\", \" \".join(i.name for i in consoles))\n\n    def build_image(self):\n        # 1. first extract the wheel\n        logging.debug(\"build install image to %s of %s\", self._image_dir, self._wheel.name)\n        with zipfile.ZipFile(str(self._wheel)) as zip_ref:\n            zip_ref.extractall(str(self._image_dir))\n            self._extracted = True\n        # 2. now add additional files not present in the package\n        new_files = self._generate_new_files()\n        # 3. finally fix the records file\n        self._fix_records(new_files)\n\n    def _records_text(self, files):\n        record_data = \"\\n\".join(\n            \"{},,\".format(os.path.relpath(six.ensure_text(str(rec)), six.ensure_text(str(self._image_dir))))\n            for rec in files\n        )\n        return record_data\n\n    def _generate_new_files(self):\n        new_files = set()\n        installer = self._dist_info / \"INSTALLER\"\n        installer.write_text(\"pip\\n\")\n        new_files.add(installer)\n        # inject a no-op root element, as workaround for bug added\n        # by https://github.com/pypa/pip/commit/c7ae06c79#r35523722\n        marker = self._image_dir / \"{}.virtualenv\".format(self._dist_info.name)\n        marker.write_text(\"\")\n        new_files.add(marker)\n        folder = mkdtemp()\n        try:\n            to_folder = Path(folder)\n            rel = os.path.relpath(\n                six.ensure_text(str(self._creator.script_dir)), six.ensure_text(str(self._creator.purelib))\n            )\n            for name, module in self._console_scripts.items():\n                new_files.update(\n                    Path(os.path.normpath(six.ensure_text(str(self._image_dir / rel / i.name))))\n                    for i in self._create_console_entry_point(name, module, to_folder)\n                )\n        finally:\n            shutil.rmtree(folder, ignore_errors=True)\n        return new_files\n\n    @property\n    def _dist_info(self):\n        if self._extracted is False:\n            return None  # pragma: no cover\n        if self.__dist_info is None:\n            for filename in self._image_dir.iterdir():\n                if filename.suffix == \".dist-info\":\n                    self.__dist_info = filename\n                    break\n            else:\n                raise RuntimeError(\"no dist info\")  # pragma: no cover\n        return self.__dist_info\n\n    @abstractmethod\n    def _fix_records(self, extra_record_data):\n        raise NotImplementedError\n\n    @property\n    def _console_scripts(self):\n        if self._extracted is False:\n            return None  # pragma: no cover\n        if self._console_entry_points is None:\n            self._console_entry_points = {}\n            entry_points = self._dist_info / \"entry_points.txt\"\n            if entry_points.exists():\n                parser = ConfigParser.ConfigParser()\n                with entry_points.open() as file_handler:\n                    reader = getattr(parser, \"read_file\" if PY3 else \"readfp\")\n                    reader(file_handler)\n                if \"console_scripts\" in parser.sections():\n                    for name, value in parser.items(\"console_scripts\"):\n                        match = re.match(r\"(.*?)-?\\d\\.?\\d*\", name)\n                        if match:\n                            name = match.groups(1)[0]\n                        self._console_entry_points[name] = value\n        return self._console_entry_points\n\n    def _create_console_entry_point(self, name, value, to_folder):\n        result = []\n        from distlib.scripts import ScriptMaker\n\n        maker = ScriptMaker(None, str(to_folder))\n        maker.clobber = True  # overwrite\n        maker.variants = {\"\", \"X\", \"X.Y\"}  # create all variants\n        maker.set_mode = True  # ensure they are executable\n        maker.executable = str(self._creator.exe)\n        specification = \"{} = {}\".format(name, value)\n        new_files = maker.make(specification)\n        result.extend(Path(i) for i in new_files)\n        return result\n\n    def clear(self):\n        if self._image_dir.exists():\n            shutil.rmtree(six.ensure_text(str(self._image_dir)))\n\n    def has_image(self):\n        return self._image_dir.exists() and next(self._image_dir.iterdir()) is not None\n", "path": "src/virtualenv/seed/via_app_data/pip_install/base.py"}], "after_files": [{"content": "from __future__ import absolute_import, unicode_literals\n\nimport logging\nimport os\nimport re\nimport shutil\nimport zipfile\nfrom abc import ABCMeta, abstractmethod\nfrom tempfile import mkdtemp\n\nimport six\nfrom six import PY3\n\nfrom virtualenv.util import ConfigParser\nfrom virtualenv.util.path import Path\n\n\n@six.add_metaclass(ABCMeta)\nclass PipInstall(object):\n    def __init__(self, wheel, creator, image_folder):\n        self._wheel = wheel\n        self._creator = creator\n        self._image_dir = image_folder\n        self._extracted = False\n        self.__dist_info = None\n        self._console_entry_points = None\n\n    @abstractmethod\n    def _sync(self, src, dst):\n        raise NotImplementedError\n\n    def install(self):\n        self._extracted = True\n        # sync image\n        for filename in self._image_dir.iterdir():\n            into = self._creator.purelib / filename.name\n            if into.exists():\n                if into.is_dir() and not into.is_symlink():\n                    shutil.rmtree(str(into))\n                else:\n                    into.unlink()\n            self._sync(filename, into)\n        # generate console executables\n        consoles = set()\n        script_dir = self._creator.script_dir\n        for name, module in self._console_scripts.items():\n            consoles.update(self._create_console_entry_point(name, module, script_dir))\n        logging.debug(\"generated console scripts %s\", \" \".join(i.name for i in consoles))\n\n    def build_image(self):\n        # 1. first extract the wheel\n        logging.debug(\"build install image to %s of %s\", self._image_dir, self._wheel.name)\n        with zipfile.ZipFile(str(self._wheel)) as zip_ref:\n            zip_ref.extractall(str(self._image_dir))\n            self._extracted = True\n        # 2. now add additional files not present in the package\n        new_files = self._generate_new_files()\n        # 3. finally fix the records file\n        self._fix_records(new_files)\n\n    def _records_text(self, files):\n        record_data = \"\\n\".join(\n            \"{},,\".format(os.path.relpath(six.ensure_text(str(rec)), six.ensure_text(str(self._image_dir))))\n            for rec in files\n        )\n        return record_data\n\n    def _generate_new_files(self):\n        new_files = set()\n        installer = self._dist_info / \"INSTALLER\"\n        installer.write_text(\"pip\\n\")\n        new_files.add(installer)\n        # inject a no-op root element, as workaround for bug in https://github.com/pypa/pip/issues/7226\n        marker = self._image_dir / \"{}.virtualenv\".format(self._dist_info.stem)\n        marker.write_text(\"\")\n        new_files.add(marker)\n        folder = mkdtemp()\n        try:\n            to_folder = Path(folder)\n            rel = os.path.relpath(\n                six.ensure_text(str(self._creator.script_dir)), six.ensure_text(str(self._creator.purelib))\n            )\n            for name, module in self._console_scripts.items():\n                new_files.update(\n                    Path(os.path.normpath(six.ensure_text(str(self._image_dir / rel / i.name))))\n                    for i in self._create_console_entry_point(name, module, to_folder)\n                )\n        finally:\n            shutil.rmtree(folder, ignore_errors=True)\n        return new_files\n\n    @property\n    def _dist_info(self):\n        if self._extracted is False:\n            return None  # pragma: no cover\n        if self.__dist_info is None:\n            for filename in self._image_dir.iterdir():\n                if filename.suffix == \".dist-info\":\n                    self.__dist_info = filename\n                    break\n            else:\n                raise RuntimeError(\"no dist info\")  # pragma: no cover\n        return self.__dist_info\n\n    @abstractmethod\n    def _fix_records(self, extra_record_data):\n        raise NotImplementedError\n\n    @property\n    def _console_scripts(self):\n        if self._extracted is False:\n            return None  # pragma: no cover\n        if self._console_entry_points is None:\n            self._console_entry_points = {}\n            entry_points = self._dist_info / \"entry_points.txt\"\n            if entry_points.exists():\n                parser = ConfigParser.ConfigParser()\n                with entry_points.open() as file_handler:\n                    reader = getattr(parser, \"read_file\" if PY3 else \"readfp\")\n                    reader(file_handler)\n                if \"console_scripts\" in parser.sections():\n                    for name, value in parser.items(\"console_scripts\"):\n                        match = re.match(r\"(.*?)-?\\d\\.?\\d*\", name)\n                        if match:\n                            name = match.groups(1)[0]\n                        self._console_entry_points[name] = value\n        return self._console_entry_points\n\n    def _create_console_entry_point(self, name, value, to_folder):\n        result = []\n        from distlib.scripts import ScriptMaker\n\n        maker = ScriptMaker(None, str(to_folder))\n        maker.clobber = True  # overwrite\n        maker.variants = {\"\", \"X\", \"X.Y\"}  # create all variants\n        maker.set_mode = True  # ensure they are executable\n        maker.executable = str(self._creator.exe)\n        specification = \"{} = {}\".format(name, value)\n        new_files = maker.make(specification)\n        result.extend(Path(i) for i in new_files)\n        return result\n\n    def clear(self):\n        if self._image_dir.exists():\n            shutil.rmtree(six.ensure_text(str(self._image_dir)))\n\n    def has_image(self):\n        return self._image_dir.exists() and next(self._image_dir.iterdir()) is not None\n", "path": "src/virtualenv/seed/via_app_data/pip_install/base.py"}]}