Compare commits

...

5 Commits

Author SHA1 Message Date
benedettadavico d1ba5acd72 mebbe 2026-03-06 07:53:44 +01:00
benedettadavico b7b238584d add file 2026-03-06 07:53:44 +01:00
benedettadavico 7ed5b5477b ? 2026-03-06 07:53:44 +01:00
benedettadavico 09506683ce test.. 2026-03-06 07:53:44 +01:00
benedettadavico 98f1480ae8 attempt at fix 2026-03-06 07:53:44 +01:00
7 changed files with 277 additions and 6 deletions
@@ -15,6 +15,9 @@ env:
jobs:
publish-dry-run:
runs-on: arc-linux-latest
timeout-minutes: 35
env:
RUSTUP_PERMIT_COPY_RENAME: 1
steps:
- name: Checkout repo
uses: actions/checkout@v6
@@ -59,20 +62,60 @@ jobs:
- name: Bump versions (local only)
run: |
cargo workspaces version custom ${{ inputs.version }} \
--allow-branch ${{ github.ref_name }} \
--no-git-commit \
--yes
- name: Preflight publish checks
run: |
python3 tools/internal/check_publish_preflight.py
# Dry run may show cascading dependency errors because packages aren't
# actually uploaded - these are expected and ignored. We check for real
# errors like packaging failures, missing metadata, or invalid Cargo.toml.
- name: Publish (dry run)
run: |
output=$(cargo workspaces publish --dry-run --allow-dirty 2>&1) || true
echo "$output"
set +e
publish_status=1
max_attempts=2
attempt=1
rm -f /tmp/publish-dry-run.log
# Check for real errors (not cascading dependency errors)
# Cascading errors mention "crates.io index", real errors mention "Cargo.toml"
echo "$output" | grep -i "Cargo.toml" && exit 1 || true
while [ "$attempt" -le "$max_attempts" ]; do
echo "Dry-run publish attempt ${attempt}/${max_attempts}"
cargo workspaces publish --dry-run --allow-dirty 2>&1 | tee /tmp/publish-dry-run.log
publish_status=${PIPESTATUS[0]}
if [ "$publish_status" -eq 0 ]; then
break
fi
# Retry once for interruption/runner issues.
if [ "$attempt" -lt "$max_attempts" ] && \
{ [ "$publish_status" -eq 130 ] || [ "$publish_status" -eq 137 ]; }; then
echo "Publish dry-run interrupted (exit ${publish_status}), retrying in 10s..."
sleep 10
attempt=$((attempt + 1))
continue
fi
break
done
set -e
if grep -Eiq \
"failed to verify manifest|failed to parse manifest|invalid Cargo.toml|error: package .* has no (description|license|repository)" \
/tmp/publish-dry-run.log; then
echo "Detected real packaging/manifest errors"
exit 1
fi
# In dry-run mode, non-zero publish status is expected due to
# dependency-cascade failures against crates.io index.
if [ "$publish_status" -ne 0 ]; then
echo "Dry-run publish returned non-zero (${publish_status}) but no real manifest blockers were detected."
fi
echo "Only expected dry-run dependency cascade errors detected (if any)."
# Show the list of packages published
- name: Show package versions
@@ -17,6 +17,8 @@ on:
jobs:
publish:
runs-on: arc-linux-latest
env:
RUSTUP_PERMIT_COPY_RENAME: 1
steps:
- name: Checkout repo
uses: actions/checkout@v6
+2
View File
@@ -17,6 +17,8 @@ on:
jobs:
publish:
runs-on: arc-linux-latest
env:
RUSTUP_PERMIT_COPY_RENAME: 1
steps:
- name: Checkout repo
uses: actions/checkout@v6
@@ -15,6 +15,8 @@ env:
jobs:
version-bump:
runs-on: arc-linux-latest
env:
RUSTUP_PERMIT_COPY_RENAME: 1
permissions:
contents: write
steps:
@@ -25,6 +25,10 @@ jobs:
- name: Install cargo-workspaces
run: cargo install cargo-workspaces
- name: Preflight publish checks
run: |
python3 tools/internal/check_publish_preflight.py
- name: Publish remaining crates
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
+1
View File
@@ -1,5 +1,6 @@
[package]
name = "nym-kkt-ciphersuite"
description = "Nym KKT ciphersuite"
authors.workspace = true
repository.workspace = true
homepage.workspace = true
+217
View File
@@ -0,0 +1,217 @@
#!/usr/bin/env python3
import json
import pathlib
import subprocess
import sys
from collections import defaultdict
def dependency_section(dep):
kind = dep.get("kind") or "normal"
section = {
"normal": "dependencies",
"dev": "dev-dependencies",
"build": "build-dependencies",
}.get(kind, f"{kind}-dependencies")
target = dep.get("target")
if target:
return f"target.{target}.{section}"
return section
def manifest_member(root, manifest_path):
manifest_parent = pathlib.Path(manifest_path).resolve().parent
try:
return str(manifest_parent.relative_to(root))
except ValueError:
return str(manifest_parent)
def publish_status(pkg):
publish = pkg.get("publish")
if publish is None:
return True, "publishable to crates.io"
if isinstance(publish, list):
if not publish:
return False, "publish disabled (`publish = false`)"
if "crates-io" in publish:
return True, "publishable to crates.io"
registries = ", ".join(publish)
return False, f"publish restricted to non-crates.io registries ({registries})"
return False, f"unrecognized `publish` setting: {publish!r}"
def main():
root = pathlib.Path(".").resolve()
metadata = json.loads(
subprocess.check_output(
["cargo", "metadata", "--no-deps", "--format-version", "1"],
text=True,
)
)
packages_by_id = {pkg["id"]: pkg for pkg in metadata["packages"]}
workspace_ids = set(metadata["workspace_members"])
workspace_packages = [
packages_by_id[pkg_id] for pkg_id in workspace_ids if pkg_id in packages_by_id
]
workspace_by_name = {pkg["name"]: pkg for pkg in workspace_packages}
workspace_dir_to_name = {
str(pathlib.Path(pkg["manifest_path"]).resolve().parent): pkg["name"]
for pkg in workspace_packages
}
package_info = {}
for pkg in workspace_packages:
name = pkg["name"]
member = manifest_member(root, pkg["manifest_path"])
explicitly_publishable, publish_reason = publish_status(pkg)
package_info[name] = {
"pkg": pkg,
"member": member,
"explicitly_publishable": explicitly_publishable,
"publish_reason": publish_reason,
}
direct_issues = defaultdict(set)
workspace_deps = defaultdict(list)
for name, info in package_info.items():
pkg = info["pkg"]
member = info["member"]
explicitly_publishable = info["explicitly_publishable"]
if not explicitly_publishable:
direct_issues[name].add(info["publish_reason"])
continue
for field in ("description", "license", "repository"):
value = pkg.get(field)
if not isinstance(value, str) or not value.strip():
direct_issues[name].add(f"missing required field '{field}'")
for dep in pkg.get("dependencies", []):
section = dependency_section(dep)
dep_name = dep["name"]
dep_source = dep.get("source")
dep_workspace_name = workspace_by_name.get(dep_name, {}).get("name")
dep_path = dep.get("path")
if dep_workspace_name is None and dep_path:
dep_workspace_name = workspace_dir_to_name.get(
str(pathlib.Path(dep_path).resolve())
)
if dep_path and dep.get("req") in ("*", ""):
direct_issues[name].add(
f"{section}: path dependency '{dep_name}' has no explicit version ({dep_path})"
)
if dep_workspace_name:
dep_info = package_info[dep_workspace_name]
if not dep_info["explicitly_publishable"]:
direct_issues[name].add(
f"{section}: depends on non-publishable workspace crate '{dep_workspace_name}' ({dep_info['publish_reason']})"
)
continue
workspace_deps[name].append((dep_workspace_name, section))
continue
if dep_source and not dep_source.startswith("registry+"):
direct_issues[name].add(
f"{section}: non-registry dependency '{dep_name}' from '{dep_source}'"
)
effective_issues = {}
def collect_effective_issues(crate_name, stack):
cached = effective_issues.get(crate_name)
if cached is not None:
return cached
issues = set(direct_issues.get(crate_name, set()))
stack = stack | {crate_name}
for dep_name, dep_section in workspace_deps.get(crate_name, []):
dep_info = package_info[dep_name]
if not dep_info["explicitly_publishable"]:
issues.add(
f"{dep_section}: depends on non-publishable workspace crate '{dep_name}' ({dep_info['publish_reason']})"
)
continue
if dep_name in stack:
continue
dep_issues = collect_effective_issues(dep_name, stack)
if dep_issues:
issues.add(
f"{dep_section}: depends on blocked workspace crate '{dep_name}'"
)
effective_issues[crate_name] = issues
return issues
for crate_name in package_info:
collect_effective_issues(crate_name, set())
publish_targets = sorted(
name for name, info in package_info.items() if info["explicitly_publishable"]
)
root_blockers = sorted(
name
for name in publish_targets
if direct_issues.get(name)
)
transitive_blocked = sorted(
name
for name in publish_targets
if not direct_issues.get(name) and effective_issues.get(name)
)
disabled_by_config = sorted(
name for name, info in package_info.items() if not info["explicitly_publishable"]
)
print("Publishability preflight report:")
print(f"- workspace crates inspected: {len(package_info)}")
print(f"- crates configured for crates.io publish: {len(publish_targets)}")
print(f"- root blockers (direct issues): {len(root_blockers)}")
print(f"- downstream blocked crates (transitive): {len(transitive_blocked)}")
print(f"- crates excluded by config (publish = false / restricted): {len(disabled_by_config)}")
if root_blockers:
print("\nAction required: root blockers")
for crate_name in root_blockers:
info = package_info[crate_name]
print(f"- {crate_name} ({info['member']})")
for issue in sorted(direct_issues[crate_name]):
print(f" - {issue}")
if transitive_blocked:
print("\nDownstream blocked crates")
print("- These crates have no direct issue; they are blocked by dependencies listed below.")
for crate_name in transitive_blocked:
info = package_info[crate_name]
blockers = set()
for dep_name, dep_section in workspace_deps.get(crate_name, []):
dep_info = package_info[dep_name]
if not dep_info["explicitly_publishable"] or effective_issues.get(dep_name):
blockers.add(f"{dep_name} via {dep_section}")
print(f"- {crate_name} ({info['member']})")
for blocker in sorted(blockers):
print(f" - blocked by {blocker}")
if root_blockers or transitive_blocked:
print("\nPreflight checks failed:")
print(f"- {len(root_blockers) + len(transitive_blocked)} crate(s) configured for crates.io publish are blocked.")
sys.exit(1)
print("\nPreflight checks passed.")
if __name__ == "__main__":
main()