|
@@ -2,8 +2,8 @@ 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;
|
|
|
+// 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;
|
|
@@ -16,7 +16,7 @@ mod cache;
|
|
|
long_about = "Go Updater
|
|
|
|
|
|
This checks the https://go.dev/dl for newer versions of go.
|
|
|
-It depends upon the GOPATH and GOROOT environment variables being set.
|
|
|
+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)."
|
|
|
)]
|
|
@@ -64,6 +64,25 @@ fn get_go_env() -> Result<HashMap<String, String>> {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+fn extract_tarball(tarball: &str, target: &str) -> Result<()> {
|
|
|
+ println!("Extract {} to {}", tarball, target);
|
|
|
+ let output = Command::new("tar")
|
|
|
+ // Extract, gzipped, from file
|
|
|
+ .arg("-xzf")
|
|
|
+ .arg(tarball)
|
|
|
+ // archive contains go directory. Strip that out.
|
|
|
+ .arg("--strip-components=1")
|
|
|
+ // Set target to extract to.
|
|
|
+ .arg("-C")
|
|
|
+ .arg(target)
|
|
|
+ .output()?;
|
|
|
+ if output.status.success() {
|
|
|
+ return Ok(());
|
|
|
+ }
|
|
|
+ bail!("Extract {} failed.", tarball);
|
|
|
+}
|
|
|
+
|
|
|
+/*
|
|
|
/// Query `go version`
|
|
|
#[must_use]
|
|
|
#[allow(dead_code)]
|
|
@@ -76,7 +95,9 @@ fn find_go_version() -> Result<String> {
|
|
|
}
|
|
|
bail!("Failed to query go version.");
|
|
|
}
|
|
|
+*/
|
|
|
|
|
|
+/*
|
|
|
/// Locate the go binary
|
|
|
///
|
|
|
/// This is redundant, it should be located via GO_PATH.
|
|
@@ -89,6 +110,7 @@ fn find_go() -> Result<String> {
|
|
|
}
|
|
|
bail!("Failed to locate go binary.");
|
|
|
}
|
|
|
+*/
|
|
|
|
|
|
const GO_URL: &str = "https://go.dev/dl/";
|
|
|
|
|
@@ -122,8 +144,12 @@ and grab the section of td's. class=filename has a href, last has SHA256.
|
|
|
*/
|
|
|
|
|
|
// URL: https://go.dev/dl/go1.24.1.linux-amd64.tar.gz
|
|
|
-#[must_use]
|
|
|
+
|
|
|
/// Get go version from download link.
|
|
|
+///
|
|
|
+/// Given https://go.dev/dl/go1.24.1.linux-amd64.tar.gz
|
|
|
+/// return "1.24.1" version part.
|
|
|
+#[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") {
|
|
@@ -145,9 +171,9 @@ fn just_href(link: &str) -> Result<String> {
|
|
|
bail!("Unable to locate href");
|
|
|
}
|
|
|
|
|
|
-/// Find a href link for given os and arch (architecture)
|
|
|
+/// Find a href link for given os and arch (architecture) in a file.
|
|
|
///
|
|
|
-/// Look for <a class="download" href="
|
|
|
+/// Look for a line with <a class="download" href="
|
|
|
#[must_use]
|
|
|
fn find_arch_link(os_arch: &str, fp: &File) -> Result<String> {
|
|
|
let reader = BufReader::new(fp);
|
|
@@ -171,7 +197,7 @@ fn find_arch_link(os_arch: &str, fp: &File) -> Result<String> {
|
|
|
///
|
|
|
/// Look for <a class="download" href=""
|
|
|
#[must_use]
|
|
|
-#[deprecated]
|
|
|
+#[deprecated = "Use find_arch_link don't store the HTML in memory."]
|
|
|
fn find_link(arch: &str) -> Result<String> {
|
|
|
// , Box<dyn Error>> {
|
|
|
let fp = File::open(GO_FILE)?;
|
|
@@ -193,6 +219,7 @@ fn find_link(arch: &str) -> Result<String> {
|
|
|
/// Get value from given HashMap<String,String>
|
|
|
///
|
|
|
/// This gives us a String, not a &String, and not an Option.
|
|
|
+/// Returns empty String if key not found.
|
|
|
fn hashget(hash: &HashMap<String, String>, key: &str) -> String {
|
|
|
if let Some(value) = hash.get(key) {
|
|
|
return value.clone();
|
|
@@ -200,6 +227,33 @@ fn hashget(hash: &HashMap<String, String>, key: &str) -> String {
|
|
|
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() {
|
|
|
+ // println!("Fixing: {}", fsentry.path().display());
|
|
|
+ let mut read = meta.permissions();
|
|
|
+ read.set_readonly(false);
|
|
|
+ // Ok, this is marked readonly, change it.
|
|
|
+ set_permissions(fsentry.path(), read)?;
|
|
|
+ result = true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ Ok(result)
|
|
|
+}
|
|
|
+
|
|
|
fn main() -> Result<()> {
|
|
|
let cli = Cli::parse();
|
|
|
|
|
@@ -213,9 +267,12 @@ fn main() -> Result<()> {
|
|
|
I want to be a bit looser on requiring GOROOT later on.
|
|
|
*/
|
|
|
|
|
|
+ /*
|
|
|
// Get go environment
|
|
|
+ // The old way of doing this. Using `go env` now.
|
|
|
let go_path = env::var("GOPATH").unwrap_or(String::new());
|
|
|
let go_root = env::var("GOROOT").unwrap_or(String::new());
|
|
|
+ */
|
|
|
|
|
|
let go_env = get_go_env().context("Calling `go env` failed.")?;
|
|
|
let mut go_version = hashget(&go_env, "GOVERSION");
|
|
@@ -261,8 +318,35 @@ fn main() -> Result<()> {
|
|
|
let latest_status = cache.fetch(&abs)?;
|
|
|
let update = latest_status.download_path();
|
|
|
|
|
|
+ println!("Clearing GOROOT {}", go_root);
|
|
|
+ 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);
|
|
|
+ // This fails with permissions error
|
|
|
+ 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)?;
|
|
|
+ // Extract go.tar.gz into go_root
|
|
|
+
|
|
|
// Ok, we have the update! Now what?
|
|
|
- println!("Ready to install update.");
|
|
|
+ // Verify new GO version!
|
|
|
+ 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);
|
|
|
+
|
|
|
+
|
|
|
/*
|
|
|
Clear GOROOT.
|
|
|
mkdir_all(GOROOT).
|