This commit is contained in:
Michael Zhang 2024-11-03 02:59:57 -06:00
parent 0cc3e123b2
commit 62848ea8c8
8 changed files with 2960 additions and 35 deletions

View file

@ -1,30 +0,0 @@
name: Build Agda
on: [push]
jobs:
build:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, macOS-latest]
# agda-version: [2.6.2, 2.7.0]
steps:
- name: Cache cabal
uses: actions/cache@v4
with:
path: ~/.cabal
key: cabal-dir-${{ matrix.os }}
- uses: haskell-actions/setup@v2
with:
ghc-version: 9.8.2
cabal-version: 3.10.3.0
- run: mkdir -p installdir
- name: Install Agda
run: cabal install --installdir installdir Agda
- run: tar cvf agda.tar.gz -C "$(dirname $(dirname $(realpath installdir/agda)))" .
- uses: actions/upload-artifact@v4
with:
name: agda.tar.gz
path: agda.tar.gz

2699
Cargo.lock generated Normal file

File diff suppressed because it is too large Load diff

17
Cargo.toml Normal file
View file

@ -0,0 +1,17 @@
[package]
name = "agdaup"
version = "0.1.0"
edition = "2021"
[dependencies]
anyhow = "1.0.92"
clap = { version = "4.5.20", features = ["derive"] }
dirs = "5.0.1"
futures = "0.3.31"
manic = "0.8.1"
phf = { version = "0.11.2", features = ["macros"] }
reqwest = { version = "0.12.9", features = ["json", "stream"] }
serde = { version = "1.0.214", features = ["derive"] }
serde_json = "1.0.132"
tokio = { version = "1.41.0", features = ["macros", "rt-multi-thread"] }
zip-extract = { version = "0.2.1", features = ["bzip2", "chrono", "deflate", "deflate-flate2", "deflate-miniz", "deflate-zlib"] }

View file

@ -1,2 +0,0 @@
FROM ubuntu:latest

View file

@ -1,3 +0,0 @@
services:
dev:
build: .

2
installers/install.sh Normal file
View file

@ -0,0 +1,2 @@
#!/usr/bin/env bash
curl

View file

