Repository: mesonbuild/meson

Issue:
Ninja backend with Windows network paths does not work using MSVC
**Describe the bug**
Trying to build a project residing on a network paths on Windows leads to two different problems:

1) A false positive same src-build directory exception can be raised. This occurs when the stat identifier fields are not populated, i.e., are 0, following an otherwise successful os.stat call. This is likely filesystem protocol driver specific.
2) Using ninja with the MSVC's toolchain (cl.exe & link.exe) fails to compile any files on a network path as the paths are converted to an all-frontslash string in the ninja backend. This leads to the path now starting with a front-slash and being interpreted as a command-line option since \\DEVICE\file was turned into //DEVICE/file.

**To Reproduce**
1.

* Have some mounted device on Windows, it can be mapped to a drive letter or not.
* Create a simple meson.build file with a single main.cc source.
* Run `meson builddir`

Exception raised: `Source and build directories must not be the same. Create a pristine build directory.'`

2.

* Same setup as 1)
* Comment out the `if` in msetup.py:134
* Run `meson builddir`

Ninja command fails, specifying an argument is unknown (the source file path).

**Expected behavior**
1) No exception: we should be able to compare the paths textually since they have been passed through abspath and realpath. For safety, this can only be done if indeed the os.stat exhibits the behavior described (identifier values at 0) 
2) Network paths should not be affected by the front-slash replace. This can be localized to Windows by doing a 2nd pass on the ninja line and replacing `//` by `\\`, which should only affect those cases (I would expect multiple slashes to be taken care of upstream by some path normalization).

**system parameters**
* This is a simple MSVC compilation on Windows 10, using MSVC of any version (they all interpret command-line arguments in the same way).
* Tested with Python 3.9.1
* Meson version 0.56.0, master (as of cb10ba75d43b27a5cf85c687098f690a13a26645)
* `ninja --version`: 1.10.2

Will submit a pull request with the suggested changes.


Discussion:

Resolving patch:
diff --git a/mesonbuild/backend/ninjabackend.py b/mesonbuild/backend/ninjabackend.py
--- a/mesonbuild/backend/ninjabackend.py
+++ b/mesonbuild/backend/ninjabackend.py
@@ -374,6 +374,13 @@ def write(self, outfile):
         # do not require quoting, unless explicitly specified, which is necessary for
         # the csc compiler.
         line = line.replace('\\', '/')
+        if mesonlib.is_windows():
+            # Support network paths as backslash, otherwise they are interpreted as
+            # arguments for compile/link commands when using MSVC
+            line = ' '.join(
+                (l.replace('//', '\\\\', 1) if l.startswith('//') else l)
+                for l in line.split(' ')
+            )
         outfile.write(line)
 
         if use_rspfile:
diff --git a/mesonbuild/msetup.py b/mesonbuild/msetup.py
--- a/mesonbuild/msetup.py
+++ b/mesonbuild/msetup.py
@@ -131,8 +131,11 @@ def validate_core_dirs(self, dir1: str, dir2: str) -> T.Tuple[str, str]:
             raise MesonException('{} is not a directory'.format(dir1))
         if not stat.S_ISDIR(os.stat(ndir2).st_mode):
             raise MesonException('{} is not a directory'.format(dir2))
-        if os.path.samefile(dir1, dir2):
-            raise MesonException('Source and build directories must not be the same. Create a pristine build directory.')
+        if os.path.samefile(ndir1, ndir2):
+            # Fallback to textual compare if undefined entries found
+            has_undefined = any((s.st_ino == 0 and s.st_dev == 0) for s in (os.stat(ndir1), os.stat(ndir2)))
+            if not has_undefined or ndir1 == ndir2:
+                raise MesonException('Source and build directories must not be the same. Create a pristine build directory.')
         if self.has_build_file(ndir1):
             if self.has_build_file(ndir2):
                 raise MesonException('Both directories contain a build file {}.'.format(environment.build_filename))


Test patch: