diff --git a/.github/workflows/ci-crates-publish-dry-run.yml b/.github/workflows/ci-crates-publish-dry-run.yml index 3d023ef245..2bff35312f 100644 --- a/.github/workflows/ci-crates-publish-dry-run.yml +++ b/.github/workflows/ci-crates-publish-dry-run.yml @@ -65,6 +65,141 @@ jobs: --no-git-commit \ --yes + - name: Preflight publish checks + run: | + python3 - <<'PY' + import tomllib + import pathlib + import sys + + root = pathlib.Path(".") + ws = tomllib.loads((root / "Cargo.toml").read_text()) + members = ws["workspace"]["members"] + errors = [] + workspace_members = {} + + for member in members: + manifest = root / member / "Cargo.toml" + if not manifest.exists(): + continue + + data = tomllib.loads(manifest.read_text()) + package = data.get("package") + if not package: + continue + + publish = package.get("publish", True) + publishable = publish is not False + workspace_members[package.get("name", member)] = { + "member": member, + "manifest": manifest, + "publishable": publishable, + "data": data, + } + + def dependency_sections(crate_data): + for section in ("dependencies", "dev-dependencies", "build-dependencies"): + deps = crate_data.get(section, {}) + if isinstance(deps, dict): + yield section, deps + + target = crate_data.get("target", {}) + if isinstance(target, dict): + for target_name, target_data in target.items(): + if not isinstance(target_data, dict): + continue + for section in ("dependencies", "dev-dependencies", "build-dependencies"): + deps = target_data.get(section, {}) + if isinstance(deps, dict): + yield f"target.{target_name}.{section}", deps + + def resolve_workspace_dep_name(dep_name, spec, manifest): + if not isinstance(spec, dict): + return None + + if spec.get("workspace") is True: + return dep_name + + candidate = spec.get("package", dep_name) + if candidate in workspace_members: + return candidate + + dep_path = spec.get("path") + if not dep_path: + return None + + dep_manifest = (manifest.parent / dep_path / "Cargo.toml").resolve() + if not dep_manifest.exists(): + return None + + dep_data = tomllib.loads(dep_manifest.read_text()) + dep_package = dep_data.get("package", {}) + resolved_name = dep_package.get("name") + if resolved_name in workspace_members: + return resolved_name + return None + + for member in members: + manifest = root / member / "Cargo.toml" + if not manifest.exists(): + continue + + data = tomllib.loads(manifest.read_text()) + package = data.get("package") + if not package: + continue + if package.get("publish", True) is False: + continue + + crate_name = package.get("name", member) + + for field in ("description", "license", "repository"): + value = package.get(field) + has_value = ( + (isinstance(value, str) and bool(value.strip())) + or (isinstance(value, dict) and value.get("workspace") is True) + ) + if not has_value: + errors.append( + f"{member}: package '{crate_name}' missing required field '{field}'" + ) + + for section, dependencies in dependency_sections(data): + for dep_name, spec in dependencies.items(): + if isinstance(spec, dict) and "path" in spec: + if spec.get("workspace") is True: + continue + if "version" not in spec: + errors.append( + f"{member}: {section} '{dep_name}' uses path dependency without version ({spec['path']})" + ) + + resolved_dep = resolve_workspace_dep_name(dep_name, spec, manifest) + if ( + resolved_dep + and resolved_dep in workspace_members + and not workspace_members[resolved_dep]["publishable"] + ): + errors.append( + f"{member}: package '{crate_name}' ({section}) depends on non-publishable workspace crate '{resolved_dep}'" + ) + + workspace_deps = ws.get("workspace", {}).get("dependencies", {}) + for dep_name, spec in workspace_deps.items(): + if isinstance(spec, dict) and "path" in spec and "version" not in spec: + errors.append( + f"workspace.dependencies: '{dep_name}' has path but no version" + ) + + if errors: + print("Preflight checks failed:") + for err in errors: + print(f"- {err}") + sys.exit(1) + + print("Preflight checks passed.") + 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. diff --git a/common/nym-kkt-ciphersuite/Cargo.toml b/common/nym-kkt-ciphersuite/Cargo.toml index 65722caab8..28e4aecae8 100644 --- a/common/nym-kkt-ciphersuite/Cargo.toml +++ b/common/nym-kkt-ciphersuite/Cargo.toml @@ -10,7 +10,6 @@ license.workspace = true rust-version.workspace = true readme.workspace = true version.workspace = true -publish = false [dependencies] thiserror = { workspace = true }