lol
This commit is contained in:
parent
0cc3e123b2
commit
62848ea8c8
8 changed files with 2960 additions and 35 deletions
30
.github/workflows/build.yml
vendored
30
.github/workflows/build.yml
vendored
|
@ -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
2699
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
17
Cargo.toml
Normal file
17
Cargo.toml
Normal 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"] }
|
|
@ -1,2 +0,0 @@
|
||||||
FROM ubuntu:latest
|
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
services:
|
|
||||||
dev:
|
|
||||||
build: .
|
|
2
installers/install.sh
Normal file
2
installers/install.sh
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
curl
|
109
src/get_latest_release_info.rs
Normal file
109
src/get_latest_release_info.rs
Normal 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
133
src/main.rs
Normal 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(())
|
||||||
|
}
|
Loading…
Reference in a new issue