Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 47 additions & 21 deletions scripts/publish_release.py
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,44 @@ def version_record(self, crate: str, version: str) -> RegistryRecord:
return RegistryRecord(True, checksum)


def package_archive_checksum(
crate: str, version: str, extra_arguments: Sequence[str] = ()
) -> str:
command = [
"cargo",
"package",
"--locked",
"--no-verify",
"-p",
crate,
*extra_arguments,
]
result = subprocess.run(
command, cwd=ROOT, check=False, capture_output=True, text=True
)
if result.returncode != 0:
raise PublishError(
f"cargo package failed for {crate}:\n{result.stderr.strip()}"
)
archive = ROOT / "target" / "package" / f"{crate}-{version}.crate"
try:
return hashlib.sha256(archive.read_bytes()).hexdigest()
except OSError as error:
raise PublishError(f"could not hash packaged archive {archive}: {error}") from None


def is_unpublished_dependency_failure(output: str) -> bool:
message = output.lower()
return all(
marker in message
for marker in (
"failed to select a version for the requirement",
"candidate versions found which didn't match",
"location searched: crates.io index",
)
)


def package_checksums(
manifest: ReleaseManifest, version: str, metadata: Mapping[str, Any]
) -> dict[str, str]:
Expand Down Expand Up @@ -270,27 +308,14 @@ def package_checksums(

checksums: dict[str, str] = {}
for crate in manifest.ordered_crates:
command = [
"cargo",
"package",
"--locked",
"--no-verify",
"-p",
crate,
*patch_arguments,
]
result = subprocess.run(
command, cwd=ROOT, check=False, capture_output=True, text=True
)
if result.returncode != 0:
raise PublishError(
f"cargo package failed for {crate}:\n{result.stderr.strip()}"
)
archive = ROOT / "target" / "package" / f"{crate}-{version}.crate"
try:
checksums[crate] = hashlib.sha256(archive.read_bytes()).hexdigest()
except OSError as error:
raise PublishError(f"could not hash packaged archive {archive}: {error}") from None
checksums[crate] = package_archive_checksum(crate, version)
except PublishError as error:
if not is_unpublished_dependency_failure(str(error)):
raise
checksums[crate] = package_archive_checksum(
crate, version, patch_arguments
)
return checksums


Expand Down Expand Up @@ -435,7 +460,7 @@ def publish_remaining(
api: Any,
manifest: ReleaseManifest,
version: str,
checksums: Mapping[str, str],
checksums: dict[str, str],
start_index: int,
*,
run: Callable[[list[str]], subprocess.CompletedProcess[str]] = _run_publish,
Expand All @@ -453,6 +478,7 @@ def publish_remaining(
print(result.stdout.rstrip())
if result.stderr:
print(result.stderr.rstrip(), file=sys.stderr)
checksums[crate] = package_archive_checksum(crate, version)
if result.returncode == 0:
break

Expand Down
59 changes: 42 additions & 17 deletions scripts/tests/test_publish_release.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,16 @@ def test_workspace_packages_resolve_unpublished_exact_dependencies_locally(self)
def run(command: list[str], **kwargs: object) -> subprocess.CompletedProcess[str]:
commands.append(command)
self.assertEqual(kwargs["cwd"], root)
crate = command[command.index("-p") + 1]
if crate == "consumer" and "--config" not in command:
return subprocess.CompletedProcess(
command,
1,
"",
"failed to select a version for the requirement "
"`base = \"=0.7.3\"`; candidate versions found which "
"didn't match; location searched: crates.io index",
)
return subprocess.CompletedProcess(command, 0, "", "")

with (
Expand All @@ -105,14 +115,15 @@ def run(command: list[str], **kwargs: object) -> subprocess.CompletedProcess[str
f'patch.crates-io.{crate}.path="{root.resolve() / "crates" / crate}"'
for crate in manifest.ordered_crates
}
self.assertEqual(len(commands), 2)
for command in commands:
configs = {
command[index + 1]
for index, argument in enumerate(command)
if argument == "--config"
}
self.assertEqual(configs, expected_patches)
self.assertEqual(len(commands), 3)
self.assertNotIn("--config", commands[0])
self.assertNotIn("--config", commands[1])
configs = {
commands[2][index + 1]
for index, argument in enumerate(commands[2])
if argument == "--config"
}
self.assertEqual(configs, expected_patches)


class RegistryTests(unittest.TestCase):
Expand Down Expand Up @@ -200,19 +211,33 @@ def run(command: list[str]) -> subprocess.CompletedProcess[str]:
api.records["a"] = publish_release.RegistryRecord(True, "aaa")
return subprocess.CompletedProcess(command, 0, "published", "")

publish_release.publish_remaining(
api,
manifest,
"0.7.3",
{"a": "aaa"},
0,
run=run,
sleep=delays.append,
)
refreshed: list[tuple[str, str]] = []

def package_archive_checksum(crate: str, version: str) -> str:
refreshed.append((crate, version))
return "aaa"

checksums = {"a": "staged-checksum"}
with mock.patch.object(
publish_release,
"package_archive_checksum",
side_effect=package_archive_checksum,
):
publish_release.publish_remaining(
api,
manifest,
"0.7.3",
checksums,
0,
run=run,
sleep=delays.append,
)

self.assertEqual(attempts, 2)
self.assertEqual(delays, [5])
self.assertGreaterEqual(len(api.calls), 1)
self.assertEqual(refreshed, [("a", "0.7.3"), ("a", "0.7.3")])
self.assertEqual(checksums, {"a": "aaa"})

def test_authentication_and_validation_failures_are_never_retried(self) -> None:
for output in (
Expand Down
Loading