Skip to content

Commit

Permalink
fix(config): Don't merge unmergable config
Browse files Browse the repository at this point in the history
  • Loading branch information
arlosi committed Jan 13, 2025
1 parent 265e24e commit d60db46
Show file tree
Hide file tree
Showing 5 changed files with 62 additions and 42 deletions.
16 changes: 6 additions & 10 deletions src/cargo/util/context/de.rs
Original file line number Diff line number Diff line change
Expand Up @@ -154,17 +154,13 @@ impl<'de, 'gctx> de::Deserializer<'de> for Deserializer<'gctx> {
where
V: de::Visitor<'de>,
{
let merge = if name == "StringList" {
true
} else if name == "UnmergedStringList" {
false
if name == "StringList" {
let vals = self.gctx.get_list_or_string(&self.key)?;
let vals: Vec<String> = vals.into_iter().map(|vd| vd.0).collect();
visitor.visit_newtype_struct(vals.into_deserializer())
} else {
return visitor.visit_newtype_struct(self);
};

let vals = self.gctx.get_list_or_string(&self.key, merge)?;
let vals: Vec<String> = vals.into_iter().map(|vd| vd.0).collect();
visitor.visit_newtype_struct(vals.into_deserializer())
visitor.visit_newtype_struct(self)
}
}

fn deserialize_enum<V>(
Expand Down
10 changes: 10 additions & 0 deletions src/cargo/util/context/key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,16 @@ impl ConfigKey {
pub fn is_root(&self) -> bool {
self.parts.is_empty()
}

/// Returns whether or not the given key string matches this key.
/// Use * to match any key part.
pub fn matches(&self, pattern: &str) -> bool {
let mut parts = self.parts();
pattern
.split('.')
.all(|pat| parts.next() == Some(pat) || pat == "*")
&& parts.next().is_none()
}
}

impl fmt::Display for ConfigKey {
Expand Down
70 changes: 42 additions & 28 deletions src/cargo/util/context/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -736,7 +736,7 @@ impl GlobalContext {
Ok(Some(CV::List(cv_list, cv_def)))
}
Some(cv) => {
// This can't assume StringList or UnmergedStringList.
// This can't assume StringList.
// Return an error, which is the behavior of merging
// multiple config.toml files with the same scenario.
bail!(
Expand Down Expand Up @@ -910,21 +910,9 @@ impl GlobalContext {
}

/// Helper for `StringList` type to get something that is a string or list.
fn get_list_or_string(
&self,
key: &ConfigKey,
merge: bool,
) -> CargoResult<Vec<(String, Definition)>> {
fn get_list_or_string(&self, key: &ConfigKey) -> CargoResult<Vec<(String, Definition)>> {
let mut res = Vec::new();

if !merge {
self.get_env_list(key, &mut res)?;

if !res.is_empty() {
return Ok(res);
}
}

match self.get_cv(key)? {
Some(CV::List(val, _def)) => res.extend(val),
Some(CV::String(val, def)) => {
Expand All @@ -943,6 +931,7 @@ impl GlobalContext {
}

/// Internal method for getting an environment variable as a list.
/// If the key is a non-mergable list and a value is found in the environment, existing values are cleared.
fn get_env_list(
&self,
key: &ConfigKey,
Expand All @@ -953,6 +942,10 @@ impl GlobalContext {
return Ok(());
};

if is_nonmergable_list(&key) {
output.clear();
}

let def = Definition::Environment(key.as_env_key().to_string());
if self.cli_unstable().advanced_env && env_val.starts_with('[') && env_val.ends_with(']') {
// Parse an environment string as a TOML array.
Expand Down Expand Up @@ -2227,13 +2220,31 @@ impl ConfigValue {
///
/// Container and non-container types cannot be mixed.
fn merge(&mut self, from: ConfigValue, force: bool) -> CargoResult<()> {
self.merge_helper(from, force, &mut ConfigKey::new())
}

fn merge_helper(
&mut self,
from: ConfigValue,
force: bool,
parts: &mut ConfigKey,
) -> CargoResult<()> {
let is_higher_priority = from.definition().is_higher_priority(self.definition());
match (self, from) {
(&mut CV::List(ref mut old, _), CV::List(ref mut new, _)) => {
if force {
old.append(new);
if is_nonmergable_list(&parts) {
// Use whichever list is higher priority.
if force || is_higher_priority {
mem::swap(new, old);
}
} else {
new.append(old);
mem::swap(new, old);
// Merge the lists together.
if force {
old.append(new);
} else {
new.append(old);
mem::swap(new, old);
}
}
old.sort_by(|a, b| a.1.cmp(&b.1));
}
Expand All @@ -2243,7 +2254,8 @@ impl ConfigValue {
Occupied(mut entry) => {
let new_def = value.definition().clone();
let entry = entry.get_mut();
entry.merge(value, force).with_context(|| {
parts.push(&key);
entry.merge_helper(value, force, parts).with_context(|| {
format!(
"failed to merge key `{}` between \
{} and {}",
Expand Down Expand Up @@ -2273,7 +2285,7 @@ impl ConfigValue {
));
}
(old, mut new) => {
if force || new.definition().is_higher_priority(old.definition()) {
if force || is_higher_priority {
mem::swap(old, &mut new);
}
}
Expand Down Expand Up @@ -2348,6 +2360,16 @@ impl ConfigValue {
}
}

/// List of which configuration lists cannot be merged.
/// Instead of merging, these the higher priority list replaces the lower priority list.
fn is_nonmergable_list(key: &ConfigKey) -> bool {
key.matches("registries.*.credential-provider")
|| key.matches("target.*.runner")
|| key.matches("host.runner")
|| key.matches("credential-alias.*")
|| key.matches("doc.browser")
}

pub fn homedir(cwd: &Path) -> Option<PathBuf> {
::home::cargo_home_with_cwd(cwd).ok()
}
Expand Down Expand Up @@ -2916,14 +2938,6 @@ impl StringList {
}
}

/// Alternative to [`StringList`] that follows precedence rules, rather than merging config values with environment values,
///
/// e.g. a string list found in the environment will be used instead of one in a config file.
///
/// This is currently only used by [`PathAndArgs`]
#[derive(Debug, Deserialize)]
pub struct UnmergedStringList(Vec<String>);

#[macro_export]
macro_rules! __shell_print {
($config:expr, $which:ident, $newline:literal, $($arg:tt)*) => ({
Expand Down
4 changes: 2 additions & 2 deletions src/cargo/util/context/path.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use super::{GlobalContext, UnmergedStringList, Value};
use super::{GlobalContext, StringList, Value};
use serde::{de::Error, Deserialize};
use std::path::PathBuf;

Expand Down Expand Up @@ -64,7 +64,7 @@ impl<'de> serde::Deserialize<'de> for PathAndArgs {
where
D: serde::Deserializer<'de>,
{
let vsl = Value::<UnmergedStringList>::deserialize(deserializer)?;
let vsl = Value::<StringList>::deserialize(deserializer)?;
let mut strings = vsl.val.0;
if strings.is_empty() {
return Err(D::Error::invalid_length(0, &"at least one element"));
Expand Down
4 changes: 2 additions & 2 deletions tests/testsuite/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2192,6 +2192,6 @@ credential-provider = ['c', 'd']
.unwrap()
.credential_provider
.unwrap();
assert_eq!(provider.path.raw_value(), "a");
assert_eq!(provider.args, ["b", "c", "d"]);
assert_eq!(provider.path.raw_value(), "c");
assert_eq!(provider.args, ["d"]);
}

0 comments on commit d60db46

Please sign in to comment.