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
30 changes: 30 additions & 0 deletions crates/fbuild-config/src/ini_parser/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -578,6 +578,36 @@ fn test_parse_flags() {
);
}

/// FastLED/fbuild#947: the INI shell-quoting layer must be consumed here,
/// exactly once, so string-valued defines reach the compiler as a single
/// direct-exec argv element like `-DPROJECT_NAME="Demo"`.
#[test]
fn test_parse_flags_dequotes_shell_layer() {
// Outer-quoted escaped form (NightDriverStrip demo env).
assert_eq!(
parse_flags(r#"-DPROJECT_NAME="\"Demo\"" -DMATRIX_WIDTH=144"#),
vec![r#"-DPROJECT_NAME="Demo""#, "-DMATRIX_WIDTH=144"]
);
// Bare escaped form (no outer quotes) — common in other projects.
assert_eq!(parse_flags(r#"-DFOO=\"Bar\""#), vec![r#"-DFOO="Bar""#]);
// Quoted values with spaces group into one token.
assert_eq!(
parse_flags(r#"-DGREETING="\"Hello World\"""#),
vec![r#"-DGREETING="Hello World""#]
);
// Quote delimiters around paths are consumed; backslashes that are
// not escaping a double quote stay literal (Windows paths).
assert_eq!(
parse_flags(r#"-I"C:\Program Files\SDK" -Ifoo"#),
vec![r"-IC:\Program Files\SDK", "-Ifoo"]
);
// Single-quoted regions are fully literal.
assert_eq!(
parse_flags(r#"-DMSG='\"x\"' -DY=1"#),
vec![r#"-DMSG=\"x\""#, "-DY=1"]
);
}

#[test]
fn test_parse_lib_deps() {
assert_eq!(
Expand Down
17 changes: 13 additions & 4 deletions crates/fbuild-config/src/ini_parser/values.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,13 @@ pub(super) fn strip_inline_comment(s: &str) -> String {
/// - Multi-line: one flag per line
/// - `-D FLAG` → `-DFLAG` normalization
/// - Preserves arguments for `-include`, `-I`, `-L`, etc.
/// - Consumes the INI shell-quoting layer (FastLED/fbuild#947): quote
/// delimiters group and are stripped, and `\"` (outside single quotes)
/// is an escaped literal `"`. PlatformIO feeds `build_flags` through
/// Python `shlex`, so `-DNAME="\"Demo\""` must emerge here as the
/// single direct-exec argv element `-DNAME="Demo"`. Backslashes not
/// escaping a double quote stay literal (Windows paths) — the one
/// deliberate divergence from POSIX shlex.
pub(super) fn parse_flags(flags_str: &str) -> Vec<String> {
let mut result = Vec::new();

Expand All @@ -31,21 +38,23 @@ pub(super) fn parse_flags(flags_str: &str) -> Vec<String> {
continue;
}

let chars = trimmed.chars();
let mut chars = trimmed.chars().peekable();
let mut current = String::new();
let mut in_quotes = false;
let mut quote_char = ' ';

for c in chars {
while let Some(c) = chars.next() {
match c {
'\\' if chars.peek() == Some(&'"') && !(in_quotes && quote_char == '\'') => {
chars.next();
current.push('"');
}
'"' | '\'' if !in_quotes => {
in_quotes = true;
quote_char = c;
current.push(c);
}
c if in_quotes && c == quote_char => {
in_quotes = false;
current.push(c);
}
' ' | '\t' if !in_quotes => {
if !current.is_empty() {
Expand Down
Loading