1 use std::{ 2 path::{Path, PathBuf}, 3 process::Command, 4 }; 5 6 use crate::builder::Builder; 7 use crate::channel; 8 use crate::util::t; 9 10 #[derive(Copy, Clone)] 11 pub(crate) enum OverlayKind { 12 Rust, 13 LLVM, 14 Cargo, 15 Clippy, 16 Miri, 17 Rustfmt, 18 RustDemangler, 19 RLS, 20 RustAnalyzer, 21 } 22 23 impl OverlayKind { legal_and_readme(&self) -> &[&str]24 fn legal_and_readme(&self) -> &[&str] { 25 match self { 26 OverlayKind::Rust => &["COPYRIGHT", "LICENSE-APACHE", "LICENSE-MIT", "README.md", "NOTICE"], 27 OverlayKind::LLVM => { 28 &["src/llvm-project/llvm/LICENSE.TXT", "src/llvm-project/llvm/README.txt"] 29 } 30 OverlayKind::Cargo => &[ 31 "src/tools/cargo/README.md", 32 "src/tools/cargo/LICENSE-MIT", 33 "src/tools/cargo/LICENSE-APACHE", 34 "src/tools/cargo/LICENSE-THIRD-PARTY", 35 ], 36 OverlayKind::Clippy => &[ 37 "src/tools/clippy/README.md", 38 "src/tools/clippy/LICENSE-APACHE", 39 "src/tools/clippy/LICENSE-MIT", 40 ], 41 OverlayKind::Miri => &[ 42 "src/tools/miri/README.md", 43 "src/tools/miri/LICENSE-APACHE", 44 "src/tools/miri/LICENSE-MIT", 45 ], 46 OverlayKind::Rustfmt => &[ 47 "src/tools/rustfmt/README.md", 48 "src/tools/rustfmt/LICENSE-APACHE", 49 "src/tools/rustfmt/LICENSE-MIT", 50 ], 51 OverlayKind::RustDemangler => { 52 &["src/tools/rust-demangler/README.md", "LICENSE-APACHE", "LICENSE-MIT"] 53 } 54 OverlayKind::RLS => &["src/tools/rls/README.md", "LICENSE-APACHE", "LICENSE-MIT"], 55 OverlayKind::RustAnalyzer => &[ 56 "src/tools/rust-analyzer/README.md", 57 "src/tools/rust-analyzer/LICENSE-APACHE", 58 "src/tools/rust-analyzer/LICENSE-MIT", 59 ], 60 } 61 } 62 version(&self, builder: &Builder<'_>) -> String63 fn version(&self, builder: &Builder<'_>) -> String { 64 match self { 65 OverlayKind::Rust => builder.rust_version(), 66 OverlayKind::LLVM => builder.rust_version(), 67 OverlayKind::RustDemangler => builder.release_num("rust-demangler"), 68 OverlayKind::Cargo => { 69 builder.cargo_info.version(builder, &builder.release_num("cargo")) 70 } 71 OverlayKind::Clippy => { 72 builder.clippy_info.version(builder, &builder.release_num("clippy")) 73 } 74 OverlayKind::Miri => builder.miri_info.version(builder, &builder.release_num("miri")), 75 OverlayKind::Rustfmt => { 76 builder.rustfmt_info.version(builder, &builder.release_num("rustfmt")) 77 } 78 OverlayKind::RLS => builder.release(&builder.release_num("rls")), 79 OverlayKind::RustAnalyzer => builder 80 .rust_analyzer_info 81 .version(builder, &builder.release_num("rust-analyzer/crates/rust-analyzer")), 82 } 83 } 84 } 85 86 pub(crate) struct Tarball<'a> { 87 builder: &'a Builder<'a>, 88 89 pkgname: String, 90 component: String, 91 target: Option<String>, 92 product_name: String, 93 overlay: OverlayKind, 94 95 temp_dir: PathBuf, 96 image_dir: PathBuf, 97 overlay_dir: PathBuf, 98 bulk_dirs: Vec<PathBuf>, 99 100 include_target_in_component_name: bool, 101 is_preview: bool, 102 permit_symlinks: bool, 103 } 104 105 impl<'a> Tarball<'a> { new(builder: &'a Builder<'a>, component: &str, target: &str) -> Self106 pub(crate) fn new(builder: &'a Builder<'a>, component: &str, target: &str) -> Self { 107 Self::new_inner(builder, component, Some(target.into())) 108 } 109 new_targetless(builder: &'a Builder<'a>, component: &str) -> Self110 pub(crate) fn new_targetless(builder: &'a Builder<'a>, component: &str) -> Self { 111 Self::new_inner(builder, component, None) 112 } 113 new_inner(builder: &'a Builder<'a>, component: &str, target: Option<String>) -> Self114 fn new_inner(builder: &'a Builder<'a>, component: &str, target: Option<String>) -> Self { 115 let pkgname = crate::dist::pkgname(builder, component); 116 117 let mut temp_dir = builder.out.join("tmp").join("tarball").join(component); 118 if let Some(target) = &target { 119 temp_dir = temp_dir.join(target); 120 } 121 let _ = std::fs::remove_dir_all(&temp_dir); 122 123 let image_dir = temp_dir.join("image"); 124 let overlay_dir = temp_dir.join("overlay"); 125 126 Self { 127 builder, 128 129 pkgname, 130 component: component.into(), 131 target, 132 product_name: "Rust".into(), 133 overlay: OverlayKind::Rust, 134 135 temp_dir, 136 image_dir, 137 overlay_dir, 138 bulk_dirs: Vec::new(), 139 140 include_target_in_component_name: false, 141 is_preview: false, 142 permit_symlinks: false, 143 } 144 } 145 set_overlay(&mut self, overlay: OverlayKind)146 pub(crate) fn set_overlay(&mut self, overlay: OverlayKind) { 147 self.overlay = overlay; 148 } 149 set_product_name(&mut self, name: &str)150 pub(crate) fn set_product_name(&mut self, name: &str) { 151 self.product_name = name.into(); 152 } 153 include_target_in_component_name(&mut self, include: bool)154 pub(crate) fn include_target_in_component_name(&mut self, include: bool) { 155 self.include_target_in_component_name = include; 156 } 157 is_preview(&mut self, is: bool)158 pub(crate) fn is_preview(&mut self, is: bool) { 159 self.is_preview = is; 160 } 161 permit_symlinks(&mut self, flag: bool)162 pub(crate) fn permit_symlinks(&mut self, flag: bool) { 163 self.permit_symlinks = flag; 164 } 165 image_dir(&self) -> &Path166 pub(crate) fn image_dir(&self) -> &Path { 167 t!(std::fs::create_dir_all(&self.image_dir)); 168 &self.image_dir 169 } 170 add_file(&self, src: impl AsRef<Path>, destdir: impl AsRef<Path>, perms: u32)171 pub(crate) fn add_file(&self, src: impl AsRef<Path>, destdir: impl AsRef<Path>, perms: u32) { 172 // create_dir_all fails to create `foo/bar/.`, so when the destination is "." this simply 173 // uses the base directory as the destination directory. 174 let destdir = if destdir.as_ref() == Path::new(".") { 175 self.image_dir.clone() 176 } else { 177 self.image_dir.join(destdir.as_ref()) 178 }; 179 180 t!(std::fs::create_dir_all(&destdir)); 181 self.builder.install(src.as_ref(), &destdir, perms); 182 } 183 add_renamed_file( &self, src: impl AsRef<Path>, destdir: impl AsRef<Path>, new_name: &str, )184 pub(crate) fn add_renamed_file( 185 &self, 186 src: impl AsRef<Path>, 187 destdir: impl AsRef<Path>, 188 new_name: &str, 189 ) { 190 let destdir = self.image_dir.join(destdir.as_ref()); 191 t!(std::fs::create_dir_all(&destdir)); 192 self.builder.copy(src.as_ref(), &destdir.join(new_name)); 193 } 194 add_legal_and_readme_to(&self, destdir: impl AsRef<Path>)195 pub(crate) fn add_legal_and_readme_to(&self, destdir: impl AsRef<Path>) { 196 for file in self.overlay.legal_and_readme() { 197 self.add_file(self.builder.src.join(file), destdir.as_ref(), 0o644); 198 } 199 } 200 add_dir(&self, src: impl AsRef<Path>, dest: impl AsRef<Path>)201 pub(crate) fn add_dir(&self, src: impl AsRef<Path>, dest: impl AsRef<Path>) { 202 let dest = self.image_dir.join(dest.as_ref()); 203 204 t!(std::fs::create_dir_all(&dest)); 205 self.builder.cp_r(src.as_ref(), &dest); 206 } 207 add_bulk_dir(&mut self, src: impl AsRef<Path>, dest: impl AsRef<Path>)208 pub(crate) fn add_bulk_dir(&mut self, src: impl AsRef<Path>, dest: impl AsRef<Path>) { 209 self.bulk_dirs.push(dest.as_ref().to_path_buf()); 210 self.add_dir(src, dest); 211 } 212 generate(self) -> GeneratedTarball213 pub(crate) fn generate(self) -> GeneratedTarball { 214 let mut component_name = self.component.clone(); 215 if self.is_preview { 216 component_name.push_str("-preview"); 217 } 218 if self.include_target_in_component_name { 219 component_name.push('-'); 220 component_name.push_str( 221 &self 222 .target 223 .as_ref() 224 .expect("include_target_in_component_name used in a targetless tarball"), 225 ); 226 } 227 228 self.run(|this, cmd| { 229 cmd.arg("generate") 230 .arg("--image-dir") 231 .arg(&this.image_dir) 232 .arg(format!("--component-name={}", &component_name)); 233 234 if let Some((dir, dirs)) = this.bulk_dirs.split_first() { 235 let mut arg = dir.as_os_str().to_os_string(); 236 for dir in dirs { 237 arg.push(","); 238 arg.push(dir); 239 } 240 cmd.arg("--bulk-dirs").arg(&arg); 241 } 242 243 this.non_bare_args(cmd); 244 }) 245 } 246 combine(self, tarballs: &[GeneratedTarball]) -> GeneratedTarball247 pub(crate) fn combine(self, tarballs: &[GeneratedTarball]) -> GeneratedTarball { 248 let mut input_tarballs = tarballs[0].path.as_os_str().to_os_string(); 249 for tarball in &tarballs[1..] { 250 input_tarballs.push(","); 251 input_tarballs.push(&tarball.path); 252 } 253 254 self.run(|this, cmd| { 255 cmd.arg("combine").arg("--input-tarballs").arg(input_tarballs); 256 this.non_bare_args(cmd); 257 }) 258 } 259 bare(self) -> GeneratedTarball260 pub(crate) fn bare(self) -> GeneratedTarball { 261 // Bare tarballs should have the top level directory match the package 262 // name, not "image". We rename the image directory just before passing 263 // into rust-installer. 264 let dest = self.temp_dir.join(self.package_name()); 265 t!(std::fs::rename(&self.image_dir, &dest)); 266 267 self.run(|this, cmd| { 268 let distdir = crate::dist::distdir(this.builder); 269 t!(std::fs::create_dir_all(&distdir)); 270 cmd.arg("tarball") 271 .arg("--input") 272 .arg(&dest) 273 .arg("--output") 274 .arg(distdir.join(this.package_name())); 275 }) 276 } 277 package_name(&self) -> String278 fn package_name(&self) -> String { 279 if let Some(target) = &self.target { 280 format!("{}-{}", self.pkgname, target) 281 } else { 282 self.pkgname.clone() 283 } 284 } 285 non_bare_args(&self, cmd: &mut Command)286 fn non_bare_args(&self, cmd: &mut Command) { 287 cmd.arg("--rel-manifest-dir=rustlib") 288 .arg("--legacy-manifest-dirs=rustlib,cargo") 289 .arg(format!("--product-name={}", self.product_name)) 290 .arg(format!("--success-message={} installed.", self.component)) 291 .arg(format!("--package-name={}", self.package_name())) 292 .arg("--non-installed-overlay") 293 .arg(&self.overlay_dir) 294 .arg("--output-dir") 295 .arg(crate::dist::distdir(self.builder)); 296 } 297 run(self, build_cli: impl FnOnce(&Tarball<'a>, &mut Command)) -> GeneratedTarball298 fn run(self, build_cli: impl FnOnce(&Tarball<'a>, &mut Command)) -> GeneratedTarball { 299 t!(std::fs::create_dir_all(&self.overlay_dir)); 300 self.builder.create(&self.overlay_dir.join("version"), &self.overlay.version(self.builder)); 301 if let Some(info) = self.builder.rust_info().info() { 302 channel::write_commit_hash_file(&self.overlay_dir, &info.sha); 303 channel::write_commit_info_file(&self.overlay_dir, info); 304 } 305 for file in self.overlay.legal_and_readme() { 306 self.builder.install(&self.builder.src.join(file), &self.overlay_dir, 0o644); 307 } 308 309 let mut cmd = self.builder.tool_cmd(crate::tool::Tool::RustInstaller); 310 311 let package_name = self.package_name(); 312 self.builder.info(&format!("Dist {}", package_name)); 313 let _time = crate::util::timeit(self.builder); 314 315 build_cli(&self, &mut cmd); 316 cmd.arg("--work-dir").arg(&self.temp_dir); 317 if let Some(formats) = &self.builder.config.dist_compression_formats { 318 assert!(!formats.is_empty(), "dist.compression-formats can't be empty"); 319 cmd.arg("--compression-formats").arg(formats.join(",")); 320 } 321 cmd.args(&["--compression-profile", &self.builder.config.dist_compression_profile]); 322 self.builder.run(&mut cmd); 323 324 // Ensure there are no symbolic links in the tarball. In particular, 325 // rustup-toolchain-install-master and most versions of Windows can't handle symbolic links. 326 let decompressed_output = self.temp_dir.join(&package_name); 327 if !self.builder.config.dry_run() && !self.permit_symlinks { 328 for entry in walkdir::WalkDir::new(&decompressed_output) { 329 let entry = t!(entry); 330 if entry.path_is_symlink() { 331 panic!("generated a symlink in a tarball: {}", entry.path().display()); 332 } 333 } 334 } 335 336 // Use either the first compression format defined, or "gz" as the default. 337 let ext = self 338 .builder 339 .config 340 .dist_compression_formats 341 .as_ref() 342 .and_then(|formats| formats.get(0)) 343 .map(|s| s.as_str()) 344 .unwrap_or("gz"); 345 346 GeneratedTarball { 347 path: crate::dist::distdir(self.builder).join(format!("{}.tar.{}", package_name, ext)), 348 decompressed_output, 349 work: self.temp_dir, 350 } 351 } 352 } 353 354 #[derive(Debug, Clone)] 355 pub struct GeneratedTarball { 356 path: PathBuf, 357 decompressed_output: PathBuf, 358 work: PathBuf, 359 } 360 361 impl GeneratedTarball { tarball(&self) -> &Path362 pub(crate) fn tarball(&self) -> &Path { 363 &self.path 364 } 365 decompressed_output(&self) -> &Path366 pub(crate) fn decompressed_output(&self) -> &Path { 367 &self.decompressed_output 368 } 369 work_dir(&self) -> &Path370 pub(crate) fn work_dir(&self) -> &Path { 371 &self.work 372 } 373 } 374