Repository: mesonbuild/meson

Issue:
Using generated files as qt resources fails with VS backend
Seems that using 'File' as qresource argument is not supported.

Usecase: including files generated at build time into resources, for example translations.

```meson
cfg = configuration_data()

#generate some files
#set their paths into configuration

rc_file = configure_file(
  configuration : cfg,
  input : 'test.qrc.in',
  output : 'test.qrc',
)

processed = qt5_mod.preprocess(
  qresources : rc_file,
)
```

Tried to implement it. Get ninja error about no rule to make resulting rc file.


Discussion:
Hi @dndanik, it might be related to #2438 I'll check this later.
#2438 fix allows using 'File' as qresources . But it doesn't work with configured files. It seems that it produces invalid ninja dependency.
Ok I'll investigate that. I guess that the object returned by configure_file is neither a string nor a File.
Using absolute path works around this problem.

```meson
cfg = configuration_data()

#generate some files
#set their paths into configuration

rc_file = configure_file(
  configuration : cfg,
  input : 'test.qrc.in',
  output : 'test.qrc',
)

processed = qt5_mod.preprocess(
  qresources : join_paths(meson.current_build_dir(), 'test.qrc'),
)
```

Resolving patch:
diff --git a/mesonbuild/modules/qt.py b/mesonbuild/modules/qt.py
--- a/mesonbuild/modules/qt.py
+++ b/mesonbuild/modules/qt.py
@@ -15,7 +15,7 @@
 import os
 from .. import mlog
 from .. import build
-from ..mesonlib import MesonException, Popen_safe, extract_as_list
+from ..mesonlib import MesonException, Popen_safe, extract_as_list, File
 from ..dependencies import Qt4Dependency, Qt5Dependency
 import xml.etree.ElementTree as ET
 from . import ModuleReturnValue, get_include_args
@@ -71,19 +71,47 @@ def _detect_tools(self, env, method):
                 mlog.log(' {}:'.format(compiler_name.lower()), mlog.red('NO'))
         self.tools_detected = True
 
-    def parse_qrc(self, state, fname):
-        abspath = os.path.join(state.environment.source_dir, state.subdir, fname)
-        relative_part = os.path.dirname(fname)
+    def parse_qrc(self, state, rcc_file):
+        if type(rcc_file) is str:
+            abspath = os.path.join(state.environment.source_dir, state.subdir, rcc_file)
+            rcc_dirname = os.path.dirname(abspath)
+        elif type(rcc_file) is File:
+            abspath = rcc_file.absolute_path(state.environment.source_dir, state.environment.build_dir)
+            rcc_dirname = os.path.dirname(abspath)
+
         try:
             tree = ET.parse(abspath)
             root = tree.getroot()
             result = []
             for child in root[0]:
                 if child.tag != 'file':
-                    mlog.warning("malformed rcc file: ", os.path.join(state.subdir, fname))
+                    mlog.warning("malformed rcc file: ", os.path.join(state.subdir, rcc_file))
                     break
                 else:
-                    result.append(os.path.join(relative_part, child.text))
+                    resource_path = child.text
+                    # We need to guess if the pointed resource is:
+                    #   a) in build directory -> implies a generated file
+                    #   b) in source directory
+                    #   c) somewhere else external dependency file to bundle
+                    #
+                    # Also from qrc documentation: relative path are always from qrc file
+                    # So relative path must always be computed from qrc file !
+                    if os.path.isabs(resource_path):
+                        # a)
+                        if resource_path.startswith(os.path.abspath(state.environment.build_dir)):
+                            resource_relpath = os.path.relpath(resource_path, state.environment.build_dir)
+                            result.append(File(is_built=True, subdir='', fname=resource_relpath))
+                        # either b) or c)
+                        else:
+                            result.append(File(is_built=False, subdir=state.subdir, fname=resource_path))
+                    else:
+                        path_from_rcc = os.path.normpath(os.path.join(rcc_dirname, resource_path))
+                        # a)
+                        if path_from_rcc.startswith(state.environment.build_dir):
+                            result.append(File(is_built=True, subdir=state.subdir, fname=resource_path))
+                        # b)
+                        else:
+                            result.append(File(is_built=False, subdir=state.subdir, fname=path_from_rcc))
             return result
         except Exception:
             return []
@@ -102,11 +130,11 @@ def preprocess(self, state, args, kwargs):
         if len(rcc_files) > 0:
             if not self.rcc.found():
                 raise MesonException(err_msg.format('RCC', 'rcc-qt{}'.format(self.qt_version), self.qt_version))
-            qrc_deps = []
-            for i in rcc_files:
-                qrc_deps += self.parse_qrc(state, i)
             # custom output name set? -> one output file, multiple otherwise
             if len(args) > 0:
