Repository: mesonbuild/meson

Issue:
windows.compile_resources() can't be used with custom targets
```meson
    rc_target = custom_target('Windows resource file',
        command : [preprocess_command, rcdefs, '@INPUT@', '@OUTPUT@'],
        build_always : true,
        input : 'taisei.rc.in',
        output : 'taisei.rc',
    )

    version_deps += winmod.compile_resources(rc_target)
```

```
Meson encountered an error in file src/meson.build, line 59, column 4:
Windows resource arguments must be strings or files not <CustomTargetHolder Windows resource file@cus: ['/data/git/taisei/scripts/configure-file.py', '--rootdir', '/data/git/taisei', '--fallback-version', 'v1.1.0-9999', '-DMESON_BUILD_TYPE=release', '-DICONS_DIR=/data/git/taisei/misc/icons', '-DBUILDTYPE_DEFINE=#define RELEASE_BUILD', '@INPUT@', '@OUTPUT@']>
```

This bug makes it impossible to reliably regenerate the `.rc` source on every rebuild.
Add something like depend_files to windows.compile_resources()
Resource script can include various other files (bitmap, cursor, font, html, icon, message table, binary data, manifest), it would be nice if it were possible to declare the resource script depends on these.


Discussion:
Related to https://github.com/mesonbuild/meson/issues/2789, the fix is to make `windows.compile_resources()` use a `custom_target` instead of a `generator`.
Compile resources parses the dependency file's contents and adds dependencies to files listed in it. This should already work.
Perhaps I should have noted I meant in the Windows module. I created a sample project with a resource file that includes an application manifest, and there does not seem to be any dependency on it.
~~~
CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "foo.manifest"
~~~

Resolving patch:
diff --git a/mesonbuild/modules/windows.py b/mesonbuild/modules/windows.py
--- a/mesonbuild/modules/windows.py
+++ b/mesonbuild/modules/windows.py
@@ -67,11 +67,42 @@ def compile_resources(self, state, args, kwargs):
             suffix = 'o'
         if not rescomp.found():
             raise MesonException('Could not find Windows resource compiler %s.' % ' '.join(rescomp.get_command()))
-        res_kwargs = {'output': '@BASENAME@.' + suffix,
-                      'arguments': res_args}
-        res_gen = build.Generator([rescomp], res_kwargs)
-        res_output = res_gen.process_files('Windows resource', args, state)
-        return ModuleReturnValue(res_output, [res_output])
+
+        res_targets = []
+
+        def add_target(src):
+            if isinstance(src, list):
+                for subsrc in src:
+                    add_target(subsrc)
+                return
+
+            if hasattr(src, 'held_object'):
+                src = src.held_object
+
+            res_kwargs = {
+                'output': '@BASENAME@.' + suffix,
+                'input': [src],
+                'command': [rescomp] + res_args,
+            }
+
+            if isinstance(src, (str, mesonlib.File)):
+                name = 'file {!r}'.format(str(src))
+            elif isinstance(src, build.CustomTarget):
+                if len(src.get_outputs()) > 1:
+                    raise MesonException('windows.compile_resources does not accept custom targets with more than 1 output.')
+
+                name = 'target {!r}'.format(src.get_id())
+            else:
+                raise MesonException('Unexpected source type {!r}. windows.compile_resources accepts only strings, files, custom targets, and lists thereof.'.format(src))
+
+            # Path separators are not allowed in target names
+            name = name.replace('/', '_').replace('\\', '_')
+
+            res_targets.append(build.CustomTarget('Windows resource for ' + name, state.subdir, state.subproject, res_kwargs))
+
+        add_target(args)
+
+        return ModuleReturnValue(res_targets, [res_targets])
 
 def initialize():
     return WindowsModule()


