cargo/sources/
directory.rs1use std::collections::HashMap;
2use std::fmt::{self, Debug, Formatter};
3use std::path::{Path, PathBuf};
4
5use serde::Deserialize;
6
7use crate::core::source::MaybePackage;
8use crate::core::{Dependency, Package, PackageId, Source, SourceId, Summary};
9use crate::sources::PathSource;
10use crate::util::errors::{CargoResult, CargoResultExt};
11use crate::util::paths;
12use crate::util::{Config, Sha256};
13
14pub struct DirectorySource<'cfg> {
15 source_id: SourceId,
16 root: PathBuf,
17 packages: HashMap<PackageId, (Package, Checksum)>,
18 config: &'cfg Config,
19}
20
21#[derive(Deserialize)]
22struct Checksum {
23 package: Option<String>,
24 files: HashMap<String, String>,
25}
26
27impl<'cfg> DirectorySource<'cfg> {
28 pub fn new(path: &Path, id: SourceId, config: &'cfg Config) -> DirectorySource<'cfg> {
29 DirectorySource {
30 source_id: id,
31 root: path.to_path_buf(),
32 config,
33 packages: HashMap::new(),
34 }
35 }
36}
37
38impl<'cfg> Debug for DirectorySource<'cfg> {
39 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
40 write!(f, "DirectorySource {{ root: {:?} }}", self.root)
41 }
42}
43
44impl<'cfg> Source for DirectorySource<'cfg> {
45 fn query(&mut self, dep: &Dependency, f: &mut dyn FnMut(Summary)) -> CargoResult<()> {
46 let packages = self.packages.values().map(|p| &p.0);
47 let matches = packages.filter(|pkg| dep.matches(pkg.summary()));
48 for summary in matches.map(|pkg| pkg.summary().clone()) {
49 f(summary);
50 }
51 Ok(())
52 }
53
54 fn fuzzy_query(&mut self, _dep: &Dependency, f: &mut dyn FnMut(Summary)) -> CargoResult<()> {
55 let packages = self.packages.values().map(|p| &p.0);
56 for summary in packages.map(|pkg| pkg.summary().clone()) {
57 f(summary);
58 }
59 Ok(())
60 }
61
62 fn supports_checksums(&self) -> bool {
63 true
64 }
65
66 fn requires_precise(&self) -> bool {
67 true
68 }
69
70 fn source_id(&self) -> SourceId {
71 self.source_id
72 }
73
74 fn update(&mut self) -> CargoResult<()> {
75 self.packages.clear();
76 let entries = self.root.read_dir().chain_err(|| {
77 format!(
78 "failed to read root of directory source: {}",
79 self.root.display()
80 )
81 })?;
82
83 for entry in entries {
84 let entry = entry?;
85 let path = entry.path();
86
87 if let Some(s) = path.file_name().and_then(|s| s.to_str()) {
91 if s.starts_with('.') {
92 continue;
93 }
94 }
95
96 if !path.join("Cargo.toml").exists() {
112 continue;
113 }
114
115 let mut src = PathSource::new(&path, self.source_id, self.config);
116 src.update()?;
117 let mut pkg = src.root_package()?;
118
119 let cksum_file = path.join(".cargo-checksum.json");
120 let cksum = paths::read(&path.join(cksum_file)).chain_err(|| {
121 format!(
122 "failed to load checksum `.cargo-checksum.json` \
123 of {} v{}",
124 pkg.package_id().name(),
125 pkg.package_id().version()
126 )
127 })?;
128 let cksum: Checksum = serde_json::from_str(&cksum).chain_err(|| {
129 format!(
130 "failed to decode `.cargo-checksum.json` of \
131 {} v{}",
132 pkg.package_id().name(),
133 pkg.package_id().version()
134 )
135 })?;
136
137 if let Some(package) = &cksum.package {
138 pkg.manifest_mut()
139 .summary_mut()
140 .set_checksum(package.clone());
141 }
142 self.packages.insert(pkg.package_id(), (pkg, cksum));
143 }
144
145 Ok(())
146 }
147
148 fn download(&mut self, id: PackageId) -> CargoResult<MaybePackage> {
149 self.packages
150 .get(&id)
151 .map(|p| &p.0)
152 .cloned()
153 .map(MaybePackage::Ready)
154 .ok_or_else(|| anyhow::format_err!("failed to find package with id: {}", id))
155 }
156
157 fn finish_download(&mut self, _id: PackageId, _data: Vec<u8>) -> CargoResult<Package> {
158 panic!("no downloads to do")
159 }
160
161 fn fingerprint(&self, pkg: &Package) -> CargoResult<String> {
162 Ok(pkg.package_id().version().to_string())
163 }
164
165 fn verify(&self, id: PackageId) -> CargoResult<()> {
166 let (pkg, cksum) = match self.packages.get(&id) {
167 Some(&(ref pkg, ref cksum)) => (pkg, cksum),
168 None => anyhow::bail!("failed to find entry for `{}` in directory source", id),
169 };
170
171 for (file, cksum) in cksum.files.iter() {
172 let file = pkg.root().join(file);
173 let actual = Sha256::new()
174 .update_path(&file)
175 .chain_err(|| format!("failed to calculate checksum of: {}", file.display()))?
176 .finish_hex();
177 if &*actual != cksum {
178 anyhow::bail!(
179 "the listed checksum of `{}` has changed:\n\
180 expected: {}\n\
181 actual: {}\n\
182 \n\
183 directory sources are not intended to be edited, if \
184 modifications are required then it is recommended \
185 that [replace] is used with a forked copy of the \
186 source\
187 ",
188 file.display(),
189 cksum,
190 actual
191 );
192 }
193 }
194
195 Ok(())
196 }
197
198 fn describe(&self) -> String {
199 format!("directory source `{}`", self.root.display())
200 }
201
202 fn add_to_yanked_whitelist(&mut self, _pkgs: &[PackageId]) {}
203
204 fn is_yanked(&mut self, _pkg: PackageId) -> CargoResult<bool> {
205 Ok(false)
206 }
207}