xref: /aosp_15_r20/development/tools/external_crates/crate_tool/src/pseudo_crate.rs (revision 90c8c64db3049935a07c6143d7fd006e26f8ecca)
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