1 use anyhow::{Context, Error};
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 {
26 pub(crate) fn from_component(component: &str) -> Self {
28 "rust" => PkgType::Rust,
29 "rust-src" => PkgType::RustSrc,
30 "cargo" => PkgType::Cargo,
31 "rls" | "rls-preview" => PkgType::Rls,
32 "rust-analyzer" | "rust-analyzer-preview" => PkgType::RustAnalyzer,
33 "clippy" | "clippy-preview" => PkgType::Clippy,
34 "rustfmt" | "rustfmt-preview" => PkgType::Rustfmt,
35 "llvm-tools" | "llvm-tools-preview" => PkgType::LlvmTools,
36 "miri" | "miri-preview" => PkgType::Miri,
37 other => PkgType::Other(other.into()),
41 /// The directory containing the `Cargo.toml` of this component inside the monorepo, to
42 /// retrieve the source code version. If `None` is returned Rust's version will be used.
43 fn rust_monorepo_path(&self) -> Option<&'static str> {
45 PkgType::Cargo => Some("src/tools/cargo"),
46 PkgType::Rls => Some("src/tools/rls"),
47 PkgType::RustAnalyzer => Some("src/tools/rust-analyzer/crates/rust-analyzer"),
48 PkgType::Clippy => Some("src/tools/clippy"),
49 PkgType::Rustfmt => Some("src/tools/rustfmt"),
50 PkgType::Miri => Some("src/tools/miri"),
51 PkgType::Rust => None,
52 PkgType::RustSrc => None,
53 PkgType::LlvmTools => None,
54 PkgType::Other(_) => None,
58 /// First part of the tarball name.
59 fn tarball_component_name(&self) -> &str {
61 PkgType::Rust => "rust",
62 PkgType::RustSrc => "rust-src",
63 PkgType::Cargo => "cargo",
64 PkgType::Rls => "rls",
65 PkgType::RustAnalyzer => "rust-analyzer",
66 PkgType::Clippy => "clippy",
67 PkgType::Rustfmt => "rustfmt",
68 PkgType::LlvmTools => "llvm-tools",
69 PkgType::Miri => "miri",
70 PkgType::Other(component) => component,
74 /// Whether this package has the same version as Rust itself, or has its own `version` and
75 /// `git-commit-hash` files inside the tarball.
76 fn should_use_rust_version(&self) -> bool {
78 PkgType::Cargo => false,
79 PkgType::Rls => false,
80 PkgType::RustAnalyzer => false,
81 PkgType::Clippy => false,
82 PkgType::Rustfmt => false,
83 PkgType::LlvmTools => false,
84 PkgType::Miri => false,
86 PkgType::Rust => true,
87 PkgType::RustSrc => true,
88 PkgType::Other(_) => true,
93 #[derive(Debug, Default, Clone)]
94 pub(crate) struct VersionInfo {
95 pub(crate) version: Option<String>,
96 pub(crate) git_commit: Option<String>,
97 pub(crate) present: bool,
100 pub(crate) struct Versions {
102 rustc_version: String,
103 monorepo_root: PathBuf,
105 package_versions: HashMap<PkgType, String>,
106 versions: HashMap<PkgType, VersionInfo>,
113 monorepo_root: &Path,
114 ) -> Result<Self, Error> {
116 channel: channel.into(),
117 rustc_version: std::fs::read_to_string(monorepo_root.join("src").join("version"))
118 .context("failed to read the rustc version from src/version")?
121 monorepo_root: monorepo_root.into(),
122 dist_path: dist_path.into(),
123 package_versions: HashMap::new(),
124 versions: HashMap::new(),
128 pub(crate) fn channel(&self) -> &str {
132 pub(crate) fn version(&mut self, mut package: &PkgType) -> Result<VersionInfo, Error> {
133 if package.should_use_rust_version() {
134 package = &PkgType::Rust;
137 match self.versions.get(package) {
138 Some(version) => Ok(version.clone()),
140 let version_info = self.load_version_from_tarball(package)?;
141 self.versions.insert(package.clone(), version_info.clone());
147 fn load_version_from_tarball(&mut self, package: &PkgType) -> Result<VersionInfo, Error> {
148 let tarball_name = self.tarball_name(package, DEFAULT_TARGET)?;
149 let tarball = self.dist_path.join(tarball_name);
151 let file = match File::open(&tarball) {
153 Err(err) if err.kind() == std::io::ErrorKind::NotFound => {
154 // Missing tarballs do not return an error, but return empty data.
155 return Ok(VersionInfo::default());
157 Err(err) => return Err(err.into()),
159 let mut tar = Archive::new(GzDecoder::new(file));
161 let mut version = None;
162 let mut git_commit = None;
163 for entry in tar.entries()? {
164 let mut entry = entry?;
167 match entry.path()?.components().nth(1).and_then(|c| c.as_os_str().to_str()) {
168 Some("version") => dest = &mut version,
169 Some("git-commit-hash") => dest = &mut git_commit,
172 let mut buf = String::new();
173 entry.read_to_string(&mut buf)?;
176 // Short circuit to avoid reading the whole tar file if not necessary.
177 if version.is_some() && git_commit.is_some() {
182 Ok(VersionInfo { version, git_commit, present: true })
185 pub(crate) fn disable_version(&mut self, package: &PkgType) {
186 match self.versions.get_mut(package) {
188 *version = VersionInfo::default();
191 self.versions.insert(package.clone(), VersionInfo::default());
196 pub(crate) fn tarball_name(
200 ) -> Result<String, Error> {
203 package.tarball_component_name(),
204 self.package_version(package).with_context(|| format!(
205 "failed to get the package version for component {:?}",
212 pub(crate) fn package_version(&mut self, package: &PkgType) -> Result<String, Error> {
213 match self.package_versions.get(package) {
214 Some(release) => Ok(release.clone()),
216 let version = match package.rust_monorepo_path() {
218 let path = self.monorepo_root.join(path).join("Cargo.toml");
219 let cargo_toml: CargoToml = toml::from_slice(&std::fs::read(path)?)?;
220 cargo_toml.package.version
222 None => self.rustc_version.clone(),
225 let release = match self.channel.as_str() {
227 "beta" => "beta".into(),
228 "nightly" => "nightly".into(),
229 _ => format!("{}-dev", version),
232 self.package_versions.insert(package.clone(), release.clone());
239 #[derive(serde::Deserialize)]
241 package: CargoTomlPackage,
244 #[derive(serde::Deserialize)]
245 struct CargoTomlPackage {