Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'crates/project-model/src/cargo_config_file.rs')
-rw-r--r--crates/project-model/src/cargo_config_file.rs227
1 files changed, 198 insertions, 29 deletions
diff --git a/crates/project-model/src/cargo_config_file.rs b/crates/project-model/src/cargo_config_file.rs
index a1e7ed0923..5d6e5fd648 100644
--- a/crates/project-model/src/cargo_config_file.rs
+++ b/crates/project-model/src/cargo_config_file.rs
@@ -1,37 +1,135 @@
-//! Read `.cargo/config.toml` as a JSON object
-use paths::{Utf8Path, Utf8PathBuf};
+//! Read `.cargo/config.toml` as a TOML table
+use paths::{AbsPath, Utf8Path, Utf8PathBuf};
use rustc_hash::FxHashMap;
+use toml::{
+ Spanned,
+ de::{DeTable, DeValue},
+};
use toolchain::Tool;
use crate::{ManifestPath, Sysroot, utf8_stdout};
-pub(crate) type CargoConfigFile = serde_json::Map<String, serde_json::Value>;
-
-pub(crate) fn read(
- manifest: &ManifestPath,
- extra_env: &FxHashMap<String, Option<String>>,
- sysroot: &Sysroot,
-) -> Option<CargoConfigFile> {
- let mut cargo_config = sysroot.tool(Tool::Cargo, manifest.parent(), extra_env);
- cargo_config
- .args(["-Z", "unstable-options", "config", "get", "--format", "json"])
- .env("RUSTC_BOOTSTRAP", "1");
- if manifest.is_rust_manifest() {
- cargo_config.arg("-Zscript");
- }
-
- tracing::debug!("Discovering cargo config by {:?}", cargo_config);
- let json: serde_json::Map<String, serde_json::Value> = utf8_stdout(&mut cargo_config)
- .inspect(|json| {
- tracing::debug!("Discovered cargo config: {:?}", json);
- })
- .inspect_err(|err| {
- tracing::debug!("Failed to discover cargo config: {:?}", err);
- })
- .ok()
- .and_then(|stdout| serde_json::from_str(&stdout).ok())?;
-
- Some(json)
+#[derive(Clone)]
+pub struct CargoConfigFile(String);
+
+impl CargoConfigFile {
+ pub(crate) fn load(
+ manifest: &ManifestPath,
+ extra_env: &FxHashMap<String, Option<String>>,
+ sysroot: &Sysroot,
+ ) -> Option<Self> {
+ let mut cargo_config = sysroot.tool(Tool::Cargo, manifest.parent(), extra_env);
+ cargo_config
+ .args(["-Z", "unstable-options", "config", "get", "--format", "toml", "--show-origin"])
+ .env("RUSTC_BOOTSTRAP", "1");
+ if manifest.is_rust_manifest() {
+ cargo_config.arg("-Zscript");
+ }
+
+ tracing::debug!("Discovering cargo config by {cargo_config:?}");
+ utf8_stdout(&mut cargo_config)
+ .inspect(|toml| {
+ tracing::debug!("Discovered cargo config: {toml:?}");
+ })
+ .inspect_err(|err| {
+ tracing::debug!("Failed to discover cargo config: {err:?}");
+ })
+ .ok()
+ .map(CargoConfigFile)
+ }
+
+ pub(crate) fn read<'a>(&'a self) -> Option<CargoConfigFileReader<'a>> {
+ CargoConfigFileReader::new(&self.0)
+ }
+
+ #[cfg(test)]
+ pub(crate) fn from_string_for_test(s: String) -> Self {
+ CargoConfigFile(s)
+ }
+}
+
+pub(crate) struct CargoConfigFileReader<'a> {
+ toml_str: &'a str,
+ line_ends: Vec<usize>,
+ table: Spanned<DeTable<'a>>,
+}
+
+impl<'a> CargoConfigFileReader<'a> {
+ fn new(toml_str: &'a str) -> Option<Self> {
+ let toml = DeTable::parse(toml_str)
+ .inspect_err(|err| tracing::debug!("Failed to parse cargo config into toml: {err:?}"))
+ .ok()?;
+ let mut last_line_end = 0;
+ let line_ends = toml_str
+ .lines()
+ .map(|l| {
+ last_line_end += l.len() + 1;
+ last_line_end
+ })
+ .collect();
+
+ Some(CargoConfigFileReader { toml_str, table: toml, line_ends })
+ }
+
+ pub(crate) fn get_spanned(
+ &self,
+ accessor: impl IntoIterator<Item = &'a str>,
+ ) -> Option<&Spanned<DeValue<'a>>> {
+ let mut keys = accessor.into_iter();
+ let mut val = self.table.get_ref().get(keys.next()?)?;
+ for key in keys {
+ let DeValue::Table(map) = val.get_ref() else { return None };
+ val = map.get(key)?;
+ }
+ Some(val)
+ }
+
+ pub(crate) fn get(&self, accessor: impl IntoIterator<Item = &'a str>) -> Option<&DeValue<'a>> {
+ self.get_spanned(accessor).map(|it| it.as_ref())
+ }
+
+ pub(crate) fn get_origin_root(&self, spanned: &Spanned<DeValue<'a>>) -> Option<&AbsPath> {
+ let span = spanned.span();
+
+ for &line_end in &self.line_ends {
+ if line_end < span.end {
+ continue;
+ }
+
+ let after_span = &self.toml_str[span.end..line_end];
+
+ // table.key = "value" # /parent/.cargo/config.toml
+ // | |
+ // span.end line_end
+ let origin_path = after_span
+ .strip_prefix([',']) // strip trailing comma
+ .unwrap_or(after_span)
+ .trim_start()
+ .strip_prefix(['#'])
+ .and_then(|path| {
+ let path = path.trim();
+ if path.starts_with("environment variable")
+ || path.starts_with("--config cli option")
+ {
+ None
+ } else {
+ Some(path)
+ }
+ });
+
+ return origin_path.and_then(|path| {
+ <&Utf8Path>::from(path)
+ .try_into()
+ .ok()
+ // Two levels up to the config file.
+ // See https://doc.rust-lang.org/cargo/reference/config.html#config-relative-paths
+ .and_then(AbsPath::parent)
+ .and_then(AbsPath::parent)
+ });
+ }
+
+ None
+ }
}
pub(crate) fn make_lockfile_copy(
@@ -54,3 +152,74 @@ pub(crate) fn make_lockfile_copy(
}
}
}
+
+#[test]
+fn cargo_config_file_reader_works() {
+ #[cfg(target_os = "windows")]
+ let root = "C://ROOT";
+
+ #[cfg(not(target_os = "windows"))]
+ let root = "/ROOT";
+
+ let toml = format!(
+ r##"
+alias.foo = "abc"
+alias.bar = "🙂" # {root}/home/.cargo/config.toml
+alias.sub-example = [
+ "sub", # {root}/foo/.cargo/config.toml
+ "example", # {root}/❤️💛💙/💝/.cargo/config.toml
+]
+build.rustflags = [
+ "--flag", # {root}/home/.cargo/config.toml
+ "env", # environment variable `CARGO_BUILD_RUSTFLAGS`
+ "cli", # --config cli option
+]
+env.CARGO_WORKSPACE_DIR.relative = true # {root}/home/.cargo/config.toml
+env.CARGO_WORKSPACE_DIR.value = "" # {root}/home/.cargo/config.toml
+"##
+ );
+
+ let reader = CargoConfigFileReader::new(&toml).unwrap();
+
+ let alias_foo = reader.get_spanned(["alias", "foo"]).unwrap();
+ assert_eq!(alias_foo.as_ref().as_str().unwrap(), "abc");
+ assert!(reader.get_origin_root(alias_foo).is_none());
+
+ let alias_bar = reader.get_spanned(["alias", "bar"]).unwrap();
+ assert_eq!(alias_bar.as_ref().as_str().unwrap(), "🙂");
+ assert_eq!(reader.get_origin_root(alias_bar).unwrap().as_str(), format!("{root}/home"));
+
+ let alias_sub_example = reader.get_spanned(["alias", "sub-example"]).unwrap();
+ assert!(reader.get_origin_root(alias_sub_example).is_none());
+ let alias_sub_example = alias_sub_example.as_ref().as_array().unwrap();
+
+ assert_eq!(alias_sub_example[0].get_ref().as_str().unwrap(), "sub");
+ assert_eq!(
+ reader.get_origin_root(&alias_sub_example[0]).unwrap().as_str(),
+ format!("{root}/foo")
+ );
+
+ assert_eq!(alias_sub_example[1].get_ref().as_str().unwrap(), "example");
+ assert_eq!(
+ reader.get_origin_root(&alias_sub_example[1]).unwrap().as_str(),
+ format!("{root}/❤️💛💙/💝")
+ );
+
+ let build_rustflags = reader.get(["build", "rustflags"]).unwrap().as_array().unwrap();
+ assert_eq!(
+ reader.get_origin_root(&build_rustflags[0]).unwrap().as_str(),
+ format!("{root}/home")
+ );
+ assert!(reader.get_origin_root(&build_rustflags[1]).is_none());
+ assert!(reader.get_origin_root(&build_rustflags[2]).is_none());
+
+ let env_cargo_workspace_dir =
+ reader.get(["env", "CARGO_WORKSPACE_DIR"]).unwrap().as_table().unwrap();
+ let env_relative = &env_cargo_workspace_dir["relative"];
+ assert!(env_relative.as_ref().as_bool().unwrap());
+ assert_eq!(reader.get_origin_root(env_relative).unwrap().as_str(), format!("{root}/home"));
+
+ let env_val = &env_cargo_workspace_dir["value"];
+ assert_eq!(env_val.as_ref().as_str().unwrap(), "");
+ assert_eq!(reader.get_origin_root(env_val).unwrap().as_str(), format!("{root}/home"));
+}