• 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     fs::{copy, read_dir, read_to_string, remove_dir_all, remove_file, rename, write},
19     os::unix::fs::symlink,
20     path::{Path, PathBuf},
21     process::Command,
22 };
23 
24 use anyhow::{anyhow, bail, Context, Result};
25 use crate_config::CrateConfig;
26 use glob::glob;
27 use google_metadata::GoogleMetadata;
28 use itertools::Itertools;
29 use license_checker::{find_licenses, LicenseState};
30 use name_and_version::NamedAndVersioned;
31 use rooted_path::RootedPath;
32 use semver::Version;
33 use test_mapping::TestMapping;
34 
35 use crate::{
36     android_bp::run_cargo_embargo,
37     copy_dir,
38     crate_type::Crate,
39     ensure_exists_and_empty,
40     license::{most_restrictive_type, update_module_license_files},
41     patch::Patch,
42     pseudo_crate::{CargoVendorClean, PseudoCrate},
43     SuccessOrError,
44 };
45 
46 #[derive(Debug)]
47 pub struct ManagedCrate<State: ManagedCrateState> {
48     /// The crate with Android customizations, a subdirectory of crates/.
49     android_crate: Crate,
50     config: OnceCell<CrateConfig>,
51     extra: State,
52 }
53 
54 #[derive(Debug)]
55 pub struct New {}
56 
57 /// Crate state indicating we have run `cargo vendor` on the pseudo-crate.
58 #[derive(Debug)]
59 pub struct Vendored {
60     /// The vendored copy of the crate, from running `cargo vendor`
61     vendored_crate: Crate,
62 }
63 
64 /// Crate state indicating we have copied the vendored code to a temporary build
65 /// directory, copied over Android customizations, and applied patches.
66 #[derive(Debug)]
67 pub struct CopiedAndPatched {
68     /// The vendored copy of the crate, from running `cargo vendor`
69     vendored_crate: Crate,
70     /// The license terms and associated license files.
71     licenses: LicenseState,
72 }
73 pub trait ManagedCrateState {}
74 impl ManagedCrateState for New {}
75 impl ManagedCrateState for Vendored {}
76 impl ManagedCrateState for CopiedAndPatched {}
77 
78 static CUSTOMIZATIONS: &[&str] = &[
79     "*.bp",
80     "*.bp.fragment",
81     "*.mk",
82     "android_config.toml",
83     "android",
84     "cargo_embargo.json",
85     "patches",
86     "METADATA",
87     "TEST_MAPPING",
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     fn android_crate_path(&self) -> &RootedPath {
98         self.android_crate.path()
99     }
config(&self) -> &CrateConfig100     pub fn config(&self) -> &CrateConfig {
101         self.config.get_or_init(|| {
102             CrateConfig::read(self.android_crate_path().abs()).unwrap_or_else(|e| {
103                 panic!(
104                     "Failed to read crate config {}/{}: {}",
105                     self.android_crate_path(),
106                     crate_config::CONFIG_FILE_NAME,
107                     e
108                 )
109             })
110         })
111     }
patch_dir(&self) -> RootedPath112     fn patch_dir(&self) -> RootedPath {
113         self.android_crate_path().join("patches").unwrap()
114     }
temporary_build_directory(&self) -> RootedPath115     fn temporary_build_directory(&self) -> RootedPath {
116         self.android_crate
117             .path()
118             .with_same_root("out/rust-crate-temporary-build")
119             .unwrap()
120             .join(self.name())
121             .unwrap()
122     }
patches(&self) -> Result<Vec<PathBuf>>123     pub fn patches(&self) -> Result<Vec<PathBuf>> {
124         let mut patches = Vec::new();
125         let patch_dir = self.patch_dir();
126         if patch_dir.abs().exists() {
127             for entry in
128                 read_dir(&patch_dir).context(format!("Failed to read_dir {}", patch_dir))?
129             {
130                 let entry = entry?;
131                 if entry.file_name() == "Android.bp.patch"
132                     || entry.file_name() == "Android.bp.diff"
133                     || entry.file_name() == "rules.mk.diff"
134                 {
135                     continue;
136                 }
137                 patches.push(entry.path());
138             }
139         }
140         patches.sort();
141 
142         Ok(patches)
143     }
recontextualize_patches(&self) -> Result<()>144     pub fn recontextualize_patches(&self) -> Result<()> {
145         let output = Command::new("git")
146             .args(["status", "--porcelain", "."])
147             .current_dir(self.android_crate_path())
148             .output()?
149             .success_or_error()?;
150         if !output.stdout.is_empty() {
151             return Err(anyhow!(
152                 "Crate directory {} has uncommitted changes",
153                 self.android_crate_path()
154             ));
155         }
156         let mut new_patch_contents = Vec::new();
157         for patch in self.patches()? {
158             println!("Recontextualizing {}", patch.display());
159             // Patch files can be in many different formats, and patch is very
160             // forgiving. We might be able to use "git apply -R --directory=crates/foo"
161             // once we have everything in the same format.
162             Command::new("patch")
163                 .args(["-R", "-p1", "-l", "--reject-file=-", "--no-backup-if-mismatch", "-i"])
164                 .arg(&patch)
165                 .current_dir(self.android_crate_path())
166                 .spawn()?
167                 .wait()?
168                 .success_or_error()?;
169             Command::new("git")
170                 .args(["add", "."])
171                 .current_dir(self.android_crate_path())
172                 .spawn()?
173                 .wait()?
174                 .success_or_error()?;
175             let output = Command::new("git")
176                 .args([
177                     "diff",
178                     format!("--relative=crates/{}", self.name()).as_str(),
179                     "-p",
180                     "--stat",
181                     "-R",
182                     "--staged",
183                     ".",
184                 ])
185                 .current_dir(self.android_crate_path())
186                 .output()?
187                 .success_or_error()?;
188             Command::new("git")
189                 .args(["restore", "--staged", "."])
190                 .current_dir(self.android_crate_path())
191                 .spawn()?
192                 .wait()?
193                 .success_or_error()?;
194             Command::new("git")
195                 .args(["restore", "."])
196                 .current_dir(self.android_crate_path())
197                 .spawn()?
198                 .wait()?
199                 .success_or_error()?;
200             Command::new("git")
201                 .args(["clean", "-f", "."])
202                 .current_dir(self.android_crate_path())
203                 .spawn()?
204                 .wait()?
205                 .success_or_error()?;
206             let patch_contents = read_to_string(&patch)?;
207             let parsed = Patch::parse(&patch_contents);
208             new_patch_contents.push((patch, parsed.reassemble(&output.stdout)));
209         }
210         for (path, contents) in new_patch_contents {
211             write(path, contents)?;
212         }
213         Ok(())
214     }
215 }
216 
217 impl ManagedCrate<New> {
new(android_crate: Crate) -> Self218     pub fn new(android_crate: Crate) -> Self {
219         ManagedCrate { android_crate, config: OnceCell::new(), extra: New {} }
220     }
into_vendored( self, pseudo_crate: &PseudoCrate<CargoVendorClean>, ) -> Result<ManagedCrate<Vendored>>221     fn into_vendored(
222         self,
223         pseudo_crate: &PseudoCrate<CargoVendorClean>,
224     ) -> Result<ManagedCrate<Vendored>> {
225         let vendored_crate =
226             Crate::from(pseudo_crate.vendored_dir_for(self.android_crate.name())?.clone())?;
227         Ok(ManagedCrate {
228             android_crate: self.android_crate,
229             config: self.config,
230             extra: Vendored { vendored_crate },
231         })
232     }
regenerate( self, pseudo_crate: &PseudoCrate<CargoVendorClean>, run_cargo_embargo: bool, ) -> Result<ManagedCrate<CopiedAndPatched>>233     pub fn regenerate(
234         self,
235         pseudo_crate: &PseudoCrate<CargoVendorClean>,
236         run_cargo_embargo: bool,
237     ) -> Result<ManagedCrate<CopiedAndPatched>> {
238         let regenerated = self.into_vendored(pseudo_crate)?.regenerate(run_cargo_embargo)?;
239         Ok(regenerated)
240     }
241 }
242 
243 impl ManagedCrate<Vendored> {
into_copied_and_patched( self, licenses: LicenseState, ) -> Result<ManagedCrate<CopiedAndPatched>>244     fn into_copied_and_patched(
245         self,
246         licenses: LicenseState,
247     ) -> Result<ManagedCrate<CopiedAndPatched>> {
248         Ok(ManagedCrate {
249             android_crate: self.android_crate,
250             config: self.config,
251             extra: CopiedAndPatched { vendored_crate: self.extra.vendored_crate, licenses },
252         })
253     }
254     /// Makes a clean copy of the vendored crates in a temporary build directory.
copy_to_temporary_build_directory(&self) -> Result<()>255     fn copy_to_temporary_build_directory(&self) -> Result<()> {
256         let build_dir = self.temporary_build_directory();
257         ensure_exists_and_empty(&build_dir)?;
258         remove_dir_all(&build_dir).context(format!("Failed to remove {}", build_dir))?;
259         copy_dir(self.extra.vendored_crate.path(), &build_dir).context(format!(
260             "Failed to copy {} to {}",
261             self.extra.vendored_crate.path(),
262             self.temporary_build_directory()
263         ))?;
264         Ok(())
265     }
266     /// Copies Android-specific customizations, such as Android.bp, METADATA, etc. from
267     /// the managed crate directory to the temporary build directory. These are things that
268     /// we have added and know need to be preserved.
copy_customizations(&self) -> Result<()>269     fn copy_customizations(&self) -> Result<()> {
270         let dest_dir = self.temporary_build_directory();
271         for pattern in CUSTOMIZATIONS {
272             let full_pattern = self.android_crate.path().join(pattern)?;
273             for entry in glob(
274                 full_pattern
275                     .abs()
276                     .to_str()
277                     .ok_or(anyhow!("Failed to convert path {} to str", full_pattern))?,
278             )? {
279                 let entry = entry?;
280                 let filename = entry
281                     .file_name()
282                     .context(format!("Failed to get file name for {}", entry.display()))?
283                     .to_os_string();
284                 if entry.is_dir() {
285                     copy_dir(&entry, &dest_dir.join(filename)?).context(format!(
286                         "Failed to copy {} to {}",
287                         entry.display(),
288                         dest_dir
289                     ))?;
290                 } else {
291                     let dest_file = dest_dir.join(&filename)?;
292                     if dest_file.abs().exists() {
293                         return Err(anyhow!("Destination file {} exists", dest_file));
294                     }
295                     copy(&entry, dest_dir.join(filename)?).context(format!(
296                         "Failed to copy {} to {}",
297                         entry.display(),
298                         dest_dir
299                     ))?;
300                 }
301             }
302         }
303         Ok(())
304     }
305     /// Applies patches from the patches/ directory in a deterministic order.
306     ///
307     /// Patches to the Android.bp file are excluded as they are applied
308     /// later, by cargo_embargo
apply_patches(&self) -> Result<()>309     fn apply_patches(&self) -> Result<()> {
310         for patch in self.patches()? {
311             Command::new("patch")
312                 .args(["-p1", "-l", "--no-backup-if-mismatch", "-i"])
313                 .arg(&patch)
314                 .current_dir(self.temporary_build_directory())
315                 .output()?
316                 .success_or_error()
317                 .context(format!(
318                     "Failed to apply patch file {}",
319                     patch.file_name().unwrap_or_default().to_string_lossy()
320                 ))?;
321         }
322         Ok(())
323     }
regenerate(self, run_cargo_embargo: bool) -> Result<ManagedCrate<CopiedAndPatched>>324     pub fn regenerate(self, run_cargo_embargo: bool) -> Result<ManagedCrate<CopiedAndPatched>> {
325         self.copy_to_temporary_build_directory()?;
326         self.copy_customizations()?;
327 
328         // Delete stuff that we don't want to keep around, as specified in the
329         // android_config.toml
330         for deletion in self.config().deletions() {
331             let dir = self.temporary_build_directory().join(deletion)?;
332             if dir.abs().is_dir() {
333                 remove_dir_all(dir)?;
334             } else {
335                 remove_file(dir)?;
336             }
337         }
338 
339         self.apply_patches()?;
340 
341         let licenses = find_licenses(
342             self.temporary_build_directory(),
343             self.name(),
344             self.android_crate.license(),
345         )?;
346         let regenerated = self.into_copied_and_patched(licenses)?;
347         regenerated.regenerate(run_cargo_embargo)?;
348         Ok(regenerated)
349     }
350 }
351 
352 impl ManagedCrate<CopiedAndPatched> {
regenerate(&self, run_cargo_embargo: bool) -> Result<()>353     pub fn regenerate(&self, run_cargo_embargo: bool) -> Result<()> {
354         // License logic must happen AFTER applying patches, because we use patches
355         // to add missing license files. It must also happen BEFORE cargo_embargo,
356         // because cargo_embargo needs to put license information in the Android.bp.
357         self.update_license_files()?;
358 
359         // If the crate has an out/ directory, that means we cannot skip running cargo_embargo.
360         // The out/ directory is used by cargo_embargo to preserve files generated
361         // by a build script. Soong can't run build scripts, so we rely on cargo_embargo
362         // to run `cargo build` and save the intermediates.
363         if run_cargo_embargo || self.android_crate_path().abs().join("out").exists() {
364             self.run_cargo_embargo()?;
365         }
366 
367         self.update_metadata()?;
368         self.fix_test_mapping()?;
369         // Fails on dangling symlinks, which happens when we run on the log crate.
370         checksum::generate(self.temporary_build_directory())?;
371 
372         let android_crate_dir = self.android_crate.path();
373         remove_dir_all(android_crate_dir)?;
374         rename(self.temporary_build_directory(), android_crate_dir)?;
375 
376         Ok(())
377     }
update_license_files(&self) -> Result<()>378     fn update_license_files(&self) -> Result<()> {
379         // For every chosen license, we must be able to find an associated
380         // license file.
381         if !self.extra.licenses.unsatisfied.is_empty() {
382             bail!(
383                 "Could not find license files for some licenses: {:?}",
384                 self.extra.licenses.unsatisfied
385             );
386         }
387 
388         // SOME license must apply to the code. If none apply, that's an error.
389         if self.extra.licenses.satisfied.is_empty() {
390             bail!("No license terms were found for this crate");
391         }
392 
393         // There should be no license files for terms not mentioned in Cargo.toml
394         // license expression. For example, if Cargo.toml says "Apache-2.0" and we
395         // find a file named "LICENSE-MIT", that suggests that something suspicious might
396         // be going on.
397         if !self.extra.licenses.unexpected.is_empty() {
398             bail!(
399                 "Found unexpected license files that don't correspond to any terms of {}: {:?}",
400                 self.android_crate.license().unwrap_or("<license terms not found in Cargo.toml>"),
401                 self.extra.licenses.unexpected
402             );
403         }
404 
405         // Per go/thirdparty/licenses#multiple:
406         // "Delete any LICENSE files or license texts that were not selected and are not in use."
407         for unneeded_license_file in self.extra.licenses.unneeded.values() {
408             remove_file(self.temporary_build_directory().abs().join(unneeded_license_file))?;
409         }
410 
411         // Per http://go/thirdpartyreviewers#license:
412         // "There must be a file called LICENSE containing an allowed third party license."
413 
414         let license_files = self
415             .extra
416             .licenses
417             .satisfied
418             .values()
419             .map(|path| path.as_path())
420             .collect::<BTreeSet<_>>();
421         let canonical_license_file_name = Path::new("LICENSE");
422         let canonical_license_path = self.temporary_build_directory().abs().join("LICENSE");
423         if license_files.len() == 1 {
424             // If there's a single applicable license file, it must either
425             // be called LICENSE, or else we need to symlink LICENSE to it.
426             let license_file = license_files.first().unwrap();
427             if *license_file != canonical_license_file_name {
428                 if canonical_license_path.exists() {
429                     // TODO: Maybe just blindly delete LICENSE and replace it with a symlink.
430                     // Currently, we have to use a deletions config in android_config.toml
431                     // to remove it.
432                     bail!("Found a single license file {}, but we can't create a symlink to it because a file named LICENSE already exists", license_file.display());
433                 } else {
434                     symlink(license_file, canonical_license_path)?;
435                 }
436             }
437         } else {
438             // We found multiple license files. Per go/thirdparty/licenses#multiple they should
439             // be concatenated into a single LICENSE file, separated by dashed line dividers.
440             let mut license_contents = Vec::new();
441             for file in license_files {
442                 license_contents
443                     .push(read_to_string(self.temporary_build_directory().abs().join(file))?);
444             }
445             // TODO: Maybe warn if LICENSE file exists. But there are cases where we can't just delete it
446             // because it contains *some* of the necessary license texts.
447             write(
448                 canonical_license_path,
449                 license_contents.iter().join("\n\n------------------\n\n"),
450             )?;
451         }
452 
453         update_module_license_files(&self.temporary_build_directory(), &self.extra.licenses)?;
454         Ok(())
455     }
456     /// Runs cargo_embargo on the crate in the temporary build directory.
457     ///
458     /// Because cargo_embargo can modify Cargo.lock files, we save them, if present.
run_cargo_embargo(&self) -> Result<()>459     fn run_cargo_embargo(&self) -> Result<()> {
460         let temporary_build_path = self.temporary_build_directory();
461 
462         let cargo_lock = temporary_build_path.join("Cargo.lock")?;
463         let saved_cargo_lock = temporary_build_path.join("Cargo.lock.saved")?;
464         if cargo_lock.abs().exists() {
465             rename(&cargo_lock, &saved_cargo_lock)?;
466         }
467 
468         run_cargo_embargo(&temporary_build_path)?
469             .success_or_error()
470             .context(format!("cargo_embargo execution failed for {}", self.name()))?;
471 
472         if cargo_lock.abs().exists() {
473             remove_file(&cargo_lock)?;
474         }
475         if saved_cargo_lock.abs().exists() {
476             rename(saved_cargo_lock, cargo_lock)?;
477         }
478 
479         Ok(())
480     }
481     /// Updates the METADATA file in the temporary build directory.
update_metadata(&self) -> Result<()>482     fn update_metadata(&self) -> Result<()> {
483         let mut metadata =
484             GoogleMetadata::try_from(self.temporary_build_directory().join("METADATA").unwrap())?;
485         metadata.update(
486             self.name(),
487             self.extra.vendored_crate.version().to_string(),
488             self.extra.vendored_crate.description(),
489             most_restrictive_type(&self.extra.licenses),
490         );
491         metadata.write()?;
492 
493         Ok(())
494     }
495     /// Updates the TEST_MAPPING file in the temporary build directory, by
496     /// removing deleted tests and adding new tests as post-submits.
fix_test_mapping(&self) -> Result<()>497     fn fix_test_mapping(&self) -> Result<()> {
498         let mut tm = TestMapping::read(self.temporary_build_directory())?;
499         let mut changed = tm.fix_import_paths();
500         changed |= tm.add_new_tests_to_postsubmit()?;
501         changed |= tm.remove_unknown_tests()?;
502         // TODO: Add an option to fix up the reverse dependencies.
503         if changed {
504             println!("Updating TEST_MAPPING for {}", self.name());
505             tm.write()?;
506         }
507         Ok(())
508     }
509 }
510