• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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