xref: /aosp_15_r20/external/bazelbuild-rules_rust/crate_universe/src/splicing/cargo_config.rs (revision d4726bddaa87cc4778e7472feed243fa4b6c267f)
1 //! Tools for parsing [Cargo configuration](https://doc.rust-lang.org/cargo/reference/config.html) files
2 
3 use std::collections::BTreeMap;
4 use std::fs;
5 use std::path::Path;
6 use std::str::FromStr;
7 
8 use crate::utils;
9 use anyhow::{bail, Result};
10 use serde::{Deserialize, Serialize};
11 
12 /// The [`[registry]`](https://doc.rust-lang.org/cargo/reference/config.html#registry)
13 /// table controls the default registry used when one is not specified.
14 #[derive(Debug, Deserialize, Serialize, PartialEq, Eq)]
15 pub(crate) struct Registry {
16     /// name of the default registry
17     pub(crate) default: String,
18 
19     /// authentication token for crates.io
20     pub(crate) token: Option<String>,
21 }
22 
23 /// The [`[source]`](https://doc.rust-lang.org/cargo/reference/config.html#source)
24 /// table defines the registry sources available.
25 #[derive(Debug, Deserialize, Serialize, PartialEq, Eq)]
26 pub(crate) struct Source {
27     /// replace this source with the given named source
28     #[serde(rename = "replace-with")]
29     pub(crate) replace_with: Option<String>,
30 
31     /// URL to a registry source
32     #[serde(default = "default_registry_url")]
33     pub(crate) registry: String,
34 }
35 
36 /// This is the default registry url per what's defined by Cargo.
default_registry_url() -> String37 fn default_registry_url() -> String {
38     utils::CRATES_IO_INDEX_URL.to_owned()
39 }
40 
41 #[derive(Debug, Deserialize, Serialize, PartialEq, Eq)]
42 /// registries other than crates.io
43 pub(crate) struct AdditionalRegistry {
44     /// URL of the registry index
45     pub(crate) index: String,
46 
47     /// authentication token for the registry
48     pub(crate) token: Option<String>,
49 }
50 
51 /// A subset of a Cargo configuration file. The schema here is only what
52 /// is required for parsing registry information.
53 /// See [cargo docs](https://doc.rust-lang.org/cargo/reference/config.html#configuration-format)
54 /// for more details.
55 #[derive(Debug, Deserialize, Serialize, PartialEq, Eq)]
56 pub(crate) struct CargoConfig {
57     /// registries other than crates.io
58     #[serde(default = "default_registries")]
59     pub(crate) registries: BTreeMap<String, AdditionalRegistry>,
60 
61     #[serde(default = "default_registry")]
62     pub(crate) registry: Registry,
63 
64     /// source definition and replacement
65     #[serde(default = "BTreeMap::new")]
66     pub(crate) source: BTreeMap<String, Source>,
67 }
68 
69 /// Each Cargo config is expected to have a default `crates-io` registry.
default_registries() -> BTreeMap<String, AdditionalRegistry>70 fn default_registries() -> BTreeMap<String, AdditionalRegistry> {
71     let mut registries = BTreeMap::new();
72     registries.insert(
73         "crates-io".to_owned(),
74         AdditionalRegistry {
75             index: default_registry_url(),
76             token: None,
77         },
78     );
79     registries
80 }
81 
82 /// Each Cargo config has a default registry for `crates.io`.
default_registry() -> Registry83 fn default_registry() -> Registry {
84     Registry {
85         default: "crates-io".to_owned(),
86         token: None,
87     }
88 }
89 
90 impl Default for CargoConfig {
default() -> Self91     fn default() -> Self {
92         let registries = default_registries();
93         let registry = default_registry();
94         let source = Default::default();
95 
96         Self {
97             registries,
98             registry,
99             source,
100         }
101     }
102 }
103 
104 impl FromStr for CargoConfig {
105     type Err = anyhow::Error;
106 
from_str(s: &str) -> Result<Self, Self::Err>107     fn from_str(s: &str) -> Result<Self, Self::Err> {
108         let incoming: CargoConfig = toml::from_str(s)?;
109         let mut config = Self::default();
110         config.registries.extend(incoming.registries);
111         config.source.extend(incoming.source);
112         config.registry = incoming.registry;
113         Ok(config)
114     }
115 }
116 
117 impl CargoConfig {
118     /// Load a Cargo config from a path to a file on disk.
try_from_path(path: &Path) -> Result<Self>119     pub(crate) fn try_from_path(path: &Path) -> Result<Self> {
120         let content = fs::read_to_string(path)?;
121         Self::from_str(&content)
122     }
123 
124     /// Look up a registry [Source] by its url.
get_source_from_url(&self, url: &str) -> Option<&Source>125     pub(crate) fn get_source_from_url(&self, url: &str) -> Option<&Source> {
126         if let Some(found) = self.source.values().find(|v| v.registry == url) {
127             Some(found)
128         } else if url == utils::CRATES_IO_INDEX_URL {
129             self.source.get("crates-io")
130         } else {
131             None
132         }
133     }
134 
get_registry_index_url_by_name(&self, name: &str) -> Option<&str>135     pub(crate) fn get_registry_index_url_by_name(&self, name: &str) -> Option<&str> {
136         if let Some(registry) = self.registries.get(name) {
137             Some(&registry.index)
138         } else if let Some(source) = self.source.get(name) {
139             Some(&source.registry)
140         } else {
141             None
142         }
143     }
144 
resolve_replacement_url<'a>(&'a self, url: &'a str) -> Result<&'a str>145     pub(crate) fn resolve_replacement_url<'a>(&'a self, url: &'a str) -> Result<&'a str> {
146         if let Some(source) = self.get_source_from_url(url) {
147             if let Some(replace_with) = &source.replace_with {
148                 if let Some(replacement) = self.get_registry_index_url_by_name(replace_with) {
149                     Ok(replacement)
150                 } else {
151                     bail!("Tried to replace registry {} with registry named {} but didn't have metadata about the replacement", url, replace_with);
152                 }
153             } else {
154                 Ok(url)
155             }
156         } else {
157             Ok(url)
158         }
159     }
160 }
161 
162 #[cfg(test)]
163 mod test {
164     use super::*;
165 
166     #[test]
registry_settings()167     fn registry_settings() {
168         let temp_dir = tempfile::tempdir().unwrap();
169         let config = temp_dir.as_ref().join("config.toml");
170 
171         fs::write(&config, textwrap::dedent(
172             r#"
173                 # Makes artifactory the default registry and saves passing --registry parameter
174                 [registry]
175                 default = "art-crates-remote"
176 
177                 [registries]
178                 # Remote repository proxy in Artifactory (read-only)
179                 art-crates-remote = { index = "https://artprod.mycompany/artifactory/git/cargo-remote.git" }
180 
181                 # Optional, use with --registry to publish to crates.io
182                 crates-io = { index = "https://github.com/rust-lang/crates.io-index" }
183 
184                 [net]
185                 git-fetch-with-cli = true
186             "#,
187         )).unwrap();
188 
189         let config = CargoConfig::try_from_path(&config).unwrap();
190         assert_eq!(
191             config,
192             CargoConfig {
193                 registries: BTreeMap::from([
194                     (
195                         "art-crates-remote".to_owned(),
196                         AdditionalRegistry {
197                             index: "https://artprod.mycompany/artifactory/git/cargo-remote.git"
198                                 .to_owned(),
199                             token: None,
200                         },
201                     ),
202                     (
203                         "crates-io".to_owned(),
204                         AdditionalRegistry {
205                             index: "https://github.com/rust-lang/crates.io-index".to_owned(),
206                             token: None,
207                         },
208                     ),
209                 ]),
210                 registry: Registry {
211                     default: "art-crates-remote".to_owned(),
212                     token: None,
213                 },
214                 source: BTreeMap::new(),
215             },
216         )
217     }
218 
219     #[test]
registry_settings_get_index_url_by_name_from_source()220     fn registry_settings_get_index_url_by_name_from_source() {
221         let temp_dir = tempfile::tempdir().unwrap();
222         let config = temp_dir.as_ref().join("config.toml");
223 
224         fs::write(&config, textwrap::dedent(
225             r#"
226                 [registries]
227                 art-crates-remote = { index = "https://artprod.mycompany/artifactory/git/cargo-remote.git" }
228 
229                 [source.crates-io]
230                 replace-with = "some-mirror"
231 
232                 [source.some-mirror]
233                 registry = "https://artmirror.mycompany/artifactory/cargo-mirror.git"
234             "#,
235         )).unwrap();
236 
237         let config = CargoConfig::try_from_path(&config).unwrap();
238         assert_eq!(
239             config.get_registry_index_url_by_name("some-mirror"),
240             Some("https://artmirror.mycompany/artifactory/cargo-mirror.git"),
241         );
242     }
243 
244     #[test]
registry_settings_get_index_url_by_name_from_registry()245     fn registry_settings_get_index_url_by_name_from_registry() {
246         let temp_dir = tempfile::tempdir().unwrap();
247         let config = temp_dir.as_ref().join("config.toml");
248 
249         fs::write(&config, textwrap::dedent(
250             r#"
251                 [registries]
252                 art-crates-remote = { index = "https://artprod.mycompany/artifactory/git/cargo-remote.git" }
253 
254                 [source.crates-io]
255                 replace-with = "art-crates-remote"
256             "#,
257         )).unwrap();
258 
259         let config = CargoConfig::try_from_path(&config).unwrap();
260         assert_eq!(
261             config.get_registry_index_url_by_name("art-crates-remote"),
262             Some("https://artprod.mycompany/artifactory/git/cargo-remote.git"),
263         );
264     }
265 
266     #[test]
registry_settings_get_source_from_url()267     fn registry_settings_get_source_from_url() {
268         let temp_dir = tempfile::tempdir().unwrap();
269         let config = temp_dir.as_ref().join("config.toml");
270 
271         fs::write(
272             &config,
273             textwrap::dedent(
274                 r#"
275                 [source.some-mirror]
276                 registry = "https://artmirror.mycompany/artifactory/cargo-mirror.git"
277             "#,
278             ),
279         )
280         .unwrap();
281 
282         let config = CargoConfig::try_from_path(&config).unwrap();
283         assert_eq!(
284             config
285                 .get_source_from_url("https://artmirror.mycompany/artifactory/cargo-mirror.git")
286                 .map(|s| s.registry.as_str()),
287             Some("https://artmirror.mycompany/artifactory/cargo-mirror.git"),
288         );
289     }
290 
291     #[test]
resolve_replacement_url_no_replacement()292     fn resolve_replacement_url_no_replacement() {
293         let temp_dir = tempfile::tempdir().unwrap();
294         let config = temp_dir.as_ref().join("config.toml");
295 
296         fs::write(&config, "").unwrap();
297 
298         let config = CargoConfig::try_from_path(&config).unwrap();
299 
300         assert_eq!(
301             config
302                 .resolve_replacement_url(utils::CRATES_IO_INDEX_URL)
303                 .unwrap(),
304             utils::CRATES_IO_INDEX_URL
305         );
306         assert_eq!(
307             config
308                 .resolve_replacement_url("https://artmirror.mycompany/artifactory/cargo-mirror.git")
309                 .unwrap(),
310             "https://artmirror.mycompany/artifactory/cargo-mirror.git"
311         );
312     }
313 
314     #[test]
resolve_replacement_url_registry()315     fn resolve_replacement_url_registry() {
316         let temp_dir = tempfile::tempdir().unwrap();
317         let config = temp_dir.as_ref().join("config.toml");
318 
319         fs::write(&config, textwrap::dedent(
320             r#"
321                 [registries]
322                 art-crates-remote = { index = "https://artprod.mycompany/artifactory/git/cargo-remote.git" }
323 
324                 [source.crates-io]
325                 replace-with = "some-mirror"
326 
327                 [source.some-mirror]
328                 registry = "https://artmirror.mycompany/artifactory/cargo-mirror.git"
329             "#,
330         )).unwrap();
331 
332         let config = CargoConfig::try_from_path(&config).unwrap();
333         assert_eq!(
334             config
335                 .resolve_replacement_url(utils::CRATES_IO_INDEX_URL)
336                 .unwrap(),
337             "https://artmirror.mycompany/artifactory/cargo-mirror.git"
338         );
339         assert_eq!(
340             config
341                 .resolve_replacement_url("https://artmirror.mycompany/artifactory/cargo-mirror.git")
342                 .unwrap(),
343             "https://artmirror.mycompany/artifactory/cargo-mirror.git"
344         );
345         assert_eq!(
346             config
347                 .resolve_replacement_url(
348                     "https://artprod.mycompany/artifactory/git/cargo-remote.git"
349                 )
350                 .unwrap(),
351             "https://artprod.mycompany/artifactory/git/cargo-remote.git"
352         );
353     }
354 
355     #[test]
resolve_replacement_url_source()356     fn resolve_replacement_url_source() {
357         let temp_dir = tempfile::tempdir().unwrap();
358         let config = temp_dir.as_ref().join("config.toml");
359 
360         fs::write(&config, textwrap::dedent(
361             r#"
362                 [registries]
363                 art-crates-remote = { index = "https://artprod.mycompany/artifactory/git/cargo-remote.git" }
364 
365                 [source.crates-io]
366                 replace-with = "art-crates-remote"
367 
368                 [source.some-mirror]
369                 registry = "https://artmirror.mycompany/artifactory/cargo-mirror.git"
370             "#,
371         )).unwrap();
372 
373         let config = CargoConfig::try_from_path(&config).unwrap();
374         assert_eq!(
375             config
376                 .resolve_replacement_url(utils::CRATES_IO_INDEX_URL)
377                 .unwrap(),
378             "https://artprod.mycompany/artifactory/git/cargo-remote.git"
379         );
380         assert_eq!(
381             config
382                 .resolve_replacement_url("https://artmirror.mycompany/artifactory/cargo-mirror.git")
383                 .unwrap(),
384             "https://artmirror.mycompany/artifactory/cargo-mirror.git"
385         );
386         assert_eq!(
387             config
388                 .resolve_replacement_url(
389                     "https://artprod.mycompany/artifactory/git/cargo-remote.git"
390                 )
391                 .unwrap(),
392             "https://artprod.mycompany/artifactory/git/cargo-remote.git"
393         );
394     }
395 }
396