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
6 changes: 6 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,16 @@ dependencies = [
dev = [
"coverage[toml]>=7.6,<8.0",
"mypy>=1.10,<2.0",
# pip and uv must stay declared here: ./scripts/uv requires .venv/bin/uv and
# ./scripts/bootstrap.sh requires .venv/bin/pip, but `uv sync` makes the venv
# exactly match the lockfile (and recreates it on interpreter upgrades) —
# undeclared, both binaries vanish and the bootstrap chain breaks.
"pip>=24.0,<26.0",
"pre-commit>=4.0,<5.0",
"pytest>=8.3,<9.0",
"pytest-asyncio>=0.24,<1.0",
"ruff>=0.6,<1.0",
"uv>=0.4,<1.0",
]

[project.scripts]
Expand Down
5 changes: 5 additions & 0 deletions scripts/bootstrap.sh
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,11 @@ PYTHON_BIN="$(find_python_312)"

if [[ ! -d "${VENV_DIR}" ]]; then
"${PYTHON_BIN}" -m venv "${VENV_DIR}"
elif [[ ! -x "${PIP_BIN}" && ! -x "${UV_BIN}" ]]; then
# A uv-recreated venv (e.g. after a Homebrew Python upgrade) contains
# neither pip nor uv; rebuild it so the pip -> uv -> sync chain below works.
echo "Existing .venv has neither pip nor uv; rebuilding it." >&2
"${PYTHON_BIN}" -m venv --clear "${VENV_DIR}"
fi

if [[ ! -x "${UV_BIN}" ]]; then
Expand Down
26 changes: 26 additions & 0 deletions tests/test_dev_env_contract.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
"""Contract: the bootstrap chain depends on uv and pip living inside .venv.

``uv sync`` makes the environment exactly match the lockfile, and uv
recreates the venv outright when the base interpreter changes (e.g. a
Homebrew Python upgrade). If ``uv`` and ``pip`` are not declared dev
dependencies they vanish on the next sync or recreation, breaking
``./scripts/uv`` (which requires ``.venv/bin/uv``) and
``./scripts/bootstrap.sh`` (which uses ``.venv/bin/pip`` to install uv).
"""

from __future__ import annotations

import re
import tomllib
from pathlib import Path

_PYPROJECT = Path(__file__).resolve().parent.parent / "pyproject.toml"


def test_dev_extras_pin_uv_and_pip_for_the_bootstrap_chain() -> None:
with open(_PYPROJECT, "rb") as handle:
data = tomllib.load(handle)
dev = data["project"]["optional-dependencies"]["dev"]
names = {re.split(r"[><=\[ ]", item, maxsplit=1)[0] for item in dev}
assert "uv" in names, "uv must stay a dev dependency or syncs strip .venv/bin/uv"
assert "pip" in names, "pip must stay a dev dependency or bootstrap.sh cannot recover"
39 changes: 39 additions & 0 deletions uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading