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 cell::OnceCell, 17 collections::BTreeSet, 18 env, 19 fs::{create_dir_all, read_dir, write}, 20 path::Path, 21 }; 22 23 use anyhow::{anyhow, bail, Context, Result}; 24 use crates_index::DependencyKind; 25 use crates_io_util::CratesIoIndex; 26 use google_metadata::GoogleMetadata; 27 use itertools::Itertools; 28 use license_checker::find_licenses; 29 use name_and_version::{NameAndVersion, NameAndVersionRef, NamedAndVersioned}; 30 use repo_config::RepoConfig; 31 use rooted_path::RootedPath; 32 use semver::{Version, VersionReq}; 33 use serde::Serialize; 34 35 use crate::{ 36 android_bp::cargo_embargo_autoconfig, 37 copy_dir, 38 crate_collection::CrateCollection, 39 crate_type::Crate, 40 crates_io::{AndroidDependencies, DependencyChanges, SafeVersions}, 41 license::{most_restrictive_type, update_module_license_files}, 42 managed_crate::ManagedCrate, 43 pseudo_crate::{CargoVendorDirty, PseudoCrate}, 44 upgradable::{IsUpgradableTo, MatchesWithCompatibilityRule, SemverCompatibilityRule}, 45 SuccessOrError, 46 }; 47 48 #[derive(Serialize, Default, Debug)] 49 struct UpdateSuggestions { 50 updates: Vec<UpdateSuggestion>, 51 } 52 53 #[derive(Serialize, Default, Debug)] 54 struct UpdateSuggestion { 55 name: String, 56 #[serde(skip)] 57 old_version: String, 58 version: String, 59 } 60 61 pub struct ManagedRepo { 62 path: RootedPath, 63 config: OnceCell<RepoConfig>, 64 crates_io: CratesIoIndex, 65 } 66 67 impl ManagedRepo { new(path: RootedPath, offline: bool) -> Result<ManagedRepo>68 pub fn new(path: RootedPath, offline: bool) -> Result<ManagedRepo> { 69 Ok(ManagedRepo { 70 path, 71 config: OnceCell::new(), 72 crates_io: if offline { CratesIoIndex::new_offline()? } else { CratesIoIndex::new()? }, 73 }) 74 } config(&self) -> &RepoConfig75 pub fn config(&self) -> &RepoConfig { 76 self.config.get_or_init(|| { 77 RepoConfig::read(self.path.abs()).unwrap_or_else(|e| { 78 panic!( 79 "Failed to read crate config {}/{}: {}", 80 self.path, 81 repo_config::CONFIG_FILE_NAME, 82 e 83 ) 84 }) 85 }) 86 } pseudo_crate(&self) -> PseudoCrate<CargoVendorDirty>87 fn pseudo_crate(&self) -> PseudoCrate<CargoVendorDirty> { 88 PseudoCrate::new(self.path.join("pseudo_crate").unwrap()) 89 } contains(&self, crate_name: &str) -> bool90 fn contains(&self, crate_name: &str) -> bool { 91 self.managed_dir_for(crate_name).abs().exists() 92 } managed_dir(&self) -> RootedPath93 fn managed_dir(&self) -> RootedPath { 94 self.path.join("crates").unwrap() 95 } managed_dir_for(&self, crate_name: &str) -> RootedPath96 fn managed_dir_for(&self, crate_name: &str) -> RootedPath { 97 self.managed_dir().join(crate_name).unwrap() 98 } legacy_dir_for(&self, crate_name: &str, version: Option<&Version>) -> Result<RootedPath>99 fn legacy_dir_for(&self, crate_name: &str, version: Option<&Version>) -> Result<RootedPath> { 100 match version { 101 Some(v) => { 102 let cc = self.legacy_crates_for(crate_name)?; 103 let nv = NameAndVersionRef::new(crate_name, v); 104 Ok(cc 105 .get(&nv as &dyn NamedAndVersioned) 106 .ok_or(anyhow!("Failed to find crate {} v{}", crate_name, v))? 107 .path() 108 .clone()) 109 } 110 None => { 111 Ok(self.path.with_same_root("external/rust/crates").unwrap().join(crate_name)?) 112 } 113 } 114 } legacy_crates_for(&self, crate_name: &str) -> Result<CrateCollection>115 fn legacy_crates_for(&self, crate_name: &str) -> Result<CrateCollection> { 116 let mut cc = self.new_cc(); 117 cc.add_from(format!("external/rust/crates/{}", crate_name))?; 118 Ok(cc) 119 } legacy_crates(&self) -> Result<CrateCollection>120 fn legacy_crates(&self) -> Result<CrateCollection> { 121 let mut cc = self.new_cc(); 122 cc.add_from("external/rust/crates")?; 123 Ok(cc) 124 } new_cc(&self) -> CrateCollection125 fn new_cc(&self) -> CrateCollection { 126 CrateCollection::new(self.path.root()) 127 } managed_crate_for( &self, crate_name: &str, ) -> Result<ManagedCrate<crate::managed_crate::New>>128 fn managed_crate_for( 129 &self, 130 crate_name: &str, 131 ) -> Result<ManagedCrate<crate::managed_crate::New>> { 132 Ok(ManagedCrate::new(Crate::from(self.managed_dir_for(crate_name))?)) 133 } all_crate_names(&self) -> Result<BTreeSet<String>>134 pub fn all_crate_names(&self) -> Result<BTreeSet<String>> { 135 let mut managed_dirs = BTreeSet::new(); 136 if self.managed_dir().abs().exists() { 137 for entry in read_dir(self.managed_dir())? { 138 let entry = entry?; 139 if entry.path().is_dir() { 140 managed_dirs.insert(entry.file_name().into_string().map_err(|e| { 141 anyhow!("Failed to convert {} to string", e.to_string_lossy()) 142 })?); 143 } 144 } 145 } 146 Ok(managed_dirs) 147 } analyze_import(&self, crate_name: &str) -> Result<()>148 pub fn analyze_import(&self, crate_name: &str) -> Result<()> { 149 if self.contains(crate_name) { 150 println!("Crate already imported at {}", self.managed_dir_for(crate_name)); 151 return Ok(()); 152 } 153 let legacy_dir = self.legacy_dir_for(crate_name, None)?; 154 if legacy_dir.abs().exists() { 155 println!("Legacy crate already imported at {}", legacy_dir); 156 return Ok(()); 157 } 158 159 if !self.config().is_allowed(crate_name) { 160 println!("Crate {crate_name} is on the import denylist"); 161 return Ok(()); 162 } 163 164 let mut managed_crates = self.new_cc(); 165 managed_crates.add_from(self.managed_dir().rel())?; 166 let legacy_crates = self.legacy_crates()?; 167 168 let cio_crate = self.crates_io.get_crate(crate_name)?; 169 170 for version in cio_crate.safe_versions() { 171 println!("Version {}", version.version()); 172 let mut found_problems = false; 173 for (dep, req) in version.android_deps_with_version_reqs() { 174 let cc = if managed_crates.contains_crate(dep.crate_name()) { 175 &managed_crates 176 } else { 177 &legacy_crates 178 }; 179 if !cc.contains_crate(dep.crate_name()) { 180 found_problems = true; 181 println!( 182 " Dep {} {} has not been imported to Android", 183 dep.crate_name(), 184 dep.requirement() 185 ); 186 if !self.config().is_allowed(dep.crate_name()) { 187 println!(" And {} is on the import denylist", dep.crate_name()); 188 } 189 // This is a no-op because our dependency code only considers normal deps anyway. 190 // TODO: Fix the deps code. 191 if matches!(dep.kind(), DependencyKind::Dev) { 192 println!(" But this is a dev dependency, probably only needed if you want to run the tests"); 193 } 194 if dep.is_optional() { 195 println!(" But this is an optional dependency, used by the following features: {}", dep.features().join(", ")); 196 } 197 continue; 198 } 199 let versions = cc.get_versions(dep.crate_name()).collect::<Vec<_>>(); 200 let has_matching_version = versions.iter().any(|(nv, _)| { 201 req.matches_with_compatibility_rule( 202 nv.version(), 203 SemverCompatibilityRule::Loose, 204 ) 205 }); 206 if !has_matching_version { 207 found_problems = true; 208 } 209 if !has_matching_version || versions.len() > 1 { 210 if has_matching_version { 211 println!(" Dep {} has multiple versions available. You may need to override the default choice in cargo_embargo.json", dep.crate_name()); 212 } 213 for (_, dep_crate) in versions { 214 println!( 215 " Dep {} {} is {}satisfied by v{} at {}", 216 dep.crate_name(), 217 dep.requirement(), 218 if req.matches_with_compatibility_rule( 219 dep_crate.version(), 220 SemverCompatibilityRule::Loose 221 ) { 222 "" 223 } else { 224 "not " 225 }, 226 dep_crate.version(), 227 dep_crate.path() 228 ); 229 } 230 } 231 } 232 if !found_problems { 233 println!(" No problems found with this version.") 234 } 235 } 236 Ok(()) 237 } import(&self, crate_name: &str, version: &str, autoconfig: bool) -> Result<()>238 pub fn import(&self, crate_name: &str, version: &str, autoconfig: bool) -> Result<()> { 239 if self.contains(crate_name) { 240 bail!("Crate already imported at {}", self.managed_dir_for(crate_name)); 241 } 242 let legacy_dir = self.legacy_dir_for(crate_name, None)?; 243 if legacy_dir.abs().exists() { 244 bail!("Legacy crate already imported at {}", legacy_dir); 245 } 246 if !self.config().is_allowed(crate_name) { 247 bail!("Crate {crate_name} is on the import denylist"); 248 } 249 250 let pseudo_crate = self.pseudo_crate(); 251 let version = Version::parse(version)?; 252 let nv = NameAndVersionRef::new(crate_name, &version); 253 pseudo_crate.cargo_add(&nv)?; 254 let pseudo_crate = pseudo_crate.vendor()?; 255 256 let vendored_dir = pseudo_crate.vendored_dir_for(crate_name)?; 257 let managed_dir = self.managed_dir_for(crate_name); 258 println!("Creating {} from vendored crate", managed_dir); 259 copy_dir(vendored_dir, &managed_dir)?; 260 261 println!("Sprinkling Android glitter on {}:", crate_name); 262 263 let krate = Crate::from(managed_dir.clone())?; 264 265 println!(" Finding license files"); 266 let licenses = find_licenses(krate.path().abs(), krate.name(), krate.license())?; 267 268 update_module_license_files(&krate.path().abs(), &licenses)?; 269 270 println!(" Creating METADATA"); 271 let metadata = GoogleMetadata::init( 272 krate.path().join("METADATA")?, 273 krate.name(), 274 krate.version().to_string(), 275 krate.description(), 276 most_restrictive_type(&licenses), 277 )?; 278 metadata.write()?; 279 280 println!(" Creating cargo_embargo.json and Android.bp"); 281 if autoconfig { 282 // TODO: Copy to a temp dir, because otherwise we might run cargo and create/modify Cargo.lock. 283 cargo_embargo_autoconfig(&managed_dir)? 284 .success_or_error() 285 .context("Failed to generate cargo_embargo.json with 'cargo_embargo autoconfig'")?; 286 } else { 287 write( 288 krate.path().abs().join("cargo_embargo.json"), 289 r#"{ 290 "run_cargo": false, 291 "min_sdk_version": "29" 292 }"#, 293 )?; 294 } 295 296 if !licenses.unsatisfied.is_empty() { 297 println!( 298 r#" 299 Unable to find license files for the following license terms: {:?} 300 301 Please look in {managed_dir} and try to locate the license file 302 303 If you find the license file: 304 * That means there's a bug in our detection logic. 305 * Please file a bug at http://go/android-rust-crate-bug 306 307 If you can't find the license file: 308 * This is usually because the source repo for the crate contains several crates in 309 separate directories, with a license file at the root level that's not included in 310 each crate 311 * Please go to the upstream repo for the crate at {} 312 * Download the license file and create a patch for it. Instructions for creating patches 313 are at https://android.googlesource.com/platform/external/rust/android-crates-io/+/refs/heads/main/README.md#how-to-add-a-patch-file 314 * Run `crate_tool regenerate {}` after you have added a patch for the license file 315 316 We apologize for the inconvenience."#, 317 licenses.unsatisfied.iter().map(|u| u.to_string()).join(", "), 318 krate.repository().unwrap_or("(Crate repository URL not found in Cargo.toml)"), 319 krate.name() 320 ); 321 } else { 322 self.regenerate([&crate_name].iter(), true)?; 323 println!( 324 "Please edit {} and run 'regenerate' for this crate", 325 managed_dir.rel().join("cargo_embargo.json").display() 326 ); 327 } 328 329 Ok(()) 330 } regenerate<T: AsRef<str>>( &self, crates: impl Iterator<Item = T>, run_cargo_embargo: bool, ) -> Result<()>331 pub fn regenerate<T: AsRef<str>>( 332 &self, 333 crates: impl Iterator<Item = T>, 334 run_cargo_embargo: bool, 335 ) -> Result<()> { 336 let pseudo_crate = self.pseudo_crate().vendor()?; 337 for crate_name in crates { 338 println!("Regenerating {}", crate_name.as_ref()); 339 let mc = self.managed_crate_for(crate_name.as_ref())?; 340 // TODO: Don't give up if there's a failure. 341 mc.regenerate(&pseudo_crate, run_cargo_embargo)?; 342 } 343 344 pseudo_crate.regenerate_crate_list()?; 345 346 Ok(()) 347 } preupload_check(&self, files: &[String]) -> Result<()>348 pub fn preupload_check(&self, files: &[String]) -> Result<()> { 349 let pseudo_crate = self.pseudo_crate().vendor()?; 350 let deps = pseudo_crate.deps().keys().cloned().collect::<BTreeSet<_>>(); 351 352 let managed_dirs = self.all_crate_names()?; 353 354 if deps != managed_dirs { 355 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: {}", 356 self.managed_dir(), managed_dirs.difference(&deps).join(", "), deps.difference(&managed_dirs).join(", "))); 357 } 358 359 let crate_list = pseudo_crate.read_crate_list("crate-list.txt")?; 360 if !deps.is_subset(&crate_list) { 361 bail!("Deps in pseudo_crate/Cargo.toml don't match deps in crate-list.txt\nCargo.toml: {}\ncrate-list.txt: {}", 362 deps.iter().join(", "), crate_list.iter().join(", ")); 363 } 364 365 let expected_deleted_crates = 366 crate_list.difference(&deps).cloned().collect::<BTreeSet<_>>(); 367 let deleted_crates = pseudo_crate.read_crate_list("deleted-crates.txt")?; 368 if deleted_crates != expected_deleted_crates { 369 bail!( 370 "Deleted crate list is inconsistent. Expected: {}, Found: {}", 371 expected_deleted_crates.iter().join(", "), 372 deleted_crates.iter().join(", ") 373 ); 374 } 375 376 // Per https://android.googlesource.com/platform/tools/repohooks/, 377 // the REPO_PATH environment variable is the path of the git repo relative to the 378 // root of the Android source tree. 379 let prefix = self.path.rel().strip_prefix(env::var("REPO_PATH")?)?; 380 let changed_android_crates = files 381 .iter() 382 .filter_map(|file| Path::new(file).strip_prefix(prefix).ok()) 383 .filter_map(|path| { 384 let components = path.components().collect::<Vec<_>>(); 385 if path.starts_with("crates/") && components.len() > 2 { 386 Some(components[1].as_os_str().to_string_lossy().to_string()) 387 } else { 388 None 389 } 390 }) 391 .collect::<BTreeSet<_>>(); 392 393 for crate_name in changed_android_crates { 394 println!("Verifying checksums for {}", crate_name); 395 checksum::verify(self.managed_dir_for(&crate_name).abs())?; 396 } 397 Ok(()) 398 } recontextualize_patches<T: AsRef<str>>( &self, crates: impl Iterator<Item = T>, ) -> Result<()>399 pub fn recontextualize_patches<T: AsRef<str>>( 400 &self, 401 crates: impl Iterator<Item = T>, 402 ) -> Result<()> { 403 for crate_name in crates { 404 let mc = self.managed_crate_for(crate_name.as_ref())?; 405 mc.recontextualize_patches()?; 406 } 407 Ok(()) 408 } updatable_crates(&self) -> Result<()>409 pub fn updatable_crates(&self) -> Result<()> { 410 let mut cc = self.new_cc(); 411 cc.add_from(self.managed_dir().rel())?; 412 413 for krate in cc.values() { 414 let cio_crate = self.crates_io.get_crate(krate.name())?; 415 let upgrades = 416 cio_crate.versions_gt(krate.version()).map(|v| v.version()).collect::<Vec<_>>(); 417 if !upgrades.is_empty() { 418 println!( 419 "{} v{}:\n {}", 420 krate.name(), 421 krate.version(), 422 upgrades 423 .iter() 424 .chunks(10) 425 .into_iter() 426 .map(|mut c| { c.join(", ") }) 427 .join(",\n ") 428 ); 429 } 430 } 431 Ok(()) 432 } analyze_updates(&self, crate_name: impl AsRef<str>) -> Result<()>433 pub fn analyze_updates(&self, crate_name: impl AsRef<str>) -> Result<()> { 434 let mut managed_crates = self.new_cc(); 435 managed_crates.add_from(self.managed_dir().rel())?; 436 let legacy_crates = self.legacy_crates()?; 437 438 let krate = self.managed_crate_for(crate_name.as_ref())?; 439 println!("Analyzing updates to {} v{}", krate.name(), krate.android_version()); 440 let patches = krate.patches()?; 441 if !patches.is_empty() { 442 println!("This crate has patches, so expect a fun time trying to update it:"); 443 for patch in patches { 444 println!( 445 " {}", 446 Path::new(patch.file_name().ok_or(anyhow!("No file name"))?).display() 447 ); 448 } 449 } 450 451 let cio_crate = self.crates_io.get_crate(crate_name)?; 452 453 let base_version = cio_crate.get_version(krate.android_version()).ok_or(anyhow!( 454 "{} v{} not found in crates.io", 455 krate.name(), 456 krate.android_version() 457 ))?; 458 let base_deps = base_version.android_version_reqs_by_name(); 459 460 let mut newer_versions = cio_crate.versions_gt(krate.android_version()).peekable(); 461 if newer_versions.peek().is_none() { 462 println!("There are no newer versions of this crate."); 463 } 464 for version in newer_versions { 465 println!("Version {}", version.version()); 466 let mut found_problems = false; 467 let parsed_version = semver::Version::parse(version.version())?; 468 if !krate 469 .android_version() 470 .is_upgradable_to(&parsed_version, SemverCompatibilityRule::Strict) 471 { 472 found_problems = true; 473 if !krate 474 .android_version() 475 .is_upgradable_to(&parsed_version, SemverCompatibilityRule::Loose) 476 { 477 println!(" Not semver-compatible, even by relaxed standards"); 478 } else { 479 println!(" Semver-compatible, but only by relaxed standards since major version is 0"); 480 } 481 } 482 // Check to see if the update has any missing dependencies. 483 // We try to be a little clever about this in the following ways: 484 // * Only consider deps that are likely to be relevant to Android. For example, ignore Windows-only deps. 485 // * If a dep is missing, but the same dep exists for the current version of the crate, it's probably not actually necessary. 486 // * Use relaxed version requirements, treating 0.x and 0.y as compatible, even though they aren't according to semver rules. 487 for (dep, req) in version.android_deps_with_version_reqs() { 488 let cc = if managed_crates.contains_crate(dep.crate_name()) { 489 &managed_crates 490 } else { 491 &legacy_crates 492 }; 493 if !cc.contains_crate(dep.crate_name()) { 494 found_problems = true; 495 println!( 496 " Dep {} {} has not been imported to Android", 497 dep.crate_name(), 498 dep.requirement() 499 ); 500 if !dep.is_new_dep(&base_deps) { 501 println!(" But the current version has the same dependency, and it seems to work"); 502 } else { 503 continue; 504 } 505 } 506 for (_, dep_crate) in cc.get_versions(dep.crate_name()) { 507 if !req.matches_with_compatibility_rule( 508 dep_crate.version(), 509 SemverCompatibilityRule::Loose, 510 ) { 511 found_problems = true; 512 println!( 513 " Dep {} {} is not satisfied by v{} at {}", 514 dep.crate_name(), 515 dep.requirement(), 516 dep_crate.version(), 517 dep_crate.path() 518 ); 519 if !dep.is_changed_dep(&base_deps) { 520 println!(" But the current version has the same dependency and it seems to work.") 521 } 522 } 523 } 524 } 525 if !found_problems { 526 println!(" No problems found with this version.") 527 } 528 } 529 530 Ok(()) 531 } suggest_updates( &self, consider_patched_crates: bool, semver_compatibility: SemverCompatibilityRule, json: bool, ) -> Result<()>532 pub fn suggest_updates( 533 &self, 534 consider_patched_crates: bool, 535 semver_compatibility: SemverCompatibilityRule, 536 json: bool, 537 ) -> Result<()> { 538 let mut suggestions = UpdateSuggestions::default(); 539 let mut managed_crates = self.new_cc(); 540 managed_crates.add_from(self.managed_dir().rel())?; 541 let legacy_crates = self.legacy_crates()?; 542 543 for krate in managed_crates.values() { 544 let cio_crate = self.crates_io.get_crate(krate.name())?; 545 546 let base_version = cio_crate.get_version(krate.version()); 547 if base_version.is_none() { 548 if !json { 549 println!( 550 "Skipping crate {} v{} because it was not found in crates.io", 551 krate.name(), 552 krate.version() 553 ); 554 } 555 continue; 556 } 557 let base_version = base_version.unwrap(); 558 let base_deps = base_version.android_version_reqs_by_name(); 559 560 let patch_dir = krate.path().join("patches").unwrap(); 561 if patch_dir.abs().exists() && !consider_patched_crates { 562 if !json { 563 println!( 564 "Skipping crate {} v{} because it has patches", 565 krate.name(), 566 krate.version() 567 ); 568 } 569 continue; 570 } 571 572 for version in cio_crate.versions_gt(krate.version()).rev() { 573 let parsed_version = semver::Version::parse(version.version())?; 574 if !krate.version().is_upgradable_to(&parsed_version, semver_compatibility) { 575 continue; 576 } 577 if !version.android_deps_with_version_reqs().any(|(dep, req)| { 578 if !dep.is_changed_dep(&base_deps) { 579 return false; 580 } 581 let cc = if managed_crates.contains_crate(dep.crate_name()) { 582 &managed_crates 583 } else { 584 &legacy_crates 585 }; 586 for (_, dep_crate) in cc.get_versions(dep.crate_name()) { 587 if req.matches_with_compatibility_rule( 588 dep_crate.version(), 589 SemverCompatibilityRule::Loose, 590 ) { 591 return false; 592 } 593 } 594 true 595 }) { 596 suggestions.updates.push(UpdateSuggestion { 597 name: krate.name().to_string(), 598 old_version: krate.version().to_string(), 599 version: version.version().to_string(), 600 }); 601 break; 602 } 603 } 604 } 605 606 if json { 607 println!("{}", serde_json::to_string_pretty(&suggestions)?) 608 } else { 609 for suggestion in suggestions.updates { 610 println!( 611 "Upgrade crate {} v{} to {}", 612 suggestion.name, suggestion.old_version, suggestion.version, 613 ); 614 } 615 } 616 617 Ok(()) 618 } update(&self, crate_name: impl AsRef<str>, version: impl AsRef<str>) -> Result<()>619 pub fn update(&self, crate_name: impl AsRef<str>, version: impl AsRef<str>) -> Result<()> { 620 let crate_name = crate_name.as_ref(); 621 let version = Version::parse(version.as_ref())?; 622 623 let pseudo_crate = self.pseudo_crate(); 624 let managed_crate = self.managed_crate_for(crate_name)?; 625 let mut crate_updates = vec![NameAndVersion::new(crate_name.to_string(), version.clone())]; 626 627 let cio_crate = self.crates_io.get_crate(crate_name)?; 628 let cio_crate_version = cio_crate 629 .get_version(&version) 630 .ok_or(anyhow!("Could not find {crate_name} {version} on crates.io"))?; 631 632 for dependent_crate_name in managed_crate.config().update_with() { 633 let dep = cio_crate_version 634 .dependencies() 635 .iter() 636 .find(|dep| dep.crate_name() == dependent_crate_name) 637 .ok_or(anyhow!( 638 "Could not find crate {dependent_crate_name} as a dependency of {crate_name}" 639 ))?; 640 let req = VersionReq::parse(dep.requirement())?; 641 let dep_cio_crate = self.crates_io.get_crate(dependent_crate_name)?; 642 let version = dep_cio_crate 643 .safe_versions() 644 .find(|v| { 645 if let Ok(parsed_version) = Version::parse(v.version()) { 646 req.matches(&parsed_version) 647 } else { 648 false 649 } 650 }) 651 .ok_or(anyhow!( 652 "Failed to find a version of {dependent_crate_name} that satisfies {}", 653 dep.requirement() 654 ))?; 655 println!("Also updating {dependent_crate_name} to {}", version.version()); 656 crate_updates.push(NameAndVersion::new( 657 dependent_crate_name.to_string(), 658 Version::parse(version.version())?, 659 )); 660 } 661 662 for nv in &crate_updates { 663 pseudo_crate.remove(nv.name())?; 664 } 665 for nv in &crate_updates { 666 pseudo_crate.cargo_add(nv)?; 667 } 668 self.regenerate(crate_updates.iter().map(|nv| nv.name()), true)?; 669 Ok(()) 670 } init(&self) -> Result<()>671 pub fn init(&self) -> Result<()> { 672 if self.path.abs().exists() { 673 return Err(anyhow!("{} already exists", self.path)); 674 } 675 create_dir_all(&self.path).context(format!("Failed to create {}", self.path))?; 676 let crates_dir = self.path.join("crates")?; 677 create_dir_all(&crates_dir).context(format!("Failed to create {}", crates_dir))?; 678 self.pseudo_crate().init()?; 679 Ok(()) 680 } verify_checksums<T: AsRef<str>>(&self, crates: impl Iterator<Item = T>) -> Result<()>681 pub fn verify_checksums<T: AsRef<str>>(&self, crates: impl Iterator<Item = T>) -> Result<()> { 682 for krate in crates { 683 println!("Verifying checksums for {}", krate.as_ref()); 684 checksum::verify(self.managed_dir_for(krate.as_ref()).abs())?; 685 } 686 Ok(()) 687 } 688 } 689