Repository: mesonbuild/meson

Issue:
windows.compile_resources dependencies
In the `meson.build` for X server, I want to write something like:

```meson
convert = find_program('convert')
ico = custom_target(
    'xlogo-icon',
    command: [convert, '-background', 'transparent', '-trim', '-define', 'icon:auto-resize', '@INPUT@', '@OUTPUT@'],
    input: 'X.svg',
    output: 'X.ico',
    install: false,
    build_always: false,
)

rsrc = windows.compile_resources(
    'XWin.rc',
    # depend_files: ico, ???
    # include_directories: ico.private_dir_include(), ???
)
```

where `XWin.rc` contains an `ICON "X.ico"` line, and have correct dependencies exist.

#3571 adds the depend_files: keyword, but that doesn't appear to permit a custom_target, currently

There is also the problem that the directory that the custom target generates `X.ico` into needs to be added to the resource compiler's include directories:  Not sure if that should be by giving the custom_target object a method to access that, or by having `compile_resources()` extract them from the dependencies...


Discussion:

Resolving patch:
diff --git a/mesonbuild/backend/backends.py b/mesonbuild/backend/backends.py
--- a/mesonbuild/backend/backends.py
+++ b/mesonbuild/backend/backends.py
@@ -820,7 +820,7 @@ def eval_custom_target_command(self, target, absolute_outputs=False):
         outdir = self.get_target_dir(target)
         if absolute_outputs:
             source_root = self.environment.get_source_dir()
-            build_root = self.environment.get_source_dir()
+            build_root = self.environment.get_build_dir()
             outdir = os.path.join(self.environment.get_build_dir(), outdir)
         outputs = []
         for i in target.get_outputs():
diff --git a/mesonbuild/interpreter.py b/mesonbuild/interpreter.py
--- a/mesonbuild/interpreter.py
+++ b/mesonbuild/interpreter.py
@@ -798,6 +798,10 @@ def __setitem__(self, index, value):
     def __delitem__(self, index):
         raise InterpreterException('Cannot delete a member of a CustomTarget')
 
+    def outdir_include(self):
+        return IncludeDirsHolder(build.IncludeDirs('', [], False,
+                                                   [os.path.join('@BUILD_ROOT@', self.interpreter.backend.get_target_dir(self.held_object))]))
+
 class RunTargetHolder(InterpreterObject, ObjectHolder):
     def __init__(self, name, command, args, dependencies, subdir, subproject):
         InterpreterObject.__init__(self)
diff --git a/mesonbuild/modules/windows.py b/mesonbuild/modules/windows.py
--- a/mesonbuild/modules/windows.py
+++ b/mesonbuild/modules/windows.py
@@ -20,6 +20,7 @@
 from . import get_include_args
 from . import ModuleReturnValue
 from . import ExtensionModule
+from ..interpreter import CustomTargetHolder
 from ..interpreterbase import permittedKwargs, FeatureNewKwargs
 
 class WindowsModule(ExtensionModule):
@@ -30,13 +31,17 @@ def detect_compiler(self, compilers):
                 return compilers[l]
         raise MesonException('Resource compilation requires a C or C++ compiler.')
 
-    @FeatureNewKwargs('windows.compile_resources', '0.47.0', ['depend_files'])
-    @permittedKwargs({'args', 'include_directories', 'depend_files'})
+    @FeatureNewKwargs('windows.compile_resources', '0.47.0', ['depend_files', 'depends'])
+    @permittedKwargs({'args', 'include_directories', 'depend_files', 'depends'})
     def compile_resources(self, state, args, kwargs):
         comp = self.detect_compiler(state.compilers)
 
         extra_args = mesonlib.stringlistify(kwargs.get('args', []))
-        wrc_deps = extract_as_list(kwargs, 'depend_files', pop = True)
+        wrc_depend_files = extract_as_list(kwargs, 'depend_files', pop = True)
+        wrc_depends = extract_as_list(kwargs, 'depends', pop = True)
+        for d in wrc_depends:
+            if isinstance(d, CustomTargetHolder):
+                extra_args += get_include_args([d.outdir_include()])
         inc_dirs = extract_as_list(kwargs, 'include_directories', pop = True)
         for incd in inc_dirs:
             if not isinstance(incd.held_object, (str, build.IncludeDirs)):
@@ -85,7 +90,8 @@ def add_target(src):
                 'output': '@BASENAME@.' + suffix,
                 'input': [src],
                 'command': [rescomp] + res_args,
-                'depend_files': wrc_deps,
+                'depend_files': wrc_depend_files,
+                'depends': wrc_depends,
             }
 
             if isinstance(src, (str, mesonlib.File)):


Test patch:
diff --git a/test cases/windows/15 resources with custom target depend_files/ico/gen-ico.py b/test cases/windows/15 resources with custom target depend_files/ico/gen-ico.py
new file mode 100755
--- /dev/null
+++ b/test cases/windows/15 resources with custom target depend_files/ico/gen-ico.py	
@@ -0,0 +1,6 @@
+#!/usr/bin/env python3
+
+import sys
+
+with open(sys.argv[1], 'rb') as infile, open(sys.argv[2], 'wb') as outfile:
+    outfile.write(infile.read())
diff --git a/test cases/windows/15 resources with custom target depend_files/ico/meson.build b/test cases/windows/15 resources with custom target depend_files/ico/meson.build
new file mode 100644
--- /dev/null
+++ b/test cases/windows/15 resources with custom target depend_files/ico/meson.build	
@@ -0,0 +1,8 @@
+ico_writer = find_program('gen-ico.py')
+
+ico = custom_target('makeico',
+        input : 'sample.ico.in',
+        output : 'sample.ico',
+        command : [ico_writer, '@INPUT@', '@OUTPUT@'],
+        install : false,
+        build_always : false)
diff --git a/test cases/windows/15 resources with custom target depend_files/ico/sample.ico.in b/test cases/windows/15 resources with custom target depend_files/ico/sample.ico.in
new file mode 100644
Binary files /dev/null and b/test cases/windows/15 resources with custom target depend_files/ico/sample.ico.in differ
diff --git a/test cases/windows/15 resources with custom target depend_files/meson.build b/test cases/windows/15 resources with custom target depend_files/meson.build
new file mode 100644
--- /dev/null
+++ b/test cases/windows/15 resources with custom target depend_files/meson.build	
@@ -0,0 +1,69 @@
+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('ico')
+subdir('res')
+
+exe = executable('prog', 'prog.c',
+  res,
+  gui_app : true)
+
+test('winmain', exe)
diff --git a/test cases/windows/15 resources with custom target depend_files/prog.c b/test cases/windows/15 resources with custom target depend_files/prog.c
new file mode 100644
--- /dev/null
+++ b/test cases/windows/15 resources with custom target depend_files/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/15 resources with custom target depend_files/res/meson.build b/test cases/windows/15 resources with custom target depend_files/res/meson.build
new file mode 100644
--- /dev/null
+++ b/test cases/windows/15 resources with custom target depend_files/res/meson.build	
@@ -0,0 +1,4 @@
+win = import('windows')
+
+res = win.compile_resources('myres.rc',
+        depends: ico)
diff --git a/test cases/windows/15 resources with custom target depend_files/res/myres.rc b/test cases/windows/15 resources with custom target depend_files/res/myres.rc
new file mode 100644
--- /dev/null
+++ b/test cases/windows/15 resources with custom target depend_files/res/myres.rc	
@@ -0,0 +1,3 @@
+#include<windows.h>
+
+1 ICON "sample.ico"