Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,10 @@ jobs:
with:
python-version: ${{ matrix.python-version }}

- run: pip install -U pip tox
- run: tox -e py
- uses: astral-sh/setup-uv@v5
- run: uv venv
- run: uv pip install -U tox-uv
- run: .venv/bin/tox -e py
- uses: codecov/codecov-action@v4
with:
files: .tox/test-reports/coverage.xml
Expand Down
3 changes: 1 addition & 2 deletions src/pickley/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@
"version",
"version_check_delay",
}
KNOWN_ENTRYPOINTS = {bstrap.PICKLEY: (bstrap.PICKLEY,), "tox": ("tox",), "uv": ("uv", "uvx")}
PLATFORM = platform.system().lower()


Expand Down Expand Up @@ -750,7 +749,7 @@ def configured_entrypoints(self, canonical_name) -> Optional[list]:
if value:
return value

return KNOWN_ENTRYPOINTS.get(canonical_name)
return bstrap.KNOWN_ENTRYPOINTS.get(canonical_name)

def require_bootstrap(self):
"""
Expand Down
49 changes: 36 additions & 13 deletions src/pickley/bstrap.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import argparse
import json
import os
import re
import shutil
import subprocess
import sys
Expand All @@ -27,6 +28,7 @@
CURRENT_PYTHON_MM = sys.version_info[:2]
UV_CUTOFF = (3, 8)
USE_UV = CURRENT_PYTHON_MM >= UV_CUTOFF # Default to `uv` for python versions >= this
KNOWN_ENTRYPOINTS = {PICKLEY: (PICKLEY,), "tox": ("tox",), "uv": ("uv", "uvx")}


class _Reporter:
Expand Down Expand Up @@ -84,9 +86,8 @@ def seed_pickley_config(self, desired_cfg):
if not hdry(f"Would seed {msg}"):
Reporter.inform(f"Seeding {msg}")
ensure_folder(pickley_config.parent)
with open(pickley_config, "wt") as fh:
json.dump(desired_cfg, fh, sort_keys=True, indent=2)
fh.write("\n")
payload = json.dumps(desired_cfg, sort_keys=True, indent=2)
pickley_config.write_text(f"{payload}\n")

def bootstrap_pickley(self):
"""Run `pickley bootstrap` in a temporary venv"""
Expand Down Expand Up @@ -167,12 +168,36 @@ def __init__(self, pickley_base):
def auto_bootstrap_uv(self):
self.freshly_bootstrapped = self.bootstrap_reason()
if self.freshly_bootstrapped:
Reporter.trace(f"Auto-bootstrapping uv, reason: {self.freshly_bootstrapped}")
Reporter.inform(f"Auto-bootstrapping uv, reason: {self.freshly_bootstrapped}")
uv_tmp = self.download_uv()
shutil.move(uv_tmp / "uv", self.pickley_base / "uv")
shutil.move(uv_tmp / "uvx", self.pickley_base / "uvx")
shutil.rmtree(uv_tmp, ignore_errors=True)

# Touch cooldown file to let pickley know no need to check for uv upgrade for a while
cooldown_relative_path = f"{DOT_META}/.cache/uv.cooldown"
cooldown_path = self.pickley_base / cooldown_relative_path
ensure_folder(cooldown_path.parent, dryrun=False)
cooldown_path.write_text("")
Reporter.debug(f"[bootstrap] Touched {cooldown_relative_path}")

# Let pickley know which version of uv is installed
uv_version = run_program(self.uv_path, "--version", fatal=False, dryrun=False)
if uv_version:
m = re.search(r"(\d+\.\d+\.\d+)", uv_version)
if m:
uv_version = m.group(1)
manifest_relative_path = f"{DOT_META}/.manifest/uv.manifest.json"
manifest_path = self.pickley_base / manifest_relative_path
manifest = {
"entrypoints": KNOWN_ENTRYPOINTS["uv"],
"tracked_settings": {"auto_upgrade_spec": "uv"},
"version": uv_version,
}
ensure_folder(manifest_path.parent, dryrun=False)
manifest_path.write_text(json.dumps(manifest))
Reporter.debug(f"[bootstrap] Saved {manifest_relative_path}")

def bootstrap_reason(self):
if not self.uv_path.exists():
return "uv not present"
Expand Down Expand Up @@ -210,8 +235,7 @@ def download_uv(self, version=None, dryrun=False):
def built_in_download(target, url):
request = Request(url)
response = urlopen(request, timeout=10)
with open(target, "wb") as fh:
fh.write(response.read())
target.write_bytes(response.read())


def clean_env_vars(keys=("__PYVENV_LAUNCHER__", "CLICOLOR_FORCE", "PYTHONPATH")):
Expand Down Expand Up @@ -320,9 +344,9 @@ def run_program(program, *args, **kwargs):
description = " ".join(short(x) for x in args)
description = f"{short(program)} {description}"
if not hdry(f"Would run: {description}", dryrun=kwargs.pop("dryrun", None)):
Reporter.inform(f"Running: {description}")
if fatal:
stdout = stderr = None
Reporter.debug(f"Running: {description}")

else:
stdout = stderr = subprocess.PIPE
Expand Down Expand Up @@ -350,13 +374,12 @@ def seed_mirror(mirror, path, section):
msg = f"{short(config_path)} with {mirror}"
if not hdry(f"Would seed {msg}"):
Reporter.inform(f"Seeding {msg}")
with open(config_path, "wt") as fh:
if section == "pip" and not mirror.startswith('"'):
# This assumes user passed a reasonable URL as --mirror, no further validation is done
# We only ensure the URL is quoted, as uv.toml requires it
mirror = f'"{mirror}"'
if section == "pip" and not mirror.startswith('"'):
# This assumes user passed a reasonable URL as --mirror, no further validation is done
# We only ensure the URL is quoted, as uv.toml requires it
mirror = f'"{mirror}"'

fh.write(f"[{section}]\nindex-url = {mirror}\n")
config_path.write_text(f"[{section}]\nindex-url = {mirror}\n")

except Exception as e:
Reporter.inform(f"Seeding {path} failed: {e}")
Expand Down
6 changes: 4 additions & 2 deletions src/pickley/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -309,7 +309,9 @@ def auto_upgrade_uv(cooldown_hours=12):
settings = TrackedSettings()
settings.auto_upgrade_spec = "uv"
pspec = PackageSpec("uv", settings=settings)
perform_upgrade(pspec)

# Automatic background upgrade of `uv` is not treated as fatal, for more resilience
perform_upgrade(pspec, fatal=False)


@main.command()
Expand Down Expand Up @@ -401,7 +403,7 @@ def bootstrap(base_folder, pickley_spec):
runez.Anchored.add(CFG.base)
setup_audit_log()
if bstrap.USE_UV:
auto_upgrade_uv(cooldown_hours=0)
auto_upgrade_uv()

bootstrap_marker = CFG.manifests / ".bootstrap.json"
if not bootstrap_marker.exists():
Expand Down
4 changes: 2 additions & 2 deletions tests/test_bootstrap.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ def test_bootstrap_command(cli):
assert CFG._uv_bootstrap.freshly_bootstrapped == "uv not present"
assert "Deleted .pk/uv-0.0.1" in cli.logged
assert "Auto-bootstrapping uv, reason: uv not present" in cli.logged
assert "Saved .pk/.manifest/uv.manifest.json" in cli.logged
assert "[bootstrap] Saved .pk/.manifest/uv.manifest.json" in cli.logged
assert CFG.program_version(".local/bin/uv")

else:
Expand Down Expand Up @@ -54,7 +54,7 @@ def test_bootstrap_script(cli, monkeypatch):

# Verify that uv is seeded even in dryrun mode
uv_path = CFG.resolved_path(".local/bin/uv")
assert not runez.is_executable(uv_path) # Not seed by conftest.py (it seeds ./uv)
assert not runez.is_executable(uv_path) # Not seeded by conftest.py (it seeds ./uv)

# Simulate bogus mirror, verify that we fail bootstrap in that case
cli.run("-nvv", cli.project_folder, "-mhttp://localhost:12345")
Expand Down
Loading