Skip to content

Commit 3c6d458

Browse files
committed
Support {prefix} and {lowerprefix} markers in config.json dl key.
These new markers allow Cargo to supply a directory name (similar to that used in crates.io-index) as part of a crate's download URL, enabling simpler hosting of crates. Previously, a `file` URL would need to put all crates into a single huge directory (such as `/srv/crates/`), e.g.: file:///srv/crates/{crate}/{crate}-{version}.crate With the `{prefix}` marker, a more efficient directory structure may be used, e.g.: file:///srv/crates/{prefix}/{crate}/{crate}-{version}.crate An example crate of `cargo-0.44.1.crate` would map to the path: /srv/crates/ca/rg/cargo/cargo-0.44.1.crate
1 parent 9b94513 commit 3c6d458

File tree

3 files changed

+66
-8
lines changed

3 files changed

+66
-8
lines changed

src/cargo/sources/registry/mod.rs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,8 @@ pub const CRATES_IO_INDEX: &str = "https://github.com/rust-lang/crates.io-index"
185185
pub const CRATES_IO_REGISTRY: &str = "crates-io";
186186
const CRATE_TEMPLATE: &str = "{crate}";
187187
const VERSION_TEMPLATE: &str = "{version}";
188+
const PREFIX_TEMPLATE: &str = "{prefix}";
189+
const LOWER_PREFIX_TEMPLATE: &str = "{lowerprefix}";
188190

