2 use flate2::read::GzDecoder;
3 use std::collections::HashMap;
6 use std::path::{Path, PathBuf};
9 const DEFAULT_TARGET: &str = "x86_64-unknown-linux-gnu";
11 #[derive(Debug, Hash, Eq, PartialEq, Clone)]
12 pub(crate) enum PkgType {
27 pub(crate) fn from_component(component: &str) -> Self {
29 "rust" => PkgType::Rust,
30 "rust-src" => PkgType::RustSrc,
31 "rustc" => PkgType::Rustc,
32 "cargo" => PkgType::Cargo,
33 "rls" | "rls-preview" => PkgType::Rls,
34 "rust-analyzer" | "rust-analyzer-preview" => PkgType::RustAnalyzer,
35 "clippy" | "clippy-preview" => PkgType::Clippy,
36 "rustfmt" | "rustfmt-preview" => PkgType::Rustfmt,
37 "llvm-tools" | "llvm-tools-preview" => PkgType::LlvmTools,
38 "miri" | "miri-preview" => PkgType::Miri,
39 other => PkgType::Other(other.into()),
43 /// First part of the tarball name.
44 fn tarball_component_name(&self) -> &str {
46 PkgType::Rust => "rust",
47 PkgType::RustSrc => "rust-src",
48 PkgType::Rustc => "rustc",
49 PkgType::Cargo => "cargo",
50 PkgType::Rls => "rls",
51 PkgType::RustAnalyzer => "rust-analyzer",
52 PkgType::Clippy => "clippy",
53 PkgType::Rustfmt => "rustfmt",
54 PkgType::LlvmTools => "llvm-tools",
55 PkgType::Miri => "miri",
56 PkgType::Other(component) => component,
60 /// Whether this package has the same version as Rust itself, or has its own `version` and
61 /// `git-commit-hash` files inside the tarball.
62 fn should_use_rust_version(&self) -> bool {
64 PkgType::Cargo => false,
65 PkgType::Rls => false,
66 PkgType::RustAnalyzer => false,
67 PkgType::Clippy => false,
68 PkgType::Rustfmt => false,
69 PkgType::LlvmTools => false,
70 PkgType::Miri => false,
72 PkgType::Rust => true,
73 PkgType::RustSrc => true,
74 PkgType::Rustc => true,
75 PkgType::Other(_) => true,
79 /// Whether this package is target-independent or not.
80 fn target_independent(&self) -> bool {
81 *self == PkgType::RustSrc
85 #[derive(Debug, Default, Clone)]
86 pub(crate) struct VersionInfo {
87 pub(crate) version: Option<String>,
88 pub(crate) git_commit: Option<String>,
89 pub(crate) present: bool,
92 pub(crate) struct Versions {
95 versions: HashMap<PkgType, VersionInfo>,
99 pub(crate) fn new(channel: &str, dist_path: &Path) -> Result<Self, Error> {
100 Ok(Self { channel: channel.into(), dist_path: dist_path.into(), versions: HashMap::new() })
103 pub(crate) fn channel(&self) -> &str {
107 pub(crate) fn version(&mut self, mut package: &PkgType) -> Result<VersionInfo, Error> {
108 if package.should_use_rust_version() {
109 package = &PkgType::Rust;
112 match self.versions.get(package) {
113 Some(version) => Ok(version.clone()),
115 let version_info = self.load_version_from_tarball(package)?;
116 self.versions.insert(package.clone(), version_info.clone());
122 fn load_version_from_tarball(&mut self, package: &PkgType) -> Result<VersionInfo, Error> {
123 let tarball_name = self.tarball_name(package, DEFAULT_TARGET)?;
124 let tarball = self.dist_path.join(tarball_name);
126 let file = match File::open(&tarball) {
128 Err(err) if err.kind() == std::io::ErrorKind::NotFound => {
129 // Missing tarballs do not return an error, but return empty data.
130 return Ok(VersionInfo::default());
132 Err(err) => return Err(err.into()),
134 let mut tar = Archive::new(GzDecoder::new(file));
136 let mut version = None;
137 let mut git_commit = None;
138 for entry in tar.entries()? {
139 let mut entry = entry?;
142 match entry.path()?.components().nth(1).and_then(|c| c.as_os_str().to_str()) {
143 Some("version") => dest = &mut version,
144 Some("git-commit-hash") => dest = &mut git_commit,
147 let mut buf = String::new();
148 entry.read_to_string(&mut buf)?;
151 // Short circuit to avoid reading the whole tar file if not necessary.
152 if version.is_some() && git_commit.is_some() {
157 Ok(VersionInfo { version, git_commit, present: true })
160 pub(crate) fn archive_name(
165 ) -> Result<String, Error> {
166 let component_name = package.tarball_component_name();
167 let version = match self.channel.as_str() {
168 "stable" => self.rustc_version().into(),
169 "beta" => "beta".into(),
170 "nightly" => "nightly".into(),
171 _ => format!("{}-dev", self.rustc_version()),
174 if package.target_independent() {
175 Ok(format!("{}-{}.{}", component_name, version, extension))
177 Ok(format!("{}-{}-{}.{}", component_name, version, target, extension))
181 pub(crate) fn tarball_name(&self, package: &PkgType, target: &str) -> Result<String, Error> {
182 self.archive_name(package, target, "tar.gz")
185 pub(crate) fn rustc_version(&self) -> &str {
186 const RUSTC_VERSION: &str = include_str!("../../../version");