Skip to content

Commit 3953a64

Browse files
committed
Support custom registries
1 parent d2c0723 commit 3953a64

File tree

4 files changed

+232
-124
lines changed

4 files changed

+232
-124
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ remove_dir_all = "0.5.2"
3838
base64 = "0.12.0"
3939
getrandom = { version = "0.1.12", features = ["std"] }
4040
thiserror = "1.0.20"
41+
git2 = "0.13.12"
4142

4243
[target.'cfg(unix)'.dependencies]
4344
nix = "0.11.0"

src/crates/cratesio.rs

Lines changed: 0 additions & 120 deletions
This file was deleted.

src/crates/mod.rs

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
mod cratesio;
21
mod git;
32
mod local;
3+
mod registry;
44

55
use crate::Workspace;
66
use failure::Error;
@@ -14,7 +14,7 @@ trait CrateTrait: std::fmt::Display {
1414
}
1515

1616
enum CrateType {
17-
CratesIO(cratesio::CratesIOCrate),
17+
CratesIO(registry::RegistryCrate),
1818
Git(git::GitRepo),
1919
Local(local::Local),
2020
}
@@ -23,10 +23,21 @@ enum CrateType {
2323
pub struct Crate(CrateType);
2424

2525
impl Crate {
26+
/// Load a create from specified registry.
27+
pub fn registry(registry_index: &str, name: &str, version: &str) -> Self {
28+
Crate(CrateType::CratesIO(registry::RegistryCrate::new(
29+
registry::Registry::Alternative(registry::AlternativeRegistry::new(registry_index)),
30+
name,
31+
version,
32+
)))
33+
}
34+
2635
/// Load a crate from the [crates.io registry](https://crates.io).
2736
pub fn crates_io(name: &str, version: &str) -> Self {
28-
Crate(CrateType::CratesIO(cratesio::CratesIOCrate::new(
29-
name, version,
37+
Crate(CrateType::CratesIO(registry::RegistryCrate::new(
38+
registry::Registry::CratesIo,
39+
name,
40+
version,
3041
)))
3142
}
3243

src/crates/registry.rs

Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
use super::CrateTrait;
2+
use crate::Workspace;
3+
use failure::{Error, ResultExt};
4+
use flate2::read::GzDecoder;
5+
use log::info;
6+
use std::fs::File;
7+
use std::io::{BufReader, BufWriter, Read};
8+
use std::path::{Path, PathBuf};
9+
use tar::Archive;
10+
11+
static CRATES_ROOT: &str = "https://static.crates.io/crates";
12+
13+
impl RegistryCrate {
14+
pub(super) fn new(registry: Registry, name: &str, version: &str) -> Self {
15+
RegistryCrate {
16+
registry,
17+
name: name.into(),
18+
version: version.into(),
19+
}
20+
}
21+
22+
fn cache_path(&self, workspace: &Workspace) -> PathBuf {
23+
workspace
24+
.cache_dir()
25+
.join(self.registry.cache_folder())
26+
.join(&self.name)
27+
.join(format!("{}-{}.crate", self.name, self.version))
28+
}
29+
30+
fn fetch_url(&self, workspace: &Workspace) -> Result<String, Error> {
31+
match &self.registry {
32+
Registry::CratesIo => Ok(format!(
33+
"{0}/{1}/{1}-{2}.crate",
34+
CRATES_ROOT, self.name, self.version
35+
)),
36+
Registry::Alternative(alt) => {
37+
let index_path = workspace
38+
.cache_dir()
39+
.join("registry-index")
40+
.join(alt.index_folder());
41+
if !index_path.exists() {
42+
let url = alt.index();
43+
git2::Repository::clone(url, index_path.clone())
44+
.with_context(|_| format!("unable to update_index at {}", url))?;
45+
info!("cloned registry index");
46+
}
47+
let config = std::fs::read_to_string(index_path.join("config.json"))?;
48+
let template_url = serde_json::from_str::<IndexConfig>(&config)
49+
.context("registry has invalid config.json")?
50+
.dl;
51+
let replacements = [("{crate}", &self.name), ("{version}", &self.version)];
52+
53+
let url = if replacements
54+
.iter()
55+
.any(|(key, _)| template_url.contains(key))
56+
{
57+
let mut url = template_url;
58+
for (key, value) in &replacements {
59+
url = url.replace(key, value);
60+
}
61+
url
62+
} else {
63+
format!("{}/{}/{}/download", template_url, self.name, self.version)
64+
};
65+
66+
Ok(url)
67+
}
68+
}
69+
}
70+
}
71+
72+
#[derive(serde::Deserialize)]
73+
struct IndexConfig {
74+
dl: String,
75+
}
76+
77+
pub struct AlternativeRegistry {
78+
registry_index: String,
79+
}
80+
81+
impl AlternativeRegistry {
82+
pub fn new(registry_index: impl Into<String>) -> AlternativeRegistry {
83+
AlternativeRegistry {
84+
registry_index: registry_index.into(),
85+
}
86+
}
87+
88+
fn index(&self) -> &str {
89+
self.registry_index.as_str()
90+
}
91+
92+
fn index_folder(&self) -> String {
93+
// https://en.wikipedia.org/wiki/Comparison_of_file_systems#Limits
94+
self.registry_index.as_str().replace(
95+
&['/', '\\', '<', '>', '|', '?', '*', '"', ':', '.'][..],
96+
&"-",
97+
)
98+
}
99+
}
100+
101+
pub enum Registry {
102+
CratesIo,
103+
Alternative(AlternativeRegistry),
104+
}
105+
106+
impl Registry {
107+
fn cache_folder(&self) -> String {
108+
match self {
109+
Registry::CratesIo => "cratesios-sources".into(),
110+
Registry::Alternative(alt) => format!("{}-sources", alt.index()),
111+
}
112+
}
113+
114+
fn name(&self) -> String {
115+
match self {
116+
Registry::CratesIo => "crates.io".into(),
117+
Registry::Alternative(_alt) => todo!(),
118+
}
119+
}
120+
}
121+
122+
pub(super) struct RegistryCrate {
123+
registry: Registry,
124+
name: String,
125+
version: String,
126+
}
127+
128+
impl CrateTrait for RegistryCrate {
129+
fn fetch(&self, workspace: &Workspace) -> Result<(), Error> {
130+
let local = self.cache_path(workspace);
131+
if local.exists() {
132+
info!("crate {} {} is already in cache", self.name, self.version);
133+
return Ok(());
134+
}
135+
136+
info!("fetching crate {} {}...", self.name, self.version);
137+
if let Some(parent) = local.parent() {
138+
std::fs::create_dir_all(parent)?;
139+
}
140+
141+
let mut resp = workspace
142+
.http_client()
143+
.get(&self.fetch_url(workspace)?)
144+
.send()?
145+
.error_for_status()?;
146+
resp.copy_to(&mut BufWriter::new(File::create(&local)?))?;
147+
148+
Ok(())
149+
}
150+
151+
fn purge_from_cache(&self, workspace: &Workspace) -> Result<(), Error> {
152+
let path = self.cache_path(workspace);
153+
if path.exists() {
154+
crate::utils::remove_file(&path)?;
155+
}
156+
Ok(())
157+
}
158+
159+
fn copy_source_to(&self, workspace: &Workspace, dest: &Path) -> Result<(), Error> {
160+
let cached = self.cache_path(workspace);
161+
let mut file = File::open(cached)?;
162+
let mut tar = Archive::new(GzDecoder::new(BufReader::new(&mut file)));
163+
164+
info!(
165+
"extracting crate {} {} into {}",
166+
self.name,
167+
self.version,
168+
dest.display()
169+
);
170+
if let Err(err) = unpack_without_first_dir(&mut tar, dest) {
171+
let _ = crate::utils::remove_dir_all(dest);
172+
Err(err
173+
.context(format!(
174+
"unable to download {} version {}",
175+
self.name, self.version
176+
))
177+
.into())
178+
} else {
179+
Ok(())
180+
}
181+
}
182+
}
183+
184+
impl std::fmt::Display for RegistryCrate {
185+
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
186+
write!(
187+
f,
188+
"{} crate {} {}",
189+
self.registry.name(),
190+
self.name,
191+
self.version
192+
)
193+
}
194+
}
195+
196+
fn unpack_without_first_dir<R: Read>(archive: &mut Archive<R>, path: &Path) -> Result<(), Error> {
197+
let entries = archive.entries()?;
198+
for entry in entries {
199+
let mut entry = entry?;
200+
let relpath = {
201+
let path = entry.path();
202+
let path = path?;
203+
path.into_owned()
204+
};
205+
let mut components = relpath.components();
206+
// Throw away the first path component
207+
components.next();
208+
let full_path = path.join(&components.as_path());
209+
if let Some(parent) = full_path.parent() {
210+
std::fs::create_dir_all(parent)?;
211+
}
212+
entry.unpack(&full_path)?;
213+
}
214+
215+
Ok(())
216+
}

0 commit comments

Comments
 (0)