Files
ruff/scripts/bump-workspace-crate-versions.py
David Peter c64734bcea [ty] Enable return type inference in scripts and tools (#29439)
## Summary

Enable return type inference for all 24 standalone Python scripts
checked by ty in CI, along with `py-fuzzer` and `ty_benchmark`.
2026-10-16 12:41:30 +00:00

145 lines
5.2 KiB
Python

# Naively increment the patch version of each crate in the workspace.
#
# This excludes crates which are versioned with Ruff's command-line interface.
#
# After incrementing the version in each member `Cargo.toml`, it updates the version pins in the
# root `Cargo.toml` to match.
# /// script
# requires-python = ">=3.13"
# dependencies = []
#
# [tool.ty.analysis]
# return-type-inference = "always"
#
# [tool.ty.rules]
# truthiness-test-of-none-union = "warn"
# blanket-ignore-comment = "warn"
# missing-type-argument = "warn"
# possibly-unresolved-reference = "warn"
# unsound-return-statement = "warn"
# unsound-yield = "warn"
# unsupported-dynamic-base = "warn"
# division-by-zero = "warn"
# dynamic-function-decorator-return = "warn"
# unsound-assignment = "warn"
# redundant-condition-strict = "warn"
# disjoint-cast = "warn"
# missing-direct-dependency = "warn"
#
# [tool.uv]
# no-build = true
# exclude-newer = "P7D"
# require-build-hashes = true
# preview-features = ["build-dependency-hashes", "missing-exclude-newer-package-lock"]
# ///
from __future__ import annotations
import json
import pathlib
import subprocess
import tomllib
NO_BUMP_CRATES = {"ruff", "ruff_linter", "ruff_wasm"}
def main() -> None:
# Pre-sync NO_BUMP_CRATES versions before running cargo metadata.
#
# Rooster updates crate versions but isn't workspace-aware, so it doesn't update
# the workspace dependency pins in the root Cargo.toml. This can cause cargo metadata
# to fail with version mismatches (e.g., when going from 0.15.x to 0.16.0).
# We fix the pins first by reading the crate versions directly.
script_dir = pathlib.Path(__file__).parent
workspace_manifest = script_dir.parent / "Cargo.toml"
workspace_manifest_contents = workspace_manifest.read_text()
parsed_workspace_manifest = tomllib.loads(workspace_manifest_contents)
for crate_name in NO_BUMP_CRATES:
crate_manifest = script_dir.parent / "crates" / crate_name / "Cargo.toml"
if not crate_manifest.exists():
continue
crate_version = tomllib.loads(crate_manifest.read_text())["package"]["version"]
manifest_dependency = parsed_workspace_manifest["workspace"][
"dependencies"
].get(crate_name)
if manifest_dependency is None:
continue
manifest_version = manifest_dependency["version"]
if manifest_version != crate_version:
workspace_manifest_contents = workspace_manifest_contents.replace(
f'{crate_name} = {{ version = "{manifest_version}"',
f'{crate_name} = {{ version = "{crate_version}"',
)
workspace_manifest.write_text(workspace_manifest_contents)
# Now cargo metadata will succeed.
result = subprocess.run(
["cargo", "metadata", "--format-version", "1"],
capture_output=True,
text=True,
check=True,
)
content = json.loads(result.stdout)
packages = {package["id"]: package for package in content["packages"]}
workspace_manifest_contents = workspace_manifest.read_text()
parsed_workspace_manifest = tomllib.loads(workspace_manifest_contents)
version_changes = {}
for workspace_member in content["workspace_members"]:
manifest = pathlib.Path(packages[workspace_member]["manifest_path"])
name = packages[workspace_member]["name"]
# Only crates released to crates.io participate in the workspace release sequence.
if packages[workspace_member].get("publish") == []:
continue
# For the members we're not bumping, we'll still make sure that the version pinned in the
# workspace manifest matches the version of the crate. This is done because Rooster isn't
# Cargo workspace aware and won't otherwise bump these when updating the member `Cargo.toml`
# files.
if name in NO_BUMP_CRATES:
manifest_dependency = parsed_workspace_manifest["workspace"][
"dependencies"
].get(name)
if manifest_dependency is None:
continue
manifest_version = manifest_dependency["version"]
metadata_version = packages[workspace_member]["version"]
if manifest_version != metadata_version:
version_changes[name] = (manifest_version, metadata_version)
continue
# For other members, bump the patch version.
version = packages[workspace_member]["version"]
version_parts = [int(part) for part in version.split(".")]
new_version = f"{version_parts[0]}.{version_parts[1]}.{version_parts[2] + 1}"
contents = manifest.read_text()
contents = contents.replace(
'version = "' + version + '"',
'version = "' + new_version + '"',
1,
)
version_changes[name] = (version, new_version)
manifest.write_text(contents)
# Update all the pins in the workspace root.
for name, (old_version, new_version) in version_changes.items():
workspace_manifest_contents = workspace_manifest_contents.replace(
f'{name} = {{ version = "{old_version}"',
f'{name} = {{ version = "{new_version}"',
)
workspace_manifest.write_text(workspace_manifest_contents)
if __name__ == "__main__":
main()