+                qrc_deps = []
+                for i in rcc_files:
+                    qrc_deps += self.parse_qrc(state, i)
                 name = args[0]
                 rcc_kwargs = {'input': rcc_files,
                               'output': name + '.cpp',
@@ -116,7 +144,11 @@ def preprocess(self, state, args, kwargs):
                 sources.append(res_target)
             else:
                 for rcc_file in rcc_files:
-                    basename = os.path.basename(rcc_file)
+                    qrc_deps = self.parse_qrc(state, rcc_file)
+                    if type(rcc_file) is str:
+                        basename = os.path.basename(rcc_file)
+                    elif type(rcc_file) is File:
+                        basename = os.path.basename(rcc_file.fname)
                     name = 'qt' + str(self.qt_version) + '-' + basename.replace('.', '_')
                     rcc_kwargs = {'input': rcc_file,
                                   'output': name + '.cpp',


Test patch:
diff --git a/test cases/frameworks/4 qt/meson.build b/test cases/frameworks/4 qt/meson.build
--- a/test cases/frameworks/4 qt/meson.build	
+++ b/test cases/frameworks/4 qt/meson.build	
@@ -56,7 +56,7 @@ foreach qt : ['qt4', 'qt5']
     endif
 
     # Test that setting a unique name with a positional argument works
-    qtmodule.preprocess(qt + 'teststuff', qresources : ['stuff.qrc', 'stuff2.qrc'], method : get_option('method'))
+    qtmodule.preprocess(qt + 'teststuff', qresources : files(['stuff.qrc', 'stuff2.qrc']), method : get_option('method'))
 
     qexe = executable(qt + 'app',
       sources : ['main.cpp', 'mainWindow.cpp', # Sources that don't need preprocessing.
diff --git a/test cases/frameworks/4 qt/subfolder/generator.py b/test cases/frameworks/4 qt/subfolder/generator.py
new file mode 100644
--- /dev/null
+++ b/test cases/frameworks/4 qt/subfolder/generator.py	
@@ -0,0 +1,6 @@
+#!/usr/bin/env python3
+import sys
+
+if len(sys.argv) > 1:
+    with open(sys.argv[1], "w") as output:
+        output.write("Hello World")
diff --git a/test cases/frameworks/4 qt/subfolder/main.cpp b/test cases/frameworks/4 qt/subfolder/main.cpp
--- a/test cases/frameworks/4 qt/subfolder/main.cpp	
+++ b/test cases/frameworks/4 qt/subfolder/main.cpp	
@@ -1,9 +1,28 @@
 #include <QImage>
+#include <QFile>
+#include <QString>
 
 int main(int argc, char **argv) {
+  #ifndef UNITY_BUILD
   Q_INIT_RESOURCE(stuff3);
-  QImage qi(":/thing.png");
-  if(qi.width() != 640) {
+  Q_INIT_RESOURCE(stuff4);
+  #endif
+
+  for(auto fname:{":/thing.png", ":/thing4.png"})
+  {
+    QImage img1(fname);
+    if(img1.width() != 640) {
+        return 1;
+    }
+  }
+
+  for(auto fname:{":/txt_resource.txt",":/txt_resource2.txt"})
+  {
+    QFile file(fname);
+    if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
+      return 1;
+    QString line = file.readLine();
+    if(line.compare("Hello World"))
       return 1;
   }
   return 0;
diff --git a/test cases/frameworks/4 qt/subfolder/meson.build b/test cases/frameworks/4 qt/subfolder/meson.build
--- a/test cases/frameworks/4 qt/subfolder/meson.build	
+++ b/test cases/frameworks/4 qt/subfolder/meson.build	
@@ -1,4 +1,32 @@
 
-qresources = qtmodule.preprocess(qresources : 'resources/stuff3.qrc')
+simple_gen = find_program('generator.py', required : true)
 
-app = executable('subfolder', 'main.cpp', qresources, dependencies : qtdep)
+txt_resource = custom_target('txt_resource',
+  output : 'txt_resource.txt',
+  command : [simple_gen, '@OUTPUT@'],
+)
+
+cfg = configuration_data()
+
+cfg.set('filepath', meson.current_source_dir()+'/../thing2.png')
+cfg.set('txt_resource', txt_resource.full_path())
+# here we abuse the system by guessing build dir layout
+cfg.set('txt_resource2', 'txt_resource.txt')
+
+
+rc_file = configure_file(
+  configuration : cfg,
+  input : 'resources/stuff4.qrc.in',
+  output : 'stuff4.qrc',
+)
+
+extra_cpp_args = []
+if meson.is_unity()
+  extra_cpp_args += '-DUNITY_BUILD'
+  qresources = qtmodule.preprocess(qt + '_subfolder_unity_ressource',qresources : ['resources/stuff3.qrc', rc_file])
+else
+  qresources = qtmodule.preprocess(qresources : ['resources/stuff3.qrc', rc_file])
+endif
+
+app = executable('subfolder', 'main.cpp', qresources, dependencies : qtdep, cpp_args: extra_cpp_args)
+test(qt + 'subfolder', app)
diff --git a/test cases/frameworks/4 qt/subfolder/resources/stuff4.qrc.in b/test cases/frameworks/4 qt/subfolder/resources/stuff4.qrc.in
new file mode 100644
--- /dev/null
+++ b/test cases/frameworks/4 qt/subfolder/resources/stuff4.qrc.in	
@@ -0,0 +1,8 @@
+<!DOCTYPE RCC>
+<RCC version="1.0">
+  <qresource>
+      <file alias="thing4.png">@filepath@</file>
+      <file alias="txt_resource.txt">@txt_resource@</file>
+      <file alias="txt_resource2.txt">@txt_resource2@</file>
+  </qresource>
+</RCC>