Skip to content
Closed
Show file tree
Hide file tree
Changes from all 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: 3 additions & 3 deletions devenv/lib/archive.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ def download(
if os.path.exists(dest):
return dest

headers = {}
headers: dict[str, str] = {}
if url.startswith("https://ghcr.io/v2/homebrew"):
# downloading homebrew blobs requires auth
# you can get an anonymous token from https://ghcr.io/token?service=ghcr.io&scope=repository%3Ahomebrew/core/go%3Apull
Expand Down Expand Up @@ -87,8 +87,8 @@ def download(
if not secrets.compare_digest(checksum.hexdigest(), sha256):
raise RuntimeError(
f"checksum mismatch for {url}:\n"
f"- got: {checksum.hexdigest()}\n"
f"- expected: {sha256}\n"
+ f"- got: {checksum.hexdigest()}\n"
+ f"- expected: {sha256}\n"
)

atomic_replace(tmpf.name, dest)
Expand Down
64 changes: 64 additions & 0 deletions devenv/lib/bun.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
from __future__ import annotations

import os
import shutil
import stat
import tempfile
from zipfile import ZipFile

from devenv.lib import archive
from devenv.lib import fs
from devenv.lib import proc


def _install(url: str, sha256: str, into: str) -> None:
with tempfile.TemporaryDirectory(dir=into) as tmpd:
archive_file = archive.download(url, sha256, dest=f"{tmpd}/bun.zip")
os.makedirs(into, exist_ok=True)
with ZipFile(archive_file, mode="r") as zipf:
bun_member = next(
m for m in zipf.filelist if m.filename.endswith("/bun")
)
zipf.extract(bun_member, path=f"{tmpd}")

target = f"{into}/bun"
os.replace(f"{tmpd}/{bun_member.filename}", target)
mode = os.stat(target).st_mode
os.chmod(target, mode | stat.S_IEXEC)

os.symlink(target, f"{into}/bunx")
os.chmod(f"{into}/bunx", mode | stat.S_IEXEC)


def uninstall(binroot: str) -> None:
for fp in (f"{binroot}/bun", f"{binroot}/bunx"):
try:
os.remove(fp)
except FileNotFoundError:
# it's better to do this than to guard with
# os.path.exists(fp) because if it's an invalid or circular
# symlink the result'll be False!
pass


def _version(binpath: str) -> str:
return proc.run((binpath, "--version"), stdout=True).strip()


def install(version: str, url: str, sha256: str, reporoot: str) -> None:
binroot = fs.ensure_binroot(reporoot)
binpath = f"{binroot}/bun"

if shutil.which("bun", path=binroot) == binpath:
installed_version = _version(binpath)
if version == installed_version:
return
print(f"installed bun {installed_version} is unexpected!")

print(f"installing bun {version}...")
uninstall(binroot)
_install(url, sha256, binroot)

installed_version = _version(binpath)
if version != installed_version:
raise SystemExit("Failed to install bun {version}!")