Repository: mesonbuild/meson

Issue:
Objects generated by generator are not linked on Visual Studio backend
**Describe the bug**
When generating objects with `generator()` and adding them as sources to a target, these objects are not actually linked into the target when using `--backend=vs`.

**To Reproduce**
Consider the following small example project:
`meson.build`:
```
project('meson_vs_generator_repro', 'c', version: '0.1')

nasm = find_program('nasm')
nasm_gen = generator(nasm, output: '@BASENAME@.o', arguments: ['-f', 'win64', '@INPUT@', '-o', '@OUTPUT@'])

sources = []
sources += 'main.c'
sources += nasm_gen.process('func.asm')

exe = executable('repro', sources: sources)
```

`main.c`:
```c
#include <stdio.h>

int asmfunc();

int main(int argc, char **argv) {
    printf("Hello: %d\n", asmfunc());
    return 0;
}
```

`func.asm`:
```nasm
section .text

global asmfunc

asmfunc:
    mov eax, 1337
    ret
```
Then run `meson setup build --backend=vs` on Windows with VS installed and try to build the `repro` project using Visual Studio.

**Expected behavior**
The program compiles and runs, printing `Hello: 1337`, like it would with the ninja backend.

**Actual behavior**
Building fails with `unresolved external symbol asmfunc referenced in function main`.

**System Parameters**
* Not a cross build
* Windows 11
* Python 3.11.3
* Latest master version of meson at the time of writing, i.e. `1.3.99`
* Visual Studio 2022. Ninja wasn't used in the actual example, but when comparing the behavior to ninja, ninja `1.11.0` was used.

**Comments**
The backend generates a `CustomBuild` tag for each file processed by the generator in the corresponding vcxproj file:
```
<CustomBuild Include="..\func.asm">
    <Command>[...];</Command>
    <Outputs>repro@exe\func.o</Outputs>
    <AdditionalInputs>[...]</AdditionalInputs>
</CustomBuild>
```
But this doesn't make Visual Studio link the generated outputs.

A solution might be adding `<Object Include="repro@exe\func.o"/>` tags for every generated file, and specifying `<CustomBuildBeforeTargets>Link</CustomBuildBeforeTargets>` in the project's properties.

Of course I'm aware that meson now supports nasm directly, but nasm is just an example here. The same happens for any generator outputting objects.



Discussion:
> Of course I'm aware that meson now supports nasm directly, but nasm is just an example here. The same happens for any generator outputting objects.

And the nasm language isn't supported on the VS backend anyway.
~~The same happens for generated sources (as opposed to objects), which aren't being compiled into the project.~~ Nevermind, this must have been because of some different issue. Generated sources worked for me now.
Investigating further shows that these deliberately weren't added because Visual Studio is supposed to link them itself: https://github.com/mesonbuild/meson/commit/baa6df2ce73877747c6327a0850a3eaa00e69f5f , but that's not the case for me on VS 2022. I'll see if I can test other versions.
I dug up an installer of VS2015 (the version referenced in the linked commit), and even there the generated objects were not linked automatically.
I also tested older versions of VS (2013 and 2010) and none of them linked the CustomBuild outputs. So these objects can probably be manually included unconditionally. I'll make a PR.

Resolving patch:
diff --git a/mesonbuild/backend/vs2010backend.py b/mesonbuild/backend/vs2010backend.py
--- a/mesonbuild/backend/vs2010backend.py
+++ b/mesonbuild/backend/vs2010backend.py
@@ -894,18 +894,6 @@ def add_include_dirs(self, lang, parent_node, file_inc_dirs):
         dirs = file_inc_dirs[lang]
         ET.SubElement(parent_node, "AdditionalIncludeDirectories").text = ';'.join(dirs)
 
-    @staticmethod
-    def has_objects(objects, additional_objects, generated_objects):
-        # Ignore generated objects, those are automatically used by MSBuild because they are part of
-        # the CustomBuild Outputs.
-        return len(objects) + len(additional_objects) > 0
-
-    @staticmethod
-    def add_generated_objects(node, generated_objects):
-        # Do not add generated objects to project file. Those are automatically used by MSBuild, because
-        # they are part of the CustomBuild Outputs.
-        return
-
     @staticmethod
     def escape_preprocessor_define(define: str) -> str:
         # See: https://msdn.microsoft.com/en-us/library/bb383819.aspx
@@ -1779,17 +1767,20 @@ def path_normalize_add(path, lis):
         for o in custom_objs:
             additional_objects.append(o)
 
+        # VS automatically links CustomBuild outputs whose name ends in .obj or .res,
+        # but the others need to be included explicitly
+        explicit_link_gen_objs = [obj for obj in gen_objs if not obj.endswith(('.obj', '.res'))]
+
         previous_objects = []
-        if self.has_objects(objects, additional_objects, gen_objs):
+        if len(objects) + len(additional_objects) + len(explicit_link_gen_objs) > 0:
             inc_objs = ET.SubElement(root, 'ItemGroup')
             for s in objects:
                 relpath = os.path.join(proj_to_build_root, s.rel_to_builddir(self.build_to_src))
                 if path_normalize_add(relpath, previous_objects):
                     ET.SubElement(inc_objs, 'Object', Include=relpath)
-            for s in additional_objects:
+            for s in additional_objects + explicit_link_gen_objs:
                 if path_normalize_add(s, previous_objects):
                     ET.SubElement(inc_objs, 'Object', Include=s)
-            self.add_generated_objects(inc_objs, gen_objs)
 
         ET.SubElement(root, 'Import', Project=r'$(VCTargetsPath)\Microsoft.Cpp.targets')
         self.add_regen_dependency(root)


Test patch:
diff --git a/test cases/common/52 object generator/meson.build b/test cases/common/52 object generator/meson.build
--- a/test cases/common/52 object generator/meson.build	
+++ b/test cases/common/52 object generator/meson.build	
@@ -29,6 +29,14 @@ gen2 = generator(python,
  arguments : [comp, cc, '@INPUT@', '@OUTPUT0@'])
 generated2 = gen2.process(['source3.c'])
 
-e = executable('prog', 'prog.c', generated, generated2)
+# Generate an object file ending with .o even on Windows.
+# The VS backend needs to handle .o objects differently from .obj objects.
+gen3 = generator(python,
+ output : '@BASENAME@.o',
+ arguments : [comp, cc, '@INPUT@', '@OUTPUT@'])
+
+generated3 = gen3.process(['source4.c'])
+
+e = executable('prog', 'prog.c', generated, generated2, generated3)
 
 test('objgen', e)
\ No newline at end of file
diff --git a/test cases/common/52 object generator/prog.c b/test cases/common/52 object generator/prog.c
--- a/test cases/common/52 object generator/prog.c	
+++ b/test cases/common/52 object generator/prog.c	
@@ -1,7 +1,8 @@
 int func1_in_obj(void);
 int func2_in_obj(void);
 int func3_in_obj(void);
+int func4_in_obj(void);
 
 int main(void) {
-    return func1_in_obj() + func2_in_obj() + func3_in_obj();
+    return func1_in_obj() + func2_in_obj() + func3_in_obj() + func4_in_obj();
 }
diff --git a/test cases/common/52 object generator/source4.c b/test cases/common/52 object generator/source4.c
new file mode 100644
--- /dev/null
+++ b/test cases/common/52 object generator/source4.c	
@@ -0,0 +1,3 @@
+int func4_in_obj(void) {
+    return 0;
+}