@ -0,0 +1,109 @@
use std::env::consts::{ARCH, OS};
use anyhow::{bail, Result};
use phf::phf_map;
use reqwest::{
header::{HeaderMap, HeaderValue},
Client, ClientBuilder,
};
#[derive(Debug, Serialize, Deserialize)]
pub struct Release {
pub id: u64,
pub tag_name: String,
pub assets: Vec<Asset>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Asset {
pub id: u64,
pub url: String,
pub size: u64,
pub name: String,
pub browser_download_url: String,
}
impl Asset {
pub fn get_asset_info(&self) -> AssetInfo {
let parts = self.name.split("-").collect::<Vec<_>>();
assert!(parts[0] == "agda");
let version = parts[1].to_owned();
let arch = parts[2].to_owned();
let os = parts
.iter()
.skip(3)
.take_while(|p| !p.starts_with("ghc"))
.map(|s| *s)
.collect::<Vec<_>>()
.join("-");
let ghc = parts[3 + os.split("-").count()]
.trim_start_matches("ghc")
.to_owned();
AssetInfo {
arch,
version,
os,
ghc,
}
}
}
#[derive(Debug)]
pub struct AssetInfo {
pub arch: String,
pub version: String,
pub os: String,
pub ghc: String,
}
static ALLOWED_ARCHS: phf::Map<&'static str, &'static [&'static str]> = phf_map! {
"x86_64" => &["x64"],
"aarch64" => &["arm64"],
};
static ALLOWED_OSS: phf::Map<&'static str, &'static [&'static str]> = phf_map! {
"macos" => &["macos"],
"windows" => &["windows"],
};
impl AssetInfo {
pub fn applies_to_this_machine(&self) -> bool {
let allowed_archs = match ALLOWED_ARCHS.get(&ARCH) {
Some(v) => v,
None => return false,
};
if !allowed_archs.contains(&self.arch.as_str()) {
return false;
}
let parts = self.os.split("-").collect::<Vec<_>>();
let allowed_oss = match ALLOWED_OSS.get(&OS) {
Some(v) => v,
None => return false,
};
if !allowed_oss.contains(&parts[0]) {
return false;
}
// Any more?
return true;
}
}
pub async fn get_latest_github_release_info(client: &Client) -> Result<Release> {
let resp = client
.get("https://api.github.com/repos/wenkokke/setup-agda/releases")
.send()
.await?;
let data: Vec<Release> = resp.json().await?;
let latest_release = match data.into_iter().find(|r| r.tag_name == "latest") {
Some(v) => v,
None => bail!("could not find latest release"),
};
Ok(latest_release)
}

133
src/main.rs Normal file
View file

@ -0,0 +1,133 @@
#[macro_use]
extern crate serde;
mod get_latest_release_info;
use std::{
fs::{self, File, Permissions},
io::Write,
os::unix::fs::PermissionsExt,
};
use anyhow::{bail, Context, Result};
use clap::{Parser, Subcommand};
use futures::StreamExt;
use get_latest_release_info::{Asset, AssetInfo};
use serde_json::Value;
#[derive(Debug, Parser)]
struct Opt {
#[clap(subcommand)]
command: Command,
}
#[derive(Debug, Subcommand)]
enum Command {
Show,
Update,
Install,
List {
/// List available versions
#[clap(short, long)]
available: bool,
},
}
// Rough layout
#[tokio::main]
async fn main() -> Result<()> {
let opt = Opt::parse();
println!("Hello, world!");
let client = {
use reqwest::{
header::{HeaderMap, HeaderValue},
ClientBuilder,
};
let mut h = HeaderMap::new();
h.insert("User-Agent", HeaderValue::from_str("mzhang28-agdaup")?);
ClientBuilder::new().default_headers(h).build()?
};
let bin_dir = dirs::executable_dir();
let data_dir = match dirs::data_dir() {
Some(v) => v.join("agdaup"),
None => bail!("no data dir?"),
};
let my_bin_dir = data_dir.join("bin");
match opt.command {
Command::Show => todo!(),
Command::Update => todo!(),
Command::Install => {
let release_info =
get_latest_release_info::get_latest_github_release_info(&client).await?;
let mut assets = release_info
.assets
.iter()
.filter(|asset| asset.get_asset_info().applies_to_this_machine())
.collect::<Vec<_>>();
assets.sort_by_key(|asset| &asset.name);
let desired_asset = assets[assets.len() - 1];
let asset_info = desired_asset.get_asset_info();
let version_dir = data_dir.join("versions").join(asset_info.version);
fs::remove_dir_all(&version_dir).context("could not remove version dir")?;
fs::create_dir_all(&version_dir).context("could not create version dir")?;
println!("Downloading {}", desired_asset.browser_download_url);
let resp = client
.get(&desired_asset.browser_download_url)
.send()
.await
.context("could not get url")?;
let download_path = version_dir.join("download.zip");
let mut stream = resp.bytes_stream();
{
let mut file = File::create(&download_path)?;
while let Some(chunk) = stream.next().await {
let chunk = chunk?;
file.write_all(&chunk)?;
}
}
{
let file = File::open(&download_path).context("could not open downloaded file")?;
zip_extract::extract(&file, &version_dir, true).context("could not extract")?;
}
#[cfg(unix)]
{
let agda_bin = version_dir.join("bin").join("agda");
let meta = fs::metadata(&agda_bin).context("could not get metadata")?;
let mut perm = meta.permissions();
perm.set_mode(perm.mode() | 0o100);
fs::set_permissions(&agda_bin, perm).context("could not set permission")?;
fs::create_dir_all(&my_bin_dir).context("could not create bin dir")?;
fs::remove_file(my_bin_dir.join("agda"))
.context("could not remove existing symlink")?;
std::os::unix::fs::symlink(&agda_bin, my_bin_dir.join("agda"))
.context("could not symlink")?;
if let Some(bin_dir) = bin_dir {
fs::remove_file(bin_dir.join("agda"));
std::os::unix::fs::symlink(&agda_bin, bin_dir.join("agda"))?;
}
}
}
Command::List { available } => todo!(),
}
Ok(())
}