diff --git a/pm/workspace.py b/pm/workspace.py index 18316036e1..9d7a652162 100644 --- a/pm/workspace.py +++ b/pm/workspace.py @@ -82,7 +82,9 @@ def _copy_core_inputs(source: Path, destination: Path) -> None: files.update(str(p.relative_to(source)) for p in source.glob(pattern)) files.update(p.name for p in source.glob("*.py")) - excluded = {".git", ".venv", "venv", "node_modules", "__pycache__", "build", "dist", "release", "uv.lock"} + # uv.lock is not excluded: the root lock is never copied (only ``files`` are; lock_and_sync + # seeds or resolves it), and pm/uv.lock is the PM runtime's input (pm/runtime.py::_inputs). + excluded = {".git", ".venv", "venv", "node_modules", "__pycache__", "build", "dist", "release"} def ignore(directory, names): return [name for name in names if name in excluded or name.startswith(".") or name.endswith(".egg-info") or (Path(directory) / name).is_symlink()] diff --git a/tests/pm/test_workspace_build_inputs.py b/tests/pm/test_workspace_build_inputs.py index 70ac8c6c43..468a7b0d04 100644 --- a/tests/pm/test_workspace_build_inputs.py +++ b/tests/pm/test_workspace_build_inputs.py @@ -240,3 +240,24 @@ def test_plugin_can_move_compatible_transitive_but_not_exact_requirement(tmp_pat capture_output=True, text=True, check=True, timeout=30) assert result.stdout.strip() == "1.0 1.3" assert (baseline / "uv.lock").read_bytes() == first_lock + + +def test_member_uv_lock_travels_with_its_member(tmp_path): + """pm/runtime.py::_inputs reads /pm/uv.lock; the snapshot must carry it.""" + core = tmp_path / "core" + member = core / "pm" + member.mkdir(parents=True) + (core / "pyproject.toml").write_text( + '[project]\nname="core"\nversion="1"\nrequires-python=">=3.11"\n' + '[tool.setuptools.packages.find]\ninclude=["pm"]\n', + encoding="utf-8", + ) + (core / "uv.lock").write_text("version = 1\n# root lock\n", encoding="utf-8") + (member / "pyproject.toml").write_text('[project]\nname="pm"\nversion="1"\n', encoding="utf-8") + (member / "uv.lock").write_text("version = 1\n# member lock\n", encoding="utf-8") + + destination = tmp_path / "stage" + workspace._copy_core_inputs(core, destination) + + assert (destination / "pm/uv.lock").read_text(encoding="utf-8") == "version = 1\n# member lock\n" + assert not (destination / "uv.lock").exists(), "the root lock is seeded by lock_and_sync, not copied"