Repository navigation
Expand file tree
/
Copy pathsetup_primer_project.py
More file actions
181 lines (153 loc) · 6.38 KB
/
Copy pathsetup_primer_project.py
File metadata and controls
181 lines (153 loc) · 6.38 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
#!/usr/bin/env -S uv run --script
#
# /// script
# requires-python = ">=3.11"
# dependencies = ["mypy-primer"]
#
# [tool.ty.rules]
# 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"
#
# [tool.uv]
# no-build = true
# no-binary-package = ["mypy-primer"]
# build-constraint-dependencies = ["setuptools==84.0.0"]
# # 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 the script's lockfile in sync with the mypy-primer pin in the project's uv.lock file
# # so memory reports and ecosystem jobs use the same project definitions.
# mypy-primer = { git = "https://github.com/hauntsaninja/mypy_primer" }
# ///
"""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()