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