blob: 79f2ef8dfc450cff1196bf1c03982560317cd9c7 [file] [log] [blame]
use anyhow::Error;
use flate2::read::GzDecoder;
use std::collections::HashMap;
use std::fs::File;
use std::io::Read;
use std::path::{Path, PathBuf};
use tar::Archive;
const DEFAULT_TARGET: &str = "x86_64-unknown-linux-gnu";
const RUSTC_VERSION: &str = include_str!("../../../version");
#[derive(Debug, Hash, Eq, PartialEq, Clone)]
pub(crate) enum PkgType {
Rust,
RustSrc,
Cargo,
Rls,
RustAnalyzer,
Clippy,
Rustfmt,
LlvmTools,
Miri,
Other(String),
}
impl PkgType {
pub(crate) fn from_component(component: &str) -> Self {
match component {
"rust" => PkgType::Rust,
"rust-src" => PkgType::RustSrc,
"cargo" => PkgType::Cargo,
"rls" | "rls-preview" => PkgType::Rls,
"rust-analyzer" | "rust-analyzer-preview" => PkgType::RustAnalyzer,
"clippy" | "clippy-preview" => PkgType::Clippy,
"rustfmt" | "rustfmt-preview" => PkgType::Rustfmt,
"llvm-tools" | "llvm-tools-preview" => PkgType::LlvmTools,
"miri" | "miri-preview" => PkgType::Miri,
other => PkgType::Other(other.into()),
}
}
/// First part of the tarball name.
fn tarball_component_name(&self) -> &str {
match self {
PkgType::Rust => "rust",
PkgType::RustSrc => "rust-src",
PkgType::Cargo => "cargo",
PkgType::Rls => "rls",
PkgType::RustAnalyzer => "rust-analyzer",
PkgType::Clippy => "clippy",
PkgType::Rustfmt => "rustfmt",
PkgType::LlvmTools => "llvm-tools",
PkgType::Miri => "miri",
PkgType::Other(component) => component,
}
}
/// Whether this package has the same version as Rust itself, or has its own `version` and
/// `git-commit-hash` files inside the tarball.
fn should_use_rust_version(&self) -> bool {
match self {
PkgType::Cargo => false,
PkgType::Rls => false,
PkgType::RustAnalyzer => false,
PkgType::Clippy => false,
PkgType::Rustfmt => false,
PkgType::LlvmTools => false,
PkgType::Miri => false,
PkgType::Rust => true,
PkgType::RustSrc => true,
PkgType::Other(_) => true,
}
}
/// Whether this package is target-independent or not.
fn target_independent(&self) -> bool {
*self == PkgType::RustSrc
}
}
#[derive(Debug, Default, Clone)]
pub(crate) struct VersionInfo {
pub(crate) version: Option<String>,
pub(crate) git_commit: Option<String>,
pub(crate) present: bool,
}
pub(crate) struct Versions {
channel: String,
dist_path: PathBuf,
versions: HashMap<PkgType, VersionInfo>,
}
impl Versions {
pub(crate) fn new(channel: &str, dist_path: &Path) -> Result<Self, Error> {
Ok(Self { channel: channel.into(), dist_path: dist_path.into(), versions: HashMap::new() })
}
pub(crate) fn channel(&self) -> &str {
&self.channel
}
pub(crate) fn version(&mut self, mut package: &PkgType) -> Result<VersionInfo, Error> {
if package.should_use_rust_version() {
package = &PkgType::Rust;
}
match self.versions.get(package) {
Some(version) => Ok(version.clone()),
None => {
let version_info = self.load_version_from_tarball(package)?;
self.versions.insert(package.clone(), version_info.clone());
Ok(version_info)
}
}
}
fn load_version_from_tarball(&mut self, package: &PkgType) -> Result<VersionInfo, Error> {
let tarball_name = self.tarball_name(package, DEFAULT_TARGET)?;
let tarball = self.dist_path.join(tarball_name);
let file = match File::open(&tarball) {
Ok(file) => file,
Err(err) if err.kind() == std::io::ErrorKind::NotFound => {
// Missing tarballs do not return an error, but return empty data.
return Ok(VersionInfo::default());
}
Err(err) => return Err(err.into()),
};
let mut tar = Archive::new(GzDecoder::new(file));
let mut version = None;
let mut git_commit = None;
for entry in tar.entries()? {
let mut entry = entry?;
let dest;
match entry.path()?.components().nth(1).and_then(|c| c.as_os_str().to_str()) {
Some("version") => dest = &mut version,
Some("git-commit-hash") => dest = &mut git_commit,
_ => continue,
}
let mut buf = String::new();
entry.read_to_string(&mut buf)?;
*dest = Some(buf);
// Short circuit to avoid reading the whole tar file if not necessary.
if version.is_some() && git_commit.is_some() {
break;
}
}
Ok(VersionInfo { version, git_commit, present: true })
}
pub(crate) fn disable_version(&mut self, package: &PkgType) {
match self.versions.get_mut(package) {
Some(version) => {
*version = VersionInfo::default();
}
None => {
self.versions.insert(package.clone(), VersionInfo::default());
}
}
}
pub(crate) fn tarball_name(
&mut self,
package: &PkgType,
target: &str,
) -> Result<String, Error> {
let component_name = package.tarball_component_name();
let version = match self.channel.as_str() {
"stable" => RUSTC_VERSION.into(),
"beta" => "beta".into(),
"nightly" => "nightly".into(),
_ => format!("{}-dev", RUSTC_VERSION),
};
if package.target_independent() {
Ok(format!("{}-{}.tar.gz", component_name, version))
} else {
Ok(format!("{}-{}-{}.tar.gz", component_name, version, target))
}
}
pub(crate) fn rustc_version(&self) -> &str {
RUSTC_VERSION
}
}