xref: /aosp_15_r20/development/tools/external_crates/crate_tool/src/managed_repo.rs (revision 90c8c64db3049935a07c6143d7fd006e26f8ecca)
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