Repository: mesonbuild/meson

Issue:
sdl2 does not build with the "vs" backend
SDL2 fails to build with the following error
```
> meson compile -C build
INFO: autodetecting backend as vs2022
INFO: calculating backend command to run: msbuild "ABC\build\ABC.sln" -maxCpuCount -verbosity:minimal
MSBuild version 17.5.0+6f08c67f3 for .NET Framework

  SDL_windows_main.c
ABC\subprojects\SDL2-2.26.0\include\SDL_config.h(58,1): fatal  error C1189: #error:  Wrong SDL_config.h, check your include path? [ABC\build\subprojects\SDL2-2.26.0\5f2ac02@@sdl2main@sta.vcxproj]
```
when the `vs` backend is used. It compiles fine with the `ninja` backend.

As far as I can tell the reason is that the include directories are passed to the compiler in different order:

* with `vs`: `..\..\..\subprojects\SDL2-2.26.0\include;include;`
* with `ninja`: `"-Isubprojects\SDL2-2.26.0\include" "-I..\subprojects\SDL2-2.26.0\include"`

(I think it's a bit hard to understand what's going on because they are different relative paths, but the order is swapped.)

So the `SDL_config.h` that has the `#error` is found first with the `vs` backend. Removing or renaming the file makes it build OK (as far as I have tested).

Could this be a `meson` issue?


Discussion:
ping @eli-schwartz 
So ninja is the normal build system. The vs one is somewhat supported but not to the same level. Wonder if CI should be added to test vs.
From the SDL2 source tree:

```c
#ifdef USING_GENERATED_CONFIG_H
#error Wrong SDL_config.h, check your include path?
#endif
```

