123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436 |
- use anyhow::{Context, Result, bail};
- use cache::relative_to_absolute;
- use clap::{Parser, Subcommand};
- use std::collections::HashMap;
- use std::env;
- use std::fs::{File, create_dir_all, read_dir, remove_dir_all, set_permissions};
- use std::io::{BufRead, BufReader};
- use std::path::PathBuf;
- use std::process::Command;
- use std::time::Duration;
- mod cache;
- #[derive(Parser)]
- #[command(
- about = "Go Updater",
- long_about = "Go Updater
- This checks https://go.dev/dl for newer versions of go.
- It depends upon go being in the path, and optionally GOPATH being set.
- This can't update a package manager installed version of go (permissions)."
- )]
- struct Cli {
-
- #[command(subcommand)]
- command: Option<Commands>,
- }
- #[derive(Subcommand)]
- enum Commands {
-
- Update {},
-
- Info {},
- }
- #[must_use]
- fn get_go_env() -> Result<HashMap<String, String>> {
- let output = Command::new("go").arg("env").output()?;
- if output.status.success() {
- let mut result = HashMap::<String, String>::new();
- let output_str = std::str::from_utf8(output.stdout.as_slice())?;
- for mut line in output_str.split("\n") {
- line = line.trim();
- if let Some(parts) = line.split_once("=") {
- let value = parts.1.trim_matches('\'');
- result.insert(parts.0.to_string(), value.to_string());
- }
- }
-
- Ok(result)
- } else {
- bail!("Failed to query `go env`");
- }
- }
- fn extract_tarball(tarball: &str, target: &str) -> Result<()> {
- println!("Extract {} to {}", tarball, target);
- let output = Command::new("tar")
-
- .arg("-xzf")
- .arg(tarball)
-
- .arg("--strip-components=1")
-
- .arg("-C")
- .arg(target)
- .output()?;
- if output.status.success() {
- return Ok(());
- }
- bail!("Extract {} failed.", tarball);
- }
- const GO_URL: &str = "https://go.dev/dl/";
- #[must_use]
- fn version_from_url(url: &str, arch: &str) -> Option<String> {
- if let Some(parts) = url.split_once(arch) {
- if let Some(part) = parts.0.rsplit_once("/go") {
- let part = part.1.trim_matches('.');
- return Some(part.to_string());
- }
- }
- None
- }
- #[must_use]
- fn just_href(link: &str) -> Result<String> {
- let parts = link.split_once("href=\"").unwrap_or(("", "")).1;
- let href = parts.split_once("\"").unwrap_or(("", "")).0;
- if !href.is_empty() {
- return Ok(href.to_string());
- }
- bail!("Unable to locate href");
- }
- #[must_use]
- fn find_arch_link(os_arch: &str, fp: &File) -> Result<String> {
- let reader = BufReader::new(fp);
- for line in reader.lines() {
- if let Ok(line) = line {
- if line.contains("a class=\"download\"") {
- if line.contains(os_arch) {
-
- return just_href(&line);
- }
- }
- }
- }
- bail!("Unable to locate OS architecture download link");
- }
- fn hashget(hash: &HashMap<String, String>, key: &str) -> String {
- if let Some(value) = hash.get(key) {
- return value.clone();
- }
- String::new()
- }
- fn recursive_remove_readonly(path: &str) -> Result<bool> {
- let mut result: bool = false;
- for entry in read_dir(path)? {
- let fsentry = entry?;
- if let Ok(meta) = fsentry.metadata() {
- if meta.is_dir() {
- if let Ok(r) =
- recursive_remove_readonly(&fsentry.path().to_string_lossy().to_string())
- {
- if r {
- result = true;
- }
- }
- }
- if meta.permissions().readonly() {
-
- let mut read = meta.permissions();
- read.set_readonly(false);
-
- set_permissions(fsentry.path(), read)?;
- result = true;
- }
- }
- }
- Ok(result)
- }
- fn main() -> Result<()> {
- let cli = Cli::parse();
-
- let home = env::var("HOME")?;
- let cache_dir = format!("{home}/.cache/go-up");
-
-
- let go_env = get_go_env().context("Calling `go env` failed.")?;
- let mut go_version = hashget(&go_env, "GOVERSION");
- if go_version.starts_with("go") {
- go_version.remove(0);
- go_version.remove(0);
- }
- let go_arch = hashget(&go_env, "GOARCH");
- let go_os = hashget(&go_env, "GOOS");
- let go_path = hashget(&go_env, "GOPATH");
- let go_root = hashget(&go_env, "GOROOT");
-
- let go_os_arch = format!("{go_os}-{go_arch}");
-
- let cache = cache::Cache::new(PathBuf::from(cache_dir), None)?;
- let ex = cache.expire(Duration::from_secs(7 * 60 * 60 * 24))?;
- if ex {
- println!("Expired files from cache.");
- }
- match &cli.command {
- Some(Commands::Update {}) => {
- let status = cache.fetch(GO_URL)?;
-
-
-
- let filename = status.download_path();
-
- let fp = File::open(filename)?;
- let link = find_arch_link(&go_os_arch, &fp);
- if let Ok(relative) = link {
-
- let latest_version = version_from_url(&relative, &go_os_arch);
- if let Some(latest) = latest_version {
- println!("Version: {} [have {}]", latest, go_version);
- if go_version != latest {
- println!("Downloading newer version...");
- let abs = relative_to_absolute(GO_URL, &relative).unwrap();
- let latest_status = cache.fetch(&abs)?;
- let update = latest_status.download_path();
- println!("Clearing GOROOT {}", go_root);
-
-
- recursive_remove_readonly(&go_path)?;
- remove_dir_all(&go_root)?;
- create_dir_all(&go_root)?;
- if !go_path.is_empty() {
- if go_path != go_root {
- println!("Clearing GOPATH {}", go_path);
-
- recursive_remove_readonly(&go_path)?;
- remove_dir_all(&go_path)?;
- create_dir_all(&go_path)?;
- }
- }
- println!("Extracting...");
- extract_tarball(&update.to_string_lossy().to_string(), &go_root)?;
-
-
-
- let new_go_env = get_go_env().context("Calling `go env` with new go.")?;
- let mut new_go_version = hashget(&new_go_env, "GOVERSION");
- if new_go_version.starts_with("go") {
- new_go_version.remove(0);
- new_go_version.remove(0);
- }
- println!("Updated {} to {}", go_version, new_go_version);
-
-
- } else {
- println!("You're already good to GO.");
- }
- } else {
- println!("Finding version failed: [{}]", relative);
- }
- } else {
- bail!("Unable to locate download link");
- }
-
- }
- Some(Commands::Info {}) => {
- println!("GOPATH {}", go_path);
- println!("GOROOT {}", go_root);
- println!("go ver {}", go_version);
- println!("go_arch {}", go_arch);
- println!("go os {}", go_os);
- println!("go os arch: {}", go_os_arch);
- }
- None => {
-
- let _show_help: Cli = Cli::parse_from(["", "--help"]);
- }
- }
- Ok(())
- }
|