Skip to content

Commit 35eb599

Browse files
committed
Fix Windows VIRTUAL_ENV regex and dogfood finecode for docs build
- Give the `for %%i in (...)` and plain `set "VIRTUAL_ENV=..."` forms of activate.bat separate capture groups; the shared group previously swallowed the `VIRTUAL_ENV=` literal in the plain-set case, so every such venv was misdetected as relocated. Add regression tests covering both forms on and off relocation. - Build docs via `finecode run build_docs` in CI instead of invoking mkdocs directly, threading FINECODE_LOG_LEVEL through setup-dev-workspace.sh and prepare-envs; cache venvs in docs.yml to reuse a preceding CI run's install. - Bump dev workspace Python to 3.14 in ci-cd.yml and docs.yml. - Drop the unused `dep_graph` optional dependency group from pyproject.toml.
1 parent 9b7584a commit 35eb599

6 files changed

Lines changed: 147 additions & 16 deletions

File tree

.github/workflows/ci-cd.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ defaults:
2828

2929
env:
3030
# python version for dev workspace
31-
DEV_WORKSPACE_PYTHON_VERSION: '3.13'
31+
DEV_WORKSPACE_PYTHON_VERSION: '3.14'
3232

3333

3434
jobs:

.github/workflows/docs.yml

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,26 +12,44 @@ jobs:
1212

1313
steps:
1414
- uses: actions/checkout@v5
15+
with:
16+
# setuptools-scm uses tags to get the current version, fetch history and tags
17+
# to get correct version
18+
fetch-depth: 0
19+
fetch-tags: 'true'
20+
21+
- name: Determine FineCode log level
22+
run: |
23+
if [ "${RUNNER_DEBUG:-0}" = "1" ]; then
24+
echo "FINECODE_LOG_LEVEL=DEBUG" >> "$GITHUB_ENV"
25+
else
26+
echo "FINECODE_LOG_LEVEL=INFO" >> "$GITHUB_ENV"
27+
fi
1528
1629
- name: Set up Python
1730
uses: actions/setup-python@v5
1831
with:
19-
python-version: '3.13'
32+
python-version: '3.14'
2033

21-
- name: Install docs dependencies
22-
run: |
23-
python -m pip install --upgrade pip
24-
# TODO: prepare_env docs
25-
python -m venv .venvs/docs
26-
source .venvs/docs/bin/activate
27-
python -m pip install --group="docs"
34+
# Same cache key pattern as ci-cd.yml so a preceding CI run on the same commit
35+
# can seed this cache and skip most of the install work.
36+
- name: Cache all venvs
37+
uses: actions/cache@v4
38+
with:
39+
path: |
40+
.venvs
41+
**/.venvs
42+
key: ${{ runner.os }}-venvs-${{ hashFiles('**/pyproject.toml', '**/preset.toml') }}
43+
44+
- name: Install dependencies
45+
run: sh scripts/setup-dev-workspace.sh
2846

2947
- name: Build docs
3048
env:
3149
MKDOCS_SITE_URL: https://finecode-dev.github.io
3250
run: |
33-
source .venvs/docs/bin/activate
34-
mkdocs build
51+
source .venvs/dev_workspace/bin/activate
52+
python -m finecode run --log-level="$FINECODE_LOG_LEVEL" build_docs
3553
3654
- name: Deploy to finecode.github.io
3755
uses: peaceiris/actions-gh-pages@v4

pyproject.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,6 @@ dev_workspace = [
4343
"opentelemetry-instrumentation==0.62.*"
4444
]
4545
dev = ["psutil==7.2.*", "debugpy==1.8.*"]
46-
dep_graph = ["fine_dep_graph~=0.1.0a0", "fine_dep_graph_falkordb~=0.1.0a0"]
4746

4847
[build-system]
4948
requires = ["setuptools>=64", "setuptools-scm>=8"]

scripts/setup-dev-workspace.sh

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,9 @@ recreate_venv() {
4646
EDITABLE_ARGS=$(python scripts/list_dev_workspace_editables.py)
4747
uv pip install --python "$VENV_PYTHON" --group dev_workspace $EDITABLE_ARGS -e .
4848

49-
"$VENV_PYTHON" -m finecode prepare-envs
49+
# FINECODE_LOG_LEVEL is set by CI (INFO normally, DEBUG on a debug re-run); it is
50+
# unset in the devcontainer, where it falls back to INFO.
51+
"$VENV_PYTHON" -m finecode prepare-envs --log-level="${FINECODE_LOG_LEVEL:-INFO}"
5052
}
5153

5254
if [ -d "$VENV_DIR" ] && is_valid_venv; then

src/finecode/wm_server/runner/finecode_cmd.py

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,14 @@
88

99
# activate.bat records the same path, but some generators (e.g. uv) route it through
1010
# a `for %%i in ("...") do @set "VIRTUAL_ENV=%%~fi"` indirection instead of a plain
11-
# `set`, so the literal path lives inside the `for ... in (...)` clause.
11+
# `set "VIRTUAL_ENV=..."`, so the literal path lives inside the `for ... in (...)`
12+
# clause rather than after `VIRTUAL_ENV=`. The two forms need separate capture groups:
13+
# a plain `set "VIRTUAL_ENV=<path>"` line quotes the *whole* assignment, not just the
14+
# path, so a single shared capture group would swallow the `VIRTUAL_ENV=` literal too.
1215
_VIRTUAL_ENV_WIN_RE = re.compile(
13-
r'^\s*(?:@?for\s+%%\w+\s+in\s+\(|@?set\s+)"(?P<path>[^"]+)"', re.MULTILINE | re.IGNORECASE
16+
r'^\s*@?for\s+%%\w+\s+in\s+\("(?P<for_path>[^"]+)"\)\s+do\s+@?set\s+"VIRTUAL_ENV=%%~fi"'
17+
r'|^\s*@?set\s+"VIRTUAL_ENV=(?P<set_path>[^"]+)"',
18+
re.MULTILINE | re.IGNORECASE,
1419
)
1520