I think it makes sense that the point of a builddir path is to override the sourcedir path, so this is probably a Meson bug in the VS backend. (SDL2 is depending on this to allow a config.h file that is generic, *or* override it with a custom one.)
Here is a very simple project that exhibits the same issue, with the build directories included.
[vs-backend-bug.zip](https://github.com/mesonbuild/meson/files/11115570/vs-backend-bug.zip)

The include directories are very visibly swapped:
```xml
        <ItemGroup>
                <CLCompile Include="\\VBoxSvr\vmshare\asd2\x.c">
                        <AdditionalOptions>%(AdditionalOptions)</AdditionalOptions>
                        <PreprocessorDefinitions>%(PreprocessorDefinitions)</PreprocessorDefinitions>
                        <AdditionalIncludeDirectories>x@exe;.;\\VBoxSvr\vmshare\asd2\;\\VBoxSvr\vmshare\asd2\include;include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
                        <ObjectFileName>$(IntDir)__VBoxSvr_vmshare_asd2_x.c.obj</ObjectFileName>
                </CLCompile>
        </ItemGroup>
```
and
```ninja
build x.exe.p/__VBoxSvr_vmshare_asd2_x.c.obj: c_COMPILER \\VBoxSvr/vmshare/asd2/x.c
 DEPFILE = "x.exe.p\__VBoxSvr_vmshare_asd2_x.c.obj.d"
 DEPFILE_UNQUOTED = x.exe.p\__VBoxSvr_vmshare_asd2_x.c.obj.d
 ARGS = "-Ix.exe.p" "-I." "-I\\VBoxSvr\vmshare\asd2" "-Iinclude" "-I\\VBoxSvr\vmshare\asd2\include" "/MDd" "/nologo" "/showIncludes" "/utf-8" "/W2" "/Od" "/Zi" "/Fdx.exe.p\__VBoxSvr_vmshare_asd2_x.c.pdb"
```
I think here is the difference:
```py
# vs2010backend.py:1068
            for d in reversed(target.get_include_dirs()):
                # reversed is used to keep order of includes
                for i in reversed(d.get_incdirs()):
                    curdir = os.path.join(d.get_curdir(), i)
                    try:
                        args.append('-I' + self.relpath(curdir, target.subdir)) # build dir
                        args.append('-I' + os.path.join(proj_to_src_root, curdir))  # src dir
```
and
```py
# ninjabackend.py:2753
        for i in reversed(target.get_include_dirs()):
            basedir = i.get_curdir()
            # We should iterate include dirs in reversed orders because
            # -Ipath will add to begin of array. And without reverse
            # flags will be added in reversed order.
            for d in reversed(i.get_incdirs()):
                # Add source subdir first so that the build subdir overrides it
                (compile_obj, includeargs) = self.generate_inc_dir(compiler, d, basedir, i.is_system)
                commands += compile_obj
                commands += includeargs
```

The vs backend adds the "src dir" last, so that takes precedence over the "build dir", hence the include order comes out to
```
\\VBoxSvr\vmshare\asd2\include
include
```

There is even a comment in `ninjabackend.py`:
```py
# Add source subdir first so that the build subdir overrides it
```
Originally it did it the other way around so we fixed it. Apparently not in vs backend then...
Hmmm indeed, looks like 2bb58c909fd80cc8ce053b5f1b6565bd28a416d2 changed the order in the `ninja` backend. It also added a test, but unfortunately the `vs` backend still passed (passes) it.
I can make a PR that extends the `common/include order` test and swaps those two lines in the `vs` backend. Would that be OK?
Yes, that seems extremely reasonable to me.

Resolving patch:
diff --git a/mesonbuild/backend/vs2010backend.py b/mesonbuild/backend/vs2010backend.py
--- a/mesonbuild/backend/vs2010backend.py
+++ b/mesonbuild/backend/vs2010backend.py
@@ -1070,8 +1070,9 @@ def gen_vcxproj(self, target, ofname, guid):
                 for i in reversed(d.get_incdirs()):
                     curdir = os.path.join(d.get_curdir(), i)
                     try:
-                        args.append('-I' + self.relpath(curdir, target.subdir)) # build dir
+                        # Add source subdir first so that the build subdir overrides it
                         args.append('-I' + os.path.join(proj_to_src_root, curdir))  # src dir
+                        args.append('-I' + self.relpath(curdir, target.subdir)) # build dir
                     except ValueError:
                         # Include is on different drive
                         args.append('-I' + os.path.normpath(curdir))


Test patch:
diff --git a/test cases/common/130 include order/inc3/meson.build b/test cases/common/130 include order/inc3/meson.build
new file mode 100644
--- /dev/null
+++ b/test cases/common/130 include order/inc3/meson.build	
@@ -0,0 +1,2 @@
+configure_file(output: 'prefer-build-dir-over-src-dir.h',
+               configuration: configuration_data())
diff --git a/test cases/common/130 include order/inc3/prefer-build-dir-over-src-dir.h b/test cases/common/130 include order/inc3/prefer-build-dir-over-src-dir.h
new file mode 100644
--- /dev/null
+++ b/test cases/common/130 include order/inc3/prefer-build-dir-over-src-dir.h	
@@ -0,0 +1 @@
+#error "inc3/prefer-build-dir-over-src-dir.h included"
diff --git a/test cases/common/130 include order/meson.build b/test cases/common/130 include order/meson.build
--- a/test cases/common/130 include order/meson.build	
+++ b/test cases/common/130 include order/meson.build	
@@ -12,6 +12,8 @@ project('include order', 'c')
 
 # Custom target dir with a built header
 subdir('ctsub')
+# Configures a header file
+subdir('inc3')
 # Defines an internal dep
 subdir('sub1')
 # Defines a per-target include path
@@ -32,5 +34,5 @@ test('eh', e)
 test('oh', f)
 
 # Test that the order in include_directories() is maintained
-incs = include_directories('inc1', 'inc2')
+incs = include_directories('inc1', 'inc2', 'inc3')
 executable('ordertest', 'ordertest.c', include_directories: incs)
diff --git a/test cases/common/130 include order/ordertest.c b/test cases/common/130 include order/ordertest.c
--- a/test cases/common/130 include order/ordertest.c	
+++ b/test cases/common/130 include order/ordertest.c	
@@ -1,4 +1,5 @@
 #include "hdr.h"
+#include "prefer-build-dir-over-src-dir.h"
 
 #if !defined(SOME_DEFINE) || SOME_DEFINE != 42
 #error "Should have picked up hdr.h from inc1/hdr.h"