]> git.lizzy.rs Git - rust.git/blob - src/tools/build-manifest/src/versions.rs
build-manifest: add documentation on the PkgType methods
[rust.git] / src / tools / build-manifest / src / versions.rs
1 use anyhow::{Context, 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     Cargo,
16     Rls,
17     RustAnalyzer,
18     Clippy,
19     Rustfmt,
20     LlvmTools,
21     Miri,
22     Other(String),
23 }
24
25 impl PkgType {
26     pub(crate) fn from_component(component: &str) -> Self {
27         match component {
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()),
38         }
39     }
40
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> {
44         match self {
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,
55         }
56     }
57
58     /// First part of the tarball name.
59     fn tarball_component_name(&self) -> &str {
60         match self {
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,
71         }
72     }
73
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 {
77         match self {
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,
85
86             PkgType::Rust => true,
87             PkgType::RustSrc => true,
88             PkgType::Other(_) => true,
89         }
90     }
91 }
92
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,
98 }
99
100 pub(crate) struct Versions {
101     channel: String,
102     rustc_version: String,
103     monorepo_root: PathBuf,
104     dist_path: PathBuf,
105     package_versions: HashMap<PkgType, String>,
106     versions: HashMap<PkgType, VersionInfo>,
107 }
108
109 impl Versions {
110     pub(crate) fn new(
111         channel: &str,
112         dist_path: &Path,
113         monorepo_root: &Path,
114     ) -> Result<Self, Error> {
115         Ok(Self {
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")?
119                 .trim()
120                 .to_string(),
121             monorepo_root: monorepo_root.into(),
122             dist_path: dist_path.into(),
123             package_versions: HashMap::new(),
124             versions: HashMap::new(),
125         })
126     }
127
128     pub(crate) fn channel(&self) -> &str {
129         &self.channel
130     }
131
132     pub(crate) fn version(&mut self, mut package: &PkgType) -> Result<VersionInfo, Error> {
133         if package.should_use_rust_version() {
134             package = &PkgType::Rust;
135         }
136
137         match self.versions.get(package) {
138             Some(version) => Ok(version.clone()),
139             None => {
140                 let version_info = self.load_version_from_tarball(package)?;
141                 self.versions.insert(package.clone(), version_info.clone());
142                 Ok(version_info)
143             }
144         }
145     }
146
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);
150
151         let file = match File::open(&tarball) {
152             Ok(file) => file,
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());
156             }
157             Err(err) => return Err(err.into()),
158         };
159         let mut tar = Archive::new(GzDecoder::new(file));
160
161         let mut version = None;
162         let mut git_commit = None;
163         for entry in tar.entries()? {
164             let mut entry = entry?;
165
166             let dest;
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,
170                 _ => continue,
171             }
172             let mut buf = String::new();
173             entry.read_to_string(&mut buf)?;
174             *dest = Some(buf);
175
176             // Short circuit to avoid reading the whole tar file if not necessary.
177             if version.is_some() && git_commit.is_some() {
178                 break;
179             }
180         }
181
182         Ok(VersionInfo { version, git_commit, present: true })
183     }
184
185     pub(crate) fn disable_version(&mut self, package: &PkgType) {
186         match self.versions.get_mut(package) {
187             Some(version) => {
188                 *version = VersionInfo::default();
189             }
190             None => {
191                 self.versions.insert(package.clone(), VersionInfo::default());
192             }
193         }
194     }
195
196     pub(crate) fn tarball_name(
197         &mut self,
198         package: &PkgType,
199         target: &str,
200     ) -> Result<String, Error> {
201         Ok(format!(
202             "{}-{}-{}.tar.gz",
203             package.tarball_component_name(),
204             self.package_version(package).with_context(|| format!(
205                 "failed to get the package version for component {:?}",
206                 package,
207             ))?,
208             target
209         ))
210     }
211
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()),
215             None => {
216                 let version = match package.rust_monorepo_path() {
217                     Some(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
221                     }
222                     None => self.rustc_version.clone(),
223                 };
224
225                 let release = match self.channel.as_str() {
226                     "stable" => version,
227                     "beta" => "beta".into(),
228                     "nightly" => "nightly".into(),
229                     _ => format!("{}-dev", version),
230                 };
231
232                 self.package_versions.insert(package.clone(), release.clone());
233                 Ok(release)
234             }
235         }
236     }
237 }
238
239 #[derive(serde::Deserialize)]
240 struct CargoToml {
241     package: CargoTomlPackage,
242 }
243
244 #[derive(serde::Deserialize)]
245 struct CargoTomlPackage {
246     version: String,
247 }