1621

@@ -54,7 +59,12 @@ def _recorded_venv_path(venv_dir_path: Path) -> Path | None:
5459
if match is None:
5560
return None
5661

57-
return Path(match.group("path"))
62+
if sys.platform == "win32":
63+
path_str = match.group("for_path") or match.group("set_path")
64+
else:
65+
path_str = match.group("path")
66+
67+
return Path(path_str)
5868

5969

6070
def get_python_cmd(project_path: Path, env_name: str) -> str:
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
from __future__ import annotations
2+
3+
import pathlib
4+
5+
import pytest
6+
7+
from finecode.wm_server.runner import finecode_cmd
8+
9+
# These tests exercise the win32 branch of `_recorded_venv_path` / `get_python_cmd`
10+
# regardless of the host OS the suite runs on, by monkeypatching `sys.platform` as
11+
# seen by `finecode_cmd` and building a Windows-style `Scripts/` venv layout.
12+
13+
14+
def _make_windows_venv(
15+
venv_dir_path: pathlib.Path, *, activate_bat_content: str
16+
) -> None:
17+
scripts_dir = venv_dir_path / "Scripts"
18+
scripts_dir.mkdir(parents=True)
19+
(scripts_dir / "python.exe").write_text("")
20+
(scripts_dir / "activate.bat").write_text(activate_bat_content)
21+
22+
23+
@pytest.fixture(autouse=True)
24+
def _win32(monkeypatch: pytest.MonkeyPatch) -> None:
25+
monkeypatch.setattr(finecode_cmd.sys, "platform", "win32")
26+
27+
28+
def test_plain_set_format_is_not_mistaken_for_relocation(
29+
tmp_path: pathlib.Path,
30+
) -> None:
31+
"""Regression test: a plain `set "VIRTUAL_ENV=<path>"` line quotes the whole
32+
assignment, not just the path. The old regex captured everything inside the
33+
quotes (including the literal `VIRTUAL_ENV=` prefix), so the recorded path
34+
could never equal the real venv path and every such venv looked "relocated"
35+
even when it was not.
36+
"""
37+
project_path = tmp_path / "myproject"
38+
venv_dir_path = finecode_cmd.get_venv_dir_path(project_path, "dev_workspace")
39+
_make_windows_venv(
40+
venv_dir_path,
41+
activate_bat_content=f'set "VIRTUAL_ENV={venv_dir_path}"\n',
42+
)
43+
44+
result = finecode_cmd.get_python_cmd(project_path, "dev_workspace")
45+
46+
assert result == (venv_dir_path / "Scripts" / "python.exe").as_posix()
47+
48+
49+
def test_plain_set_format_raises_when_path_actually_differs(
50+
tmp_path: pathlib.Path,
51+
) -> None:
52+
project_path = tmp_path / "new_location" / "myproject"
53+
venv_dir_path = finecode_cmd.get_venv_dir_path(project_path, "dev_workspace")
54+
old_venv_dir_path = (
55+
tmp_path / "old_location" / "myproject" / ".venvs" / "dev_workspace"
56+
)
57+
_make_windows_venv(
58+
venv_dir_path,
59+
activate_bat_content=f'set "VIRTUAL_ENV={old_venv_dir_path}"\n',
60+
)
61+
62+
with pytest.raises(finecode_cmd.VenvRelocatedError):
63+
finecode_cmd.get_python_cmd(project_path, "dev_workspace")
64+
65+
66+
def test_for_loop_indirection_format_is_not_mistaken_for_relocation(
67+
tmp_path: pathlib.Path,
68+
) -> None:
69+
"""uv-generated activate.bat files route VIRTUAL_ENV through a
70+
`for %%i in ("...") do @set "VIRTUAL_ENV=%%~fi"` indirection instead of a
71+
plain `set`."""
72+
project_path = tmp_path / "myproject"
73+
venv_dir_path = finecode_cmd.get_venv_dir_path(project_path, "dev_workspace")
74+
_make_windows_venv(
75+
venv_dir_path,
76+
activate_bat_content=(
77+
f'for %%i in ("{venv_dir_path}") do @set "VIRTUAL_ENV=%%~fi"\n'
78+
),
79+
)
80+
81+
result = finecode_cmd.get_python_cmd(project_path, "dev_workspace")
82+
83+
assert result == (venv_dir_path / "Scripts" / "python.exe").as_posix()
84+
85+
86+
def test_for_loop_indirection_format_raises_when_path_actually_differs(
87+
tmp_path: pathlib.Path,
88+
) -> None:
89+
project_path = tmp_path / "new_location" / "myproject"
90+
venv_dir_path = finecode_cmd.get_venv_dir_path(project_path, "dev_workspace")
91+
old_venv_dir_path = (
92+
tmp_path / "old_location" / "myproject" / ".venvs" / "dev_workspace"
93+
)
94+
_make_windows_venv(
95+
venv_dir_path,
96+
activate_bat_content=(
97+
f'for %%i in ("{old_venv_dir_path}") do @set "VIRTUAL_ENV=%%~fi"\n'
98+
),
99+
)
100+
101+
with pytest.raises(finecode_cmd.VenvRelocatedError):
102+
finecode_cmd.get_python_cmd(project_path, "dev_workspace")

0 commit comments

Comments
 (0)