|
8 | 8 | import argparse |
9 | 9 | import json |
10 | 10 | import os |
| 11 | +import re |
11 | 12 | import shutil |
12 | 13 | import subprocess |
13 | 14 | import sys |
|
27 | 28 | CURRENT_PYTHON_MM = sys.version_info[:2] |
28 | 29 | UV_CUTOFF = (3, 8) |
29 | 30 | USE_UV = CURRENT_PYTHON_MM >= UV_CUTOFF # Default to `uv` for python versions >= this |
| 31 | +KNOWN_ENTRYPOINTS = {PICKLEY: (PICKLEY,), "tox": ("tox",), "uv": ("uv", "uvx")} |
30 | 32 |
|
31 | 33 |
|
32 | 34 | class _Reporter: |
@@ -84,9 +86,8 @@ def seed_pickley_config(self, desired_cfg): |
84 | 86 | if not hdry(f"Would seed {msg}"): |
85 | 87 | Reporter.inform(f"Seeding {msg}") |
86 | 88 | ensure_folder(pickley_config.parent) |
87 | | - with open(pickley_config, "wt") as fh: |
88 | | - json.dump(desired_cfg, fh, sort_keys=True, indent=2) |
89 | | - fh.write("\n") |
| 89 | + payload = json.dumps(desired_cfg, sort_keys=True, indent=2) |
| 90 | + pickley_config.write_text(f"{payload}\n") |
90 | 91 |
|
91 | 92 | def bootstrap_pickley(self): |
92 | 93 | """Run `pickley bootstrap` in a temporary venv""" |
@@ -167,12 +168,36 @@ def __init__(self, pickley_base): |
167 | 168 | def auto_bootstrap_uv(self): |
168 | 169 | self.freshly_bootstrapped = self.bootstrap_reason() |
169 | 170 | if self.freshly_bootstrapped: |
170 | | - Reporter.trace(f"Auto-bootstrapping uv, reason: {self.freshly_bootstrapped}") |
| 171 | + Reporter.inform(f"Auto-bootstrapping uv, reason: {self.freshly_bootstrapped}") |
171 | 172 | uv_tmp = self.download_uv() |
172 | 173 | shutil.move(uv_tmp / "uv", self.pickley_base / "uv") |
173 | 174 | shutil.move(uv_tmp / "uvx", self.pickley_base / "uvx") |
174 | 175 | shutil.rmtree(uv_tmp, ignore_errors=True) |
175 | 176 |
|
| 177 | + # Touch cooldown file to let pickley know no need to check for uv upgrade for a while |
| 178 | + cooldown_relative_path = f"{DOT_META}/.cache/uv.cooldown" |
| 179 | + cooldown_path = self.pickley_base / cooldown_relative_path |
| 180 | + ensure_folder(cooldown_path.parent, dryrun=False) |
| 181 | + cooldown_path.write_text("") |
| 182 | + Reporter.debug(f"[bootstrap] Touched {cooldown_relative_path}") |
| 183 | + |
| 184 | + # Let pickley know which version of uv is installed |
| 185 | + uv_version = run_program(self.uv_path, "--version", fatal=False, dryrun=False) |
| 186 | + if uv_version: |
| 187 | + m = re.search(r"(\d+\.\d+\.\d+)", uv_version) |
| 188 | + if m: |
| 189 | + uv_version = m.group(1) |
| 190 | + manifest_relative_path = f"{DOT_META}/.manifest/uv.manifest.json" |
| 191 | + manifest_path = self.pickley_base / manifest_relative_path |
| 192 | + manifest = { |
| 193 | + "entrypoints": KNOWN_ENTRYPOINTS["uv"], |
| 194 | + "tracked_settings": {"auto_upgrade_spec": "uv"}, |
| 195 | + "version": uv_version, |
| 196 | + } |
| 197 | + ensure_folder(manifest_path.parent, dryrun=False) |
| 198 | + manifest_path.write_text(json.dumps(manifest)) |
| 199 | + Reporter.debug(f"[bootstrap] Saved {manifest_relative_path}") |
| 200 | + |
176 | 201 | def bootstrap_reason(self): |
177 | 202 | if not self.uv_path.exists(): |
178 | 203 | return "uv not present" |
@@ -210,8 +235,7 @@ def download_uv(self, version=None, dryrun=False): |
210 | 235 | def built_in_download(target, url): |
211 | 236 | request = Request(url) |
212 | 237 | response = urlopen(request, timeout=10) |
213 | | - with open(target, "wb") as fh: |
214 | | - fh.write(response.read()) |
| 238 | + target.write_bytes(response.read()) |
215 | 239 |
|
216 | 240 |
|
217 | 241 | def clean_env_vars(keys=("__PYVENV_LAUNCHER__", "CLICOLOR_FORCE", "PYTHONPATH")): |
@@ -320,9 +344,9 @@ def run_program(program, *args, **kwargs): |
320 | 344 | description = " ".join(short(x) for x in args) |
321 | 345 | description = f"{short(program)} {description}" |
322 | 346 | if not hdry(f"Would run: {description}", dryrun=kwargs.pop("dryrun", None)): |
| 347 | + Reporter.inform(f"Running: {description}") |
323 | 348 | if fatal: |
324 | 349 | stdout = stderr = None |
325 | | - Reporter.debug(f"Running: {description}") |
326 | 350 |
|
327 | 351 | else: |
328 | 352 | stdout = stderr = subprocess.PIPE |
@@ -350,13 +374,12 @@ def seed_mirror(mirror, path, section): |
350 | 374 | msg = f"{short(config_path)} with {mirror}" |
351 | 375 | if not hdry(f"Would seed {msg}"): |
352 | 376 | Reporter.inform(f"Seeding {msg}") |
353 | | - with open(config_path, "wt") as fh: |
354 | | - if section == "pip" and not mirror.startswith('"'): |
355 | | - # This assumes user passed a reasonable URL as --mirror, no further validation is done |
356 | | - # We only ensure the URL is quoted, as uv.toml requires it |
357 | | - mirror = f'"{mirror}"' |
| 377 | + if section == "pip" and not mirror.startswith('"'): |
| 378 | + # This assumes user passed a reasonable URL as --mirror, no further validation is done |
| 379 | + # We only ensure the URL is quoted, as uv.toml requires it |
| 380 | + mirror = f'"{mirror}"' |
358 | 381 |
|
359 | | - fh.write(f"[{section}]\nindex-url = {mirror}\n") |
| 382 | + config_path.write_text(f"[{section}]\nindex-url = {mirror}\n") |
360 | 383 |
|
361 | 384 | except Exception as e: |
362 | 385 | Reporter.inform(f"Seeding {path} failed: {e}") |
|
0 commit comments