Skip to content

Commit 244738a

Browse files
authored
Fix site-packages search order for venv with --system-site-packages (fixes #1716) (#1717)
In a venv created with --system-site-packages, user-site-packages were being searched before the virtual environment's site-packages, which violates PEP 405. The fix ensures that: - In a venv: venv site-packages come first, then user-site-packages, then system site-packages (PEP 405) - Outside a venv: user-site-packages come before system site-packages (PEP 370) Added tests to verify the correct ordering in both scenarios. Made-with: Cursor
1 parent 6def020 commit 244738a

File tree

2 files changed

+177
-1
lines changed

2 files changed

+177
-1
lines changed

cuda_pathfinder/cuda/pathfinder/_utils/find_sub_dirs.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,5 +54,12 @@ def find_sub_dirs_all_sitepackages(sub_dirs: Sequence[str]) -> list[str]:
5454
if site.ENABLE_USER_SITE:
5555
user_site = site.getusersitepackages()
5656
if user_site:
57-
parent_dirs.insert(0, user_site)
57+
# Determine insertion index based on whether we're in a virtual environment (fixes #1716):
58+
# - In venv (PEP 405): venv site-packages should come first, then user-site-packages,
59+
# then system site-packages. Insert at index 1 (after venv, before system).
60+
# - Not in venv (PEP 370): user-site-packages should come before system site-packages.
61+
# Insert at index 0.
62+
# Detect venv by checking if sys.prefix differs from sys.base_prefix
63+
insert_idx = 1 if sys.prefix != sys.base_prefix else 0
64+
parent_dirs.insert(insert_idx, user_site)
5865
return find_sub_dirs(parent_dirs, sub_dirs)

cuda_pathfinder/tests/test_utils_find_sub_dirs.py

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,3 +89,172 @@ def test_find_sub_dirs_sys_path_no_math():
8989
def test_find_sub_dirs_all_sitepackages_no_match():
9090
result = find_sub_dirs_all_sitepackages((NONEXISTENT,))
9191
assert result == []
92+
93+
94+
def test_find_sub_dirs_all_sitepackages_venv_order(mocker, tmp_path):
95+
"""Test that in a venv with --system-site-packages, search order is: venv, user, system.
96+
97+
This test verifies fix for issue #1716: user-site-packages should come after
98+
venv site-packages but before system site-packages.
99+
"""
100+
# Create test directories
101+
venv_site = tmp_path / "venv" / "lib" / "python3.12" / "site-packages"
102+
user_site = tmp_path / "user" / ".local" / "lib" / "python3.12" / "site-packages"
103+
system_site = tmp_path / "system" / "lib" / "python3.12" / "dist-packages"
104+
105+
venv_site.mkdir(parents=True)
106+
user_site.mkdir(parents=True)
107+
system_site.mkdir(parents=True)
108+
109+
# Create a test subdirectory in each
110+
test_subdir = ("nvidia", "cuda_runtime", "lib")
111+
(venv_site / "nvidia" / "cuda_runtime" / "lib").mkdir(parents=True)
112+
(user_site / "nvidia" / "cuda_runtime" / "lib").mkdir(parents=True)
113+
(system_site / "nvidia" / "cuda_runtime" / "lib").mkdir(parents=True)
114+
115+
# Mock site.getsitepackages() to return venv first, then system
116+
mocker.patch(
117+
"cuda.pathfinder._utils.find_sub_dirs.site.getsitepackages",
118+
return_value=[str(venv_site), str(system_site)],
119+
)
120+
# Mock user site-packages
121+
mocker.patch(
122+
"cuda.pathfinder._utils.find_sub_dirs.site.getusersitepackages",
123+
return_value=str(user_site),
124+
)
125+
mocker.patch(
126+
"cuda.pathfinder._utils.find_sub_dirs.site.ENABLE_USER_SITE",
127+
True,
128+
)
129+
# Mock sys.prefix != sys.base_prefix to simulate venv
130+
mocker.patch(
131+
"cuda.pathfinder._utils.find_sub_dirs.sys.prefix",
132+
str(tmp_path / "venv"),
133+
)
134+
mocker.patch(
135+
"cuda.pathfinder._utils.find_sub_dirs.sys.base_prefix",
136+
str(tmp_path / "system"),
137+
)
138+
139+
# Clear cache to ensure mocks take effect
140+
from cuda.pathfinder._utils.find_sub_dirs import find_sub_dirs_cached
141+
142+
find_sub_dirs_cached.cache_clear()
143+
144+
result = find_sub_dirs_all_sitepackages(test_subdir)
145+
146+
# Verify order: venv should come first, then user, then system
147+
assert len(result) == 3
148+
assert result[0] == str(venv_site / "nvidia" / "cuda_runtime" / "lib")
149+
assert result[1] == str(user_site / "nvidia" / "cuda_runtime" / "lib")
150+
assert result[2] == str(system_site / "nvidia" / "cuda_runtime" / "lib")
151+
152+
153+
def test_find_sub_dirs_all_sitepackages_non_venv_order(mocker, tmp_path):
154+
"""Test that outside a venv, search order is: user, system.
155+
156+
This verifies PEP 370 behavior: user-site-packages should come before
157+
system site-packages when not in a venv.
158+
"""
159+
# Create test directories
160+
user_site = tmp_path / "user" / ".local" / "lib" / "python3.12" / "site-packages"
161+
system_site = tmp_path / "system" / "lib" / "python3.12" / "dist-packages"
162+
163+
user_site.mkdir(parents=True)
164+
system_site.mkdir(parents=True)
165+
166+
# Create a test subdirectory in each
167+
test_subdir = ("nvidia", "cuda_runtime", "lib")
168+
(user_site / "nvidia" / "cuda_runtime" / "lib").mkdir(parents=True)
169+
(system_site / "nvidia" / "cuda_runtime" / "lib").mkdir(parents=True)
170+
171+
# Mock site.getsitepackages() to return only system site-packages
172+
mocker.patch(
173+
"cuda.pathfinder._utils.find_sub_dirs.site.getsitepackages",
174+
return_value=[str(system_site)],
175+
)
176+
# Mock user site-packages
177+
mocker.patch(
178+
"cuda.pathfinder._utils.find_sub_dirs.site.getusersitepackages",
179+
return_value=str(user_site),
180+
)
181+
mocker.patch(
182+
"cuda.pathfinder._utils.find_sub_dirs.site.ENABLE_USER_SITE",
183+
True,
184+
)
185+
# Mock sys.prefix == sys.base_prefix to simulate non-venv
186+
mocker.patch(
187+
"cuda.pathfinder._utils.find_sub_dirs.sys.prefix",
188+
str(tmp_path / "system"),
189+
)
190+
mocker.patch(
191+
"cuda.pathfinder._utils.find_sub_dirs.sys.base_prefix",
192+
str(tmp_path / "system"),
193+
)
194+
195+
# Clear cache to ensure mocks take effect
196+
from cuda.pathfinder._utils.find_sub_dirs import find_sub_dirs_cached
197+
198+
find_sub_dirs_cached.cache_clear()
199+
200+
result = find_sub_dirs_all_sitepackages(test_subdir)
201+
202+
# Verify order: user should come first, then system
203+
assert len(result) == 2
204+
assert result[0] == str(user_site / "nvidia" / "cuda_runtime" / "lib")
205+
assert result[1] == str(system_site / "nvidia" / "cuda_runtime" / "lib")
206+
207+
208+
def test_find_sub_dirs_all_sitepackages_venv_first_match(mocker, tmp_path):
209+
"""Test that in a venv, venv site-packages is searched first (fixes #1716).
210+
211+
This test ensures that when a file exists in both venv and user site-packages,
212+
the venv version is found first, matching Python's import behavior.
213+
"""
214+
# Create test directories
215+
venv_site = tmp_path / "venv" / "lib" / "python3.12" / "site-packages"
216+
user_site = tmp_path / "user" / ".local" / "lib" / "python3.12" / "site-packages"
217+
218+
venv_site.mkdir(parents=True)
219+
user_site.mkdir(parents=True)
220+
221+
# Create the same subdirectory in both
222+
test_subdir = ("nvidia", "cuda_runtime", "lib")
223+
(venv_site / "nvidia" / "cuda_runtime" / "lib").mkdir(parents=True)
224+
(user_site / "nvidia" / "cuda_runtime" / "lib").mkdir(parents=True)
225+
226+
# Mock site.getsitepackages() to return venv first
227+
mocker.patch(
228+
"cuda.pathfinder._utils.find_sub_dirs.site.getsitepackages",
229+
return_value=[str(venv_site)],
230+
)
231+
# Mock user site-packages
232+
mocker.patch(
233+
"cuda.pathfinder._utils.find_sub_dirs.site.getusersitepackages",
234+
return_value=str(user_site),
235+
)
236+
mocker.patch(
237+
"cuda.pathfinder._utils.find_sub_dirs.site.ENABLE_USER_SITE",
238+
True,
239+
)
240+
# Mock sys.prefix != sys.base_prefix to simulate venv
241+
mocker.patch(
242+
"cuda.pathfinder._utils.find_sub_dirs.sys.prefix",
243+
str(tmp_path / "venv"),
244+
)
245+
mocker.patch(
246+
"cuda.pathfinder._utils.find_sub_dirs.sys.base_prefix",
247+
str(tmp_path / "system"),
248+
)
249+
250+
# Clear cache to ensure mocks take effect
251+
from cuda.pathfinder._utils.find_sub_dirs import find_sub_dirs_cached
252+
253+
find_sub_dirs_cached.cache_clear()
254+
255+
result = find_sub_dirs_all_sitepackages(test_subdir)
256+
257+
# Verify venv comes first (this would fail with old code that puts user first)
258+
assert len(result) >= 2
259+
assert result[0] == str(venv_site / "nvidia" / "cuda_runtime" / "lib")
260+
assert result[1] == str(user_site / "nvidia" / "cuda_runtime" / "lib")

0 commit comments

Comments
 (0)