Skip to content

Commit 515aa46

Browse files
committed
Implement a local registry type
This flavor of registry is intended to behave very similarly to the standard remote registry, except everything is contained locally on the filesystem instead. There are a few components to this new flavor of registry: 1. The registry itself is rooted at a particular directory, owning all structure beneath it. 2. There is an `index` folder with the same structure as the crates.io index describing the local registry (e.g. contents, versions, checksums, etc). 3. Inside the root will also be a list of `.crate` files which correspond to those described in the index. All crates must be of the form `name-version.crate` and be the same `.crate` files from crates.io itself. This support can currently be used via the previous implementation of source overrides with the new type: ```toml [source.crates-io] replace-with = 'my-awesome-registry' [source.my-awesome-registry] local-registry = 'path/to/registry' ``` I will soon follow up with a tool which can be used to manage these local registries externally.
1 parent bf4b8f7 commit 515aa46

14 files changed

+570
-76
lines changed

src/cargo/core/resolver/mod.rs

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,6 @@ struct Context {
217217
activations: HashMap<(String, SourceId), Vec<Rc<Summary>>>,
218218
resolve_graph: Graph<PackageId>,
219219
resolve_features: HashMap<PackageId, HashSet<String>>,
220-
visited: HashSet<PackageId>,
221220
}
222221

223222
/// Builds the list of all packages required to build the first argument.
@@ -233,6 +232,7 @@ pub fn resolve(summary: &Summary, method: &Method, registry: &mut Registry)
233232
let _p = profile::start(format!("resolving: {}", summary.package_id()));
234233
let cx = try!(activate_deps_loop(cx, registry, Rc::new(summary.clone()),
235234
method));
235+
try!(check_cycles(&cx, summary.package_id()));
236236

237237
let mut resolve = Resolve {
238238
graph: cx.resolve_graph,
@@ -247,8 +247,6 @@ pub fn resolve(summary: &Summary, method: &Method, registry: &mut Registry)
247247
resolve.checksums.insert(summary.package_id().clone(), cksum);
248248
}
249249

250-
try!(check_cycles(&cx));
251-
252250
trace!("resolved: {:?}", resolve);
253251
Ok(resolve)
254252
}
@@ -841,18 +839,18 @@ impl Context {
841839
}
842840
}
843841

