Skip to content

Commit

Permalink
Add toolchain
Browse files Browse the repository at this point in the history
  • Loading branch information
cnpryer committed Oct 14, 2023
1 parent 0f9e9d7 commit 4e85e4d
Show file tree
Hide file tree
Showing 9 changed files with 766 additions and 2 deletions.
6 changes: 4 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,15 @@ MANIFEST
.vscode/

# Environments
/.env
/.venv
.env
.venv
/env/
/venv/
/ENV/
/env.bak/
/venv.bak/

.github_token

# Misc.
.DS_Store
7 changes: 7 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 14 additions & 0 deletions crates/huak_python_manager/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
[package]
name = "huak_python_manager"
description = "A Python interpreter management system for Huak."
version = "0.0.0"
edition.workspace = true
rust-version.workspace = true
authors.workspace = true
license.workspace = true

[dependencies]
huak_home = { version = "0.0.0", path = "../huak_home" }

[lints]
workspace = true
18 changes: 18 additions & 0 deletions crates/huak_python_manager/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Python Manager

A Python interpreter management system for Huak.

## Usage

```
huak_python_manager install 3.11
```

## How it works

### Installing a Python interpreter

1. Fetch the interpreter from https://github.com/indygreg/python-build-standalone using GitHub API.
1. Validate the checksum of the interpreter.
1. Extract the interpreter using `tar`.
1. Place the interpreter in Huak's home directory (~/.huak/bin/).
2 changes: 2 additions & 0 deletions crates/huak_python_manager/requirements-dev.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
polars==0.19.8
requests==2.31.0
185 changes: 185 additions & 0 deletions crates/huak_python_manager/scripts/generate_python_releases.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
"""This module generates releases.rs for the huak_python_manager crate."""
import re
import subprocess
from typing import NamedTuple
import requests
from pathlib import Path
from urllib.parse import unquote
import polars as pl


FILE = Path(__file__)
ROOT = Path(
subprocess.check_output(["git", "rev-parse", "--show-toplevel"], text=True).strip()
)
CRATE = "huak_python_manager"
TOKEN = (FILE.parent / ".github_token").read_text().strip()

RELEASE_URL = "https://api.github.com/repos/indygreg/python-build-standalone/releases"
HEADERS = headers = {
"Accept": "application/vnd.github+json",
"Authorization": f"Bearer {TOKEN}",
}

VERSION_PATTERN = re.compile(r"cpython-(\d+\.\d+\.\d+)")
OS_PATTERN = re.compile(r"-(windows|apple|linux)-")
ARCHITECTURE_PATTERN = re.compile(r"-(aarch64|i686|x86_64)-")
BUILD_PATTERN = re.compile(r"-(pgo\+lto|pgo)-")


class Release(NamedTuple):
kind: str
version: str
os: str
architecture: str
build_configuration: str
checksum: str
url_suffix: str

def to_rust_string(self) -> str:
(major, minor, patch) = self.version.split(".")
version = f"Version::new({major}, {minor}, {patch})"
return f"""\
Release::new("{self.kind}", {version}, "{self.os}", "{self.architecture}", "{self.build_configuration}", "{self.checksum}", "{self.url_suffix}")\
""" # noqa


session = requests.Session()
release_json = session.get(RELEASE_URL).json()


def is_checksum_url(url: str) -> bool:
return url.endswith(".sha256") or url.endswith("SHA256SUMS")


def get_checksum(url: str) -> str | None:
res = session.get(url)
res.raise_for_status()
return res.text.strip()


generated = pl.read_parquet(FILE.parent / "generated_python_releases.parquet")
new_releases = {"url": [], "string": []}

# Identify releases with checksums published.
has_checksum = set()
for release in release_json:
for asset in release["assets"]:
if asset["browser_download_url"].endswith(".sha256"):
has_checksum.add(asset["browser_download_url"].removesuffix(".sha256"))


module = f"""\
//! This file was generated by `{FILE.name}`.
const DOWNLOAD_URL: &str = "https://github.com/indygreg/python-build-standalone/releases/download/";
#[rustfmt::skip]
pub const RELEASES: &[Release] = &[\
""" # noqa
for release in release_json:
for asset in release["assets"]:
# Avoid making requests for releases we've already generated.
matching = generated.filter(pl.col("url").eq(asset["browser_download_url"]))
if not matching.is_empty():
string = matching.select(pl.col("string")).to_series()[0]
module += "\n\t" + string + ","
continue

# Skip any releases that don't have checksums
if asset["browser_download_url"] not in has_checksum:
print(f"no checksum for {asset['name']}")
continue

url = unquote(asset["browser_download_url"])

# Skip builds not included in the pattern
build_matches = re.search(BUILD_PATTERN, url)
if not build_matches:
continue
build_str = build_matches.group(1)

# Skip architectures not included in the pattern
arch_matches = re.search(ARCHITECTURE_PATTERN, url)
if not arch_matches:
continue
arch_str = arch_matches.group(1)

checksum_str = get_checksum(asset["browser_download_url"] + ".sha256")
version_str = re.search(VERSION_PATTERN, url).group(1)
os_str = re.search(OS_PATTERN, url).group(1)
release = Release(
"cpython",
version_str,
os_str,
arch_str,
build_str,
checksum_str,
asset["browser_download_url"].removeprefix(
"https://github.com/indygreg/python-build-standalone/releases/download/"
),
)
new_releases["url"].append(asset["browser_download_url"])
new_releases["string"].append(release.to_rust_string())
module += "\n\t" + release.to_rust_string() + ","
module += """\n];
pub struct Release<'a> {
pub kind: &'a str,
pub version: Version,
pub os: &'a str,
pub architecture: &'a str,
pub build_configuration: &'a str,
pub checksum: &'a str,
url_suffix: &'a str,
}
impl Release<'static> {
const fn new(
kind: &'static str,
version: Version,
os: &'static str,
architecture: &'static str,
build_configuration: &'static str,
checksum: &'static str,
url_suffix: &'static str,
) -> Self {
Self {
kind,
version,
os,
architecture,
build_configuration,
checksum,
url_suffix,
}
}
pub fn url(&self) -> String {
format!("{}{}", DOWNLOAD_URL, self.url_suffix)
}
}
pub struct Version {
pub major: u8,
pub minor: u8,
pub patch: u8,
}
impl Version {
const fn new(major: u8, minor: u8, patch: u8) -> Self {
Self {
major,
minor,
patch,
}
}
}
"""

path = ROOT / "crates" / CRATE / "src" / "releases.rs"
path.write_text(module)

new_releases = pl.DataFrame(new_releases, schema={"url": pl.Utf8, "string": pl.Utf8})
path = FILE.parent / "generated_python_releases.parquet"
pl.concat((generated, new_releases)).write_parquet(path)
Binary file not shown.
7 changes: 7 additions & 0 deletions crates/huak_python_manager/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
use huak_home::huak_home_dir;
mod releases;

fn main() {
println!("{:?}", huak_home_dir());
println!("{:?}", releases::RELEASES[0].url());
}
Loading

0 comments on commit 4e85e4d

Please sign in to comment.