Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 20 additions & 18 deletions asv/benchmark.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,25 @@ def _help(args):
}


def _apply_path_env():
"""Apply ``ASV_PYTHONPATH`` to ``sys.path`` for this process.

Managed environments already drop bare ``PYTHONPATH`` in
``Environment.run_executable`` (isolation). ``ExistingEnvironment`` /
``--python=same`` inherits the host environment, including
``PYTHONPATH``, which in-tree builds rely on for a build directory.
Do not strip host ``PYTHONPATH`` here; that broke discovery under
``--python=same`` (gh-1537) and also undid ``ASV_PYTHONPATH`` after
``run_executable`` rewrote it to ``PYTHONPATH``.
"""
asv_pythonpath = os.environ.get('ASV_PYTHONPATH')
if not asv_pythonpath:
return
for path in reversed(asv_pythonpath.split(os.pathsep)):
if path and path not in sys.path:
sys.path.insert(0, path)


def main():
# Remove asv package directory from `sys.path`. This script file resides
# there although it's not part of the package, so Python prepends it to
Expand All @@ -68,24 +87,7 @@ def main():
mode = sys.argv[1]
args = sys.argv[2:]

env = os.environ.copy()
# --- Modify sys.path for the current interpreter ---
if 'ASV_PYTHONPATH' in env:
new_paths = env['ASV_PYTHONPATH'].split(os.pathsep)
for path in reversed(new_paths): # Add to the front to prioritize
if path not in sys.path:
sys.path.insert(0, path)
# Remove ASV_PYTHONPATH from env, as it's no longer needed after sys.path update
env.pop('ASV_PYTHONPATH')
else:
# Clean up sys.path if PYTHONPATH was set but ASV_PYTHONPATH is not
if 'PYTHONPATH' in env:
old_paths = env['PYTHONPATH'].split(os.pathsep)
for path in old_paths:
if path in sys.path:
sys.path.remove(path)

env.pop('PYTHONPATH')
_apply_path_env()

if mode in commands:
commands[mode](args)
Expand Down
3 changes: 3 additions & 0 deletions changelog.d/1537.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Preserve host ``PYTHONPATH`` under ``--python=same`` / discovery
(do not strip it when ``ASV_PYTHONPATH`` is unset). Managed environments
still drop bare ``PYTHONPATH`` for isolation.
4 changes: 3 additions & 1 deletion docs/source/env_vars.rst
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ behavior are also set:
- ``PATH``: environment-specific binary directories prepended
- ``PIP_USER``: ``false``
- ``PYTHONNOUSERSITE``: ``True`` (for conda environments only)
- ``PYTHONPATH``: unset (if really needed, can be overridden by setting ``ASV_PYTHONPATH``)
- ``PYTHONPATH``: unset for asv-managed environments (if really needed,
set ``ASV_PYTHONPATH`` instead). With ``--python=same`` / an existing
environment, the host ``PYTHONPATH`` is retained.

.. note::

Expand Down
63 changes: 63 additions & 0 deletions test/test_benchmark_path_env.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# Licensed under a 3-clause BSD style license - see LICENSE.rst
"""Path env handling in asv.benchmark (PYTHONPATH / ASV_PYTHONPATH)."""

import importlib
import sys

import pytest

from asv.benchmark import _apply_path_env


@pytest.fixture
def path_marker(tmp_path, monkeypatch):
"""A unique module importable only via an extra sys.path entry."""
marker = f"asv1537_marker_{tmp_path.name.replace('-', '_')}"
root = tmp_path / "pyroot"
root.mkdir()
(root / f"{marker}.py").write_text("value = 42\n", encoding="utf-8")
yield str(root), marker
sys.modules.pop(marker, None)
while str(root) in sys.path:
sys.path.remove(str(root))


def test_pythonpath_kept_when_asv_pythonpath_unset(path_marker, monkeypatch):
# gh-1537: --python=same + host PYTHONPATH (e.g. scipy build dir)
root, marker = path_marker
monkeypatch.setenv("PYTHONPATH", root)
monkeypatch.delenv("ASV_PYTHONPATH", raising=False)
# Interpreter startup puts PYTHONPATH on sys.path; simulate that.
if root not in sys.path:
sys.path.insert(0, root)

_apply_path_env()

mod = importlib.import_module(marker)
assert mod.value == 42


def test_asv_pythonpath_applied(path_marker, monkeypatch):
root, marker = path_marker
monkeypatch.delenv("PYTHONPATH", raising=False)
monkeypatch.setenv("ASV_PYTHONPATH", root)
assert root not in sys.path

_apply_path_env()

assert root in sys.path
mod = importlib.import_module(marker)
assert mod.value == 42


def test_asv_pythonpath_prepended_before_existing(path_marker, monkeypatch, tmp_path):
root, marker = path_marker
other = tmp_path / "other"
other.mkdir()
monkeypatch.setenv("ASV_PYTHONPATH", root)
monkeypatch.delenv("PYTHONPATH", raising=False)
sys.path.insert(0, str(other))

_apply_path_env()

assert sys.path.index(root) < sys.path.index(str(other))
Loading