Test patch:
diff --git a/test cases/windows/13 resources with custom targets/meson.build b/test cases/windows/13 resources with custom targets/meson.build
new file mode 100644
--- /dev/null
+++ b/test cases/windows/13 resources with custom targets/meson.build	
@@ -0,0 +1,70 @@
+project('winmain', 'c')
+
+# MinGW windres has a bug due to which it doesn't parse args with space properly:
+# https://github.com/mesonbuild/meson/pull/1346
+# https://sourceware.org/bugzilla/show_bug.cgi?id=4933
+if meson.get_compiler('c').get_id() == 'gcc' and host_machine.system() == 'windows'
+  # Construct build_to_src and skip this test if it has spaces
+  # because then the -I flag to windres will also have spaces
+  # and we know the test will fail
+  src_parts = meson.source_root().split('/')
+  build_parts = meson.build_root().split('/')
+
+  # Get the common path (which might just be '/' or 'C:/')
+  common = []
+  done = false
+  count = 0
+  if src_parts.length() > build_parts.length()
+    parts = build_parts
+    other = src_parts
+  else
+    parts = src_parts
+    other = build_parts
+  endif
+  foreach part : parts
+    if not done and part == other.get(count)
+      common += [part]
+    else
+      done = true
+    endif
+    count += 1
+  endforeach
+
+  # Create path components to go down from the build root to the common path
+  count = 0
+  rel = build_parts
+  foreach build : build_parts
+    if count < build_parts.length() - common.length()
+      rel += ['..']
+    endif
+    count += 1
+  endforeach
+
+  # Create path components to go up from the common path to the build root
+  count = 0
+  foreach src : src_parts
+    if count >= common.length()
+      rel += [src]
+    endif
+    count += 1
+  endforeach
+
+  build_to_src = '/'.join(rel)
+
+  if build_to_src.contains(' ')
+    message('build_to_src is: ' + build_to_src)
+    error('MESON_SKIP_TEST build_to_src has spaces')
+  endif
+  # Welcome to the end of this conditional.
+  # We hope you never have to implement something like this.
+endif
+
+subdir('res')
+
+foreach id : [0, 1, 2]
+  exe = executable('prog_@0@'.format(id), 'prog.c',
+    res[id],
+    gui_app : true)
+
+  test('winmain_@0@'.format(id), exe)
+endforeach
diff --git a/test cases/windows/13 resources with custom targets/prog.c b/test cases/windows/13 resources with custom targets/prog.c
new file mode 100644
--- /dev/null
+++ b/test cases/windows/13 resources with custom targets/prog.c	
@@ -0,0 +1,14 @@
+#include<windows.h>
+
+#define MY_ICON 1
+
+int APIENTRY
+WinMain(
+    HINSTANCE hInstance,
+    HINSTANCE hPrevInstance,
+    LPSTR lpszCmdLine,
+    int nCmdShow) {
+    HICON hIcon;
+    hIcon = LoadIcon(GetModuleHandle(NULL), MAKEINTRESOURCE(MY_ICON));
+    return hIcon ? 0 : 1;
+}
diff --git a/test cases/windows/13 resources with custom targets/res/gen-res.py b/test cases/windows/13 resources with custom targets/res/gen-res.py
new file mode 100755
--- /dev/null
+++ b/test cases/windows/13 resources with custom targets/res/gen-res.py	
@@ -0,0 +1,6 @@
+#!/usr/bin/env python3
+
+import sys
+
+with open(sys.argv[1], 'r') as infile, open(sys.argv[2], 'w') as outfile:
+    outfile.write(infile.read().format(icon=sys.argv[3]))
diff --git a/test cases/windows/13 resources with custom targets/res/meson.build b/test cases/windows/13 resources with custom targets/res/meson.build
new file mode 100644
--- /dev/null
+++ b/test cases/windows/13 resources with custom targets/res/meson.build	
@@ -0,0 +1,18 @@
+win = import('windows')
+
+rc_writer = find_program('./gen-res.py')
+
+rc_sources = []
+
+foreach id : [1, 2]
+    rc_sources += custom_target('RC source file @0@'.format(id),
+        input : 'myres.rc.in',
+        output : 'myres_@0@.rc'.format(id),
+        command : [rc_writer, '@INPUT@', '@OUTPUT@', files('sample.ico')],
+        install : false,
+        build_always : true)
+endforeach
+
+rc_sources += files('myres_static.rc')
+
+res = win.compile_resources(rc_sources)
diff --git a/test cases/windows/13 resources with custom targets/res/myres.rc.in b/test cases/windows/13 resources with custom targets/res/myres.rc.in
new file mode 100644
--- /dev/null
+++ b/test cases/windows/13 resources with custom targets/res/myres.rc.in	
@@ -0,0 +1,3 @@
+#include<windows.h>
+
+1 ICON "{icon}"
diff --git a/test cases/windows/13 resources with custom targets/res/myres_static.rc b/test cases/windows/13 resources with custom targets/res/myres_static.rc
new file mode 100644
--- /dev/null
+++ b/test cases/windows/13 resources with custom targets/res/myres_static.rc	
@@ -0,0 +1,3 @@
+#include<windows.h>
+
+1 ICON "sample.ico"
diff --git a/test cases/windows/13 resources with custom targets/res/sample.ico b/test cases/windows/13 resources with custom targets/res/sample.ico
new file mode 100644
Binary files /dev/null and b/test cases/windows/13 resources with custom targets/res/sample.ico differ