Merge pull request #122098 from liuhao1024/liuhao/cron-bugfix-122071
fix(pm): name virtual workspace members by their unique key
This commit is contained in:
@@ -230,6 +230,14 @@ def _workspace_member(plugin_dir: Path, root: Path, *, identity: Path) -> Path:
|
||||
shutil.copytree(plugin_dir, member, symlinks=True,
|
||||
ignore=_member_ignored)
|
||||
document = tomllib.loads(pyproject.read_text(encoding="utf-8-sig"))
|
||||
# uv identifies a workspace member by [project].name, so the same virtual
|
||||
# plugin enabled in two profiles would declare one name twice and fail
|
||||
# `uv lock`. A member with no build backend is metadata-only: it can carry
|
||||
# the unique key in its name, as manifest-only members already do. A
|
||||
# buildable member keeps its declared name — uv verifies it against the
|
||||
# package metadata its backend produces.
|
||||
# tool.uv.package = true opts into uv package mode: real build metadata, so buildable.
|
||||
virtual = "build-system" not in document and document.get("tool", {}).get("uv", {}).get("package") is not True
|
||||
changed = declaration.install_requirements != declaration.requirements
|
||||
if changed:
|
||||
document["project"]["dependencies"] = list(declaration.install_requirements)
|
||||
@@ -245,7 +253,9 @@ def _workspace_member(plugin_dir: Path, root: Path, *, identity: Path) -> Path:
|
||||
continue # The referenced tree was copied with this member.
|
||||
spec["path"] = (identity / relative).resolve().as_posix()
|
||||
changed = True
|
||||
if changed:
|
||||
if virtual:
|
||||
document.setdefault("project", {})["name"] = f"hermes-plugin-{key}"
|
||||
if virtual or changed:
|
||||
import tomli_w
|
||||
|
||||
(member / "pyproject.toml").write_text(tomli_w.dumps(document), encoding="utf-8")
|
||||
|
||||
@@ -677,7 +677,14 @@ def test_explicit_workspace_preserves_seed_and_replays_copied_members(locked_pro
|
||||
assert document["project"] == tomllib.loads(project.read_text(encoding="utf-8-sig"))["project"]
|
||||
[relative] = document["tool"]["uv"]["workspace"]["members"]
|
||||
copied = recorded / relative / "pyproject.toml"
|
||||
assert copied.read_bytes() == before_member
|
||||
copied_document = tomllib.loads(copied.read_text(encoding="utf-8-sig"))
|
||||
expected = tomllib.loads(before_member.decode("utf-8"))
|
||||
# A virtual member is renamed to its unique key (uv rejects two members with
|
||||
# one [project].name); everything the plugin declared must survive verbatim.
|
||||
assert copied_document["project"]["name"].startswith("hermes-plugin-member-")
|
||||
del copied_document["project"]["name"]
|
||||
del expected["project"]["name"]
|
||||
assert copied_document == expected
|
||||
assert copied.resolve().is_relative_to(recorded.resolve())
|
||||
(original_member / "pyproject.toml").unlink()
|
||||
assert workspace.members_stamp([original_member]) != stamp
|
||||
|
||||
@@ -37,3 +37,53 @@ def test_manifest_only_member_is_named_after_its_plugin_dir_and_stays_unique(tmp
|
||||
assert all(name.startswith("hermes-plugin-my-plugin-") for name in names), names
|
||||
assert names[0] != names[1]
|
||||
assert members[0].name != members[1].name
|
||||
|
||||
|
||||
def test_pyproject_member_is_renamed_by_its_key_and_stays_unique(tmp_path):
|
||||
"""A plugin that ships its own pyproject declares its real [project].name, and
|
||||
uv identifies workspace members by that name — enabling the plugin in two
|
||||
profiles then fails ``uv lock`` with "Two workspace members are both named
|
||||
…". Rename metadata-only members by their unique key, like manifest-only
|
||||
ones; a buildable member keeps the name its package metadata reports."""
|
||||
import tomllib
|
||||
from pm.workspace import _workspace_member
|
||||
|
||||
members = []
|
||||
for home in ("home-a", "home-b"):
|
||||
plugin = tmp_path / home / "plugins" / "hindsight"
|
||||
plugin.mkdir(parents=True)
|
||||
(plugin / "pyproject.toml").write_text(
|
||||
'[project]\nname = "hermes-plugin-hindsight"\nversion = "1.0.0"\n'
|
||||
'dependencies = ["hindsight-client>=0.10.1"]\n',
|
||||
encoding="utf-8",
|
||||
)
|
||||
root = tmp_path / f"gen-{home}"
|
||||
root.mkdir()
|
||||
members.append(_workspace_member(plugin, root, identity=plugin))
|
||||
names = [tomllib.loads((m / "pyproject.toml").read_text(encoding="utf-8"))["project"]["name"] for m in members]
|
||||
assert all(name.startswith("hermes-plugin-hindsight-") for name in names), names
|
||||
assert names[0] != names[1]
|
||||
document = tomllib.loads((members[0] / "pyproject.toml").read_text(encoding="utf-8"))
|
||||
assert document["project"]["version"] == "1.0.0"
|
||||
assert document["project"]["dependencies"] == ["hindsight-client>=0.10.1"]
|
||||
|
||||
|
||||
def test_buildable_pyproject_member_keeps_its_declared_name(tmp_path):
|
||||
"""uv verifies a buildable member's [project].name against the package metadata
|
||||
its backend produces, so renaming it breaks the build ("Package metadata name
|
||||
… does not match given name"); only metadata-only members may be renamed."""
|
||||
import tomllib
|
||||
from pm.workspace import _workspace_member
|
||||
|
||||
plugin = tmp_path / "home" / "plugins" / "replay"
|
||||
plugin.mkdir(parents=True)
|
||||
(plugin / "pyproject.toml").write_text(
|
||||
'[project]\nname = "replay-plugin"\nversion = "1.0"\n'
|
||||
'[build-system]\nrequires = []\nbuild-backend = "backend"\n',
|
||||
encoding="utf-8",
|
||||
)
|
||||
root = tmp_path / "gen"
|
||||
root.mkdir()
|
||||
member = _workspace_member(plugin, root, identity=plugin)
|
||||
assert (member / "pyproject.toml").read_text(encoding="utf-8") == (
|
||||
plugin / "pyproject.toml").read_text(encoding="utf-8")
|
||||
|
||||
Reference in New Issue
Block a user