use std::collections::HashMap;
use std::path::{Path, PathBuf};
use eyre::Result;
use itertools::Itertools;
use versions::Versioning;
use crate::cli::version::{ARCH, OS};
use crate::cmd::CmdLineRunner;
use crate::config::Config;
use crate::http::HTTP;
use crate::install_context::InstallContext;
use crate::plugins::core::CorePlugin;
use crate::plugins::Plugin;
use crate::toolset::{ToolVersion, Toolset};
use crate::ui::progress_report::SingleReport;
use crate::{cmd, env, file, hash};
#[derive(Debug)]
pub struct GoPlugin {
core: CorePlugin,
}
impl GoPlugin {
pub fn new() -> Self {
Self {
core: CorePlugin::new("go"),
}
}
fn fetch_remote_versions(&self) -> Result<Vec<String>> {
match self.core.fetch_remote_versions_from_rtx() {
Ok(Some(versions)) => return Ok(versions),
Ok(None) => {}
Err(e) => warn!("failed to fetch remote versions: {}", e),
}
CorePlugin::run_fetch_task_with_timeout(move || {
let repo = &*env::RTX_GO_REPO;
let output = cmd!("git", "ls-remote", "--tags", repo, "go*").read()?;
let lines = output.split('\n');
let versions = lines.map(|s| s.split("/go").last().unwrap_or_default().to_string())
.filter(|s| !s.is_empty())
.filter(|s| !regex!(r"^1($|\.0|\.0\.[0-9]|\.1|\.1rc[0-9]|\.1\.[0-9]|.2|\.2rc[0-9]|\.2\.1|.8.5rc5)$").is_match(s))
.unique()
.sorted_by_cached_key(|s| (Versioning::new(s), s.to_string()))
.collect();
Ok(versions)
})
}
fn goroot(&self, tv: &ToolVersion) -> PathBuf {
tv.install_path().join("go")
}
fn go_bin(&self, tv: &ToolVersion) -> PathBuf {
self.goroot(tv).join("bin/go")
}
fn gopath(&self, tv: &ToolVersion) -> PathBuf {
tv.install_path().join("packages")
}
fn install_default_packages(&self, tv: &ToolVersion, pr: &dyn SingleReport) -> Result<()> {
let body = file::read_to_string(&*env::RTX_GO_DEFAULT_PACKAGES_FILE).unwrap_or_default();
for package in body.lines() {
let package = package.split('#').next().unwrap_or_default().trim();
if package.is_empty() {
continue;
}
pr.set_message(format!("installing default package: {}", package));
let package = if package.contains('@') {
package.to_string()
} else {
format!("{}@latest", package)
};
let mut env = HashMap::new();
if *env::RTX_GO_SET_GOROOT != Some(false) {
env.insert("GOROOT", self.goroot(tv));
}
if *env::RTX_GO_SET_GOPATH != Some(false) {
env.insert("GOPATH", self.gopath(tv));
}
CmdLineRunner::new(self.go_bin(tv))
.with_pr(pr)
.arg("install")
.arg(package)
.envs(env)
.execute()?;
}
Ok(())
}
fn test_go(&self, tv: &ToolVersion, pr: &dyn SingleReport) -> Result<()> {
pr.set_message("go version".into());
CmdLineRunner::new(self.go_bin(tv))
.with_pr(pr)
.arg("version")
.execute()
}
fn download(&self, tv: &ToolVersion, pr: &dyn SingleReport) -> Result<PathBuf> {
let filename = format!("go{}.{}-{}.tar.gz", tv.version, platform(), arch());
let tarball_url = format!("{}/{}", &*env::RTX_GO_DOWNLOAD_MIRROR, &filename);
let tarball_path = tv.download_path().join(filename);
pr.set_message(format!("downloading {}", &tarball_url));
HTTP.download_file(&tarball_url, &tarball_path)?;
self.verify_tarball_checksum(&tarball_url, &tarball_path)?;
Ok(tarball_path)
}
fn verify_tarball_checksum(&self, tarball_url: &str, tarball_path: &Path) -> Result<()> {
if !*env::RTX_GO_SKIP_CHECKSUM {
let checksum_url = format!("{}.sha256", tarball_url);
let checksum = HTTP.get_text(checksum_url)?;
hash::ensure_checksum_sha256(tarball_path, &checksum)?;
}
Ok(())
}
fn install(&self, tv: &ToolVersion, pr: &dyn SingleReport, tarball_path: &Path) -> Result<()> {
let tarball = tarball_path
.file_name()
.unwrap_or_default()
.to_string_lossy();
pr.set_message(format!("installing {}", tarball));
file::untar(tarball_path, &tv.install_path())?;
Ok(())
}
fn verify(&self, tv: &ToolVersion, pr: &dyn SingleReport) -> Result<()> {
self.test_go(tv, pr)?;
self.install_default_packages(tv, pr)
}
}
impl Plugin for GoPlugin {
fn name(&self) -> &str {
"go"
}
fn list_remote_versions(&self) -> Result<Vec<String>> {
self.core
.remote_version_cache
.get_or_try_init(|| self.fetch_remote_versions())
.cloned()
}
fn legacy_filenames(&self) -> Result<Vec<String>> {
Ok(vec![".go-version".into()])
}
fn install_version_impl(&self, ctx: &InstallContext) -> Result<()> {
let tarball_path = self.download(&ctx.tv, ctx.pr.as_ref())?;
self.install(&ctx.tv, ctx.pr.as_ref(), &tarball_path)?;
self.verify(&ctx.tv, ctx.pr.as_ref())?;
Ok(())
}
fn uninstall_version_impl(&self, _pr: &dyn SingleReport, tv: &ToolVersion) -> Result<()> {
let gopath = self.gopath(tv);
if gopath.exists() {
cmd!("chmod", "-R", "u+wx", gopath).run()?;
}
Ok(())
}
fn list_bin_paths(&self, tv: &ToolVersion) -> Result<Vec<PathBuf>> {
let mut paths = vec![self.goroot(tv).join("bin")];
if *env::RTX_GO_SET_GOPATH != Some(false) {
paths.push(self.gopath(tv).join("bin"));
}
Ok(paths)
}
fn exec_env(
&self,
_config: &Config,
_ts: &Toolset,
tv: &ToolVersion,
) -> Result<HashMap<String, String>> {
let mut map = HashMap::new();
match (*env::RTX_GO_SET_GOROOT, env::PRISTINE_ENV.get("GOROOT")) {
(Some(false), _) | (None, Some(_)) => {}
(Some(true), _) | (None, None) => {
let goroot = self.goroot(tv).to_string_lossy().to_string();
map.insert("GOROOT".to_string(), goroot);
}
};
match (*env::RTX_GO_SET_GOPATH, env::PRISTINE_ENV.get("GOPATH")) {
(Some(false), _) | (None, Some(_)) => {}
(Some(true), _) | (None, None) => {
let gopath = self.gopath(tv).to_string_lossy().to_string();
map.insert("GOPATH".to_string(), gopath);
}
};
Ok(map)
}
}
fn platform() -> &'static str {
if cfg!(target_os = "macos") {
"darwin"
} else {
&OS
}
}
fn arch() -> &'static str {
if cfg!(target_arch = "x86_64") || cfg!(target_arch = "amd64") {
"amd64"
} else if cfg!(target_arch = "i686") || cfg!(target_arch = "i386") || cfg!(target_arch = "386")
{
"386"
} else if cfg!(target_arch = "armv6l") || cfg!(target_arch = "armv7l") {
"armv6l"
} else if cfg!(target_arch = "aarch64") || cfg!(target_arch = "arm64") {
"arm64"
} else {
&ARCH
}
}