Repository: mesonbuild/meson

Issue:
Compilers specified as full paths with spaces are botched on meson setup
**Describe the bug**

On Windows, it is possible to have paths with spaces if these are kept inside single or double quotation marks.

If these are fed to Meson through `CC` or `CXX`, or maybe through a native or cross file, `compilers.detect._get_compilers` will wrongly split the path on the first space with `BinaryTable.parse_entry`, take the first component as a "wrapper" (ccache) and the rest as the "path" to the compiler. A similar botched escape will take place for the linker.

This breaks CMake setup later, as this will get fed as a toolchain file:

```cmake
set(CMAKE_SIZEOF_VOID_P "8")
set(CMAKE_C_COMPILER_LAUNCHER "C:/Program")
set(CMAKE_C_COMPILER "Files/Microsoft" "Visual" "Studio/2022/Preview/VC/Tools/Llvm/x64/bin/clang-cl.exe")
set(CMAKE_LINKER "c:/Program" "Files/Microsoft" "Visual" "Studio/2022/Preview/VC/Tools/MSVC/14.35.32019/bin/Hostx64/x64/link.exe")
```

triggering this on the log:

```
Found CMake: C:\Program Files\CMake\bin\cmake.EXE (3.23.1)
WARNING: CMake Toolchain: Failed to determine CMake compilers state
```

`WARNING: CMake Toolchain: Failed to determine CMake compilers state` repeats once per failed `dependency()` lookup.

**To Reproduce**

Install the Clang tools from the Visual Studio Installer (here I'm using 2022 Preview), then try to set up the following environment:

```powershell
$env:CC="C:\\Program Files\\Microsoft Visual Studio\\2022\\Preview\\VC\\Tools\\Llvm\\x64\\bin\\clang-cl.exe"
$env:CXX = "c:/Program Files/Microsoft Visual Studio/2022/Preview/VC/Tools/MSVC/14.35.32019/bin/Hostx64/x64/link.exe"
meson setup build
```

meson.build:

```meson
project('test', 'c')

dep = dependency('theoradec', required: false)
```

**Expected behavior**

Meson should not escape paths to compilers unless it could clearly differentiate a path from a command list.

**system parameters**
* Is this a [cross build](https://mesonbuild.com/Cross-compilation.html) or just a plain native build (for the same computer)? **no**
* what operating system (e.g. MacOS Catalina, Windows 10, CentOS 8.0, Ubuntu 18.04, etc.) **Windows 10 21H2**
* what Python version are you using e.g. 3.8.0 **3.10.1**
* what `meson --version` **0.64.0**
* what `ninja --version` if it's a Ninja build **1.10.2**



Discussion:
> If these are fed to Meson through `CC` or `CXX`, or maybe through a native or cross file, `compilers.detect._get_compilers` will wrongly split the path on the first space with `BinaryTable.parse_entry`, take the first component as a "wrapper" (ccache) and the rest as the "path" to the compiler. A similar botched escape will take place for the linker.

That's actually completely untrue, the general issue is that the CC and CXX environment variables are defined by convention (as in, the convention that such a variable exists and is meaningful) as a shell string that undergoes shell splitting.

It may contain prefixes, like `CC="ccache cc"`, or suffixes such as `CC="gcc -m32"`, or a variety of other things.

It is parsed by `Environment._set_default_binaries_from_env()`, which in turn runs `mesonlib.split_args(cmd)`.
No problem passing those paths via cross/native files. vcpkg does it all the time in the form of:
`c = ['C:/Program Files/Microsoft Visual Studio/2022/Community/VC/Tools/MSVC/14.34.31933/bin/Hostx64/x64/cl.exe']`

resulting into:
```
# Variables from meson
set(CMAKE_SIZEOF_VOID_P "8")
set(CMAKE_C_COMPILER "C:/Program Files/Microsoft Visual Studio/2022/Community/VC/Tools/MSVC/14.34.31933/bin/Hostx64/x64/cl.exe")
set(CMAKE_CXX_COMPILER "C:/Program Files/Microsoft Visual Studio/2022/Community/VC/Tools/MSVC/14.34.31933/bin/Hostx64/x64/cl.exe")
```
> That's actually completely untrue, the general issue is that the CC and CXX environment variables are defined by convention (as in, the convention that such a variable exists and is meaningful) as a shell string that undergoes shell splitting.

Maybe it's a Linux-specific convention? It's the first time I hear about this, is it documented somewhere?

> No problem passing those paths via cross/native files. vcpkg does it all the time in the form of: `c = ['C:/Program Files/Microsoft Visual Studio/2022/Community/VC/Tools/MSVC/14.34.31933/bin/Hostx64/x64/cl.exe']`
> 

In light of having multiple (and inconsistently handled) ways of passing the compiler, should this be documented?
I faced the same issue, and could work around it by adding double quotes to the path inside the variable.  This behavior and the workaround are probably worth mentioning in the docs.
A documentation PR would be lovely. :)

Resolving patch:
diff --git a/mesonbuild/environment.py b/mesonbuild/environment.py
--- a/mesonbuild/environment.py
+++ b/mesonbuild/environment.py
@@ -747,7 +747,10 @@ def _set_default_binaries_from_env(self) -> None:
         for (name, evar), for_machine in itertools.product(opts, MachineChoice):
             p_env = _get_env_var(for_machine, self.is_cross_build(), evar)
             if p_env is not None:
-                self.binaries[for_machine].binaries.setdefault(name, mesonlib.split_args(p_env))
+                if os.path.exists(p_env):
+                    self.binaries[for_machine].binaries.setdefault(name, [p_env])
+                else:
+                    self.binaries[for_machine].binaries.setdefault(name, mesonlib.split_args(p_env))
 
     def _set_default_properties_from_env(self) -> None:
         """Properties which can also be set from the environment."""
diff --git a/mesonbuild/scripts/env2mfile.py b/mesonbuild/scripts/env2mfile.py
--- a/mesonbuild/scripts/env2mfile.py
+++ b/mesonbuild/scripts/env2mfile.py
@@ -296,7 +296,10 @@ def detect_compilers_from_envvars(envvar_suffix: str = '') -> MachineInfo:
         compilerstr = os.environ.get(envvarname + envvar_suffix)
         if not compilerstr:
             continue
-        compiler = shlex.split(compilerstr)
+        if os.path.exists(compilerstr):
+            compiler = [compilerstr]
+        else:
+            compiler = shlex.split(compilerstr)
         infos.compilers[langname] = compiler
         lang_compile_args, lang_link_args = detect_language_args_from_envvars(langname, envvar_suffix)
         if lang_compile_args:


Test patch: