diff --git a/Cargo.toml b/Cargo.toml index a67464387..c7be77ee2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,6 +21,7 @@ rust-version = "1.63" [dependencies] jobserver = { version = "0.1.30", default-features = false, optional = true } +shlex = "1.3.0" [target.'cfg(unix)'.dependencies] # Don't turn on the feature "std" for this, see https://github.com/rust-lang/cargo/issues/4866 diff --git a/src/lib.rs b/src/lib.rs index 910666068..9fd3860cc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -84,6 +84,9 @@ //! * `CC_ENABLE_DEBUG_OUTPUT` - if set, compiler command invocations and exit codes will //! be logged to stdout. This is useful for debugging build script issues, but can be //! overly verbose for normal use. +//! * `CC_SHELL_ESCAPED_FLAGS` - if set, *FLAGS will be parsed as if they were shell +//! arguments, similar to `make` and `cmake`. For example, `CFLAGS='a "b c" d\ e'` will +//! be parsed as `["a", "b", "c", "d", "e"]` instead of `["a", "\"b", "c\", "d\\", "e"]` //! * `CXX...` - see [C++ Support](#c-support). //! //! Furthermore, projects using this crate may specify custom environment variables @@ -224,6 +227,8 @@ use std::process::Child; use std::process::Command; use std::sync::{Arc, RwLock}; +use shlex::Shlex; + #[cfg(feature = "parallel")] mod parallel; mod windows; @@ -301,6 +306,7 @@ pub struct Build { apple_versions_cache: Arc, Arc>>>, emit_rerun_if_env_changed: bool, cached_compiler_family: Arc, ToolFamily>>>, + shell_escaped_flags: Option, } /// Represents the types of errors that may occur while using cc-rs. @@ -425,6 +431,7 @@ impl Build { apple_versions_cache: Arc::new(RwLock::new(HashMap::new())), emit_rerun_if_env_changed: true, cached_compiler_family: Arc::default(), + shell_escaped_flags: None, } } @@ -1277,6 +1284,15 @@ impl Build { self } + /// Configure whether *FLAGS variables are parsed using `shlex`, similarly to `make` and + /// `cmake`. + /// + /// This option defaults to `false`. + pub fn shell_escaped_flags(&mut self, shell_escaped_flags: bool) -> &mut Build { + self.shell_escaped_flags = Some(shell_escaped_flags); + self + } + #[doc(hidden)] pub fn __set_env(&mut self, a: A, b: B) -> &mut Build where @@ -3634,6 +3650,11 @@ impl Build { }) } + fn get_shell_escaped_flags(&self) -> bool { + self.shell_escaped_flags + .unwrap_or_else(|| self.getenv("CC_SHELL_ESCAPED_FLAGS").is_some()) + } + fn get_dwarf_version(&self) -> Option { // Tentatively matches the DWARF version defaults as of rustc 1.62. let target = self.get_target().ok()?; @@ -3748,12 +3769,17 @@ impl Build { } fn envflags(&self, name: &str) -> Result, Error> { - Ok(self - .getenv_with_target_prefixes(name)? - .to_string_lossy() - .split_ascii_whitespace() - .map(ToString::to_string) - .collect()) + let env_os = self.getenv_with_target_prefixes(name)?; + let env = env_os.to_string_lossy(); + + if self.get_shell_escaped_flags() { + Ok(Shlex::new(&env).collect()) + } else { + Ok(env + .split_ascii_whitespace() + .map(ToString::to_string) + .collect()) + } } fn fix_env_for_apple_os(&self, cmd: &mut Command) -> Result<(), Error> { diff --git a/tests/cflags_shell_escaped.rs b/tests/cflags_shell_escaped.rs new file mode 100644 index 000000000..7d843e524 --- /dev/null +++ b/tests/cflags_shell_escaped.rs @@ -0,0 +1,24 @@ +mod support; + +use crate::support::Test; +use std::env; + +/// This test is in its own module because it modifies the environment and would affect other tests +/// when run in parallel with them. +#[test] +fn gnu_test_parse_shell_escaped_flags() { + env::set_var("CFLAGS", "foo \"bar baz\""); + env::set_var("CC_SHELL_ESCAPED_FLAGS", "1"); + let test = Test::gnu(); + test.gcc().file("foo.c").compile("foo"); + + test.cmd(0).must_have("foo").must_have("bar baz"); + + env::remove_var("CC_SHELL_ESCAPED_FLAGS"); + let test = Test::gnu(); + test.gcc().file("foo.c").compile("foo"); + + test.cmd(0) + .must_have("foo") + .must_have_in_order("\"bar", "baz\""); +}