1 // Copyright (C) 2024 The Android Open Source Project 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 use std::{ 16 collections::HashMap, 17 fs::{copy, read_dir, read_link, read_to_string, remove_dir_all, rename, write}, 18 os::unix::fs::symlink, 19 path::PathBuf, 20 process::{Command, Output}, 21 str::from_utf8, 22 sync::LazyLock, 23 }; 24 25 use anyhow::{anyhow, ensure, Context, Result}; 26 use glob::glob; 27 use google_metadata::GoogleMetadata; 28 use license_checker::find_licenses; 29 use name_and_version::NamedAndVersioned; 30 use rooted_path::RootedPath; 31 use semver::Version; 32 use test_mapping::TestMapping; 33 34 use crate::{ 35 android_bp::run_cargo_embargo, 36 copy_dir, 37 crate_type::Crate, 38 ensure_exists_and_empty, 39 license::update_module_license_files, 40 patch::Patch, 41 pseudo_crate::{CargoVendorClean, PseudoCrate}, 42 SuccessOrError, 43 }; 44 45 #[derive(Debug)] 46 pub struct ManagedCrate<State: ManagedCrateState> { 47 android_crate: Crate, 48 extra: State, 49 } 50 51 #[derive(Debug)] 52 pub struct New {} 53 #[derive(Debug)] 54 pub struct Vendored { 55 vendored_crate: Crate, 56 } 57 #[derive(Debug)] 58 pub struct Staged { 59 vendored_crate: Crate, 60 patch_output: Vec<(String, Output)>, 61 cargo_embargo_output: Output, 62 android_bp_diff: Output, 63 } 64 pub trait ManagedCrateState {} 65 impl ManagedCrateState for New {} 66 impl ManagedCrateState for Vendored {} 67 impl ManagedCrateState for Staged {} 68 69 static CUSTOMIZATIONS: &[&str] = &[ 70 "*.bp", 71 "*.bp.fragment", 72 "*.mk", 73 "cargo_embargo.json", 74 "patches", 75 "METADATA", 76 "TEST_MAPPING", 77 "MODULE_LICENSE_*", 78 "README.android", 79 ]; 80 81 static SYMLINKS: &[&str] = &["LICENSE", "NOTICE"]; 82 83 static DELETIONS: LazyLock<HashMap<&str, &[&str]>> = LazyLock::new(|| { 84 HashMap::from([ 85 ("libbpf-sys", ["elfutils", "zlib", "libbpf"].as_slice()), 86 ("libusb1-sys", ["libusb"].as_slice()), 87 ]) 88 }); 89 90 impl<State: ManagedCrateState> ManagedCrate<State> { name(&self) -> &str91 pub fn name(&self) -> &str { 92 self.android_crate.name() 93 } android_version(&self) -> &Version94 pub fn android_version(&self) -> &Version { 95 self.android_crate.version() 96 } android_crate_path(&self) -> &RootedPath97 pub fn android_crate_path(&self) -> &RootedPath { 98 self.android_crate.path() 99 } android_bp(&self) -> RootedPath100 pub fn android_bp(&self) -> RootedPath { 101 self.android_crate_path().join("Android.bp").unwrap() 102 } cargo_embargo_json(&self) -> RootedPath103 pub fn cargo_embargo_json(&self) -> RootedPath { 104 self.android_crate_path().join("cargo_embargo.json").unwrap() 105 } staging_path(&self) -> RootedPath106 pub fn staging_path(&self) -> RootedPath { 107 self.android_crate 108 .path() 109 .with_same_root("out/rust-crate-temporary-build") 110 .unwrap() 111 .join(self.name()) 112 .unwrap() 113 } patch_dir(&self) -> RootedPath114 fn patch_dir(&self) -> RootedPath { 115 self.android_crate_path().join("patches").unwrap() 116 } patches(&self) -> Result<Vec<PathBuf>>117 pub fn patches(&self) -> Result<Vec<PathBuf>> { 118 let mut patches = Vec::new(); 119 let patch_dir = self.patch_dir(); 120 if patch_dir.abs().exists() { 121 for entry in 122 read_dir(&patch_dir).context(format!("Failed to read_dir {}", patch_dir))? 123 { 124 let entry = entry?; 125 if entry.file_name() == "Android.bp.patch" 126 || entry.file_name() == "Android.bp.diff" 127 || entry.file_name() == "rules.mk.diff" 128 { 129 continue; 130 } 131 patches.push(entry.path()); 132 } 133 } 134 135 Ok(patches) 136 } recontextualize_patches(&self) -> Result<()>137 pub fn recontextualize_patches(&self) -> Result<()> { 138 let output = Command::new("git") 139 .args(["status", "--porcelain", "."]) 140 .current_dir(self.android_crate_path()) 141 .output()? 142 .success_or_error()?; 143 if !output.stdout.is_empty() { 144 return Err(anyhow!( 145 "Crate directory {} has uncommitted changes", 146 self.android_crate_path() 147 )); 148 } 149 let mut new_patch_contents = Vec::new(); 150 for patch in self.patches()? { 151 println!("Recontextualizing {}", patch.display()); 152 // Patch files can be in many different formats, and patch is very 153 // forgiving. We might be able to use "git apply -R --directory=crates/foo" 154 // once we have everything in the same format. 155 Command::new("patch") 156 .args(["-R", "-p1", "-l", "--reject-file=-", "--no-backup-if-mismatch", "-i"]) 157 .arg(&patch) 158 .current_dir(self.android_crate_path()) 159 .spawn()? 160 .wait()? 161 .success_or_error()?; 162 Command::new("git") 163 .args(["add", "."]) 164 .current_dir(self.android_crate_path()) 165 .spawn()? 166 .wait()? 167 .success_or_error()?; 168 let output = Command::new("git") 169 .args([ 170 "diff", 171 format!("--relative=crates/{}", self.name()).as_str(), 172 "-p", 173 "--stat", 174 "-R", 175 "--staged", 176 ".", 177 ]) 178 .current_dir(self.android_crate_path()) 179 .output()? 180 .success_or_error()?; 181 Command::new("git") 182 .args(["restore", "--staged", "."]) 183 .current_dir(self.android_crate_path()) 184 .spawn()? 185 .wait()? 186 .success_or_error()?; 187 Command::new("git") 188 .args(["restore", "."]) 189 .current_dir(self.android_crate_path()) 190 .spawn()? 191 .wait()? 192 .success_or_error()?; 193 Command::new("git") 194 .args(["clean", "-f", "."]) 195 .current_dir(self.android_crate_path()) 196 .spawn()? 197 .wait()? 198 .success_or_error()?; 199 let patch_contents = read_to_string(&patch)?; 200 let parsed = Patch::parse(&patch_contents); 201 new_patch_contents.push((patch, parsed.reassemble(&output.stdout))); 202 } 203 for (path, contents) in new_patch_contents { 204 write(path, contents)?; 205 } 206 Ok(()) 207 } fix_licenses(&self) -> Result<()>208 pub fn fix_licenses(&self) -> Result<()> { 209 println!("{} = \"={}\"", self.name(), self.android_version()); 210 let state = 211 find_licenses(self.android_crate_path(), self.name(), self.android_crate.license())?; 212 if !state.unsatisfied.is_empty() { 213 println!("{:?}", state); 214 } else { 215 // For now, just update MODULE_LICENSE_* 216 update_module_license_files(self.android_crate_path(), &state)?; 217 } 218 Ok(()) 219 } fix_metadata(&self) -> Result<()>220 pub fn fix_metadata(&self) -> Result<()> { 221 println!("{} = \"={}\"", self.name(), self.android_version()); 222 let mut metadata = GoogleMetadata::try_from(self.android_crate_path().join("METADATA")?)?; 223 metadata.set_version_and_urls(self.name(), self.android_version().to_string())?; 224 metadata.migrate_archive(); 225 metadata.migrate_homepage(); 226 metadata.remove_deprecated_url(); 227 metadata.write()?; 228 Ok(()) 229 } fix_test_mapping(&self) -> Result<()>230 pub fn fix_test_mapping(&self) -> Result<()> { 231 let mut tm = TestMapping::read(self.android_crate_path().clone())?; 232 println!("{}", self.name()); 233 if tm.fix_import_paths() || tm.add_new_tests_to_postsubmit()? { 234 tm.write()?; 235 } 236 Ok(()) 237 } 238 } 239 240 impl ManagedCrate<New> { new(android_crate: Crate) -> Self241 pub fn new(android_crate: Crate) -> Self { 242 ManagedCrate { android_crate, extra: New {} } 243 } into_legacy(self) -> ManagedCrate<Vendored>244 pub fn into_legacy(self) -> ManagedCrate<Vendored> { 245 ManagedCrate { 246 android_crate: self.android_crate.clone(), 247 extra: Vendored { vendored_crate: self.android_crate }, 248 } 249 } into_vendored( self, pseudo_crate: &PseudoCrate<CargoVendorClean>, ) -> Result<ManagedCrate<Vendored>>250 fn into_vendored( 251 self, 252 pseudo_crate: &PseudoCrate<CargoVendorClean>, 253 ) -> Result<ManagedCrate<Vendored>> { 254 let vendored_crate = 255 Crate::from(pseudo_crate.vendored_dir_for(self.android_crate.name())?.clone())?; 256 Ok(ManagedCrate { android_crate: self.android_crate, extra: Vendored { vendored_crate } }) 257 } stage( self, pseudo_crate: &PseudoCrate<CargoVendorClean>, ) -> Result<ManagedCrate<Staged>>258 pub fn stage( 259 self, 260 pseudo_crate: &PseudoCrate<CargoVendorClean>, 261 ) -> Result<ManagedCrate<Staged>> { 262 self.into_vendored(pseudo_crate)?.stage() 263 } regenerate( self, update_metadata: bool, pseudo_crate: &PseudoCrate<CargoVendorClean>, ) -> Result<ManagedCrate<Staged>>264 pub fn regenerate( 265 self, 266 update_metadata: bool, 267 pseudo_crate: &PseudoCrate<CargoVendorClean>, 268 ) -> Result<ManagedCrate<Staged>> { 269 self.into_vendored(pseudo_crate)?.regenerate(update_metadata) 270 } 271 } 272 273 impl ManagedCrate<Vendored> { stage(self) -> Result<ManagedCrate<Staged>>274 pub fn stage(self) -> Result<ManagedCrate<Staged>> { 275 self.copy_to_staging()?; 276 277 // Workaround. When checking the health of a legacy crate, there is no separate vendored crate, 278 // so we just have self.android_crate and self.vendored_crate point to the same directory. 279 // In this case, there is no need to copy Android customizations into the clean vendored copy 280 // or apply the patches. 281 if self.android_crate.path() != self.extra.vendored_crate.path() { 282 self.copy_customizations()?; 283 } 284 285 let patch_output = if self.android_crate.path() != self.extra.vendored_crate.path() { 286 self.apply_patches()? 287 } else { 288 Vec::new() 289 }; 290 291 let cargo_embargo_output = run_cargo_embargo(&self.staging_path())?; 292 let android_bp_diff = self.diff_android_bp()?; 293 294 Ok(ManagedCrate { 295 android_crate: self.android_crate, 296 extra: Staged { 297 vendored_crate: self.extra.vendored_crate, 298 patch_output, 299 cargo_embargo_output, 300 android_bp_diff, 301 }, 302 }) 303 } copy_to_staging(&self) -> Result<()>304 fn copy_to_staging(&self) -> Result<()> { 305 let staging_path = self.staging_path(); 306 ensure_exists_and_empty(&staging_path)?; 307 remove_dir_all(&staging_path).context(format!("Failed to remove {}", staging_path))?; 308 copy_dir(self.extra.vendored_crate.path(), &staging_path).context(format!( 309 "Failed to copy {} to {}", 310 self.extra.vendored_crate.path(), 311 self.staging_path() 312 ))?; 313 if staging_path.join(".git")?.abs().is_dir() { 314 remove_dir_all(staging_path.join(".git")?) 315 .with_context(|| "Failed to remove .git".to_string())?; 316 } 317 Ok(()) 318 } copy_customizations(&self) -> Result<()>319 fn copy_customizations(&self) -> Result<()> { 320 let dest_dir = self.staging_path(); 321 for pattern in CUSTOMIZATIONS { 322 let full_pattern = self.android_crate.path().join(pattern)?; 323 for entry in glob( 324 full_pattern 325 .abs() 326 .to_str() 327 .ok_or(anyhow!("Failed to convert path {} to str", full_pattern))?, 328 )? { 329 let entry = entry?; 330 let filename = entry 331 .file_name() 332 .context(format!("Failed to get file name for {}", entry.display()))? 333 .to_os_string(); 334 if entry.is_dir() { 335 copy_dir(&entry, &dest_dir.join(filename)?).context(format!( 336 "Failed to copy {} to {}", 337 entry.display(), 338 dest_dir 339 ))?; 340 } else { 341 let dest_file = dest_dir.join(&filename)?; 342 if dest_file.abs().exists() { 343 return Err(anyhow!("Destination file {} exists", dest_file)); 344 } 345 copy(&entry, dest_dir.join(filename)?).context(format!( 346 "Failed to copy {} to {}", 347 entry.display(), 348 dest_dir 349 ))?; 350 } 351 } 352 } 353 for link in SYMLINKS { 354 let src_path = self.android_crate.path().join(link)?; 355 if src_path.abs().is_symlink() { 356 let dest = read_link(src_path)?; 357 if dest.exists() { 358 return Err(anyhow!( 359 "Can't symlink {} -> {} because destination exists", 360 link, 361 dest.display(), 362 )); 363 } 364 symlink(dest, dest_dir.join(link)?)?; 365 } 366 } 367 for deletion in *DELETIONS.get(self.name()).unwrap_or(&[].as_slice()) { 368 let dir = self.staging_path().join(deletion)?; 369 ensure!(dir.abs().is_dir(), "{dir} is not a directory"); 370 remove_dir_all(dir)?; 371 } 372 Ok(()) 373 } apply_patches(&self) -> Result<Vec<(String, Output)>>374 fn apply_patches(&self) -> Result<Vec<(String, Output)>> { 375 let mut patch_output = Vec::new(); 376 for patch in self.patches()? { 377 let output = Command::new("patch") 378 .args(["-p1", "-l", "--no-backup-if-mismatch", "-i"]) 379 .arg(&patch) 380 .current_dir(self.staging_path()) 381 .output()?; 382 patch_output.push(( 383 String::from_utf8_lossy(patch.file_name().unwrap().as_encoded_bytes()).to_string(), 384 output, 385 )); 386 } 387 Ok(patch_output) 388 } diff_android_bp(&self) -> Result<Output>389 fn diff_android_bp(&self) -> Result<Output> { 390 Ok(Command::new("diff") 391 .args([ 392 "-u", 393 "-w", 394 "-B", 395 "-I", 396 r#"default_team: "trendy_team_android_rust""#, 397 "-I", 398 "// has rustc warnings", 399 "-I", 400 "This file is generated by", 401 "-I", 402 "cargo_pkg_version:", 403 ]) 404 .arg(self.android_bp().rel()) 405 .arg(self.staging_path().join("Android.bp")?.rel()) 406 .current_dir(self.android_crate.path().root()) 407 .output()?) 408 } regenerate(self, update_metadata: bool) -> Result<ManagedCrate<Staged>>409 pub fn regenerate(self, update_metadata: bool) -> Result<ManagedCrate<Staged>> { 410 let staged = self.stage()?; 411 staged.check_staged()?; 412 if !staged.staging_path().abs().exists() { 413 return Err(anyhow!("Staged crate not found at {}", staged.staging_path())); 414 } 415 if update_metadata { 416 let mut metadata = 417 GoogleMetadata::try_from(staged.staging_path().join("METADATA").unwrap())?; 418 let mut writeback = false; 419 writeback |= metadata.migrate_homepage(); 420 writeback |= metadata.migrate_archive(); 421 writeback |= metadata.remove_deprecated_url(); 422 let vendored_version = staged.extra.vendored_crate.version(); 423 if staged.android_crate.version() != vendored_version { 424 metadata.set_date_to_today()?; 425 metadata.set_version_and_urls(staged.name(), vendored_version.to_string())?; 426 writeback |= true; 427 } 428 if writeback { 429 metadata.write()?; 430 } 431 } 432 433 let android_crate_dir = staged.android_crate.path(); 434 remove_dir_all(android_crate_dir)?; 435 rename(staged.staging_path(), android_crate_dir)?; 436 437 Ok(staged) 438 } 439 } 440 441 impl ManagedCrate<Staged> { vendored_version(&self) -> &Version442 pub fn vendored_version(&self) -> &Version { 443 self.extra.vendored_crate.version() 444 } check_staged(&self) -> Result<()>445 pub fn check_staged(&self) -> Result<()> { 446 if !self.patch_success() { 447 for (patch, output) in self.patch_output() { 448 if !output.status.success() { 449 return Err(anyhow!( 450 "Failed to patch {} with {}\nstdout:\n{}\nstderr:\n{}", 451 self.name(), 452 patch, 453 from_utf8(&output.stdout)?, 454 from_utf8(&output.stderr)? 455 )); 456 } 457 } 458 } 459 self.cargo_embargo_output() 460 .success_or_error() 461 .context(format!("cargo_embargo execution failed for {}", self.name()))?; 462 463 Ok(()) 464 } diff_staged(&self) -> Result<()>465 pub fn diff_staged(&self) -> Result<()> { 466 let diff_status = Command::new("diff") 467 .args(["-u", "-r", "-w", "--no-dereference"]) 468 .arg(self.staging_path().rel()) 469 .arg(self.android_crate.path().rel()) 470 .current_dir(self.extra.vendored_crate.path().root()) 471 .spawn()? 472 .wait()?; 473 if !diff_status.success() { 474 return Err(anyhow!( 475 "Found differences between {} and {}", 476 self.android_crate.path(), 477 self.staging_path() 478 )); 479 } 480 481 Ok(()) 482 } patch_success(&self) -> bool483 pub fn patch_success(&self) -> bool { 484 self.extra.patch_output.iter().all(|output| output.1.status.success()) 485 } patch_output(&self) -> &Vec<(String, Output)>486 pub fn patch_output(&self) -> &Vec<(String, Output)> { 487 &self.extra.patch_output 488 } android_bp_diff(&self) -> &Output489 pub fn android_bp_diff(&self) -> &Output { 490 &self.extra.android_bp_diff 491 } cargo_embargo_output(&self) -> &Output492 pub fn cargo_embargo_output(&self) -> &Output { 493 &self.extra.cargo_embargo_output 494 } cargo_embargo_success(&self) -> bool495 pub fn cargo_embargo_success(&self) -> bool { 496 self.extra.cargo_embargo_output.status.success() 497 } android_bp_unchanged(&self) -> bool498 pub fn android_bp_unchanged(&self) -> bool { 499 self.extra.android_bp_diff.status.success() 500 } 501 } 502