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,
|
shutil.copytree(plugin_dir, member, symlinks=True,
|
||||||
ignore=_member_ignored)
|
ignore=_member_ignored)
|
||||||
document = tomllib.loads(pyproject.read_text(encoding="utf-8-sig"))
|
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
|
changed = declaration.install_requirements != declaration.requirements
|
||||||
if changed:
|
if changed:
|
||||||
document["project"]["dependencies"] = list(declaration.install_requirements)
|
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.
|
continue # The referenced tree was copied with this member.
|
||||||
spec["path"] = (identity / relative).resolve().as_posix()
|
spec["path"] = (identity / relative).resolve().as_posix()
|
||||||
changed = True
|
changed = True
|
||||||
if changed:
|
if virtual:
|
||||||
|
document.setdefault("project", {})["name"] = f"hermes-plugin-{key}"
|
||||||
|
if virtual or changed:
|
||||||
import tomli_w
|
import tomli_w
|
||||||
|
|
||||||
(member / "pyproject.toml").write_text(tomli_w.dumps(document), encoding="utf-8")
|
(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"]
|
assert document["project"] == tomllib.loads(project.read_text(encoding="utf-8-sig"))["project"]
|
||||||
[relative] = document["tool"]["uv"]["workspace"]["members"]
|
[relative] = document["tool"]["uv"]["workspace"]["members"]
|
||||||
copied = recorded / relative / "pyproject.toml"
|
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())
|
assert copied.resolve().is_relative_to(recorded.resolve())
|
||||||
(original_member / "pyproject.toml").unlink()
|
(original_member / "pyproject.toml").unlink()
|
||||||
assert workspace.members_stamp([original_member]) != stamp
|
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 all(name.startswith("hermes-plugin-my-plugin-") for name in names), names
|
||||||
assert names[0] != names[1]
|
assert names[0] != names[1]
|
||||||
assert members[0].name != members[1].name
|
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