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