Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
129 changes: 85 additions & 44 deletions src/cargo/util/context/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1257,11 +1257,15 @@ impl GlobalContext {
output: &mut Vec<CV>,
) -> CargoResult<()> {
let includes = self.include_paths(cv, false)?;
for (path, abs_path, def) in includes {
for include in includes {
let mut cv = self
._load_file(&abs_path, seen, false, WhyLoad::FileDiscovery)
._load_file(&include.abs_path(self), seen, false, WhyLoad::FileDiscovery)
.with_context(|| {
format!("failed to load config include `{}` from `{}`", path, def)
format!(
"failed to load config include `{}` from `{}`",
include.path.display(),
include.def
)
})?;
self.load_unmerged_include(&mut cv, seen, output)?;
output.push(cv);
Expand Down Expand Up @@ -1364,58 +1368,59 @@ impl GlobalContext {
}
// Accumulate all values here.
let mut root = CV::Table(HashMap::new(), value.definition().clone());
for (path, abs_path, def) in includes {
self._load_file(&abs_path, seen, true, why_load)
for include in includes {
self._load_file(&include.abs_path(self), seen, true, why_load)
.and_then(|include| root.merge(include, true))
.with_context(|| {
format!("failed to load config include `{}` from `{}`", path, def)
format!(
"failed to load config include `{}` from `{}`",
include.path.display(),
include.def
)
})?;
}
root.merge(value, true)?;
Ok(root)
}

/// Converts the `include` config value to a list of absolute paths.
fn include_paths(
&self,
cv: &mut CV,
remove: bool,
) -> CargoResult<Vec<(String, PathBuf, Definition)>> {
let abs = |path: &str, def: &Definition| -> (String, PathBuf, Definition) {
let abs_path = match def {
Definition::Path(p) | Definition::Cli(Some(p)) => p.parent().unwrap().join(&path),
Definition::Environment(_) | Definition::Cli(None) | Definition::BuiltIn => {
self.cwd().join(&path)
}
};
(path.to_string(), abs_path, def.clone())
};
fn include_paths(&self, cv: &mut CV, remove: bool) -> CargoResult<Vec<ConfigInclude>> {
let CV::Table(table, _def) = cv else {
unreachable!()
};
let owned;
let include = if remove {
owned = table.remove("include");
owned.as_ref()
table.remove("include").map(Cow::Owned)
} else {
table.get("include")
table.get("include").map(Cow::Borrowed)
};
let includes = match include {
Some(CV::String(s, def)) => {
vec![abs(s, def)]
}
let includes = match include.map(|c| c.into_owned()) {
Some(CV::String(s, def)) => vec![ConfigInclude::new(s, def)],
Some(CV::List(list, _def)) => list
.iter()
.map(|cv| match cv {
CV::String(s, def) => Ok(abs(s, def)),
.into_iter()
.enumerate()
.map(|(idx, cv)| match cv {
CV::String(s, def) => Ok(ConfigInclude::new(s, def)),
CV::Table(mut table, def) => {
// Extract `include.path`
let s = match table.remove("path") {
Some(CV::String(s, _)) => s,
Some(other) => bail!(
"expected a string, but found {} at `include[{idx}].path` in `{def}`",
other.desc()
),
None => bail!("missing field `path` at `include[{idx}]` in `{def}`"),
};
Ok(ConfigInclude::new(s, def))
}
other => bail!(
"`include` expected a string or list of strings, but found {} in list",
other.desc()
"expected a string or table, but found {} at `include[{idx}]` in {}",
other.desc(),
other.definition(),
),
})
.collect::<CargoResult<Vec<_>>>()?,
Some(other) => bail!(
"`include` expected a string or list, but found {} in `{}`",
"expected a string or list of strings, but found {} at `include` in `{}",
other.desc(),
other.definition()
),
Expand All @@ -1424,11 +1429,13 @@ impl GlobalContext {
}
};

for (path, abs_path, def) in &includes {
if abs_path.extension() != Some(OsStr::new("toml")) {
for include in &includes {
if include.path.extension() != Some(OsStr::new("toml")) {
bail!(
"expected a config include path ending with `.toml`, \
but found `{path}` from `{def}`",
but found `{}` from `{}`",
include.path.display(),
include.def,
)
}
}
Expand All @@ -1447,14 +1454,10 @@ impl GlobalContext {
let arg_as_path = self.cwd.join(arg);
let tmp_table = if !arg.is_empty() && arg_as_path.exists() {
// --config path_to_file
let str_path = arg_as_path
.to_str()
.ok_or_else(|| {
anyhow::format_err!("config path {:?} is not utf-8", arg_as_path)
self._load_file(&arg_as_path, &mut seen, true, WhyLoad::Cli)
.with_context(|| {
format!("failed to load config from `{}`", arg_as_path.display())
})?
.to_string();
self._load_file(&self.cwd().join(&str_path), &mut seen, true, WhyLoad::Cli)
.with_context(|| format!("failed to load config from `{}`", str_path))?
} else {
let doc = toml_dotted_keys(arg)?;
let doc: toml::Value = toml::Value::deserialize(doc.into_deserializer())
Expand Down Expand Up @@ -2482,6 +2485,44 @@ pub fn save_credentials(
}
}

/// Represents a config-include value in the configuration.
///
/// This intentionally doesn't derive serde deserialization
/// to avoid any misuse of `GlobalContext::get::<ConfigInclude>()`,
/// which might lead to wrong config loading order.
struct ConfigInclude {
/// Path to a config-include configuration file.
/// Could be either relative or absolute.
path: PathBuf,
def: Definition,
}

impl ConfigInclude {
fn new(p: impl Into<PathBuf>, def: Definition) -> Self {
Self {
path: p.into(),
def,
}
}

/// Gets the absolute path of the config-include config file.
///
/// For file based include,
/// it is relative to parent directory of the config file includes it.
/// For example, if `.cargo/config.toml has a `include = "foo.toml"`,
/// Cargo will load `.cargo/foo.toml`.
///
/// For CLI based include (e.g., `--config 'include = "foo.toml"'`),
/// it is relative to the current working directory.
fn abs_path(&self, gctx: &GlobalContext) -> PathBuf {
match &self.def {
Definition::Path(p) | Definition::Cli(Some(p)) => p.parent().unwrap(),
Definition::Environment(_) | Definition::Cli(None) | Definition::BuiltIn => gctx.cwd(),
}
.join(&self.path)
}
}

#[derive(Debug, Default, Deserialize, PartialEq)]
#[serde(rename_all = "kebab-case")]
pub struct CargoHttpConfig {
Expand Down
Loading