Skip to content

Commit da60c64

Browse files
authored
Add .toml files to option "fromfile" support. (#23196)
As a reminder, the "fromfile" feature is where an option value `@path/to/file` is read from that file. If the file is .json or .yaml, we deserialize its content to a list or dict, as needed. This change adds support for .toml. A top-level TOML entity must be a table (unlike JSON/YAML which can have a top-level list). So we only support this for dict-valued options.
1 parent 9b3c156 commit da60c64

7 files changed

Lines changed: 47 additions & 10 deletions

File tree

docs/docs/using-pants/key-concepts/options.mdx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ Global options are set using the environment variable `PANTS_{OPTION_NAME}`:
4949
PANTS_LEVEL=debug pants ...
5050
```
5151

52-
Subsystem options are set using the environment variable
52+
Subsystem options are set using the environment variable
5353
`PANTS_{SCOPE}_{OPTION_NAME}`:
5454

5555
```bash
@@ -330,7 +330,7 @@ values directly in `pants.toml`, you can set the value of any option to the stri
330330
`@relative/path/from/repo/root/to/file` (note the leading `@`), and the value will be read
331331
from that file.
332332

333-
If the file name ends with `.json` or `.yaml` then the file will be parsed as the relevant
333+
If the file name ends with `.json`, `.yaml` or `.toml` then the file will be parsed as the relevant
334334
format, which is useful for list- and dict-valued options.
335335

336336
Otherwise, the file is parsed as a literal as described above for each option type.

docs/notes/2.32.x.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ The plugin API's `Get()` and `MultiGet()` constructs, deprecated in 2.30, are no
2222

2323
The Pants [Contribution Overview](https://www.pantsbuild.org/2.32/docs/contributions) now contains guidance on LLM use.
2424

25+
Dict-valued options can now [read their values](https://www.pantsbuild.org/2.32/docs/using-pants/key-concepts/options#reading-individual-option-values-from-files) from `.toml` files.
26+
2527
#### Internal Python Upgrade
2628

2729
The version of Python used by Pants itself has been updated to [3.14](https://docs.python.org/3/whatsnew/3.14.html). To support this, the [Pants Launcher Binary](https://www.pantsbuild.org/blog/2023/02/23/the-pants-launcher-binary-a-much-simpler-way-to-install-and-run-pants) (also known as [`scie-pants`](https://github.com/pantsbuild/scie-pants/)) now has a minimum version of `0.13.0`. To update to the latest launcher binary, either:

src/rust/Cargo.lock

Lines changed: 6 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/rust/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,7 @@ hyper-util = "0.1"
161161
ignore = "0.4.25"
162162
indexmap = { version = "2.13.0", features = ["std", "serde"] }
163163
indicatif = "0.18.4"
164+
indoc = "2.0.7"
164165
internment = "0.6"
165166
itertools = "0.14"
166167
libc = "0.2.182"

src/rust/options/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ whoami = { workspace = true }
2323
[dev-dependencies]
2424
maplit = { workspace = true }
2525
tempfile = { workspace = true }
26+
indoc = { workspace = true }
2627

2728
[lints]
2829
workspace = true

src/rust/options/src/fromfile.rs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use super::{BuildRoot, DictEdit, DictEditAction, ListEdit, ListEditAction};
55

66
use crate::parse::{ParseError, Parseable, mk_parse_err, parse_dict};
77
use log::warn;
8-
use serde::de::Deserialize;
8+
use serde::de::DeserializeOwned;
99
use sha2::{Digest, Sha256};
1010
use std::os::unix::ffi::OsStrExt;
1111
use std::path::{Path, PathBuf};
@@ -22,6 +22,7 @@ type ExpandedValue = (Option<PathBuf>, Option<String>);
2222
enum FromfileType {
2323
Json,
2424
Yaml,
25+
Toml,
2526
Unknown,
2627
}
2728

@@ -32,20 +33,23 @@ impl FromfileType {
3233
return FromfileType::Json;
3334
} else if ext == "yml" || ext == "yaml" {
3435
return FromfileType::Yaml;
36+
} else if ext == "toml" {
37+
return FromfileType::Toml;
3538
};
3639
}
3740
FromfileType::Unknown
3841
}
3942
}
4043

41-
fn try_deserialize<'a, DE: Deserialize<'a>>(
42-
value: &'a str,
44+
fn try_deserialize<DE: DeserializeOwned>(
45+
value: &str,
4346
path_opt: Option<PathBuf>,
4447
) -> Result<Option<DE>, ParseError> {
4548
if let Some(path) = path_opt {
4649
match FromfileType::detect(&path) {
4750
FromfileType::Json => serde_json::from_str(value).map_err(|e| mk_parse_err(e, &path)),
4851
FromfileType::Yaml => serde_yaml::from_str(value).map_err(|e| mk_parse_err(e, &path)),
52+
FromfileType::Toml => toml::from_str(value).map_err(|e| mk_parse_err(e, &path)),
4953
_ => Ok(None),
5054
}
5155
} else {

src/rust/options/src/fromfile_tests.rs

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use crate::fromfile::test_util::write_fromfile;
55
use crate::fromfile::*;
66
use crate::parse::{ParseError, Parseable};
77
use crate::{BuildRoot, DictEdit, DictEditAction, ListEdit, ListEditAction, Val};
8+
use indoc::indoc;
89
use maplit::hashmap;
910
use std::collections::HashMap;
1011
use std::fmt::Debug;
@@ -13,7 +14,7 @@ macro_rules! check_err {
1314
($res:expr, $expected_suffix:expr $(,)?) => {
1415
let actual_msg = $res.unwrap_err().render("XXX");
1516
assert!(
16-
actual_msg.ends_with($expected_suffix),
17+
actual_msg.trim_end().ends_with($expected_suffix.trim_end()),
1718
"Error message does not have expected suffix:\n{actual_msg}\nvs\n{:>width$}",
1819
$expected_suffix,
1920
width = actual_msg.len(),
@@ -271,19 +272,32 @@ fn test_expand_fromfile_to_dict() {
271272
"fromfile.json",
272273
);
273274
do_test(
274-
r#"
275+
indoc! {"
275276
FOO:
276277
BAR: 3.14
277278
BAZ:
278279
QUX: true
279280
QUUX:
280281
- 1
281282
- 2
282-
"#,
283+
"},
283284
&complex_obj,
284285
"fromfile.yaml",
285286
);
286287

288+
do_test(
289+
indoc! {"
290+
[FOO]
291+
BAR = 3.14
292+
293+
[FOO.BAZ]
294+
QUX= true
295+
QUUX = [1, 2]
296+
"},
297+
&complex_obj,
298+
"fromfile.toml",
299+
);
300+
287301
check_err!(
288302
expand_fromfile("THIS IS NOT JSON", "@", "invalid.json"),
289303
"expected value at line 1 column 1",
@@ -304,6 +318,17 @@ fn test_expand_fromfile_to_dict() {
304318
"invalid type: sequence, expected a map",
305319
);
306320

321+
check_err!(
322+
expand_fromfile("THIS IS NOT TOML", "@", "invalid.toml"),
323+
indoc! {"
324+
TOML parse error at line 1, column 6
325+
|
326+
1 | THIS IS NOT TOML
327+
| ^
328+
expected `.`, `=`
329+
"},
330+
);
331+
307332
check_err!(
308333
expand_to_dict("@/does/not/exist".to_string()),
309334
"Problem reading /does/not/exist for XXX: No such file or directory (os error 2)",

0 commit comments

Comments
 (0)