1010import os
1111import os .path
1212import platform
13- import site
1413import sys
1514import textwrap
15+ import zipfile
1616
17+ from pathlib import Path
1718from typing import Any
1819
1920from setuptools import Extension , errors , setup
20- from setuptools .command .build_ext import build_ext # pylint: disable=wrong-import-order
21+ from setuptools .command .build_ext import build_ext
22+ from setuptools .command .editable_wheel import editable_wheel
2123
2224
2325def get_version_data () -> dict [str , Any ]:
@@ -89,6 +91,14 @@ def get_classifiers(version_info: tuple[int, int, int, str, int]) -> list[str]:
8991 return classifier_list
9092
9193
94+ # The names of .pth files matter because they are read in lexicographic order.
95+ # Editable installs work because of `__editable__*.pth` files, so we need our
96+ # .pth files to come after those. But we want ours to be earlyish in the
97+ # sequence, so we start with `a`. The metacov .pth file should come before the
98+ # coverage .pth file, so we use `a0_metacov.pth` and `a1_coverage.pth`.
99+ PTH_NAME = "a1_coverage.pth"
100+
101+
92102def make_pth_file () -> None :
93103 """Make the packaged .pth file used for measuring subprocess coverage."""
94104
@@ -100,10 +110,20 @@ def make_pth_file() -> None:
100110
101111 # `import sys` is needed because .pth files are executed only if they start
102112 # with `import `.
103- with open ("zzz_coverage.pth" , "w" , encoding = "utf-8" ) as pth_file :
113+ with open (PTH_NAME , "w" , encoding = "utf-8" ) as pth_file :
104114 pth_file .write (f"import sys; exec({ code !r} )\n " )
105115
106116
117+ class EditableWheelWithPth (editable_wheel ): # type: ignore[misc]
118+ """Override the editable_wheel command to insert our .pth file into the wheel."""
119+
120+ def run (self ) -> None :
121+ super ().run ()
122+ for whl in Path (self .dist_dir ).glob ("*editable*.whl" ):
123+ with zipfile .ZipFile (whl , "a" ) as zf :
124+ zf .write (PTH_NAME , PTH_NAME )
125+
126+
107127# A replacement for the build_ext command which raises a single exception
108128# if the build fails, so we can fallback nicely.
109129
@@ -158,6 +178,7 @@ def build_extension(self, ext: Any) -> None:
158178version_data = get_version_data ()
159179make_pth_file ()
160180
181+
161182# Create the keyword arguments for setup()
162183
163184setup_args = dict (
@@ -170,17 +191,9 @@ def build_extension(self, ext: Any) -> None:
170191 "coverage" : [
171192 "htmlfiles/*.*" ,
172193 "py.typed" ,
194+ f"../{ PTH_NAME } " ,
173195 ],
174196 },
175- data_files = [
176- # Write the .pth file into all site-packages directories. Different
177- # platforms read different directories for .pth files, so put it
178- # everywhere. The process_startup function called in the .pth file
179- # does nothing the second time it's called, so it's fine to have
180- # multiple .pth files.
181- (os .path .relpath (sp , sys .prefix ), ["zzz_coverage.pth" ])
182- for sp in site .getsitepackages ()
183- ],
184197 entry_points = {
185198 "console_scripts" : [
186199 # Install a script as "coverage".
@@ -196,6 +209,7 @@ def build_extension(self, ext: Any) -> None:
196209 },
197210 cmdclass = {
198211 "build_ext" : ve_build_ext ,
212+ "editable_wheel" : EditableWheelWithPth ,
199213 },
200214 # We need to get HTML assets from our htmlfiles directory.
201215 zip_safe = False ,
0 commit comments