• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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::{create_dir, write},
17     process::Command,
18     str::from_utf8,
19 };
20 
21 use anyhow::{anyhow, Context, Result};
22 use serde::Serialize;
23 use tinytemplate::TinyTemplate;
24 
25 use crate::{ensure_exists_and_empty, NamedAndVersioned, RepoPath};
26 
27 static CARGO_TOML_TEMPLATE: &'static str = include_str!("templates/Cargo.toml.template");
28 
29 #[derive(Serialize)]
30 struct Dep {
31     name: String,
32     version: String,
33 }
34 
35 #[derive(Serialize)]
36 struct CargoToml {
37     deps: Vec<Dep>,
38 }
39 
40 pub struct PseudoCrate {
41     path: RepoPath,
42 }
43 
44 impl PseudoCrate {
new(path: RepoPath) -> PseudoCrate45     pub fn new(path: RepoPath) -> PseudoCrate {
46         PseudoCrate { path }
47     }
init<'a>( &self, crates: impl Iterator<Item = &'a (impl NamedAndVersioned + 'a)>, ) -> Result<()>48     pub fn init<'a>(
49         &self,
50         crates: impl Iterator<Item = &'a (impl NamedAndVersioned + 'a)>,
51     ) -> Result<()> {
52         if self.path.abs().exists() {
53             return Err(anyhow!("Can't init pseudo-crate because {} already exists", self.path));
54         }
55         ensure_exists_and_empty(&self.path.abs())?;
56 
57         let mut deps = Vec::new();
58         for krate in crates {
59             // Special cases:
60             // * libsqlite3-sys is a sub-crate of rusqlite
61             // * remove_dir_all has a version not known by crates.io (b/313489216)
62             if krate.name() != "libsqlite3-sys" {
63                 deps.push(Dep {
64                     name: krate.name().to_string(),
65                     version: if krate.name() == "remove_dir_all"
66                         && krate.version().to_string() == "0.7.1"
67                     {
68                         "0.7.0".to_string()
69                     } else {
70                         krate.version().to_string()
71                     },
72                 });
73             }
74         }
75 
76         let mut tt = TinyTemplate::new();
77         tt.add_template("cargo_toml", CARGO_TOML_TEMPLATE)?;
78         let cargo_toml = self.path.join(&"Cargo.toml").abs();
79         write(&cargo_toml, tt.render("cargo_toml", &CargoToml { deps })?)?;
80 
81         create_dir(self.path.join(&"src").abs()).context("Failed to create src dir")?;
82         write(self.path.join(&"src/lib.rs").abs(), "// Nothing")
83             .context("Failed to create src/lib.rs")?;
84 
85         self.vendor()
86 
87         // TODO: Run "cargo deny"
88     }
get_path(&self) -> &RepoPath89     pub fn get_path(&self) -> &RepoPath {
90         &self.path
91     }
add(&self, krate: &impl NamedAndVersioned) -> Result<()>92     pub fn add(&self, krate: &impl NamedAndVersioned) -> Result<()> {
93         let status = Command::new("cargo")
94             .args(["add", format!("{}@={}", krate.name(), krate.version()).as_str()])
95             .current_dir(self.path.abs())
96             .spawn()
97             .context("Failed to spawn 'cargo add'")?
98             .wait()
99             .context("Failed to wait on 'cargo add'")?;
100         if !status.success() {
101             return Err(anyhow!("Failed to run 'cargo add {}@{}'", krate.name(), krate.version()));
102         }
103         Ok(())
104     }
vendor(&self) -> Result<()>105     pub fn vendor(&self) -> Result<()> {
106         let output =
107             Command::new("cargo").args(["vendor"]).current_dir(self.path.abs()).output()?;
108         if !output.status.success() {
109             return Err(anyhow!(
110                 "cargo vendor failed with exit code {}\nstdout:\n{}\nstderr:\n{}",
111                 output
112                     .status
113                     .code()
114                     .map(|code| { format!("{}", code) })
115                     .unwrap_or("(unknown)".to_string()),
116                 from_utf8(&output.stdout)?,
117                 from_utf8(&output.stderr)?
118             ));
119         }
120         Ok(())
121     }
122 }
123