From 130fb77d22f86f70e5084c2c222ff3d77965918f Mon Sep 17 00:00:00 2001 From: Jake Shadle <jake.shadle@embark-studios.com> Date: Fri, 3 May 2024 15:30:12 +0200 Subject: [PATCH] Add IndexUrl::for_registry_name (#61) Resolves: #60 --- src/error.rs | 7 ++ src/index/location.rs | 144 ++++++++++++++++++++++++++++++++++++++++-- src/utils/flock.rs | 2 +- 3 files changed, 148 insertions(+), 5 deletions(-) diff --git a/src/error.rs b/src/error.rs index 43e19a9..72acad1 100644 --- a/src/error.rs +++ b/src/error.rs @@ -15,9 +15,16 @@ pub enum Error { /// was not valid utf-8 #[error("unable to use non-utf8 path {:?}", .0)] NonUtf8Path(std::path::PathBuf), + /// An environment variable was located, but had a non-utf8 value + #[error("environment variable {} has a non-utf8 value", .0)] + NonUtf8EnvVar(std::borrow::Cow<'static, str>), /// A user-provided string was not a valid crate name #[error(transparent)] InvalidKrateName(#[from] InvalidKrateName), + /// The user specified a registry name that did not exist in any searched + /// .cargo/config.toml + #[error("registry '{}' was not located in any .cargo/config.toml", .0)] + UnknownRegistry(String), /// An I/O error #[error(transparent)] Io(#[from] std::io::Error), diff --git a/src/index/location.rs b/src/index/location.rs index 2d004bb..ed99763 100644 --- a/src/index/location.rs +++ b/src/index/location.rs @@ -57,7 +57,9 @@ impl<'iu> IndexUrl<'iu> { ) -> Result<Self, Error> { // If the crates.io registry has been replaced it doesn't matter what // the protocol for it has been changed to - if let Some(replacement) = get_crates_io_replacement(config_root.clone(), cargo_home)? { + if let Some(replacement) = + get_source_replacement(config_root.clone(), cargo_home, "crates-io")? + { return Ok(replacement); } @@ -99,6 +101,64 @@ impl<'iu> IndexUrl<'iu> { Self::CratesIoGit }) } + + /// Creates an [`IndexUrl`] for the specified registry name + /// + /// 1. Checks if [`CARGO_REGISTRIES_<name>_INDEX`](https://doc.rust-lang.org/cargo/reference/config.html#registriesnameindex) is set + /// 2. Checks if the source for the registry has been [replaced](https://doc.rust-lang.org/cargo/reference/source-replacement.html) + /// 3. Uses the value of [`registries.<name>.index`](https://doc.rust-lang.org/cargo/reference/config.html#registriesnameindex) otherwise + pub fn for_registry_name( + config_root: Option<PathBuf>, + cargo_home: Option<&Path>, + registry_name: &str, + ) -> Result<Self, Error> { + // Check if the index was explicitly specified + let mut env = String::with_capacity(17 + registry_name.len() + 6); + env.push_str("CARGO_REGISTRIES_"); + + if registry_name.is_ascii() { + for c in registry_name.chars() { + if c == '-' { + env.push('_'); + } else { + env.push(c.to_ascii_uppercase()); + } + } + } else { + let mut upper = registry_name.to_uppercase(); + if upper.contains('-') { + upper = upper.replace('-', "_"); + } + + env.push_str(&upper); + } + + env.push_str("_INDEX"); + + match std::env::var(&env) { + Ok(index) => return Ok(Self::NonCratesIo(index.into())), + Err(err) => { + if let std::env::VarError::NotUnicode(_nu) = err { + return Err(Error::NonUtf8EnvVar(env.into())); + } + } + } + + if let Some(replacement) = + get_source_replacement(config_root.clone(), cargo_home, registry_name)? + { + return Ok(replacement); + } + + read_cargo_config(config_root, cargo_home, |config| { + let path = format!("/registries/{registry_name}/index"); + config + .pointer(&path)? + .as_str() + .map(|si| Self::NonCratesIo(si.to_owned().into())) + })? + .ok_or_else(|| Error::UnknownRegistry(registry_name.into())) + } } impl<'iu> From<&'iu str> for IndexUrl<'iu> { @@ -242,16 +302,18 @@ pub(crate) fn read_cargo_config<T>( Ok(None) } -/// Gets the url of a replacement registry for crates.io if one has been configured +/// Gets the url of a replacement registry for the specified registry if one has been configured /// /// See <https://doc.rust-lang.org/cargo/reference/source-replacement.html> #[inline] -pub(crate) fn get_crates_io_replacement<'iu>( +pub(crate) fn get_source_replacement<'iu>( root: Option<PathBuf>, cargo_home: Option<&Path>, + registry_name: &str, ) -> Result<Option<IndexUrl<'iu>>, Error> { read_cargo_config(root, cargo_home, |config| { - let repw = config.pointer("/source/crates-io/replace-with")?.as_str()?; + let path = format!("/source/{registry_name}/replace-with"); + let repw = config.pointer(&path)?.as_str()?; let sources = config.pointer("/source")?.as_table()?; let replace_src = sources.get(&repw.into())?.as_table()?; @@ -324,4 +386,78 @@ protocol = "git" assert_eq!(iurl.as_str(), *url); } } + + #[test] + fn custom() { + assert!(std::env::var_os("CARGO_REGISTRIES_TAME_INDEX_TEST_INDEX").is_none()); + + let td = tempfile::tempdir().unwrap(); + let root = crate::PathBuf::from_path_buf(td.path().to_owned()).unwrap(); + let cfg_toml = td.path().join(".cargo/config.toml"); + + std::fs::create_dir_all(cfg_toml.parent().unwrap()).unwrap(); + + const SPARSE: &str = r#"[registries.tame-index-test] +index = "sparse+https://some-url.com" +"#; + + const GIT: &str = r#"[registries.tame-index-test] + index = "https://some-url.com" + "#; + + { + std::fs::write(&cfg_toml, SPARSE).unwrap(); + + let iurl = + super::IndexUrl::for_registry_name(Some(root.clone()), None, "tame-index-test") + .unwrap(); + assert_eq!(iurl.as_str(), "sparse+https://some-url.com"); + assert!(iurl.is_sparse()); + + std::env::set_var( + "CARGO_REGISTRIES_TAME_INDEX_TEST_INDEX", + "sparse+https://some-other-url.com", + ); + + let iurl = + super::IndexUrl::for_registry_name(Some(root.clone()), None, "tame-index-test") + .unwrap(); + assert_eq!(iurl.as_str(), "sparse+https://some-other-url.com"); + assert!(iurl.is_sparse()); + + std::env::remove_var("CARGO_REGISTRIES_TAME_INDEX_TEST_INDEX"); + } + + { + std::fs::write(&cfg_toml, GIT).unwrap(); + + let iurl = + super::IndexUrl::for_registry_name(Some(root.clone()), None, "tame-index-test") + .unwrap(); + assert_eq!(iurl.as_str(), "https://some-url.com"); + assert!(!iurl.is_sparse()); + + std::env::set_var( + "CARGO_REGISTRIES_TAME_INDEX_TEST_INDEX", + "https://some-other-url.com", + ); + + let iurl = + super::IndexUrl::for_registry_name(Some(root.clone()), None, "tame-index-test") + .unwrap(); + assert_eq!(iurl.as_str(), "https://some-other-url.com"); + assert!(!iurl.is_sparse()); + + std::env::remove_var("CARGO_REGISTRIES_TAME_INDEX_TEST_INDEX"); + } + + #[allow(unused_variables)] + { + let err = crate::Error::UnknownRegistry("non-existant".to_owned()); + assert!(matches!( + super::IndexUrl::for_registry_name(Some(root.clone()), None, "non-existant"), + Err(err), + )); + } + } } diff --git a/src/utils/flock.rs b/src/utils/flock.rs index 7cebd65..1988cb7 100644 --- a/src/utils/flock.rs +++ b/src/utils/flock.rs @@ -30,7 +30,7 @@ pub enum LockError { #[error("failed to create parent directories for lock path")] CreateDir(std::io::Error), /// Locking is not supported if the lock file is on an NFS, though note this - /// is a bit more nuanced as NFSv4 _does_ support file locking, but is out + /// is a bit more nuanced as `NFSv4` _does_ support file locking, but is out /// of scope, at least for now #[error("NFS do not support locking")] Nfs,