189191
pub struct RegistrySource<'cfg> {
190192
source_id: SourceId,
@@ -203,10 +205,14 @@ pub struct RegistryConfig {
203205
/// The string is a template which will generate the download URL for the
204206
/// tarball of a specific version of a crate. The substrings `{crate}` and
205207
/// `{version}` will be replaced with the crate's name and version
206-
/// respectively.
208+
/// respectively. The substring `{prefix}` will be replaced with the
209+
/// crate's prefix directory name, and the substring `{lowerprefix}` will
210+
/// be replaced with the crate's prefix directory name converted to
211+
/// lowercase.
207212
///
208-
/// For backwards compatibility, if the string does not contain `{crate}` or
209-
/// `{version}`, it will be extended with `/{crate}/{version}/download` to
213+
/// For backwards compatibility, if the string does not contain any
214+
/// markers (`{crate}`, `{version}`, `{prefix}`, or ``{lowerprefix}`), it
215+
/// will be extended with `/{crate}/{version}/download` to
210216
/// support registries like crates.io which were created before the
211217
/// templating setup was created.
212218
pub dl: String,

src/cargo/sources/registry/remote.rs

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
use crate::core::{InternedString, PackageId, SourceId};
22
use crate::sources::git;
33
use crate::sources::registry::MaybeLock;
4-
use crate::sources::registry::{RegistryConfig, RegistryData, CRATE_TEMPLATE, VERSION_TEMPLATE};
4+
use crate::sources::registry::{
5+
RegistryConfig, RegistryData, CRATE_TEMPLATE, LOWER_PREFIX_TEMPLATE, PREFIX_TEMPLATE,
6+
VERSION_TEMPLATE,
7+
};
58
use crate::util::errors::{CargoResult, CargoResultExt};
69
use crate::util::paths;
710
use crate::util::{Config, Filesystem, Sha256};
@@ -16,6 +19,15 @@ use std::mem;
1619
use std::path::Path;
1720
use std::str;
1821

22+
fn make_crate_prefix(name: &str) -> String {
23+
match name.len() {
24+
1 => format!("1"),
25+
2 => format!("2"),
26+
3 => format!("3/{}", &name[..1]),
27+
_ => format!("{}/{}", &name[0..2], &name[2..4]),
28+
}
29+
}
30+
1931
pub struct RemoteRegistry<'cfg> {
2032
index_path: Filesystem,
2133
cache_path: Filesystem,
@@ -250,12 +262,19 @@ impl<'cfg> RegistryData for RemoteRegistry<'cfg> {
250262

251263
let config = self.config()?.unwrap();
252264
let mut url = config.dl;
253-
if !url.contains(CRATE_TEMPLATE) && !url.contains(VERSION_TEMPLATE) {
265+
if !url.contains(CRATE_TEMPLATE)
266+
&& !url.contains(VERSION_TEMPLATE)
267+
&& !url.contains(PREFIX_TEMPLATE)
268+
&& !url.contains(LOWER_PREFIX_TEMPLATE)
269+
{
254270
write!(url, "/{}/{}/download", CRATE_TEMPLATE, VERSION_TEMPLATE).unwrap();
255271
}
272+
let prefix = make_crate_prefix(&*pkg.name());
256273
let url = url
257274
.replace(CRATE_TEMPLATE, &*pkg.name())
258-
.replace(VERSION_TEMPLATE, &pkg.version().to_string());
275+
.replace(VERSION_TEMPLATE, &pkg.version().to_string())
276+
.replace(PREFIX_TEMPLATE, &prefix)
277+
.replace(LOWER_PREFIX_TEMPLATE, &prefix.to_lowercase());
259278

260279
Ok(MaybeLock::Download {
261280
url,
@@ -314,3 +333,18 @@ impl<'cfg> Drop for RemoteRegistry<'cfg> {
314333
self.tree.borrow_mut().take();
315334
}
316335
}
336+
337+
#[cfg(test)]
338+
mod tests {
339+
use super::make_crate_prefix;
340+
341+
#[test]
342+
fn crate_prefix() {
343+
assert_eq!(make_crate_prefix("a"), "1");
344+
assert_eq!(make_crate_prefix("ab"), "2");
345+
assert_eq!(make_crate_prefix("abc"), "3/a");
346+
assert_eq!(make_crate_prefix("Abc"), "3/A");
347+
assert_eq!(make_crate_prefix("AbCd"), "Ab/Cd");
348+
assert_eq!(make_crate_prefix("aBcDe"), "aB/cD");
349+
}
350+
}

src/doc/src/reference/registries.md

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -128,8 +128,11 @@ looks like:
128128
The keys are:
129129
- `dl`: This is the URL for downloading crates listed in the index. The value
130130
may have the markers `{crate}` and `{version}` which are replaced with the
131-
name and version of the crate to download. If the markers are not present,
132-
then the value `/{crate}/{version}/download` is appended to the end.
131+
name and version of the crate to download, or the marker `{prefix}` which is
132+
replaced with the crate's prefix, or the marker `{lowerprefix}` which is
133+
replaced with the crate's prefix converted to lowercase. If none of the
134+
markers are present, then the value `/{crate}/{version}/download` is appended
135+
to the end. See below for more about crate prefixes.
133136
- `api`: This is the base URL for the web API. This key is optional, but if it
134137
is not specified, commands such as [`cargo publish`] will not work. The web
135138
API is described below.
@@ -159,6 +162,21 @@ directories:
159162
> package names in `Cargo.toml` and the index JSON data are case-sensitive and
160163
> may contain upper and lower case characters.
161164
165+
The directory name above is calculated based on the package name converted to
166+
lowercase; it is represented by the marker `{lowerprefix}`. When the original
167+
package name is used without case conversion, the resulting directory name is
168+
represented by the marker `{prefix}`. For example, the package `MyCrate` would
169+
have a `{prefix}` of `My/Cr` and a `{lowerprefix}` of `my/cr`. In general,
170+
using `{prefix}` is recommended over `{lowerprefix}`, but there are pros and
171+
cons to each choice. Using `{prefix}` on case-insensitive filesystems results
172+
in (harmless-but-inelegant) directory aliasing. For example, `crate` and
173+
`CrateTwo` have `{prefix}` values of `cr/at` and `Cr/at`; these are distinct on
174+
Unix machines but alias to the same directory on Windows. Using directories
175+
with normalized case avoids aliasing, but on case-sensitive filesystems it's
176+
harder to suport older versions of Cargo that lack `{prefix}`/`{lowerprefix}`.
177+
For example, nginx rewrite rules can easily construct `{prefix}` but can't
178+
perform case-conversion to construct `{lowerprefix}`.
179+
162180
Registries should consider enforcing limitations on package names added to
163181
their index. Cargo itself allows names with any [alphanumeric], `-`, or `_`
164182
characters. [crates.io] imposes its own limitations, including the following:

0 commit comments

Comments
 (0)