1 use anyhow::Error; 2 use flate2::read::GzDecoder; 3 use std::collections::HashMap; 4 use std::fs::File; 5 use std::io::Read; 6 use std::path::{Path, PathBuf}; 7 use tar::Archive; 8 9 const DEFAULT_TARGET: &str = "x86_64-unknown-linux-gnu"; 10 11 #[derive(Debug, Hash, Eq, PartialEq, Clone)] 12 pub(crate) enum PkgType { 13 Rust, 14 RustSrc, 15 Rustc, 16 Cargo, 17 Rls, 18 RustAnalyzer, 19 Clippy, 20 Rustfmt, 21 LlvmTools, 22 Miri, 23 Other(String), 24 } 25 26 impl PkgType { from_component(component: &str) -> Self27 pub(crate) fn from_component(component: &str) -> Self { 28 match component { 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()), 40 } 41 } 42 43 /// First part of the tarball name. tarball_component_name(&self) -> &str44 fn tarball_component_name(&self) -> &str { 45 match self { 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, 57 } 58 } 59 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. should_use_rust_version(&self) -> bool62 fn should_use_rust_version(&self) -> bool { 63 match self { 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, 71 72 PkgType::Rust => true, 73 PkgType::RustSrc => true, 74 PkgType::Rustc => true, 75 PkgType::Other(_) => true, 76 } 77 } 78 79 /// Whether this package is target-independent or not. target_independent(&self) -> bool80 fn target_independent(&self) -> bool { 81 *self == PkgType::RustSrc 82 } 83 } 84 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, 90 } 91 92 pub(crate) struct Versions { 93 channel: String, 94 dist_path: PathBuf, 95 versions: HashMap<PkgType, VersionInfo>, 96 } 97 98 impl Versions { new(channel: &str, dist_path: &Path) -> Result<Self, Error>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() }) 101 } 102 channel(&self) -> &str103 pub(crate) fn channel(&self) -> &str { 104 &self.channel 105 } 106 version(&mut self, mut package: &PkgType) -> Result<VersionInfo, Error>107 pub(crate) fn version(&mut self, mut package: &PkgType) -> Result<VersionInfo, Error> { 108 if package.should_use_rust_version() { 109 package = &PkgType::Rust; 110 } 111 112 match self.versions.get(package) { 113 Some(version) => Ok(version.clone()), 114 None => { 115 let version_info = self.load_version_from_tarball(package)?; 116 self.versions.insert(package.clone(), version_info.clone()); 117 Ok(version_info) 118 } 119 } 120 } 121 load_version_from_tarball(&mut self, package: &PkgType) -> Result<VersionInfo, Error>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); 125 126 let file = match File::open(&tarball) { 127 Ok(file) => file, 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()); 131 } 132 Err(err) => return Err(err.into()), 133 }; 134 let mut tar = Archive::new(GzDecoder::new(file)); 135 136 let mut version = None; 137 let mut git_commit = None; 138 for entry in tar.entries()? { 139 let mut entry = entry?; 140 141 let dest; 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, 145 _ => continue, 146 } 147 let mut buf = String::new(); 148 entry.read_to_string(&mut buf)?; 149 *dest = Some(buf); 150 151 // Short circuit to avoid reading the whole tar file if not necessary. 152 if version.is_some() && git_commit.is_some() { 153 break; 154 } 155 } 156 157 Ok(VersionInfo { version, git_commit, present: true }) 158 } 159 disable_version(&mut self, package: &PkgType)160 pub(crate) fn disable_version(&mut self, package: &PkgType) { 161 match self.versions.get_mut(package) { 162 Some(version) => { 163 *version = VersionInfo::default(); 164 } 165 None => { 166 self.versions.insert(package.clone(), VersionInfo::default()); 167 } 168 } 169 } 170 archive_name( &mut self, package: &PkgType, target: &str, extension: &str, ) -> Result<String, Error>171 pub(crate) fn archive_name( 172 &mut self, 173 package: &PkgType, 174 target: &str, 175 extension: &str, 176 ) -> Result<String, Error> { 177 let component_name = package.tarball_component_name(); 178 let version = match self.channel.as_str() { 179 "stable" => self.rustc_version().into(), 180 "beta" => "beta".into(), 181 "nightly" => "nightly".into(), 182 _ => format!("{}-dev", self.rustc_version()), 183 }; 184 185 if package.target_independent() { 186 Ok(format!("{}-{}.{}", component_name, version, extension)) 187 } else { 188 Ok(format!("{}-{}-{}.{}", component_name, version, target, extension)) 189 } 190 } 191 tarball_name( &mut self, package: &PkgType, target: &str, ) -> Result<String, Error>192 pub(crate) fn tarball_name( 193 &mut self, 194 package: &PkgType, 195 target: &str, 196 ) -> Result<String, Error> { 197 self.archive_name(package, target, "tar.gz") 198 } 199 rustc_version(&self) -> &str200 pub(crate) fn rustc_version(&self) -> &str { 201 const RUSTC_VERSION: &str = include_str!("../../../version"); 202 RUSTC_VERSION.trim() 203 } 204 } 205