Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Inline multiple config files into options sources. #20549

Merged
merged 7 commits into from
Feb 17, 2024

Conversation

benjyw
Copy link
Contributor

@benjyw benjyw commented Feb 14, 2024

Previously the options sources list could have only one source
per type (flag/env/config/default). Since we support multiple
config files, we had the concept of a "merged config" that
encompassed multiple "config sources". This led to
duplication of the logic that traverses and merges sources.

This change gets rid of the concept of merged config.
Instead, the list of options sources we consult can now
include multiple config files. This is achieved by enhancing
the Source::Config enum to include an ordinal (to ensure
that configs are applied in the right order) and the path to
the config file (purely informational).

The natural #[derive(Ord)] will order these augmented
configs appropriately within all the sources. To make this
easier to achieve we change the order of the enum to be
from lowest to highest priority, so that we can add config
files naturally in the order in which we encounter them.

This also allows us to simplify some types. In particular,
we no longer need to traffic in Vec<DictEdit> - a single
source can only return a single DictEdit.

More importantly, this supports derivation history that
includes the specific config file each value comes from.

@benjyw benjyw added the category:internal CI, fixes for not-yet-released features, etc. label Feb 14, 2024
@benjyw benjyw requested review from tgolsson and huonw February 14, 2024 23:15
@@ -216,133 +215,16 @@ fn toml_table_to_dict(table: &Value) -> HashMap<String, Val> {
}
}

#[derive(Clone)]
struct ConfigSource {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We get rid of ConfigSource entirely, and fold its functionality directly into Config below, since they are now one-to-one.

})
}

fn option_name(id: &OptionId) -> String {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved from ConfigSource

let mut edits: Vec<ListEdit<T>> = vec![];
for source in self.sources.iter() {
edits.append(&mut source.get_list(id, parse_list)?);
let mut list_edits = vec![];
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved from ConfigSource

@@ -55,49 +48,6 @@ fn test_display() {
);
}

#[test]
fn test_multiple_sources() {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Multiple configs is no longer a thing to be tested separately within config_tests.rs, it is amply tested along with multiple sources in general, in tests.rs.

@@ -185,16 +137,25 @@ fn test_interpolate_config() {
.unwrap()
);

// TODO: Uncomment when we implement get_dict.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should have done this before. It needed a little massaging after uncommenting.

}

#[derive(Copy, Clone, Debug, Ord, PartialOrd, Eq, PartialEq)]
#[derive(Clone, Debug, Ord, PartialOrd, Eq, PartialEq)]
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ord will do the right thing here: order by increasing declaration order, and within Config, lexicographically by field, hence ordinal.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't notice a test for the ord behaviour; have I missed it and/or is it worth adding one?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It falls out of the documentation for the generated Ord, but can't hurt to add one, done.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking the test would be catching if someone jumps in and changes the enum (eg sorts the variants alphabetically) they may not realise that the variant and field order is specifically meaningful, rather than any order being fine (and just being necessary to allow use in BTreeMap or similar).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, a test is a good idea, added one.

}
}

fn path_strip(prefix: &str, path: &str) -> String {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For paths under the buildroot, it seemed more useful to put the relative path to the buildroot in the derivation, rather than the full path. It also makes the tests simpler.

Note that we implement our own simply path manipulation here because we have Strings in hand, not OsString or Path.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One can construct paths from strs cheaply: https://doc.rust-lang.org/stable/std/path/struct.Path.html#method.new

Did you consider that as an option?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem is going back to a String from the underlying OsString, but for now I've done it with an aggressive unwrap(), and in the future the calling code should really be using OsString or Path anyway.

if let Some(val) = getter(source, id)? {
derivations.push((*source_type, val));
derivations.push((source_type.clone(), val));
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right now the derivations will contain many hundreds of copies of the string pants.toml or whatever. In a followup I might change this to a &str with a lifetime tied to the OptionsParser, so we can re-use references.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the lifetime is too annoying/constraining, an Rc<String> or Rc<str> (or Arc) might be options to consider too.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would have to be Arc because these get passed into Python code, but yeah, not a bad solution.

@benjyw
Copy link
Contributor Author

benjyw commented Feb 14, 2024

It's a non-trivial number of diff lines, but a big chunk of that is moving code from ConfigSource directly into Config, so it's less intimidating than it looks.

Copy link
Contributor

@huonw huonw left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool. Seems to be a nice conceptual simplification!


fn path_strip(prefix: &str, path: &str) -> String {
match path.strip_prefix(prefix) {
Some(suffix) => match suffix.strip_prefix(path::MAIN_SEPARATOR) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just confirming that this separator stripping should only apply if the prefix was removed? (That is, if the prefix remains, a trailing / should too?)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct, to ensure that the stripped dir is a relpath.

}

fn path_strip(prefix: &str, path: &str) -> String {
match path.strip_prefix(prefix) {
Copy link
Contributor

@huonw huonw Feb 15, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like this would strip off a prefix in the middle of a directory, like path = /abcdef, prefix = /abc => def. Is that desirable?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, yeah, that is not good.

I think I will switch to using Path utils.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Switched to using Path.

}
}

fn path_strip(prefix: &str, path: &str) -> String {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One can construct paths from strs cheaply: https://doc.rust-lang.org/stable/std/path/struct.Path.html#method.new

Did you consider that as an option?

if let Some(val) = getter(source, id)? {
derivations.push((*source_type, val));
derivations.push((source_type.clone(), val));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the lifetime is too annoying/constraining, an Rc<String> or Rc<str> (or Arc) might be options to consider too.

}

#[derive(Copy, Clone, Debug, Ord, PartialOrd, Eq, PartialEq)]
#[derive(Clone, Debug, Ord, PartialOrd, Eq, PartialEq)]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't notice a test for the ord behaviour; have I missed it and/or is it worth adding one?

@benjyw benjyw requested a review from huonw February 16, 2024 02:21
Copy link
Contributor

@huonw huonw left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great

@benjyw benjyw merged commit 3c940a9 into pantsbuild:main Feb 17, 2024
24 checks passed
@benjyw benjyw deleted the option_sources branch February 17, 2024 06:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
category:internal CI, fixes for not-yet-released features, etc.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants