cargo_edit_9/
registry.rs

1use super::errors::*;
2use std::collections::HashMap;
3use std::path::{Path, PathBuf};
4use url::Url;
5
6const CRATES_IO_INDEX: &str = "https://github.com/rust-lang/crates.io-index";
7const CRATES_IO_REGISTRY: &str = "crates-io";
8
9/// Find the URL of a registry
10pub fn registry_url(manifest_path: &Path, registry: Option<&str>) -> CargoResult<Url> {
11    // TODO support local registry sources, directory sources, git sources: https://doc.rust-lang.org/cargo/reference/source-replacement.html?highlight=replace-with#source-replacement
12    fn read_config(
13        registries: &mut HashMap<String, Source>,
14        path: impl AsRef<Path>,
15    ) -> CargoResult<()> {
16        // TODO unit test for source replacement
17        let content = std::fs::read(path)?;
18        let config = toml_edit::easy::from_slice::<CargoConfig>(&content)
19            .map_err(|_| invalid_cargo_config())?;
20        for (key, value) in config.registries {
21            registries.entry(key).or_insert(Source {
22                registry: value.index,
23                replace_with: None,
24            });
25        }
26        for (key, value) in config.source {
27            registries.entry(key).or_insert(value);
28        }
29        Ok(())
30    }
31    // registry might be replaced with another source
32    // it's looks like a singly linked list
33    // put relations in this map.
34    let mut registries: HashMap<String, Source> = HashMap::new();
35    // ref: https://doc.rust-lang.org/cargo/reference/config.html#hierarchical-structure
36    for work_dir in manifest_path
37        .parent()
38        .expect("there must be a parent directory")
39        .ancestors()
40    {
41        let work_cargo_dir = work_dir.join(".cargo");
42        let config_path = work_cargo_dir.join("config");
43        if config_path.is_file() {
44            read_config(&mut registries, config_path)?;
45        } else {
46            let config_path = work_cargo_dir.join("config.toml");
47            if config_path.is_file() {
48                read_config(&mut registries, config_path)?;
49            }
50        }
51    }
52
53    let default_cargo_home = cargo_home()?;
54    let default_config_path = default_cargo_home.join("config");
55    if default_config_path.is_file() {
56        read_config(&mut registries, default_config_path)?;
57    } else {
58        let default_config_path = default_cargo_home.join("config.toml");
59        if default_config_path.is_file() {
60            read_config(&mut registries, default_config_path)?;
61        }
62    }
63
64    // find head of the relevant linked list
65    let mut source = match registry {
66        Some(CRATES_IO_INDEX) | None => {
67            let mut source = registries.remove(CRATES_IO_REGISTRY).unwrap_or_default();
68            source
69                .registry
70                .get_or_insert_with(|| CRATES_IO_INDEX.to_string());
71            source
72        }
73        Some(r) => registries
74            .remove(r)
75            .with_context(|| anyhow::format_err!("The registry '{}' could not be found", r))?,
76    };
77
78    // search this linked list and find the tail
79    while let Some(replace_with) = &source.replace_with {
80        let is_crates_io = replace_with == CRATES_IO_INDEX;
81        source = registries.remove(replace_with).with_context(|| {
82            anyhow::format_err!("The source '{}' could not be found", replace_with)
83        })?;
84        if is_crates_io {
85            source
86                .registry
87                .get_or_insert_with(|| CRATES_IO_INDEX.to_string());
88        }
89    }
90
91    let registry_url = source
92        .registry
93        .and_then(|x| Url::parse(&x).ok())
94        .with_context(invalid_cargo_config)?;
95
96    Ok(registry_url)
97}
98
99#[derive(Debug, Deserialize)]
100struct CargoConfig {
101    #[serde(default)]
102    registries: HashMap<String, Registry>,
103    #[serde(default)]
104    source: HashMap<String, Source>,
105}
106
107#[derive(Default, Debug, Deserialize)]
108struct Source {
109    #[serde(rename = "replace-with")]
110    replace_with: Option<String>,
111    registry: Option<String>,
112}
113
114#[derive(Debug, Deserialize)]
115struct Registry {
116    index: Option<String>,
117}
118
119fn cargo_home() -> CargoResult<PathBuf> {
120    let default_cargo_home = dirs_next::home_dir()
121        .map(|x| x.join(".cargo"))
122        .with_context(|| anyhow::format_err!("Failed to read home directory"))?;
123    let cargo_home = std::env::var("CARGO_HOME")
124        .map(PathBuf::from)
125        .unwrap_or(default_cargo_home);
126    Ok(cargo_home)
127}
128
129mod code_from_cargo {
130    #![allow(dead_code)]
131
132    #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
133    pub enum Kind {
134        Git(GitReference),
135        Path,
136        Registry,
137        LocalRegistry,
138        Directory,
139    }
140
141    #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
142    pub enum GitReference {
143        Tag(String),
144        Branch(String),
145        Rev(String),
146        DefaultBranch,
147    }
148}