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 cell::OnceCell, 17 collections::{BTreeMap, BTreeSet}, 18 env, 19 fs::{read, write}, 20 io::BufRead, 21 process::Command, 22 str::from_utf8, 23 }; 24 25 use anyhow::{anyhow, bail, Context, Result}; 26 use itertools::Itertools; 27 use name_and_version::NamedAndVersioned; 28 use rooted_path::RootedPath; 29 use semver::Version; 30 31 use crate::{crate_collection::CrateCollection, ensure_exists_and_empty, RunQuiet}; 32 33 pub struct PseudoCrate<State: PseudoCrateState> { 34 path: RootedPath, 35 extra: State, 36 } 37 38 #[derive(Debug)] 39 pub struct CargoVendorClean { 40 crates: OnceCell<CrateCollection>, 41 deps: OnceCell<BTreeMap<String, Version>>, 42 } 43 #[derive(Debug)] 44 pub struct CargoVendorDirty {} 45 46 pub trait PseudoCrateState {} 47 impl PseudoCrateState for CargoVendorDirty {} 48 impl PseudoCrateState for CargoVendorClean {} 49 50 impl<State: PseudoCrateState> PseudoCrate<State> { get_path(&self) -> &RootedPath51 pub fn get_path(&self) -> &RootedPath { 52 &self.path 53 } read_crate_list(&self, filename: &str) -> Result<BTreeSet<String>>54 pub fn read_crate_list(&self, filename: &str) -> Result<BTreeSet<String>> { 55 let mut lines = BTreeSet::new(); 56 for line in read(self.path.join(filename)?)?.lines() { 57 let line = line?; 58 if line.trim().is_empty() { 59 continue; 60 } 61 if !lines.insert(line.clone()) { 62 bail!("Duplicate entry {line} in crate list {filename}"); 63 } 64 } 65 Ok(lines) 66 } 67 } 68 69 impl PseudoCrate<CargoVendorClean> { regenerate_crate_list(&self) -> Result<()>70 pub fn regenerate_crate_list(&self) -> Result<()> { 71 let old_crate_list = self.read_crate_list("crate-list.txt")?; 72 let current_crates = self.deps().keys().cloned().collect::<BTreeSet<_>>(); 73 write( 74 self.path.join("crate-list.txt")?, 75 format!("{}\n", old_crate_list.union(¤t_crates).join("\n")), 76 )?; 77 write( 78 self.path.join("deleted-crates.txt")?, 79 format!("{}\n", old_crate_list.difference(¤t_crates).join("\n")), 80 )?; 81 Ok(()) 82 } version_of(&self, crate_name: &str) -> Result<Version>83 fn version_of(&self, crate_name: &str) -> Result<Version> { 84 self.deps() 85 .get(crate_name) 86 .cloned() 87 .ok_or(anyhow!("Crate {} not found in Cargo.toml", crate_name)) 88 } deps(&self) -> &BTreeMap<String, Version>89 pub fn deps(&self) -> &BTreeMap<String, Version> { 90 self.extra.deps.get_or_init(|| { 91 let output = Command::new("cargo") 92 .args(["tree", "--depth=1", "--prefix=none"]) 93 .current_dir(&self.path) 94 .run_quiet_and_expect_success() 95 .unwrap(); 96 let mut deps = BTreeMap::new(); 97 for line in from_utf8(&output.stdout).unwrap().lines().skip(1) { 98 let words = line.split(' ').collect::<Vec<_>>(); 99 if words.len() < 2 { 100 panic!("Failed to parse crate name and version from cargo tree: {}", line); 101 } 102 let version = words[1] 103 .strip_prefix('v') 104 .ok_or(anyhow!("Failed to parse version: {}", words[1])) 105 .unwrap(); 106 deps.insert(words[0].to_string(), Version::parse(version).unwrap()); 107 } 108 deps 109 }) 110 } vendored_dir_for(&self, crate_name: &str) -> Result<&RootedPath>111 pub fn vendored_dir_for(&self, crate_name: &str) -> Result<&RootedPath> { 112 let version = self.version_of(crate_name)?; 113 for (nv, krate) in self.crates().get_versions(crate_name) { 114 if *nv.version() == version { 115 return Ok(krate.path()); 116 } 117 } 118 Err(anyhow!("Couldn't find vendored directory for {} v{}", crate_name, version.to_string())) 119 } crates(&self) -> &CrateCollection120 fn crates(&self) -> &CrateCollection { 121 self.extra.crates.get_or_init(|| { 122 let out_dir = env::var("OUT_DIR").unwrap_or("out".to_string()); 123 let vendor_dir = self 124 .get_path() 125 .with_same_root(format!("{out_dir}/rust-vendored-crates")) 126 .unwrap() 127 .join(self.get_path().rel()) 128 .unwrap(); 129 Command::new("cargo") 130 .args(["vendor", "--versioned-dirs"]) 131 .arg(vendor_dir.abs()) 132 .current_dir(&self.path) 133 .run_quiet_and_expect_success() 134 .unwrap(); 135 let mut crates = CrateCollection::new(self.path.root()); 136 crates.add_from(vendor_dir).unwrap(); 137 crates 138 }) 139 } mark_dirty(self) -> PseudoCrate<CargoVendorDirty>140 fn mark_dirty(self) -> PseudoCrate<CargoVendorDirty> { 141 PseudoCrate { path: self.path, extra: CargoVendorDirty {} } 142 } 143 #[allow(dead_code)] cargo_add( self, krate: &impl NamedAndVersioned, ) -> Result<PseudoCrate<CargoVendorDirty>>144 pub fn cargo_add( 145 self, 146 krate: &impl NamedAndVersioned, 147 ) -> Result<PseudoCrate<CargoVendorDirty>> { 148 let dirty = self.mark_dirty(); 149 dirty.cargo_add(krate)?; 150 Ok(dirty) 151 } 152 #[allow(dead_code)] cargo_add_unpinned( self, krate: &impl NamedAndVersioned, ) -> Result<PseudoCrate<CargoVendorDirty>>153 pub fn cargo_add_unpinned( 154 self, 155 krate: &impl NamedAndVersioned, 156 ) -> Result<PseudoCrate<CargoVendorDirty>> { 157 let dirty: PseudoCrate<CargoVendorDirty> = self.mark_dirty(); 158 dirty.cargo_add_unpinned(krate)?; 159 Ok(dirty) 160 } 161 #[allow(dead_code)] cargo_add_unversioned(self, crate_name: &str) -> Result<PseudoCrate<CargoVendorDirty>>162 pub fn cargo_add_unversioned(self, crate_name: &str) -> Result<PseudoCrate<CargoVendorDirty>> { 163 let dirty: PseudoCrate<CargoVendorDirty> = self.mark_dirty(); 164 dirty.cargo_add_unversioned(crate_name)?; 165 Ok(dirty) 166 } 167 } 168 169 impl PseudoCrate<CargoVendorDirty> { new(path: RootedPath) -> Self170 pub fn new(path: RootedPath) -> Self { 171 PseudoCrate { path, extra: CargoVendorDirty {} } 172 } init(&self) -> Result<()>173 pub fn init(&self) -> Result<()> { 174 if self.path.abs().exists() { 175 return Err(anyhow!("Can't init pseudo-crate because {} already exists", self.path)); 176 } 177 ensure_exists_and_empty(&self.path)?; 178 179 write( 180 self.path.join("Cargo.toml")?, 181 r#"[package] 182 name = "android-pseudo-crate" 183 version = "0.1.0" 184 edition = "2021" 185 publish = false 186 license = "Apache-2.0" 187 188 [dependencies] 189 "#, 190 )?; 191 write(self.path.join("crate-list.txt")?, "")?; 192 write(self.path.join("deleted-crates.txt")?, "")?; 193 write(self.path.join(".gitignore")?, "target/\nvendor/\n")?; 194 195 ensure_exists_and_empty(&self.path.join("src")?)?; 196 write(self.path.join("src/lib.rs")?, "// Nothing") 197 .context("Failed to create src/lib.rs")?; 198 199 Ok(()) 200 } add_internal(&self, crate_and_version_str: &str, crate_name: &str) -> Result<()>201 fn add_internal(&self, crate_and_version_str: &str, crate_name: &str) -> Result<()> { 202 if let Err(e) = Command::new("cargo") 203 .args(["add", crate_and_version_str]) 204 .current_dir(&self.path) 205 .run_quiet_and_expect_success() 206 { 207 self.remove(crate_name).with_context(|| { 208 format!("Failed to remove {} after failing to add it: {}", crate_name, e) 209 })?; 210 return Err(e); 211 } 212 Ok(()) 213 } cargo_add(&self, krate: &impl NamedAndVersioned) -> Result<()>214 pub fn cargo_add(&self, krate: &impl NamedAndVersioned) -> Result<()> { 215 self.add_internal(format!("{}@={}", krate.name(), krate.version()).as_str(), krate.name()) 216 } cargo_add_unpinned(&self, krate: &impl NamedAndVersioned) -> Result<()>217 pub fn cargo_add_unpinned(&self, krate: &impl NamedAndVersioned) -> Result<()> { 218 self.add_internal(format!("{}@{}", krate.name(), krate.version()).as_str(), krate.name()) 219 } cargo_add_unversioned(&self, crate_name: &str) -> Result<()>220 pub fn cargo_add_unversioned(&self, crate_name: &str) -> Result<()> { 221 self.add_internal(crate_name, crate_name) 222 } remove(&self, crate_name: impl AsRef<str>) -> Result<()>223 pub fn remove(&self, crate_name: impl AsRef<str>) -> Result<()> { 224 Command::new("cargo") 225 .args(["remove", crate_name.as_ref()]) 226 .current_dir(&self.path) 227 .run_quiet_and_expect_success()?; 228 Ok(()) 229 } 230 // Mark the crate clean. Ironically, we don't actually need to run "cargo vendor" 231 // immediately thanks to LazyCell. vendor(self) -> Result<PseudoCrate<CargoVendorClean>>232 pub fn vendor(self) -> Result<PseudoCrate<CargoVendorClean>> { 233 Ok(PseudoCrate { 234 path: self.path, 235 extra: CargoVendorClean { crates: OnceCell::new(), deps: OnceCell::new() }, 236 }) 237 } 238 } 239