844-
fn check_cycles(cx: &Context) -> CargoResult<()> {
842+
fn check_cycles(cx: &Context, root: &PackageId) -> CargoResult<()> {
845843
let mut summaries = HashMap::new();
846844
for summary in cx.activations.values().flat_map(|v| v) {
847845
summaries.insert(summary.package_id(), &**summary);
848846
}
849-
return visit(&cx.resolve,
850-
cx.resolve.root(),
847+
return visit(&cx.resolve_graph,
848+
root,
851849
&summaries,
852850
&mut HashSet::new(),
853851
&mut HashSet::new());
854852

855-
fn visit<'a>(resolve: &'a Resolve,
853+
fn visit<'a>(resolve: &'a Graph<PackageId>,
856854
id: &'a PackageId,
857855
summaries: &HashMap<&'a PackageId, &Summary>,
858856
visited: &mut HashSet<&'a PackageId>,
@@ -873,7 +871,7 @@ fn check_cycles(cx: &Context) -> CargoResult<()> {
873871
// dependencies.
874872
if checked.insert(id) {
875873
let summary = summaries[id];
876-
for dep in resolve.deps(id).into_iter().flat_map(|a| a) {
874+
for dep in resolve.edges(id).into_iter().flat_map(|a| a) {
877875
let is_transitive = summary.dependencies().iter().any(|d| {
878876
d.matches_id(dep) && d.is_transitive()
879877
});

src/cargo/core/source.rs

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,8 @@ enum Kind {
6363
Path,
6464
/// represents the central registry
6565
Registry,
66+
/// represents a local filesystem-based registry
67+
LocalRegistry,
6668
}
6769

6870
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
@@ -107,7 +109,7 @@ impl SourceId {
107109
/// use cargo::core::SourceId;
108110
/// SourceId::from_url("git+https://github.com/alexcrichton/\
109111
/// libssh2-static-sys#80e71a3021618eb05\
110-
/// 656c58fb7c5ef5f12bc747f".to_string());
112+
/// 656c58fb7c5ef5f12bc747f");
111113
/// ```
112114
pub fn from_url(string: &str) -> CargoResult<SourceId> {
113115
let mut parts = string.splitn(2, '+');
@@ -169,6 +171,9 @@ impl SourceId {
169171
SourceIdInner { kind: Kind::Registry, ref url, .. } => {
170172
format!("registry+{}", url)
171173
}
174+
SourceIdInner { kind: Kind::LocalRegistry, ref url, .. } => {
175+
format!("local-registry+{}", url)
176+
}
172177
}
173178
}
174179

@@ -186,6 +191,11 @@ impl SourceId {
186191
SourceId::new(Kind::Registry, url.clone())
187192
}
188193

194+
pub fn for_local_registry(path: &Path) -> CargoResult<SourceId> {
195+
let url = try!(path.to_url());
196+
Ok(SourceId::new(Kind::LocalRegistry, url))
197+
}
198+
189199
/// Returns the `SourceId` corresponding to the main repository.
190200
///
191201
/// This is the main cargo registry by default, but it can be overridden in
@@ -210,7 +220,9 @@ impl SourceId {
210220

211221
pub fn url(&self) -> &Url { &self.inner.url }
212222
pub fn is_path(&self) -> bool { self.inner.kind == Kind::Path }
213-
pub fn is_registry(&self) -> bool { self.inner.kind == Kind::Registry }
223+
pub fn is_registry(&self) -> bool {
224+
self.inner.kind == Kind::Registry || self.inner.kind == Kind::LocalRegistry
225+
}
214226

215227
pub fn is_git(&self) -> bool {
216228
match self.inner.kind {
@@ -232,6 +244,13 @@ impl SourceId {
232244
Box::new(PathSource::new(&path, self, config))
233245
}
234246
Kind::Registry => Box::new(RegistrySource::remote(self, config)),
247+
Kind::LocalRegistry => {
248+
let path = match self.inner.url.to_file_path() {
249+
Ok(p) => p,
250+
Err(()) => panic!("path sources cannot be remote"),
251+
};
252+
Box::new(RegistrySource::local(self, &path, config))
253+
}
235254
}
236255
}
237256

@@ -317,7 +336,8 @@ impl fmt::Display for SourceId {
317336
}
318337
Ok(())
319338
}
320-
SourceIdInner { kind: Kind::Registry, ref url, .. } => {
339+
SourceIdInner { kind: Kind::Registry, ref url, .. } |
340+
SourceIdInner { kind: Kind::LocalRegistry, ref url, .. } => {
321341
write!(f, "registry {}", url)
322342
}
323343
}

src/cargo/sources/config.rs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -128,11 +128,21 @@ a lock file compatible with `{orig}` cannot be generated in this situation
128128
let url = try!(url(val, &format!("source.{}.registry", name)));
129129
srcs.push(SourceId::for_registry(&url));
130130
}
131+
if let Some(val) = table.get("local-registry") {
132+
let (s, path) = try!(val.string(&format!("source.{}.local-registry",
133+
name)));
134+
let mut path = path.to_path_buf();
135+
path.pop();
136+
path.pop();
137+
path.push(s);
138+
srcs.push(try!(SourceId::for_local_registry(&path)));
139+
}
131140

132141
let mut srcs = srcs.into_iter();
133142
let src = try!(srcs.next().chain_error(|| {
134-
human(format!("no source URL specified for `source.{}`, needs \
135-
`registry` defined", name))
143+
human(format!("no source URL specified for `source.{}`, need \
144+
either `registry` or `local-registry` defined",
145+
name))
136146
}));
137147
if srcs.next().is_some() {
138148
return Err(human(format!("more than one source URL specified for \

src/cargo/sources/registry/local.rs

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
use std::fs::{self, File};
2+
use std::io::prelude::*;
3+
use std::path::{PathBuf, Path};
4+
5+
use rustc_serialize::hex::ToHex;
6+
7+
use core::PackageId;
8+
use sources::registry::{RegistryData, RegistryConfig};
9+
use util::{Config, CargoResult, ChainError, human, Sha256};
10+
11+
pub struct LocalRegistry<'cfg> {
12+
index_path: PathBuf,
13+
root: PathBuf,
14+
src_path: PathBuf,
15+
config: &'cfg Config,
16+
}
17+
18+
impl<'cfg> LocalRegistry<'cfg> {
19+
pub fn new(root: &Path,
20+
config: &'cfg Config,
21+
name: &str) -> LocalRegistry<'cfg> {
22+
LocalRegistry {
23+
src_path: config.registry_source_path().join(name),
24+
index_path: root.join("index"),
25+
root: root.to_path_buf(),
26+
config: config,
27+
}
28+
}
29+
}
30+
31+
impl<'cfg> RegistryData for LocalRegistry<'cfg> {
32+
fn index_path(&self) -> &Path {
33+
&self.index_path
34+
}
35+
36+
fn config(&self) -> CargoResult<Option<RegistryConfig>> {
37+
// Local registries don't have configuration for remote APIs or anything
38+
// like that
39+
Ok(None)
40+
}
41+
42+
fn update_index(&mut self) -> CargoResult<()> {
43+
// Nothing to update, we just use what's on disk. Verify it actually
44+
// exists though
45+
if !self.root.is_dir() {
46+
bail!("local registry path is not a directory: {}",
47+
self.root.display())
48+
}
49+
if !self.index_path.is_dir() {
50+
bail!("local registry index path is not a directory: {}",
51+
self.index_path.display())
52+
}
53+
Ok(())
54+
}
55+
56+
fn download(&mut self, pkg: &PackageId, checksum: &str)
57+
-> CargoResult<PathBuf> {
58+
let crate_file = format!("{}-{}.crate", pkg.name(), pkg.version());
59+
let crate_file = self.root.join(&crate_file);
60+
61+
// If we've already got an unpacked version of this crate, then skip the
62+
// checksum below as it is in theory already verified.
63+
let dst = format!("{}-{}", pkg.name(), pkg.version());
64+
let dst = self.src_path.join(&dst);
65+
if fs::metadata(&dst).is_ok() {
66+
return Ok(crate_file)
67+
}
68+
69+
try!(self.config.shell().status("Unpacking", pkg));
70+
71+
// We don't actually need to download anything per-se, we just need to
72+
// verify the checksum matches the .crate file itself.
73+
let mut file = try!(File::open(&crate_file).chain_error(|| {
74+
human(format!("failed to read `{}` for `{}`", crate_file.display(),
75+
pkg))
76+
}));
77+
let mut data = Vec::new();
78+
try!(file.read_to_end(&mut data).chain_error(|| {
79+
human(format!("failed to read `{}`", crate_file.display()))
80+
}));
81+
let mut state = Sha256::new();
82+
state.update(&data);
83+
if state.finish().to_hex() != checksum {
84+
bail!("failed to verify the checksum of `{}`", pkg)
85+
}
86+
87+
Ok(crate_file)
88+
}
89+
}

src/cargo/sources/registry/mod.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,7 @@ pub trait RegistryData {
226226

227227
mod index;
228228
mod remote;
229+
mod local;
229230

230231
fn short_name(id: &SourceId) -> String {
231232
let hash = hex::short_hash(id);
@@ -241,6 +242,14 @@ impl<'cfg> RegistrySource<'cfg> {
241242
RegistrySource::new(source_id, config, &name, Box::new(ops))
242243
}
243244

245+
pub fn local(source_id: &SourceId,
246+
path: &Path,
247+
config: &'cfg Config) -> RegistrySource<'cfg> {
248+
let name = short_name(source_id);
249+
let ops = local::LocalRegistry::new(path, config, &name);
250+
RegistrySource::new(source_id, config, &name, Box::new(ops))
251+
}
252+
244253
fn new(source_id: &SourceId,
245254
config: &'cfg Config,
246255
name: &str,

tests/support/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -666,3 +666,4 @@ pub static UPLOADING: &'static str = " Uploading";
666666
pub static VERIFYING: &'static str = " Verifying";
667667
pub static ARCHIVING: &'static str = " Archiving";
668668
pub static INSTALLING: &'static str = " Installing";
669+
pub static UNPACKING: &'static str = " Unpacking";

tests/support/registry.rs

Lines changed: 35 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ pub struct Package {
2424
deps: Vec<(String, String, &'static str, String)>,
2525
files: Vec<(String, String)>,
2626
yanked: bool,
27+
local: bool,
2728
}
2829

2930
pub fn init() {
@@ -62,9 +63,15 @@ impl Package {
6263
deps: Vec::new(),
6364
files: Vec::new(),
6465
yanked: false,
66+
local: false,
6567
}
6668
}
6769

70+
pub fn local(&mut self, local: bool) -> &mut Package {
71+
self.local = local;
72+
self
73+
}
74+
6875
pub fn file(&mut self, name: &str, contents: &str) -> &mut Package {
6976
self.files.push((name.to_string(), contents.to_string()));
7077
self
@@ -96,7 +103,6 @@ impl Package {
96103
self
97104
}
98105

99-
#[allow(deprecated)] // connect => join in 1.3
100106
pub fn publish(&self) {
101107
self.make_archive();
102108

@@ -109,7 +115,7 @@ impl Package {
109115
\"target\":{},\
110116
\"optional\":false,\
111117
\"kind\":\"{}\"}}", name, req, target, kind)
112-
}).collect::<Vec<_>>().connect(",");
118+
}).collect::<Vec<_>>().join(",");
113119
let cksum = {
114120
let mut c = Vec::new();
115121
File::open(&self.archive_dst()).unwrap()
@@ -128,28 +134,34 @@ impl Package {
128134
};
129135

130136
// Write file/line in the index
131-
let dst = registry_path().join(&file);
137+
let dst = if self.local {
138+
registry_path().join("index").join(&file)
139+
} else {
140+
registry_path().join(&file)
141+
};
132142
let mut prev = String::new();
133143
let _ = File::open(&dst).and_then(|mut f| f.read_to_string(&mut prev));
134144
fs::create_dir_all(dst.parent().unwrap()).unwrap();
135145
File::create(&dst).unwrap()
136146
.write_all((prev + &line[..] + "\n").as_bytes()).unwrap();
137147

138148
// Add the new file to the index
139-
let repo = git2::Repository::open(&registry_path()).unwrap();
140-
let mut index = repo.index().unwrap();
141-
index.add_path(Path::new(&file)).unwrap();
142-
index.write().unwrap();
143-
let id = index.write_tree().unwrap();
144-
145-
// Commit this change
146-
let tree = repo.find_tree(id).unwrap();
147-
let sig = repo.signature().unwrap();
148-
let parent = repo.refname_to_id("refs/heads/master").unwrap();
149-
let parent = repo.find_commit(parent).unwrap();
150-
repo.commit(Some("HEAD"), &sig, &sig,
151-
"Another commit", &tree,
152-
&[&parent]).unwrap();
149+
if !self.local {
150+
let repo = git2::Repository::open(&registry_path()).unwrap();
151+
let mut index = repo.index().unwrap();
152+
index.add_path(Path::new(&file)).unwrap();
153+
index.write().unwrap();
154+
let id = index.write_tree().unwrap();
155+
156+
// Commit this change
157+
let tree = repo.find_tree(id).unwrap();
158+
let sig = repo.signature().unwrap();
159+
let parent = repo.refname_to_id("refs/heads/master").unwrap();
160+
let parent = repo.find_commit(parent).unwrap();
161+
repo.commit(Some("HEAD"), &sig, &sig,
162+
"Another commit", &tree,
163+
&[&parent]).unwrap();
164+
}
153165
}
154166

155167
fn make_archive(&self) {
@@ -199,7 +211,12 @@ impl Package {
199211
}
200212

201213
pub fn archive_dst(&self) -> PathBuf {
202-
dl_path().join(&self.name).join(&self.vers).join("download")
214+
if self.local {
215+
registry_path().join(format!("{}-{}.crate", self.name,
216+
self.vers))
217+
} else {
218+
dl_path().join(&self.name).join(&self.vers).join("download")
219+
}
203220
}
204221
}
205222

0 commit comments

Comments
 (0)