Skip to content

Commit 39b3707

Browse files
authored
Pickup bootstrap site-packages location from sysconfig purelib path (#1211)
Hi, could you please review patch to pickup the boostrap site-packages location from the `sysconfig` `purelib` path. It fixes #1208. The bootstrap options and code now operate on a single path instead of a list, likely because `getsitepackages` previously returned a list of paths, which is no longer relevant (unless there's another reason I'm missing). I've also updated the `bootstrap` command to print the file's creation path, which I find useful for awareness. For testing, I couldn't think of a case other than creating a virtual environment to test the `bootstrap` within it. I implemented a minimal solution using the `pytest_virtualenv` plugin, which provides a fixture for this. Since creating a Basilisp environment takes 20+ seconds, I added a pytest decorator to run it only on GitHub CI. Let me know if you have other testing suggestions or if it's better to leave this out altogether. I consider these type of tests as part of integration testing (which ideally run as a separate command/job), but we don't have that setup (yet?). Thanks --------- Co-authored-by: ikappaki <[email protected]>
1 parent 21c2642 commit 39b3707

File tree

6 files changed

+92
-34
lines changed

6 files changed

+92
-34
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
88
### Fixed
99
* Fix a regression introduced in #1176 where the testrunner couldn't handle relative paths in `sys.path`, causing `basilisp test` to fail when no arugments were provided (#1204)
1010
* Fix a bug where `basilisp.process/exec` could deadlock reading process output if that output exceeded the buffer size (#1202)
11+
* Fix `basilisp boostrap` issue on MS-Windows where the boostrap file loaded too early, before Basilisp was in `sys.path` (#1208)
1112

1213
## [v0.3.6]
1314
### Added

src/basilisp/cli.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -425,11 +425,12 @@ def bootstrap_basilisp_installation(_, args: argparse.Namespace) -> None:
425425
):
426426
print_("No Basilisp bootstrap files were found.")
427427
else:
428-
for file in removed:
429-
print_(f"Removed '{file}'")
428+
if removed is not None:
429+
print_(f"Removed '{removed}'")
430430
else:
431-
basilisp.bootstrap_python(site_packages=args.site_packages)
431+
path = basilisp.bootstrap_python(site_packages=args.site_packages)
432432
print_(
433+
f"(Added {path})\n\n"
433434
"Your Python installation has been bootstrapped! You can undo this at any "
434435
"time with with `basilisp bootstrap --uninstall`."
435436
)
@@ -473,7 +474,6 @@ def _add_bootstrap_subcommand(parser: argparse.ArgumentParser) -> None:
473474
# Not intended to be used by end users.
474475
parser.add_argument(
475476
"--site-packages",
476-
action="append",
477477
help=argparse.SUPPRESS,
478478
)
479479

src/basilisp/main.py

Lines changed: 30 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import importlib
2-
import site
2+
import os
3+
import sysconfig
34
from pathlib import Path
45
from typing import Optional
56

@@ -56,42 +57,41 @@ def bootstrap(
5657
getattr(mod, fn_name)()
5758

5859

59-
def bootstrap_python(site_packages: Optional[list[str]] = None) -> None:
60-
"""Bootstrap a Python installation by installing a ``.pth`` file in the first
61-
available ``site-packages`` directory (as by
62-
:external:py:func:`site.getsitepackages`).
60+
def bootstrap_python(site_packages: Optional[str] = None) -> str:
61+
"""Bootstrap a Python installation by installing a ``.pth`` file
62+
in ``site-packages`` directory (corresponding to "purelib" in
63+
:external:py:func:`sysconfig.get_paths`). Returns the path to the
64+
``.pth`` file.
6365
6466
Subsequent startups of the Python interpreter will have Basilisp already
6567
bootstrapped and available to run."""
6668
if site_packages is None: # pragma: no cover
67-
site_packages = site.getsitepackages()
69+
site_packages = sysconfig.get_paths()["purelib"]
6870

69-
assert site_packages, "Expected at least one site-package directory"
71+
assert site_packages, "Expected a site-package directory"
7072

71-
for d in site_packages:
72-
p = Path(d)
73-
with open(p / "basilispbootstrap.pth", mode="w") as f:
74-
f.write("import basilisp.sitecustomize")
75-
break
73+
pth = Path(site_packages) / "basilispbootstrap.pth"
74+
with open(pth, mode="w") as f:
75+
f.write("import basilisp.sitecustomize")
7676

77+
return str(pth)
7778

78-
def unbootstrap_python(site_packages: Optional[list[str]] = None) -> list[str]:
79-
"""Remove any `basilispbootstrap.pth` files found in any Python site-packages
80-
directory (as by :external:py:func:`site.getsitepackages`). Return a list of
81-
removed filenames."""
79+
80+
def unbootstrap_python(site_packages: Optional[str] = None) -> Optional[str]:
81+
"""Remove the `basilispbootstrap.pth` file found in the Python site-packages
82+
directory (corresponding to "purelib" in :external:py:func:`sysconfig.get_paths`).
83+
Return the path of the removed file, if any."""
8284
if site_packages is None: # pragma: no cover
83-
site_packages = site.getsitepackages()
84-
85-
assert site_packages, "Expected at least one site-package directory"
86-
87-
removed = []
88-
for d in site_packages:
89-
p = Path(d)
90-
for file in p.glob("basilispbootstrap.pth"):
91-
try:
92-
file.unlink()
93-
except FileNotFoundError: # pragma: no cover
94-
pass
95-
else:
96-
removed.append(str(file))
85+
site_packages = sysconfig.get_paths()["purelib"]
86+
87+
assert site_packages, "Expected a site-package directory"
88+
89+
removed = None
90+
pth = Path(site_packages) / "basilispbootstrap.pth"
91+
try:
92+
os.unlink(pth)
93+
except FileNotFoundError: # pragma: no cover
94+
pass
95+
else:
96+
removed = str(pth)
9797
return removed

tests/basilisp/cli_test.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import sys
1111
import tempfile
1212
import time
13+
import venv
1314
from collections.abc import Sequence
1415
from threading import Thread
1516
from typing import Optional
@@ -108,6 +109,7 @@ def test_install(self, tmp_path: pathlib.Path, run_cli):
108109
assert bootstrap_file.read_text() == "import basilisp.sitecustomize"
109110

110111
assert res.out == (
112+
f"(Added {bootstrap_file})\n\n"
111113
"Your Python installation has been bootstrapped! You can undo this at any "
112114
"time with with `basilisp bootstrap --uninstall`.\n"
113115
)
@@ -135,6 +137,41 @@ def test_install_quiet(self, tmp_path: pathlib.Path, run_cli, capsys):
135137
res = capsys.readouterr()
136138
assert res.out == ""
137139

140+
@pytest.mark.slow
141+
def test_install_import(self, tmp_path: pathlib.Path):
142+
venv_path = tmp_path / "venv"
143+
venv.create(venv_path, with_pip=True)
144+
145+
venv_bin = venv_path / ("Scripts" if sys.platform == "win32" else "bin")
146+
pip_path = venv_bin / "pip"
147+
python_path = venv_bin / "python"
148+
basilisp_path = venv_bin / "basilisp"
149+
150+
result = subprocess.run(
151+
[pip_path, "install", "."], capture_output=True, text=True, cwd=os.getcwd()
152+
)
153+
154+
lpy_file = tmp_path / "boottest.lpy"
155+
lpy_file.write_text("(ns boottest) (defn abc [] (println (+ 155 4)))")
156+
157+
cmd_import = [python_path, "-c", "import boottest; boottest.abc()"]
158+
result = subprocess.run(
159+
cmd_import, capture_output=True, text=True, cwd=tmp_path
160+
)
161+
assert "No module named 'boottest'" in result.stderr, result
162+
163+
result = subprocess.run(
164+
[basilisp_path, "bootstrap"], capture_output=True, text=True, cwd=tmp_path
165+
)
166+
assert (
167+
"Your Python installation has been bootstrapped!" in result.stdout
168+
), result
169+
170+
result = subprocess.run(
171+
cmd_import, capture_output=True, text=True, cwd=tmp_path
172+
)
173+
assert result.stdout.strip() == "159", result
174+
138175
def test_nothing_to_uninstall(self, tmp_path: pathlib.Path, run_cli, capsys):
139176
bootstrap_file = tmp_path / "basilispbootstrap.pth"
140177
assert not bootstrap_file.exists()

tests/conftest.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,19 @@
1+
import pytest
2+
13
pytest_plugins = ["pytester"]
4+
5+
6+
def pytest_configure(config):
7+
config.addinivalue_line("markers", "slow: Marks tests as slow")
8+
9+
10+
def pytest_addoption(parser):
11+
parser.addoption("--run-slow", action="store_true", help="Run slow tests")
12+
13+
14+
@pytest.fixture(autouse=True)
15+
def skip_slow(request):
16+
if request.node.get_closest_marker("slow") and not request.config.getoption(
17+
"--run-slow"
18+
):
19+
pytest.skip("Skipping slow test. Use --run-slow to enable.")

tox.ini

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ commands =
2020
-m pytest \
2121
--import-mode=importlib \
2222
--junitxml={toxinidir}/junit/pytest/{envname}.xml \
23+
# also enable pytest marked as slow \
24+
--run-slow \
2325
{posargs}
2426

2527
[testenv:coverage]

0 commit comments

Comments
 (0)