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::{BTreeMap, BTreeSet}, 17 env, 18 fs::{create_dir, create_dir_all, read_dir, remove_file, write}, 19 os::unix::fs::symlink, 20 path::Path, 21 process::Command, 22 str::from_utf8, 23 }; 24 25 use anyhow::{anyhow, Context, Result}; 26 use crates_index::DependencyKind; 27 use glob::glob; 28 use google_metadata::GoogleMetadata; 29 use itertools::Itertools; 30 use license_checker::find_licenses; 31 use name_and_version::{NameAndVersionMap, NameAndVersionRef, NamedAndVersioned}; 32 use rooted_path::RootedPath; 33 use semver::Version; 34 use spdx::Licensee; 35 36 use crate::{ 37 android_bp::cargo_embargo_autoconfig, 38 copy_dir, 39 crate_collection::CrateCollection, 40 crate_type::Crate, 41 crates_io::{AndroidDependencies, CratesIoIndex, DependencyChanges, SafeVersions}, 42 license::{most_restrictive_type, update_module_license_files}, 43 managed_crate::ManagedCrate, 44 pseudo_crate::{CargoVendorDirty, PseudoCrate}, 45 upgradable::{IsUpgradableTo, MatchesRelaxed}, 46 SuccessOrError, 47 }; 48 49 pub struct ManagedRepo { 50 path: RootedPath, 51 crates_io: CratesIoIndex, 52 } 53 54 impl ManagedRepo { new(path: RootedPath, offline: bool) -> Result<ManagedRepo>55 pub fn new(path: RootedPath, offline: bool) -> Result<ManagedRepo> { 56 Ok(ManagedRepo { 57 path, 58 crates_io: if offline { CratesIoIndex::new_offline()? } else { CratesIoIndex::new()? }, 59 }) 60 } pseudo_crate(&self) -> PseudoCrate<CargoVendorDirty>61 fn pseudo_crate(&self) -> PseudoCrate<CargoVendorDirty> { 62 PseudoCrate::new(self.path.join("pseudo_crate").unwrap()) 63 } contains(&self, crate_name: &str) -> bool64 fn contains(&self, crate_name: &str) -> bool { 65 self.managed_dir_for(crate_name).abs().exists() 66 } managed_dir(&self) -> RootedPath67 fn managed_dir(&self) -> RootedPath { 68 self.path.join("crates").unwrap() 69 } managed_dir_for(&self, crate_name: &str) -> RootedPath70 fn managed_dir_for(&self, crate_name: &str) -> RootedPath { 71 self.managed_dir().join(crate_name).unwrap() 72 } legacy_dir_for(&self, crate_name: &str, version: Option<&Version>) -> Result<RootedPath>73 fn legacy_dir_for(&self, crate_name: &str, version: Option<&Version>) -> Result<RootedPath> { 74 match version { 75 Some(v) => { 76 let cc = self.legacy_crates_for(crate_name)?; 77 let nv = NameAndVersionRef::new(crate_name, v); 78 Ok(cc 79 .map_field() 80 .get(&nv as &dyn NamedAndVersioned) 81 .ok_or(anyhow!("Failed to find crate {} v{}", crate_name, v))? 82 .path() 83 .clone()) 84 } 85 None => { 86 Ok(self.path.with_same_root("external/rust/crates").unwrap().join(crate_name)?) 87 } 88 } 89 } legacy_crates_for(&self, crate_name: &str) -> Result<CrateCollection>90 fn legacy_crates_for(&self, crate_name: &str) -> Result<CrateCollection> { 91 let mut cc = self.new_cc(); 92 cc.add_from(format!("external/rust/crates/{}", crate_name))?; 93 Ok(cc) 94 } legacy_crates(&self) -> Result<CrateCollection>95 fn legacy_crates(&self) -> Result<CrateCollection> { 96 let mut cc = self.new_cc(); 97 cc.add_from("external/rust/crates")?; 98 Ok(cc) 99 } new_cc(&self) -> CrateCollection100 fn new_cc(&self) -> CrateCollection { 101 CrateCollection::new(self.path.root()) 102 } managed_crate_for( &self, crate_name: &str, ) -> Result<ManagedCrate<crate::managed_crate::New>>103 fn managed_crate_for( 104 &self, 105 crate_name: &str, 106 ) -> Result<ManagedCrate<crate::managed_crate::New>> { 107 Ok(ManagedCrate::new(Crate::from(self.managed_dir_for(crate_name))?)) 108 } all_crate_names(&self) -> Result<BTreeSet<String>>109 pub fn all_crate_names(&self) -> Result<BTreeSet<String>> { 110 let mut managed_dirs = BTreeSet::new(); 111 if self.managed_dir().abs().exists() { 112 for entry in read_dir(self.managed_dir())? { 113 let entry = entry?; 114 if entry.path().is_dir() { 115 managed_dirs.insert(entry.file_name().into_string().map_err(|e| { 116 anyhow!("Failed to convert {} to string", e.to_string_lossy()) 117 })?); 118 } 119 } 120 } 121 Ok(managed_dirs) 122 } migration_health( &self, crate_name: &str, verbose: bool, unpinned: bool, version: Option<&Version>, ) -> Result<Version>123 pub fn migration_health( 124 &self, 125 crate_name: &str, 126 verbose: bool, 127 unpinned: bool, 128 version: Option<&Version>, 129 ) -> Result<Version> { 130 if self.contains(crate_name) { 131 return Err(anyhow!("Crate {} already exists in {}/crates", crate_name, self.path)); 132 } 133 134 let cc = self.legacy_crates_for(crate_name)?; 135 let krate = match version { 136 Some(v) => { 137 let nv = NameAndVersionRef::new(crate_name, v); 138 match cc.map_field().get(&nv as &dyn NamedAndVersioned) { 139 Some(k) => k, 140 None => { 141 return Err(anyhow!("Did not find crate {} v{}", crate_name, v)); 142 } 143 } 144 } 145 None => { 146 if cc.map_field().len() != 1 { 147 return Err(anyhow!( 148 "Expected a single crate version for {}, but found {}. Specify a version with --versions={}=<version>", 149 crate_name, 150 cc.get_versions(crate_name).map(|(nv, _)| nv.version()).join(", "), 151 crate_name 152 )); 153 } 154 cc.map_field().values().next().unwrap() 155 } 156 }; 157 println!("Found {} v{} in {}", krate.name(), krate.version(), krate.path()); 158 159 let mut healthy_self_contained = true; 160 if krate.is_migration_denied() { 161 println!("This crate is on the migration denylist"); 162 healthy_self_contained = false; 163 } 164 165 let mc = ManagedCrate::new(Crate::from(krate.path().clone())?).into_legacy(); 166 if !mc.android_bp().abs().exists() { 167 println!("There is no Android.bp file in {}", krate.path()); 168 healthy_self_contained = false; 169 } 170 if !mc.cargo_embargo_json().abs().exists() { 171 println!("There is no cargo_embargo.json file in {}", krate.path()); 172 healthy_self_contained = false; 173 } 174 if healthy_self_contained { 175 let mc = mc.stage()?; 176 if !mc.cargo_embargo_success() { 177 println!("cargo_embargo execution did not succeed for {}", mc.staging_path(),); 178 if verbose { 179 println!( 180 "stdout:\n{}\nstderr:\n{}", 181 from_utf8(&mc.cargo_embargo_output().stdout)?, 182 from_utf8(&mc.cargo_embargo_output().stderr)?, 183 ); 184 } 185 healthy_self_contained = false; 186 } else if !mc.android_bp_unchanged() { 187 println!( 188 "Running cargo_embargo on {} produced changes to the Android.bp file", 189 mc.staging_path() 190 ); 191 if verbose { 192 println!("{}", from_utf8(&mc.android_bp_diff().stdout)?); 193 } 194 healthy_self_contained = false; 195 } 196 } 197 198 if !healthy_self_contained { 199 println!("Crate {} is UNHEALTHY", crate_name); 200 return Err(anyhow!("Crate {} is unhealthy", crate_name)); 201 } 202 203 let pseudo_crate = self.pseudo_crate(); 204 if unpinned { 205 pseudo_crate.cargo_add_unpinned(krate) 206 } else { 207 pseudo_crate.cargo_add(krate) 208 } 209 .inspect_err(|_e| { 210 let _ = pseudo_crate.remove(krate.name()); 211 })?; 212 let pseudo_crate = pseudo_crate.vendor()?; 213 214 let mc = ManagedCrate::new(Crate::from(krate.path().clone())?).stage(&pseudo_crate)?; 215 216 pseudo_crate.remove(krate.name())?; 217 218 let version = mc.vendored_version().clone(); 219 if mc.android_version() != mc.vendored_version() { 220 println!( 221 "Source and destination versions are different: {} -> {}", 222 mc.android_version(), 223 mc.vendored_version() 224 ); 225 } 226 if !mc.patch_success() { 227 println!("Patches did not apply successfully to the migrated crate"); 228 if verbose { 229 for output in mc.patch_output() { 230 if !output.1.status.success() { 231 println!( 232 "Failed to apply {}\nstdout:\n{}\nstderr:\n:{}", 233 output.0, 234 from_utf8(&output.1.stdout)?, 235 from_utf8(&output.1.stderr)? 236 ); 237 } 238 } 239 } 240 } 241 if !mc.cargo_embargo_success() { 242 println!("cargo_embargo execution did not succeed for the migrated crate"); 243 if verbose { 244 println!( 245 "stdout:\n{}\nstderr:\n{}", 246 from_utf8(&mc.cargo_embargo_output().stdout)?, 247 from_utf8(&mc.cargo_embargo_output().stderr)?, 248 ); 249 } 250 } else if !mc.android_bp_unchanged() { 251 println!("Running cargo_embargo for the migrated crate produced changes to the Android.bp file"); 252 if verbose { 253 println!("{}", from_utf8(&mc.android_bp_diff().stdout)?); 254 } 255 } 256 257 let mut diff_cmd = Command::new("diff"); 258 diff_cmd.args(["-u", "-r", "-w", "--no-dereference"]); 259 if !verbose { 260 diff_cmd.arg("-q"); 261 } 262 let diff_status = diff_cmd 263 .args(IGNORED_FILES.iter().map(|ignored| format!("--exclude={}", ignored))) 264 .args(["-I", r#"default_team: "trendy_team_android_rust""#]) 265 .arg(mc.android_crate_path().rel()) 266 .arg(mc.staging_path().rel()) 267 .current_dir(self.path.root()) 268 .spawn()? 269 .wait()?; 270 if !diff_status.success() { 271 println!( 272 "Found differences between {} and {}", 273 mc.android_crate_path(), 274 mc.staging_path() 275 ); 276 } 277 if verbose { 278 println!("All diffs:"); 279 Command::new("diff") 280 .args(["-u", "-r", "-w", "-q", "--no-dereference"]) 281 .arg(mc.android_crate_path().rel()) 282 .arg(mc.staging_path().rel()) 283 .current_dir(self.path.root()) 284 .spawn()? 285 .wait()?; 286 } 287 288 // Patching and running cargo_embargo *must* succeed. But if we are migrating with a version change, 289 // there could be some changes to the Android.bp. 290 if !mc.patch_success() 291 || !mc.cargo_embargo_success() 292 || (!mc.android_bp_unchanged() && !unpinned) 293 { 294 println!("Crate {} is UNHEALTHY", crate_name); 295 return Err(anyhow!("Crate {} is unhealthy", crate_name)); 296 } 297 298 if diff_status.success() && mc.android_bp_unchanged() { 299 println!("Crate {} is healthy", crate_name); 300 return Ok(version); 301 } 302 303 if unpinned { 304 println!("The crate was added with an unpinned version, and diffs were found which must be inspected manually"); 305 return Ok(version); 306 } 307 308 println!("Crate {} is UNHEALTHY", crate_name); 309 Err(anyhow!("Crate {} is unhealthy", crate_name)) 310 } migrate<T: AsRef<str>>( &self, crates: Vec<T>, verbose: bool, unpinned: &BTreeSet<String>, versions: &BTreeMap<String, Version>, ) -> Result<()>311 pub fn migrate<T: AsRef<str>>( 312 &self, 313 crates: Vec<T>, 314 verbose: bool, 315 unpinned: &BTreeSet<String>, 316 versions: &BTreeMap<String, Version>, 317 ) -> Result<()> { 318 let pseudo_crate = self.pseudo_crate(); 319 for crate_name in &crates { 320 let crate_name = crate_name.as_ref(); 321 let version = self.migration_health( 322 crate_name, 323 verbose, 324 unpinned.contains(crate_name), 325 versions.get(crate_name), 326 )?; 327 let src_dir = self.legacy_dir_for( 328 crate_name, 329 if unpinned.contains(crate_name) { None } else { Some(&version) }, 330 )?; 331 332 let monorepo_crate_dir = self.managed_dir(); 333 if !monorepo_crate_dir.abs().exists() { 334 create_dir(monorepo_crate_dir)?; 335 } 336 copy_dir(src_dir, self.managed_dir_for(crate_name))?; 337 if unpinned.contains(crate_name) { 338 pseudo_crate.cargo_add_unpinned(&NameAndVersionRef::new(crate_name, &version))?; 339 } else { 340 pseudo_crate.cargo_add(&NameAndVersionRef::new(crate_name, &version))?; 341 } 342 } 343 344 self.regenerate(crates.iter(), false)?; 345 346 for crate_name in &crates { 347 let crate_name = crate_name.as_ref(); 348 let src_dir = self.legacy_dir_for( 349 crate_name, 350 if unpinned.contains(crate_name) { None } else { versions.get(crate_name) }, 351 )?; 352 for entry in glob( 353 src_dir 354 .abs() 355 .join("*.bp") 356 .to_str() 357 .ok_or(anyhow!("Failed to convert path *.bp to str"))?, 358 )? { 359 remove_file(entry?)?; 360 } 361 remove_file(src_dir.join("cargo_embargo.json")?)?; 362 let test_mapping = src_dir.join("TEST_MAPPING")?; 363 if test_mapping.abs().exists() { 364 remove_file(test_mapping)?; 365 } 366 write( 367 src_dir.join("Android.bp")?, 368 format!("// This crate has been migrated to {}.\n", self.path), 369 )?; 370 } 371 372 Ok(()) 373 } analyze_import(&self, crate_name: &str) -> Result<()>374 pub fn analyze_import(&self, crate_name: &str) -> Result<()> { 375 if self.contains(crate_name) { 376 println!("Crate already imported at {}", self.managed_dir_for(crate_name)); 377 return Ok(()); 378 } 379 let legacy_dir = self.legacy_dir_for(crate_name, None)?; 380 if legacy_dir.abs().exists() { 381 println!("Legacy crate already imported at {}", legacy_dir); 382 return Ok(()); 383 } 384 385 let mut managed_crates = self.new_cc(); 386 managed_crates.add_from(self.managed_dir().rel())?; 387 let legacy_crates = self.legacy_crates()?; 388 389 let cio_crate = self.crates_io.get_crate(crate_name)?; 390 391 for version in cio_crate.versions() { 392 println!("Version {}", version.version()); 393 let mut found_problems = false; 394 for (dep, req) in version.android_deps_with_version_reqs() { 395 println!("Found dep {}", dep.crate_name()); 396 let cc = if managed_crates.contains_name(dep.crate_name()) { 397 &managed_crates 398 } else { 399 &legacy_crates 400 }; 401 if !cc.contains_name(dep.crate_name()) { 402 found_problems = true; 403 println!( 404 " Dep {} {} has not been imported to Android", 405 dep.crate_name(), 406 dep.requirement() 407 ); 408 // This is a no-op because our dependency code only considers normal deps anyway. 409 // TODO: Fix the deps code. 410 if matches!(dep.kind(), DependencyKind::Dev) { 411 println!(" But this is a dev dependency, probably only needed if you want to run the tests"); 412 } 413 if dep.is_optional() { 414 println!(" But this is an optional dependency, used by the following features: {}", dep.features().join(", ")); 415 } 416 continue; 417 } 418 let versions = cc.get_versions(dep.crate_name()).collect::<Vec<_>>(); 419 let has_matching_version = 420 versions.iter().any(|(nv, _)| req.matches_relaxed(nv.version())); 421 if !has_matching_version { 422 found_problems = true; 423 } 424 if !has_matching_version || versions.len() > 1 { 425 if has_matching_version { 426 println!(" Dep {} has multiple versions available. You may need to override the default choice in cargo_embargo.json", dep.crate_name()); 427 } 428 for (_, dep_crate) in versions { 429 println!( 430 " Dep {} {} is {}satisfied by v{} at {}", 431 dep.crate_name(), 432 dep.requirement(), 433 if req.matches_relaxed(dep_crate.version()) { "" } else { "not " }, 434 dep_crate.version(), 435 dep_crate.path() 436 ); 437 } 438 } 439 } 440 if !found_problems { 441 println!(" No problems found with this version.") 442 } 443 } 444 Ok(()) 445 } import(&self, crate_name: &str, version: &str, autoconfig: bool) -> Result<()>446 pub fn import(&self, crate_name: &str, version: &str, autoconfig: bool) -> Result<()> { 447 if self.contains(crate_name) { 448 return Err(anyhow!("Crate already imported at {}", self.managed_dir_for(crate_name))); 449 } 450 let legacy_dir = self.legacy_dir_for(crate_name, None)?; 451 if legacy_dir.abs().exists() { 452 return Err(anyhow!("Legacy crate already imported at {}", legacy_dir)); 453 } 454 455 let pseudo_crate = self.pseudo_crate(); 456 let version = Version::parse(version)?; 457 let nv = NameAndVersionRef::new(crate_name, &version); 458 pseudo_crate.cargo_add(&nv)?; 459 let pseudo_crate = pseudo_crate.vendor()?; 460 461 let vendored_dir = pseudo_crate.vendored_dir_for(crate_name)?; 462 let managed_dir = self.managed_dir_for(crate_name); 463 println!("Creating {} from vendored crate", managed_dir); 464 copy_dir(vendored_dir, &managed_dir)?; 465 466 println!("Sprinkling Android glitter on {}:", crate_name); 467 468 let krate = Crate::from(managed_dir.clone())?; 469 470 println!(" Finding license files"); 471 let licenses = find_licenses(krate.path().abs(), krate.name(), krate.license())?; 472 473 if !licenses.unsatisfied.is_empty() && licenses.satisfied.is_empty() { 474 let mut satisfied = false; 475 // Sometimes multiple crates live in a single GitHub repo. A common case 476 // is a crate with an associated proc_macro crate. In such cases, the individual 477 // crates are in subdirectories with license files at root of the repo, and 478 // the license files don't get distributed with the crates. 479 // So, if we didn't find a license file, try to guess the URL of the appropriate 480 // license file and download it. This is incredibly hacky, and only supports 481 // the most common case, which is LICENSE-APACHE. 482 if licenses.unsatisfied.len() == 1 { 483 let req = licenses.unsatisfied.first().unwrap(); 484 if let Some(repository) = krate.repository() { 485 if *req == Licensee::parse("Apache-2.0").unwrap().into_req() { 486 let url = format!("{}/master/LICENSE-APACHE", repository); 487 let body = reqwest::blocking::get( 488 url.replace("github.com", "raw.githubusercontent.com"), 489 )? 490 .text()?; 491 write(krate.path().abs().join("LICENSE"), body)?; 492 let patch_dir = krate.path().abs().join("patches"); 493 create_dir(&patch_dir)?; 494 let output = Command::new("diff") 495 .args(["-u", "/dev/null", "LICENSE"]) 496 .current_dir(krate.path().abs()) 497 .output()?; 498 write(patch_dir.join("LICENSE.patch"), output.stdout)?; 499 satisfied = true; 500 } 501 } 502 } 503 if !satisfied { 504 return Err(anyhow!( 505 "Could not find license files for all licenses. Missing {}", 506 licenses.unsatisfied.iter().join(", ") 507 )); 508 } 509 } 510 511 // If there's a single applicable license file, symlink it to LICENSE. 512 if licenses.satisfied.len() == 1 && licenses.unsatisfied.is_empty() { 513 let license_file = krate.path().join("LICENSE")?; 514 if !license_file.abs().exists() { 515 symlink( 516 licenses.satisfied.iter().next().unwrap().1.file_name().unwrap(), 517 license_file, 518 )?; 519 } 520 } 521 522 update_module_license_files(&krate.path().abs(), &licenses)?; 523 524 println!(" Creating METADATA"); 525 let metadata = GoogleMetadata::init( 526 krate.path().join("METADATA")?, 527 krate.name(), 528 krate.version().to_string(), 529 krate.description(), 530 most_restrictive_type(&licenses), 531 )?; 532 metadata.write()?; 533 534 println!(" Creating cargo_embargo.json and Android.bp"); 535 if autoconfig { 536 // TODO: Copy to a temp dir, because otherwise we might run cargo and create/modify Cargo.lock. 537 cargo_embargo_autoconfig(&managed_dir)? 538 .success_or_error() 539 .context("Failed to generate cargo_embargo.json with 'cargo_embargo autoconfig'")?; 540 } else { 541 write(krate.path().abs().join("cargo_embargo.json"), "{}")?; 542 } 543 // Workaround. Our logic for crate health assumes the crate isn't healthy if there's 544 // no Android.bp. So create an empty one. 545 write(krate.path().abs().join("Android.bp"), "")?; 546 547 self.regenerate([&crate_name].iter(), true)?; 548 println!("Please edit {} and run 'regenerate' for this crate", managed_dir); 549 550 // TODO: Create TEST_MAPPING 551 552 Ok(()) 553 } regenerate<T: AsRef<str>>( &self, crates: impl Iterator<Item = T>, update_metadata: bool, ) -> Result<()>554 pub fn regenerate<T: AsRef<str>>( 555 &self, 556 crates: impl Iterator<Item = T>, 557 update_metadata: bool, 558 ) -> Result<()> { 559 let pseudo_crate = self.pseudo_crate().vendor()?; 560 for crate_name in crates { 561 let mc = self.managed_crate_for(crate_name.as_ref())?; 562 // TODO: Don't give up if there's a failure. 563 mc.regenerate(update_metadata, &pseudo_crate)?; 564 } 565 566 pseudo_crate.regenerate_crate_list()?; 567 568 Ok(()) 569 } stage<T: AsRef<str>>(&self, crates: impl Iterator<Item = T>) -> Result<()>570 pub fn stage<T: AsRef<str>>(&self, crates: impl Iterator<Item = T>) -> Result<()> { 571 let pseudo_crate = self.pseudo_crate().vendor()?; 572 for crate_name in crates { 573 let mc = self.managed_crate_for(crate_name.as_ref())?.stage(&pseudo_crate)?; 574 // TODO: Don't give up if there's a failure. 575 mc.check_staged()?; 576 } 577 Ok(()) 578 } preupload_check(&self, files: &[String]) -> Result<()>579 pub fn preupload_check(&self, files: &[String]) -> Result<()> { 580 let pseudo_crate = self.pseudo_crate().vendor()?; 581 let deps = pseudo_crate.deps().keys().cloned().collect::<BTreeSet<_>>(); 582 583 let managed_dirs = self.all_crate_names()?; 584 585 if deps != managed_dirs { 586 return Err(anyhow!("Deps in pseudo_crate/Cargo.toml don't match directories in {}\nDirectories not in Cargo.toml: {}\nCargo.toml deps with no directory: {}", 587 self.managed_dir(), managed_dirs.difference(&deps).join(", "), deps.difference(&managed_dirs).join(", "))); 588 } 589 590 let crate_list = pseudo_crate.read_crate_list()?; 591 if deps.iter().ne(crate_list.iter()) { 592 return Err(anyhow!("Deps in pseudo_crate/Cargo.toml don't match deps in crate-list.txt\nCargo.toml: {}\ncrate-list.txt: {}", 593 deps.iter().join(", "), crate_list.iter().join(", "))); 594 } 595 596 // Per https://android.googlesource.com/platform/tools/repohooks/, 597 // the REPO_PATH environment variable is the path of the git repo relative to the 598 // root of the Android source tree. 599 let prefix = self.path.rel().strip_prefix(env::var("REPO_PATH")?)?; 600 let changed_android_crates = files 601 .iter() 602 .filter_map(|file| Path::new(file).strip_prefix(prefix).ok()) 603 .filter_map(|path| { 604 let components = path.components().collect::<Vec<_>>(); 605 if path.starts_with("crates/") && components.len() > 2 { 606 Some(components[1].as_os_str().to_string_lossy().to_string()) 607 } else { 608 None 609 } 610 }) 611 .collect::<BTreeSet<_>>(); 612 613 for crate_name in changed_android_crates { 614 println!("Checking {}", crate_name); 615 let mc = self.managed_crate_for(&crate_name)?.stage(&pseudo_crate)?; 616 mc.diff_staged()?; 617 } 618 Ok(()) 619 } fix_licenses<T: AsRef<str>>(&self, crates: impl Iterator<Item = T>) -> Result<()>620 pub fn fix_licenses<T: AsRef<str>>(&self, crates: impl Iterator<Item = T>) -> Result<()> { 621 for crate_name in crates { 622 self.managed_crate_for(crate_name.as_ref())?.fix_licenses()?; 623 } 624 Ok(()) 625 } fix_metadata<T: AsRef<str>>(&self, crates: impl Iterator<Item = T>) -> Result<()>626 pub fn fix_metadata<T: AsRef<str>>(&self, crates: impl Iterator<Item = T>) -> Result<()> { 627 for crate_name in crates { 628 self.managed_crate_for(crate_name.as_ref())?.fix_metadata()?; 629 } 630 Ok(()) 631 } recontextualize_patches<T: AsRef<str>>( &self, crates: impl Iterator<Item = T>, ) -> Result<()>632 pub fn recontextualize_patches<T: AsRef<str>>( 633 &self, 634 crates: impl Iterator<Item = T>, 635 ) -> Result<()> { 636 for crate_name in crates { 637 let mc = self.managed_crate_for(crate_name.as_ref())?; 638 mc.recontextualize_patches()?; 639 } 640 Ok(()) 641 } updatable_crates(&self) -> Result<()>642 pub fn updatable_crates(&self) -> Result<()> { 643 let mut cc = self.new_cc(); 644 cc.add_from(self.managed_dir().rel())?; 645 646 for krate in cc.map_field().values() { 647 let cio_crate = self.crates_io.get_crate(krate.name())?; 648 let upgrades = 649 cio_crate.versions_gt(krate.version()).map(|v| v.version()).collect::<Vec<_>>(); 650 if !upgrades.is_empty() { 651 println!( 652 "{} v{}:\n {}", 653 krate.name(), 654 krate.version(), 655 upgrades 656 .iter() 657 .chunks(10) 658 .into_iter() 659 .map(|mut c| { c.join(", ") }) 660 .join(",\n ") 661 ); 662 } 663 } 664 Ok(()) 665 } analyze_updates(&self, crate_name: impl AsRef<str>) -> Result<()>666 pub fn analyze_updates(&self, crate_name: impl AsRef<str>) -> Result<()> { 667 let mut managed_crates = self.new_cc(); 668 managed_crates.add_from(self.managed_dir().rel())?; 669 let legacy_crates = self.legacy_crates()?; 670 671 let krate = self.managed_crate_for(crate_name.as_ref())?; 672 println!("Analyzing updates to {} v{}", krate.name(), krate.android_version()); 673 let patches = krate.patches()?; 674 if !patches.is_empty() { 675 println!("This crate has patches, so expect a fun time trying to update it:"); 676 for patch in patches { 677 println!( 678 " {}", 679 Path::new(patch.file_name().ok_or(anyhow!("No file name"))?).display() 680 ); 681 } 682 } 683 684 let cio_crate = self.crates_io.get_crate(crate_name)?; 685 686 let base_version = cio_crate.get_version(krate.android_version()).ok_or(anyhow!( 687 "{} v{} not found in crates.io", 688 krate.name(), 689 krate.android_version() 690 ))?; 691 let base_deps = base_version.android_version_reqs_by_name(); 692 693 let mut newer_versions = cio_crate.versions_gt(krate.android_version()).peekable(); 694 if newer_versions.peek().is_none() { 695 println!("There are no newer versions of this crate."); 696 } 697 for version in newer_versions { 698 println!("Version {}", version.version()); 699 let mut found_problems = false; 700 let parsed_version = semver::Version::parse(version.version())?; 701 if !krate.android_version().is_upgradable_to(&parsed_version) { 702 found_problems = true; 703 if !krate.android_version().is_upgradable_to_relaxed(&parsed_version) { 704 println!(" Not semver-compatible, even by relaxed standards"); 705 } else { 706 println!(" Semver-compatible, but only by relaxed standards since major version is 0"); 707 } 708 } 709 // Check to see if the update has any missing dependencies. 710 // We try to be a little clever about this in the following ways: 711 // * Only consider deps that are likely to be relevant to Android. For example, ignore Windows-only deps. 712 // * If a dep is missing, but the same dep exists for the current version of the crate, it's probably not actually necessary. 713 // * Use relaxed version requirements, treating 0.x and 0.y as compatible, even though they aren't according to semver rules. 714 for (dep, req) in version.android_deps_with_version_reqs() { 715 let cc = if managed_crates.contains_name(dep.crate_name()) { 716 &managed_crates 717 } else { 718 &legacy_crates 719 }; 720 if !cc.contains_name(dep.crate_name()) { 721 found_problems = true; 722 println!( 723 " Dep {} {} has not been imported to Android", 724 dep.crate_name(), 725 dep.requirement() 726 ); 727 if !dep.is_new_dep(&base_deps) { 728 println!(" But the current version has the same dependency, and it seems to work"); 729 } else { 730 continue; 731 } 732 } 733 for (_, dep_crate) in cc.get_versions(dep.crate_name()) { 734 if !req.matches_relaxed(dep_crate.version()) { 735 found_problems = true; 736 println!( 737 " Dep {} {} is not satisfied by v{} at {}", 738 dep.crate_name(), 739 dep.requirement(), 740 dep_crate.version(), 741 dep_crate.path() 742 ); 743 if !dep.is_changed_dep(&base_deps) { 744 println!(" But the current version has the same dependency and it seems to work.") 745 } 746 } 747 } 748 } 749 if !found_problems { 750 println!(" No problems found with this version.") 751 } 752 } 753 754 Ok(()) 755 } suggest_updates(&self, consider_patched_crates: bool) -> Result<Vec<(String, String)>>756 pub fn suggest_updates(&self, consider_patched_crates: bool) -> Result<Vec<(String, String)>> { 757 let mut suggestions = Vec::new(); 758 let mut managed_crates = self.new_cc(); 759 managed_crates.add_from(self.managed_dir().rel())?; 760 let legacy_crates = self.legacy_crates()?; 761 762 for krate in managed_crates.map_field().values() { 763 let cio_crate = self.crates_io.get_crate(krate.name())?; 764 765 let base_version = cio_crate.get_version(krate.version()); 766 if base_version.is_none() { 767 println!( 768 "Skipping crate {} v{} because it was not found in crates.io", 769 krate.name(), 770 krate.version() 771 ); 772 continue; 773 } 774 let base_version = base_version.unwrap(); 775 let base_deps = base_version.android_version_reqs_by_name(); 776 777 let patch_dir = krate.path().join("patches").unwrap(); 778 if patch_dir.abs().exists() && !consider_patched_crates { 779 println!( 780 "Skipping crate {} v{} because it has patches", 781 krate.name(), 782 krate.version() 783 ); 784 continue; 785 } 786 787 for version in cio_crate.versions_gt(krate.version()).rev() { 788 let parsed_version = semver::Version::parse(version.version())?; 789 if !krate.version().is_upgradable_to_relaxed(&parsed_version) { 790 continue; 791 } 792 if !version.android_deps_with_version_reqs().any(|(dep, req)| { 793 if !dep.is_changed_dep(&base_deps) { 794 return false; 795 } 796 let cc = if managed_crates.contains_name(dep.crate_name()) { 797 &managed_crates 798 } else { 799 &legacy_crates 800 }; 801 for (_, dep_crate) in cc.get_versions(dep.crate_name()) { 802 if req.matches_relaxed(dep_crate.version()) { 803 return false; 804 } 805 } 806 true 807 }) { 808 println!( 809 "Upgrade crate {} v{} to {}", 810 krate.name(), 811 krate.version(), 812 version.version() 813 ); 814 suggestions.push((krate.name().to_string(), version.version().to_string())); 815 break; 816 } 817 } 818 } 819 Ok(suggestions) 820 } update(&self, crate_name: impl AsRef<str>, version: impl AsRef<str>) -> Result<()>821 pub fn update(&self, crate_name: impl AsRef<str>, version: impl AsRef<str>) -> Result<()> { 822 let pseudo_crate = self.pseudo_crate(); 823 let version = Version::parse(version.as_ref())?; 824 let nv = NameAndVersionRef::new(crate_name.as_ref(), &version); 825 pseudo_crate.remove(&crate_name)?; 826 pseudo_crate.cargo_add(&nv)?; 827 self.regenerate([&crate_name].iter(), true)?; 828 Ok(()) 829 } try_updates(&self) -> Result<()>830 pub fn try_updates(&self) -> Result<()> { 831 let output = Command::new("git") 832 .args(["status", "--porcelain", "."]) 833 .current_dir(&self.path) 834 .output()? 835 .success_or_error()?; 836 if !output.stdout.is_empty() { 837 return Err(anyhow!("Crate repo {} has uncommitted changes", self.path)); 838 } 839 840 for (crate_name, version) in self.suggest_updates(true)? { 841 println!("Trying to update {} to {}", crate_name, version); 842 Command::new("git") 843 .args(["restore", "."]) 844 .current_dir(&self.path) 845 .output()? 846 .success_or_error()?; 847 Command::new("git") 848 .args(["clean", "-f", "."]) 849 .current_dir(&self.path) 850 .output()? 851 .success_or_error()?; 852 if let Err(e) = self.update(&crate_name, &version) { 853 println!("Updating {} to {} failed: {}", crate_name, version, e); 854 continue; 855 } 856 let build_result = Command::new("/usr/bin/bash") 857 .args(["-c", "source build/envsetup.sh && lunch aosp_husky-trunk_staging-eng && cd external/rust && mm"]) 858 .env_remove("OUT_DIR") 859 .current_dir(self.path.root()) 860 .spawn().context("Failed to spawn mm")? 861 .wait().context("Failed to wait on mm")? 862 .success_or_error(); 863 if let Err(e) = build_result { 864 println!("Faild to build {} {}: {}", crate_name, version, e); 865 continue; 866 } 867 println!("Update {} to {} succeeded", crate_name, version); 868 } 869 Ok(()) 870 } init(&self) -> Result<()>871 pub fn init(&self) -> Result<()> { 872 if self.path.abs().exists() { 873 return Err(anyhow!("{} already exists", self.path)); 874 } 875 create_dir_all(&self.path).context(format!("Failed to create {}", self.path))?; 876 let crates_dir = self.path.join("crates")?; 877 create_dir_all(&crates_dir).context(format!("Failed to create {}", crates_dir))?; 878 self.pseudo_crate().init()?; 879 Ok(()) 880 } fix_test_mapping<T: AsRef<str>>(&self, crates: impl Iterator<Item = T>) -> Result<()>881 pub fn fix_test_mapping<T: AsRef<str>>(&self, crates: impl Iterator<Item = T>) -> Result<()> { 882 for crate_name in crates { 883 let mc = self.managed_crate_for(crate_name.as_ref())?; 884 mc.fix_test_mapping()?; 885 } 886 Ok(()) 887 } 888 } 889 890 // Files that are ignored when migrating a crate to the monorepo. 891 static IGNORED_FILES: &[&str] = &[ 892 ".appveyor.yml", 893 ".bazelci", 894 ".bazelignore", 895 ".bazelrc", 896 ".bazelversion", 897 ".buildkite", 898 ".cargo", 899 ".cargo-checksum.json", 900 ".cargo_vcs_info.json", 901 ".circleci", 902 ".cirrus.yml", 903 ".clang-format", 904 ".clang-tidy", 905 ".clippy.toml", 906 ".clog.toml", 907 ".clog.toml", 908 ".codecov.yaml", 909 ".codecov.yml", 910 ".editorconfig", 911 ".envrc", 912 ".gcloudignore", 913 ".gdbinit", 914 ".git", 915 ".git-blame-ignore-revs", 916 ".git-ignore-revs", 917 ".gitallowed", 918 ".gitattributes", 919 ".github", 920 ".gitignore", 921 ".idea", 922 ".ignore", 923 ".istanbul.yml", 924 ".mailmap", 925 ".md-inc.toml", 926 ".mdl-style.rb", 927 ".mdlrc", 928 ".pylintrc", 929 ".pylintrc-examples", 930 ".pylintrc-tests", 931 ".reuse", 932 ".rspec", 933 ".rustfmt.toml", 934 ".shellcheckrc", 935 ".standard-version", 936 ".tarpaulin.toml", 937 ".tokeignore", 938 ".travis.yml", 939 ".versionrc", 940 ".vim", 941 ".vscode", 942 ".yapfignore", 943 ".yardopts", 944 "BUILD", 945 "Cargo.lock", 946 "Cargo.lock.saved", 947 "Cargo.toml.orig", 948 "OWNERS", 949 // Deprecated config file for rules.mk. 950 "cargo2rulesmk.json", 951 // cargo_embargo intermediates. 952 "Android.bp.orig", 953 "cargo.metadata", 954 "cargo.out", 955 "target.tmp", 956 ]; 957