diff --git a/scripts/ci_test_artifact.py b/scripts/ci_test_artifact.py index a6e842b2a..164a140cd 100644 --- a/scripts/ci_test_artifact.py +++ b/scripts/ci_test_artifact.py @@ -8,6 +8,7 @@ from pathlib import Path import platform import subprocess import sys +import tomllib import xml.etree.ElementTree as ET from e2e_binary import ROOT, file_hash, source_identity @@ -71,10 +72,29 @@ def package_args(configuration): not isinstance(name, str) or not name or name.startswith("-") or name == "e2e_test" for name in packages ): raise ValueError("CI_PACKAGES must be a nonempty workspace package list") - return [part for name in sorted(set(packages)) for part in ("-p", name)] + return [part for name in sorted(set(packages)) for part in ("-p", package_spec(name))] return CONFIGURATIONS[configuration] +def locked_packages(): + return tomllib.loads((ROOT / "Cargo.lock").read_text()).get("package", []) + + +def package_spec(name): + """Qualify a workspace package whose name also resolves to a dependency. + + `crates/zstd-compat` is named `zstd` to patch crates.io, so a bare `-p zstd` + is ambiguous whenever another `zstd` version is locked. + """ + entries = [entry for entry in locked_packages() if entry.get("name") == name] + if len(entries) <= 1: + return name + local = [entry for entry in entries if "source" not in entry] + if len(local) != 1 or any(entry["version"] == local[0]["version"] for entry in entries if entry is not local[0]): + raise ValueError(f"workspace package {name} cannot be selected unambiguously") + return f"{name}@{local[0]['version']}" + + def build(archive, configuration): before = identity(configuration) archive.parent.mkdir(parents=True, exist_ok=True) diff --git a/scripts/test_ci_test_artifact.py b/scripts/test_ci_test_artifact.py index 18a53422d..13f3db290 100644 --- a/scripts/test_ci_test_artifact.py +++ b/scripts/test_ci_test_artifact.py @@ -118,6 +118,16 @@ class ArtifactIdentityTests(unittest.TestCase): with patch.dict(os.environ, {"CI_PACKAGES": '["rustfs-utils", "rustfs", "rustfs"]'}): self.assertEqual(artifact.package_args("workspace"), ["-p", "rustfs", "-p", "rustfs-utils"]) self.assertEqual(artifact.package_args("e2e"), ["-p", "e2e_test"]) + locked = [{"name": "zstd", "version": "0.13.3"}, + {"name": "zstd", "version": "0.14.3", "source": "registry+https://github.com/rust-lang/crates.io-index"}, + {"name": "rustfs", "version": "0.1.0"}] + with patch.object(artifact, "locked_packages", return_value=locked), \ + patch.dict(os.environ, {"CI_PACKAGES": '["zstd", "rustfs"]'}): + self.assertEqual(artifact.package_args("workspace"), ["-p", "rustfs", "-p", "zstd@0.13.3"]) + for clash in ([*locked[:2], {"name": "zstd", "version": "0.14.3"}], [locked[1], {**locked[1], "version": "0.15.0"}]): + with self.subTest(clash=clash), patch.object(artifact, "locked_packages", return_value=clash), \ + self.assertRaisesRegex(ValueError, "unambiguously"): + artifact.package_args("workspace") for value in ('[]', '{}', '[null]', '["--all-features"]', '["e2e_test"]', '[""]'): with self.subTest(value=value), patch.dict(os.environ, {"CI_PACKAGES": value}), self.assertRaises(ValueError): artifact.package_args("workspace")