use crate::{Command, CommandExt, ResultExt};
use std::borrow::Borrow;
use std::cmp::Ordering;
use std::collections::BTreeSet;
use std::ffi::*;
use std::fmt::{self, Display, Formatter};
use std::hash::{Hash, Hasher};
use std::io;
use std::process::Stdio;
use std::sync::Arc;
pub struct Rustup { rustup: Arc<OsString> }
impl Rustup {
pub fn default() -> io::Result<Self> { Self::new("rustup") }
pub fn new(rustup: impl AsRef<OsStr> + Into<OsString>) -> io::Result<Self> {
let rustup = rustup.into();
Command::new(&rustup).arg("--version").stdout(|| Stdio::null()).stderr(|| Stdio::null()).status0()?;
Ok(Self { rustup: Arc::new(rustup.into()) })
}
pub fn new_unchecked(rustup: impl AsRef<OsStr> + Into<OsString>) -> Self {
let rustup = rustup.into();
Self { rustup: Arc::new(rustup.into()) }
}
#[cfg(feature = "version")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "version")))]
pub fn version(&self) -> crate::Version {
Command::new(self.rustup.as_os_str()).arg("--version").stderr(|| Stdio::null()).stdout0().unwrap().parse().unwrap()
}
pub fn is_available(&self) -> bool {
Command::new(self.rustup.as_os_str()).arg("--version").stdout(|| Stdio::null()).stderr(|| Stdio::null()).status().map_or(false, |c| c.code() == Some(0))
}
pub fn toolchains(&self) -> RustupToolchains {
RustupToolchains { rustup: &self.rustup }
}
}
pub struct RustupToolchains<'r> { rustup: &'r Arc<OsString> }
impl<'r> RustupToolchains<'r> {
pub fn active(&self) -> Option<Toolchain> {
let o = self.rustup(&["show", "active-toolchain"]).stdout0_no_stderr().ok()?;
let o = o.trim().split(' ').next()?;
Some(Toolchain { rustup: self.rustup.clone(), toolchain: Arc::new(o.into()) })
}
pub fn default(&self) -> Option<Toolchain> {
let o = self.rustup(&["default"]).stdout0_no_stderr().or_die();
let o = o.trim().split(' ').next()?;
Some(Toolchain { rustup: self.rustup.clone(), toolchain: Arc::new(o.into()) })
}
pub fn installed(&self) -> BTreeSet<Toolchain> {
let o = self.rustup(&["toolchain", "list"]).stdout0().or_die();
let mut r = BTreeSet::new();
for line in o.lines() {
let line = line.trim().split(' ').next().unwrap_or("");
if line.is_empty() { continue }
r.extend(Some(Toolchain { rustup: self.rustup.clone(), toolchain: Arc::new(line.into()) }));
}
r
}
pub fn get(&self, toolchain: impl AsRef<str>) -> Option<Toolchain> {
let toolchain = toolchain.as_ref();
let o = self.rustup(&[&format!("+{}", toolchain), "show", "active-toolchain"]).env_remove("RUSTUP_TOOLCHAIN").stdout0_no_stderr().ok()?;
let o = o.trim().split(' ').next()?;
Some(Toolchain { rustup: self.rustup.clone(), toolchain: Arc::new(o.into()) })
}
pub fn install(&self, toolchain: impl AsRef<str>) -> io::Result<Toolchain> {
self.rustup(&["toolchain", "install", toolchain.as_ref()]).stdout0()?;
self.get(toolchain.as_ref()).ok_or_else(|| io::Error::new(io::ErrorKind::NotFound, format!("unable to find toolchain {}", toolchain.as_ref())))
}
pub fn uninstall(&self, toolchain: impl AsRef<str>) -> io::Result<()> {
self.rustup(&["toolchain", "install", toolchain.as_ref()]).status0()
}
fn rustup<I: IntoIterator<Item = S>, S: AsRef<OsStr>>(&self, args: I) -> Command {
let mut c = Command::new(self.rustup.as_os_str());
c.args(args);
c
}
}
#[derive(Clone, Debug)] pub struct Toolchain { rustup: Arc<OsString>, toolchain: Arc<String> }
impl AsRef<str> for Toolchain { fn as_ref(&self) -> &str { &self.toolchain } }
impl Borrow<str> for Toolchain { fn borrow(&self) -> &str { &self.toolchain } }
impl Display for Toolchain { fn fmt(&self, fmt: &mut Formatter) -> fmt::Result { Display::fmt(&self.toolchain, fmt) } }
impl Eq for Toolchain {}
impl Hash for Toolchain { fn hash<H: Hasher>(&self, state: &mut H) { self.toolchain.hash(state) } }
impl Ord for Toolchain { fn cmp(&self, other: &Self) -> Ordering { self.toolchain.cmp(&other.toolchain) } }
impl PartialEq for Toolchain { fn eq(&self, other: &Self) -> bool { self.toolchain == other.toolchain } }
impl PartialOrd for Toolchain { fn partial_cmp(&self, other: &Self) -> Option<Ordering> { self.toolchain.partial_cmp(&other.toolchain) } }
impl Toolchain {
pub fn as_str(&self) -> &str { &self.toolchain }
pub fn targets(&self) -> ToolchainTargets { ToolchainTargets { toolchain: self } }
pub fn cargo(&self) -> Command { self.run("cargo") }
pub fn rustc(&self) -> Command { self.run("rustc") }
fn run(&self, command: &str) -> Command {
let mut c = Command::new(self.rustup.as_os_str());
c.arg("run");
c.arg(self.toolchain.as_str());
c.arg(command);
c
}
}
pub struct ToolchainTargets<'t> { toolchain: &'t Toolchain }
impl<'t> ToolchainTargets<'t> {
pub fn all(&self) -> BTreeSet<Target> {
let o = self.rustup(&["target", "list"]).stdout0().or_die();
let mut r = BTreeSet::new();
for line in o.lines() {
let line = line.trim().split(' ').next().unwrap_or("");
if line.is_empty() { continue }
r.extend(Some(Target(line.into())));
}
r
}
pub fn installed(&self) -> BTreeSet<Target> {
let o = self.rustup(&["target", "list", "--installed"]).stdout0().or_die();
let mut r = BTreeSet::new();
for line in o.lines() {
let line = line.trim().split(' ').next().unwrap_or("");
if line.is_empty() { continue }
r.extend(Some(Target(line.into())));
}
r
}
pub fn get(&self, target: impl AsRef<str>) -> Option<Target> {
self.installed().take(target.as_ref())
}
pub fn add(&self, target: impl AsRef<str>) -> io::Result<()> {
if self.get(target.as_ref()).is_some() { return Ok(()) }
self.rustup(&["target", "add", target.as_ref()]).status0()
}
pub fn remove(&self, target: impl AsRef<str>) -> io::Result<()> {
self.rustup(&["target", "remove", target.as_ref()]).status0()
}
fn rustup<I: IntoIterator<Item = S>, S: AsRef<OsStr>>(&self, args: I) -> Command {
let mut c = Command::new(self.toolchain.rustup.as_os_str());
c.args(args);
c.arg("--toolchain");
c.arg(self.toolchain.toolchain.as_str());
c
}
}
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Target(String);
impl AsRef<str> for Target { fn as_ref(&self) -> &str { &self.0 } }
impl Borrow<str> for Target { fn borrow(&self) -> &str { &self.0 } }
impl Display for Target { fn fmt(&self, fmt: &mut Formatter) -> fmt::Result { Display::fmt(&self.0, fmt) } }
impl Target {
pub fn new(src: impl Into<Target>) -> Self { src.into() }
pub fn as_str(&self) -> &str { &self.0 }
}