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
9pub fn registry_url(manifest_path: &Path, registry: Option<&str>) -> CargoResult<Url> {
11 fn read_config(
13 registries: &mut HashMap<String, Source>,
14 path: impl AsRef<Path>,
15 ) -> CargoResult<()> {
16 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 let mut registries: HashMap<String, Source> = HashMap::new();
35 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 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 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}