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