1 // Copyright (C) 2023 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     fs::{read_dir, remove_dir_all},
17     path::{Path, PathBuf},
18     process::{Command, Output},
19     str::from_utf8,
20 };
21 
22 use anyhow::{anyhow, Context, Result};
23 use cargo::{
24     core::{Manifest, SourceId},
25     util::toml::read_manifest,
26     Config,
27 };
28 use semver::Version;
29 
30 use crate::{
31     copy_dir, ensure_exists_and_empty, name_and_version::IsUpgradableTo, CrateError,
32     NameAndVersionRef, NamedAndVersioned, RepoPath,
33 };
34 
35 #[derive(Debug)]
36 pub struct Crate {
37     manifest: Manifest,
38 
39     path: RepoPath,
40 
41     patch_output: Vec<Output>,
42     generate_android_bp_output: Option<Output>,
43     android_bp_diff: Option<Output>,
44 }
45 
46 impl NamedAndVersioned for Crate {
name(&self) -> &str47     fn name(&self) -> &str {
48         self.manifest.name().as_str()
49     }
version(&self) -> &Version50     fn version(&self) -> &Version {
51         self.manifest.version()
52     }
key<'k>(&'k self) -> NameAndVersionRef<'k>53     fn key<'k>(&'k self) -> NameAndVersionRef<'k> {
54         NameAndVersionRef::new(self.name(), self.version())
55     }
56 }
57 
58 impl IsUpgradableTo for Crate {}
59 
60 impl Crate {
new<P: Into<PathBuf>, Q: Into<PathBuf>>( manifest: Manifest, root: P, relpath: Q, ) -> Crate61     pub fn new<P: Into<PathBuf>, Q: Into<PathBuf>>(
62         manifest: Manifest,
63         root: P,
64         relpath: Q,
65     ) -> Crate {
66         let root: PathBuf = root.into();
67         Crate {
68             manifest,
69             path: RepoPath::new(root.clone(), relpath),
70             patch_output: Vec::new(),
71             generate_android_bp_output: None,
72             android_bp_diff: None,
73         }
74     }
from<P: Into<PathBuf>>(cargo_toml: &impl AsRef<Path>, root: P) -> Result<Crate>75     pub fn from<P: Into<PathBuf>>(cargo_toml: &impl AsRef<Path>, root: P) -> Result<Crate> {
76         let root: PathBuf = root.into();
77         let manifest_dir = cargo_toml.as_ref().parent().ok_or(anyhow!(
78             "Failed to get parent directory of manifest at {}",
79             cargo_toml.as_ref().display()
80         ))?;
81         let relpath = manifest_dir.strip_prefix(&root)?.to_path_buf();
82         let source_id = SourceId::for_path(manifest_dir)?;
83         let (manifest, _nested) =
84             read_manifest(cargo_toml.as_ref(), source_id, &Config::default()?)?;
85         match manifest {
86             cargo::core::EitherManifest::Real(r) => Ok(Crate::new(r, root, relpath)),
87             cargo::core::EitherManifest::Virtual(_) => {
88                 Err(anyhow!(CrateError::VirtualCrate(cargo_toml.as_ref().to_path_buf())))
89             }
90         }
91     }
92 
path(&self) -> &RepoPath93     pub fn path(&self) -> &RepoPath {
94         &self.path
95     }
android_bp(&self) -> RepoPath96     pub fn android_bp(&self) -> RepoPath {
97         self.path.join(&"Android.bp")
98     }
cargo_embargo_json(&self) -> RepoPath99     pub fn cargo_embargo_json(&self) -> RepoPath {
100         self.path.join(&"cargo_embargo.json")
101     }
staging_path(&self) -> RepoPath102     pub fn staging_path(&self) -> RepoPath {
103         self.path.with_same_root(
104             Path::new("out/rust-crate-temporary-build").join(self.staging_dir_name()),
105         )
106     }
patch_dir(&self) -> RepoPath107     pub fn patch_dir(&self) -> RepoPath {
108         self.staging_path().join(&"patches")
109     }
staging_dir_name(&self) -> String110     pub fn staging_dir_name(&self) -> String {
111         if let Some(dirname) = self.path.rel().file_name().and_then(|x| x.to_str()) {
112             if dirname == self.name() {
113                 return dirname.to_string();
114             }
115         }
116         format!("{}-{}", self.name(), self.version().to_string())
117     }
118 
aosp_url(&self) -> Option<String>119     pub fn aosp_url(&self) -> Option<String> {
120         if self.path.rel().starts_with("external/rust/crates") {
121             if self.path.rel().ends_with(self.name()) {
122                 Some(format!(
123                     "https://android.googlesource.com/platform/{}/+/refs/heads/main",
124                     self.path()
125                 ))
126             } else if self.path.rel().parent()?.ends_with(self.name()) {
127                 Some(format!(
128                     "https://android.googlesource.com/platform/{}/+/refs/heads/main/{}",
129                     self.path().rel().parent()?.display(),
130                     self.path().rel().file_name()?.to_str()?
131                 ))
132             } else {
133                 None
134             }
135         } else {
136             None
137         }
138     }
crates_io_url(&self) -> String139     pub fn crates_io_url(&self) -> String {
140         format!("https://crates.io/crates/{}", self.name())
141     }
142 
is_crates_io(&self) -> bool143     pub fn is_crates_io(&self) -> bool {
144         const NOT_CRATES_IO: &'static [&'static str] = &[
145             "external/rust/beto-rust/",                 // Google crates
146             "external/rust/pica/",                      // Google crate
147             "external/rust/crates/webpki/third-party/", // Internal/example code
148             "external/rust/cxx/third-party/",           // Internal/example code
149             "external/rust/cxx/demo/",                  // Internal/example code
150         ];
151         !NOT_CRATES_IO.iter().any(|prefix| self.path().rel().starts_with(prefix))
152     }
is_migration_denied(&self) -> bool153     pub fn is_migration_denied(&self) -> bool {
154         const MIGRATION_DENYLIST: &'static [&'static str] = &[
155             "external/rust/crates/openssl/", // It's complicated.
156             "external/rust/cxx/",            // It's REALLY complicated.
157         ];
158         MIGRATION_DENYLIST.iter().any(|prefix| self.path().rel().starts_with(prefix))
159     }
is_android_bp_healthy(&self) -> bool160     pub fn is_android_bp_healthy(&self) -> bool {
161         !self.is_migration_denied()
162             && self.android_bp().abs().exists()
163             && self.cargo_embargo_json().abs().exists()
164             && self.generate_android_bp_success()
165             && self.android_bp_unchanged()
166     }
patch_success(&self) -> bool167     pub fn patch_success(&self) -> bool {
168         self.patch_output.iter().all(|output| output.status.success())
169     }
generate_android_bp_success(&self) -> bool170     pub fn generate_android_bp_success(&self) -> bool {
171         self.generate_android_bp_output.as_ref().is_some_and(|output| output.status.success())
172     }
android_bp_unchanged(&self) -> bool173     pub fn android_bp_unchanged(&self) -> bool {
174         self.android_bp_diff.as_ref().is_some_and(|output| output.status.success())
175     }
176 
print(&self) -> Result<()>177     pub fn print(&self) -> Result<()> {
178         println!("{} {} {}", self.name(), self.version(), self.path());
179         if let Some(output) = &self.generate_android_bp_output {
180             println!("generate Android.bp exit status: {}", output.status);
181             println!("{}", from_utf8(&output.stdout)?);
182             println!("{}", from_utf8(&output.stderr)?);
183         }
184         if let Some(output) = &self.android_bp_diff {
185             println!("diff exit status: {}", output.status);
186             println!("{}", from_utf8(&output.stdout)?);
187             println!("{}", from_utf8(&output.stderr)?);
188         }
189         Ok(())
190     }
191 
192     // Make a clean copy of the crate in out/
stage_crate(&self) -> Result<()>193     pub fn stage_crate(&self) -> Result<()> {
194         let staging_path_absolute = self.staging_path().abs();
195         ensure_exists_and_empty(&staging_path_absolute)?;
196         remove_dir_all(&staging_path_absolute)
197             .context(format!("Failed to remove {}", staging_path_absolute.display()))?;
198         copy_dir(&self.path().abs(), &staging_path_absolute).context(format!(
199             "Failed to copy {} to {}",
200             self.path(),
201             self.staging_path()
202         ))?;
203         if staging_path_absolute.join(".git").is_dir() {
204             remove_dir_all(staging_path_absolute.join(".git"))
205                 .with_context(|| "Failed to remove .git".to_string())?;
206         }
207         Ok(())
208     }
209 
diff_android_bp(&mut self) -> Result<()>210     pub fn diff_android_bp(&mut self) -> Result<()> {
211         self.set_diff_output(
212             diff_android_bp(
213                 &self.android_bp().rel(),
214                 &self.staging_path().join(&"Android.bp").rel(),
215                 &self.path.root(),
216             )
217             .context("Failed to diff Android.bp".to_string())?,
218         );
219         Ok(())
220     }
221 
apply_patches(&mut self) -> Result<()>222     pub fn apply_patches(&mut self) -> Result<()> {
223         let patch_dir_absolute = self.patch_dir().abs();
224         if patch_dir_absolute.exists() {
225             for entry in read_dir(&patch_dir_absolute)
226                 .context(format!("Failed to read_dir {}", patch_dir_absolute.display()))?
227             {
228                 let entry = entry?;
229                 if entry.file_name() == "Android.bp.patch"
230                     || entry.file_name() == "Android.bp.diff"
231                     || entry.file_name() == "rules.mk.diff"
232                 {
233                     continue;
234                 }
235                 let entry_path = entry.path();
236                 let output = Command::new("patch")
237                     .args(["-p1", "-l", "--no-backup-if-mismatch", "-i"])
238                     .arg(&entry_path)
239                     .current_dir(self.staging_path().abs())
240                     .output()?;
241                 if !output.status.success() {
242                     println!(
243                         "Failed to apply {}\nstdout:\n{}\nstderr:\n:{}",
244                         entry_path.display(),
245                         from_utf8(&output.stdout)?,
246                         from_utf8(&output.stderr)?
247                     );
248                 }
249                 self.patch_output.push(output);
250             }
251         }
252         Ok(())
253     }
254 
android_bp_diff(&self) -> Option<&Output>255     pub fn android_bp_diff(&self) -> Option<&Output> {
256         self.android_bp_diff.as_ref()
257     }
generate_android_bp_output(&self) -> Option<&Output>258     pub fn generate_android_bp_output(&self) -> Option<&Output> {
259         self.generate_android_bp_output.as_ref()
260     }
set_generate_android_bp_output(&mut self, c2a_output: Output)261     pub fn set_generate_android_bp_output(&mut self, c2a_output: Output) {
262         self.generate_android_bp_output.replace(c2a_output);
263     }
set_diff_output(&mut self, diff_output: Output)264     pub fn set_diff_output(&mut self, diff_output: Output) {
265         self.android_bp_diff.replace(diff_output);
266     }
set_patch_output(&mut self, patch_output: Vec<Output>)267     pub fn set_patch_output(&mut self, patch_output: Vec<Output>) {
268         self.patch_output = patch_output;
269     }
270 }
271 
272 pub trait Migratable {
is_migration_eligible(&self) -> bool273     fn is_migration_eligible(&self) -> bool;
is_migratable(&self) -> bool274     fn is_migratable(&self) -> bool;
275 }
276 
277 impl Migratable for Crate {
is_migration_eligible(&self) -> bool278     fn is_migration_eligible(&self) -> bool {
279         self.is_crates_io()
280             && !self.is_migration_denied()
281             && self.android_bp().abs().exists()
282             && self.cargo_embargo_json().abs().exists()
283     }
is_migratable(&self) -> bool284     fn is_migratable(&self) -> bool {
285         self.patch_success() && self.generate_android_bp_success() && self.android_bp_unchanged()
286     }
287 }
288 
289 #[cfg(test)]
290 mod tests {
291     use std::fs::{create_dir, write};
292 
293     use super::*;
294     use anyhow::anyhow;
295     use tempfile::tempdir;
296 
write_test_manifest(temp_crate_dir: &Path, name: &str, version: &str) -> Result<PathBuf>297     fn write_test_manifest(temp_crate_dir: &Path, name: &str, version: &str) -> Result<PathBuf> {
298         let cargo_toml: PathBuf = [temp_crate_dir, &Path::new("Cargo.toml")].iter().collect();
299         write(
300             cargo_toml.as_path(),
301             format!("[package]\nname = \"{}\"\nversion = \"{}\"\n", name, version),
302         )?;
303         let lib_rs: PathBuf = [temp_crate_dir, &Path::new("src/lib.rs")].iter().collect();
304         create_dir(lib_rs.parent().ok_or(anyhow!("Failed to get parent"))?)?;
305         write(lib_rs.as_path(), "// foo")?;
306         Ok(cargo_toml)
307     }
308 
309     #[test]
test_from_and_properties() -> Result<()>310     fn test_from_and_properties() -> Result<()> {
311         let temp_crate_dir = tempdir()?;
312         let cargo_toml = write_test_manifest(temp_crate_dir.path(), "foo", "1.2.0")?;
313         let krate = Crate::from(&cargo_toml, &"/")?;
314         assert_eq!(krate.name(), "foo");
315         assert_eq!(krate.version().to_string(), "1.2.0");
316         assert!(krate.is_crates_io());
317         assert_eq!(krate.android_bp().abs(), temp_crate_dir.path().join("Android.bp"));
318         assert_eq!(
319             krate.cargo_embargo_json().abs(),
320             temp_crate_dir.path().join("cargo_embargo.json")
321         );
322         Ok(())
323     }
324 
325     #[test]
test_from_error() -> Result<()>326     fn test_from_error() -> Result<()> {
327         let temp_crate_dir = tempdir()?;
328         let cargo_toml = write_test_manifest(temp_crate_dir.path(), "foo", "1.2.0")?;
329         assert!(Crate::from(&cargo_toml, &"/blah").is_err());
330         Ok(())
331     }
332 }
333 
diff_android_bp( a: &impl AsRef<Path>, b: &impl AsRef<Path>, root: &impl AsRef<Path>, ) -> Result<Output>334 pub fn diff_android_bp(
335     a: &impl AsRef<Path>,
336     b: &impl AsRef<Path>,
337     root: &impl AsRef<Path>,
338 ) -> Result<Output> {
339     Ok(Command::new("diff")
340         .args([
341             "-u",
342             "-w",
343             "-B",
344             "-I",
345             "// has rustc warnings",
346             "-I",
347             "This file is generated by",
348             "-I",
349             "cargo_pkg_version:",
350         ])
351         .arg(a.as_ref())
352         .arg(b.as_ref())
353         .current_dir(root)
354         .output()?)
355 }
356