blob: 137c6809294a983c6449e74f9b3d5d36c06c1161 [file] [edit]
#!/usr/bin/env -S uv run --script
#
# /// script
# requires-python = ">=3.12"
# dependencies = [
# "pathspec>=1.1.1",
# ]
#
# [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"
# ///
"""Update script lockfiles.
Run `uv run scripts/check-scripts.py` to update the lockfiles.
Use `--write-ty-args-file <path>` to write the discovered scripts to an argument file for ty
instead of updating lockfiles.
"""
from __future__ import annotations
import argparse
import subprocess
import sys
from collections.abc import Iterator
from pathlib import Path
from pathspec import GitIgnoreSpec
ROOT = Path(__file__).resolve().parent.parent
# Crate resources and completion fixtures are inputs, not project scripts.
COMPLETION_FIXTURES = Path("crates/ty_completion_eval/truth")
def scripts() -> Iterator[Path]:
"""Yield paths to Python scripts containing PEP 723 metadata.
The paths are relative to the repository root. Files ignored by the root
`.gitignore`, symlinks, crate resources, and completion fixtures are
excluded. Scripts with invalid metadata are included so `uv lock` can
report the error.
"""
gitignore = GitIgnoreSpec.from_lines((ROOT / ".gitignore").read_text().splitlines())
# uv's script listing skips invalid metadata, so find candidates before
# asking uv to validate and lock them.
#
# We use `.walk()` here rather than `.rglob()` because some of our ignored directories are
# huge; `.walk()` allows us to lazily inspect whether they're gitignored before recursively
# descending into them.
for directory, dirnames, filenames in ROOT.walk():
dirnames[:] = [
dirname
for dirname in dirnames
if (path := (directory / dirname).relative_to(ROOT)) != Path(".git")
and not gitignore.match_file(f"{path.as_posix()}/")
]
for filename in filenames:
if not filename.endswith(".py"):
continue
full_path = directory / filename
path = full_path.relative_to(ROOT)
if path.is_relative_to(COMPLETION_FIXTURES):
continue
match full_path.relative_to(ROOT).parts:
case ("crates", _, "resources", *_):
continue
if (
full_path.is_symlink()
or not full_path.is_file()
or gitignore.match_file(path)
):
continue
if b"# /// script" in full_path.read_bytes().splitlines():
yield path
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__, allow_abbrev=False)
parser.add_argument(
"--write-ty-args-file",
type=Path,
help="Write script paths to an argument file for ty",
)
args = parser.parse_args()
if args.write_ty_args_file is not None:
args.write_ty_args_file.write_text(
"".join(f"{script}\n" for script in scripts()), encoding="utf-8"
)
return 0
failed = False
for script in scripts():
print(script, flush=True)
command = ["uv", "lock", "--script", str(script), "--refresh", "--no-locked"]
result = subprocess.run(command, cwd=ROOT, check=False)
failed |= result.returncode != 0
return int(failed)
if __name__ == "__main__":
sys.exit(main())