• 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     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(&current_crates).join("\n")),
76         )?;
77         write(
78             self.path.join("deleted-crates.txt")?,
79             format!("{}\n", old_crate_list.difference(&current_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