Support --registry and more options from .cargo/config.toml (#1195)

Fixed #885

Now we can take advantage of new argument `--registry` and
env overrides:
 - `CARGO_REGISTRIES_DEFAULT` if `--registry` is not specified
 - `CARGO_REGISTRIES_{registry_name}_INDEX` for the registry index url

We can also read from `.cargo/config.toml` for:
 - default registry and registries configurations
 - additional CA bundle `http.cainfo`

Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com>
This commit is contained in:
Jiahao XU 2023-07-10 13:37:41 +10:00 committed by GitHub
parent d280e122ca
commit 7dea40a99a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 297 additions and 63 deletions

View file

@ -5,6 +5,8 @@
//! Binstall reads from them to be compatible with `cargo-install`'s behavior.
use std::{
borrow::Cow,
collections::BTreeMap,
fs::File,
io,
path::{Path, PathBuf},
@ -17,39 +19,57 @@ use miette::Diagnostic;
use serde::Deserialize;
use thiserror::Error;
#[derive(Debug, Default, Deserialize)]
#[derive(Debug, Deserialize)]
pub struct Install {
/// `cargo install` destination directory
pub root: Option<PathBuf>,
}
#[derive(Debug, Default, Deserialize)]
#[derive(Debug, Deserialize)]
pub struct Http {
/// HTTP proxy in libcurl format: "host:port"
///
/// env: CARGO_HTTP_PROXY or HTTPS_PROXY or https_proxy or http_proxy
pub proxy: Option<CompactString>,
/// timeout for each HTTP request, in seconds
///
/// env: CARGO_HTTP_TIMEOUT or HTTP_TIMEOUT
pub timeout: Option<u64>,
/// path to Certificate Authority (CA) bundle
pub cainfo: Option<PathBuf>,
// TODO:
// Support field ssl-version, ssl-version.max, ssl-version.min,
// which needs `toml_edit::Item`.
}
#[derive(Eq, PartialEq, Debug, Deserialize)]
#[serde(untagged)]
pub enum Env {
Value(CompactString),
WithOptions {
value: CompactString,
force: Option<bool>,
relative: Option<bool>,
},
}
#[derive(Debug, Deserialize)]
pub struct Registry {
pub index: CompactString,
}
#[derive(Debug, Deserialize)]
pub struct DefaultRegistry {
pub default: CompactString,
}
#[derive(Debug, Default, Deserialize)]
pub struct Config {
pub install: Install,
pub http: Http,
// TODO:
// Add support for section patch, source and registry for alternative
// crates.io registry.
// TODO:
// Add field env for specifying env vars
// which needs `toml_edit::Item`.
pub install: Option<Install>,
pub http: Option<Http>,
pub env: Option<BTreeMap<CompactString, Env>>,
pub registries: Option<BTreeMap<CompactString, Registry>>,
pub registry: Option<DefaultRegistry>,
}
fn join_if_relative(path: &mut Option<PathBuf>, dir: &Path) {
fn join_if_relative(path: Option<&mut PathBuf>, dir: &Path) {
match path {
Some(path) if path.is_relative() => *path = dir.join(&path),
_ => (),
@ -81,8 +101,32 @@ impl Config {
Ok(Default::default())
} else {
let mut config: Config = toml_edit::de::from_slice(&vec)?;
join_if_relative(&mut config.install.root, dir);
join_if_relative(&mut config.http.cainfo, dir);
join_if_relative(
config
.install
.as_mut()
.and_then(|install| install.root.as_mut()),
dir,
);
join_if_relative(
config.http.as_mut().and_then(|http| http.cainfo.as_mut()),
dir,
);
if let Some(envs) = config.env.as_mut() {
for env in envs.values_mut() {
if let Env::WithOptions {
value,
relative: Some(true),
..
} = env
{
let path = Cow::Borrowed(Path::new(&value));
if path.is_relative() {
*value = dir.join(&path).to_string_lossy().into();
}
}
}
}
Ok(config)
}
}
@ -91,10 +135,19 @@ impl Config {
}
pub fn load_from_path(path: impl AsRef<Path>) -> Result<Self, ConfigLoadError> {
let path = path.as_ref();
let file = FileLock::new_shared(File::open(path)?)?;
// Any regular file must have a parent dir
Self::load_from_reader(file, path.parent().unwrap())
fn inner(path: &Path) -> Result<Config, ConfigLoadError> {
match File::open(path) {
Ok(file) => {
let file = FileLock::new_shared(file)?;
// Any regular file must have a parent dir
Config::load_from_reader(file, path.parent().unwrap())
}
Err(err) if err.kind() == io::ErrorKind::NotFound => Ok(Default::default()),
Err(err) => Err(err.into()),
}
}
inner(path.as_ref())
}
}
@ -114,11 +167,19 @@ impl From<toml_edit::de::Error> for ConfigLoadError {
}
}
impl From<toml_edit::TomlError> for ConfigLoadError {
fn from(e: toml_edit::TomlError) -> Self {
ConfigLoadError::TomlParse(Box::new(e.into()))
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::Cursor;
use std::{io::Cursor, path::MAIN_SEPARATOR};
use compact_str::format_compact;
const CONFIG: &str = r#"
[env]
@ -127,7 +188,7 @@ ENV_VAR_NAME = "value"
# Set even if already present in environment
ENV_VAR_NAME_2 = { value = "value", force = true }
# Value is relative to .cargo directory containing `config.toml`, make absolute
ENV_VAR_NAME_3 = { value = "relative/path", relative = true }
ENV_VAR_NAME_3 = { value = "relative-path", relative = true }
[http]
debug = false # HTTP debugging
@ -141,21 +202,39 @@ root = "/some/path" # `cargo install` destination directory
#[test]
fn test_loading() {
let config = Config::load_from_reader(Cursor::new(&CONFIG), Path::new("/root")).unwrap();
let config = Config::load_from_reader(Cursor::new(&CONFIG), Path::new("root")).unwrap();
assert_eq!(
config.install.root.as_deref().unwrap(),
config.install.unwrap().root.as_deref().unwrap(),
Path::new("/some/path")
);
assert_eq!(
config.http.proxy,
Some(CompactString::new_inline("host:port"))
);
assert_eq!(config.http.timeout, Some(30));
let http = config.http.unwrap();
assert_eq!(http.proxy.unwrap(), CompactString::new_inline("host:port"));
assert_eq!(http.timeout.unwrap(), 30);
assert_eq!(http.cainfo.unwrap(), Path::new("root").join("cert.pem"));
let env = config.env.unwrap();
assert_eq!(env.len(), 3);
assert_eq!(
config.http.cainfo.as_deref().unwrap(),
Path::new("/root/cert.pem")
env.get("ENV_VAR_NAME").unwrap(),
&Env::Value(CompactString::new("value"))
);
assert_eq!(
env.get("ENV_VAR_NAME_2").unwrap(),
&Env::WithOptions {
value: CompactString::new("value"),
force: Some(true),
relative: None,
}
);
assert_eq!(
env.get("ENV_VAR_NAME_3").unwrap(),
&Env::WithOptions {
value: format_compact!("root{MAIN_SEPARATOR}relative-path"),
force: None,
relative: Some(true),
}
);
}
}