mirror of
https://github.com/astral-sh/ruff.git
synced 2026-10-28 10:45:37 +08:00
<!-- Thank you for contributing to Ruff/ty! To help us out with reviewing, please consider the following: - Does this pull request include a summary of the change? (See below.) - Does this pull request include a descriptive title? (Please prefix with `[ty]` for ty pull requests.) - Does this pull request include references to any relevant issues? - Does this PR follow our AI policy (https://github.com/astral-sh/.github/blob/main/AI_POLICY.md)? --> ## Summary Adds explicit 7d cooldowns to all of our PEP 723 scripts, as well as similar cooldowns via `.npmrc`. <!-- What's the purpose of the change? What does it do, and why? --> ## Test Plan NFC. <!-- How was it tested? --> --- <sub>Stack created with <a href="https://github.com/github/gh-stack">GitHub Stacks CLI</a> • <a href="https://gh.io/stacks-feedback">Give Feedback 💬</a></sub>
170 lines
6.1 KiB
Python
170 lines
6.1 KiB
Python
#!/usr/bin/env -S uv run --script
|
|
#
|
|
# /// script
|
|
# requires-python = ">=3.11"
|
|
# dependencies = ["mypy-primer"]
|
|
#
|
|
# [tool.uv]
|
|
# # This is the default for ad hoc use. Historical ecosystem reproduction must
|
|
# # bypass the adjacent lock and select ecosystem-analyzer's exact mypy-primer
|
|
# # revision and project Python version, as shown in the module docstring.
|
|
# # `exclude-newer` still constrains mypy-primer's registry dependencies.
|
|
# exclude-newer = "P7D"
|
|
#
|
|
# [tool.uv.sources]
|
|
# # Keep this revision and the script's lockfile in sync with ecosystem-analyzer's
|
|
# # mypy-primer pin so memory reports and ecosystem jobs use the same project definitions.
|
|
# mypy-primer = { git = "https://github.com/hauntsaninja/mypy_primer", rev = "6d6eebd8d37c9b8931381e79aa99808d9378c988" }
|
|
# ///
|
|
|
|
"""Clone a mypy-primer project and set up a virtualenv with its dependencies installed.
|
|
|
|
For ecosystem-report reproduction, always select the project's ecosystem-analyzer Python version and bypass the adjacent lock with the exact mypy-primer revision pinned by ecosystem-analyzer:
|
|
|
|
uv run --python <version> --with "mypy-primer @ git+https://github.com/hauntsaninja/mypy_primer@<mypy-primer-revision>" --no-project python scripts/setup_primer_project.py <project-name> [directory] [options]
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import shlex
|
|
import subprocess
|
|
import sys
|
|
from pathlib import Path
|
|
from typing import NoReturn
|
|
|
|
from mypy_primer.model import Project
|
|
from mypy_primer.projects import get_projects
|
|
|
|
|
|
def find_project(name: str) -> Project:
|
|
projects = get_projects()
|
|
for p in projects:
|
|
if p.name == name:
|
|
return p
|
|
_project_not_found(name, projects)
|
|
|
|
|
|
def _project_not_found(name: str, projects: list[Project]) -> NoReturn:
|
|
print(f"error: project {name!r} not found", file=sys.stderr)
|
|
print("available projects:", file=sys.stderr)
|
|
for p in sorted(projects, key=lambda p: p.name):
|
|
print(f" {p.name}", file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
|
|
class _FormatMap:
|
|
def __init__(self, **values: str | list[str] | None) -> None:
|
|
self.values = values
|
|
|
|
def __getitem__(self, key: str) -> str:
|
|
if key not in self.values:
|
|
raise KeyError(key)
|
|
value = self.values[key]
|
|
if value is None:
|
|
raise ValueError(f"Required {key} to be specified")
|
|
if isinstance(value, list):
|
|
return " ".join(value)
|
|
return value
|
|
|
|
|
|
def get_ty_command(project: Project, *, ty_binary: str, venv_dir: Path) -> str:
|
|
ty_cmd = project.ty_cmd
|
|
if ty_cmd is None:
|
|
ty_cmd = "{ty} check {paths}" if project.paths else "{ty} check"
|
|
assert "{ty}" in ty_cmd
|
|
ty_cmd = ty_cmd.format_map(_FormatMap(ty=ty_binary, paths=project.paths))
|
|
return f"{ty_cmd} --python {shlex.quote(str(venv_dir))} --output-format concise"
|
|
|
|
|
|
def main() -> None:
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument("project", help="Name of a mypy-primer project")
|
|
parser.add_argument(
|
|
"directory",
|
|
nargs="?",
|
|
help="Directory to clone into (default: project name)",
|
|
)
|
|
parser.add_argument(
|
|
"--revision",
|
|
help="Git revision to check out before installing dependencies",
|
|
)
|
|
parser.add_argument(
|
|
"--exclude-newer",
|
|
help="Limit dependency resolution to packages uploaded before this timestamp",
|
|
)
|
|
parser.add_argument(
|
|
"--print-ty-command",
|
|
action="store_true",
|
|
help="Print the project-specific ty command without setting up the project",
|
|
)
|
|
args = parser.parse_args()
|
|
|
|
project = find_project(args.project)
|
|
revision = args.revision or project.revision
|
|
|
|
target_dir = Path(args.directory or project.name).resolve()
|
|
if args.print_ty_command:
|
|
print(get_ty_command(project, ty_binary="{ty}", venv_dir=target_dir / ".venv"))
|
|
return
|
|
|
|
# Use a full clone only when a historical ecosystem report revision must be checked out.
|
|
clone_cmd = [
|
|
"git",
|
|
"clone",
|
|
"--recurse-submodules",
|
|
project.location,
|
|
str(target_dir),
|
|
]
|
|
if not revision:
|
|
clone_cmd += ["--depth", "1"]
|
|
print(f"Cloning {project.location} into {target_dir}...")
|
|
subprocess.run(clone_cmd, check=True)
|
|
|
|
if revision:
|
|
print(f"Checking out revision {revision}...")
|
|
subprocess.run(["git", "checkout", revision], cwd=target_dir, check=True)
|
|
subprocess.run(
|
|
["git", "submodule", "update", "--init", "--recursive"],
|
|
cwd=target_dir,
|
|
check=True,
|
|
)
|
|
|
|
# Create venv (matching primer's Venv.make_venv())
|
|
venv_dir = target_dir / ".venv"
|
|
print(f"Creating virtualenv at {venv_dir}...")
|
|
subprocess.run(
|
|
["uv", "venv", str(venv_dir), "--python", sys.executable, "--seed", "--clear"],
|
|
check=True,
|
|
)
|
|
|
|
venv_python = venv_dir / "bin" / "python"
|
|
install_base = f"uv pip install --python {shlex.quote(str(venv_python))}"
|
|
if args.exclude_newer:
|
|
install_base += f" --exclude-newer {shlex.quote(args.exclude_newer)}"
|
|
|
|
# Run custom install command if the project defines one (matching primer's setup())
|
|
if project.install_cmd:
|
|
assert "{install}" in project.install_cmd
|
|
install_cmd = project.install_cmd.format(install=install_base)
|
|
print(f"Running install command: {install_cmd}")
|
|
# Primer install commands are trusted project metadata and may use shell syntax.
|
|
subprocess.run(install_cmd, cwd=target_dir, shell=True, check=True) # noqa: S602
|
|
|
|
# Install listed dependencies (matching primer's setup())
|
|
if project.deps:
|
|
deps_cmd_parts = shlex.split(install_base) + project.deps
|
|
print(f"Installing dependencies: {', '.join(project.deps)}")
|
|
subprocess.run(deps_cmd_parts, cwd=target_dir, check=True)
|
|
|
|
print(f"\nDone! Project set up at {target_dir}")
|
|
print(f"Activate the venv with: source {venv_dir}/bin/activate")
|
|
print("\nProject-specific ty command:")
|
|
print(" ty_binary=/path/to/ty")
|
|
ty_command = get_ty_command(project, ty_binary='"$ty_binary"', venv_dir=venv_dir)
|
|
print(f" {ty_command}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|