Repository: mesonbuild/meson

Issue:
Add annotation option support to gnome.gdbus_codegen
gdbus-codegen has an option to inject D-Bus annotations into the given XML files. The option works as follows:

`--annotate ELEMENT KEY VALUE`

This is the example at [GNOME's developer's corner](https://developer.gnome.org/gio/stable/gdbus-codegen.html):

`
gdbus-codegen --c-namespace MyApp \
  --generate-c-code myapp-generated                         \
  --annotate "org.project.InterfaceName" org.gtk.GDBus.C.Name MyFrobnicator                \
  --annotate "org.project.InterfaceName:Property" bar bat                                                    \
  --annotate "org.project.InterfaceName.Method()" org.freedesktop.DBus.Deprecated true \
  --annotate "org.project.InterfaceName.Method()[arg_name]" snake hiss                            \
  --annotate "org.project.InterfaceName::Signal" cat meow                                                    \
  --annotate "org.project.InterfaceName::Signal[arg_name]" dog wuff                                   \
  myapp-dbus-interfaces.xml
`

I have found it when porting [gnome-control-center](https://git.gnome.org/browse/gnome-control-center/tree/panels/user-accounts/Makefile.am?h=wip/inigomartinez/meson#n79). I tried to use a [custom_target as a workaround](https://git.gnome.org/browse/gnome-control-center/tree/panels/user-accounts/meson.build?h=wip/inigomartinez/meson#n122), but it doesn't work. The main problem seems to be the "multivalue" option.


Discussion:
Here goes a small snippet when I tried to fix the issue. I don't know what is going on with strings in this command which makes the code not to work.

```diff
diff --git a/mesonbuild/modules/gnome.py b/mesonbuild/modules/gnome.py
index 1632dae1..b4486031 100644
--- a/mesonbuild/modules/gnome.py
+++ b/mesonbuild/modules/gnome.py
@@ -827,7 +827,7 @@ class GnomeModule(ExtensionModule):
 
         return []
 
-    @permittedKwargs({'interface_prefix', 'namespace', 'object_manager', 'build_by_default'})
+    @permittedKwargs({'interface_prefix', 'namespace', 'object_manager', 'build_by_default', 'annotate'})
     def gdbus_codegen(self, state, args, kwargs):
         if len(args) != 2:
             raise MesonException('Gdbus_codegen takes two arguments, name and xml file.')
@@ -841,6 +841,14 @@ class GnomeModule(ExtensionModule):
             cmd += ['--c-namespace', kwargs.pop('namespace')]
         if kwargs.get('object_manager', False):
             cmd += ['--c-generate-object-manager']
+        if 'annotate' in kwargs:
+            annotations = kwargs.pop('annotate')
+            if not isinstance(annotations, list):
+                annotations = [annotations]
+            for annotation in annotations:
+                if not isinstance(annotation, str):
+                    raise MesonException('annotate values must be strings')
+                cmd += ['--annotate', annotation]
 
         # https://git.gnome.org/browse/glib/commit/?id=ee09bb704fe9ccb24d92dd86696a0e6bb8f0dc1a
         if mesonlib.version_compare(self._get_native_glib_version(state), '>= 2.51.3'):
```

The documentation states that the `annotate` option can be used multiple times, so my approach was to support it as a list, holding multiple strings, one for every `annotate` option, with the full option value (I mean `ELEMENT KEY VALUE`). Hope it's useful.

PS: Code updated to source head at d220683787182923a06525fe46e636b9c5d88ec0
From the documentation, `ELEMENT KEY VALUE` must be three separate arguments, not one. `['--annotate', annotation]` only passes one argument.
Also passing this will be bit ugly no matter what I think, maybe something like this:

```meson
gnome.gdbus_codegen(
  annotations: [
    ['element', 'key', 'val'],
    ['foo', 'bar', 'baz'],
 ]
)
```
> From the documentation, ELEMENT KEY VALUE must be three separate arguments, not one. ['--annotate', annotation] only passes one argument.

@QuLogic Atlthough I knew that they should be three separate arguments, something is happening with this command. I tried different approaches to solve this, being [custom_target](https://git.gnome.org/browse/gnome-control-center/tree/panels/user-accounts/meson.build?h=wip/inigomartinez/meson#n122) one of them. It was just a wordaround, until the proper solution was implemented.

I used it as follows, which would replicate the original command:

```meson
common_sources += custom_target(
  realmd_gen,
  input: 'data/' + realmd_namespace + '.xml',
  output: realmd_gen,
  command: [
    find_program('gdbus-codegen'),
    '--interface-prefix', realmd_namespace + '.',
    '--generate-c-code', '@OUTPUT@',
    '--c-namespace', 'UmRealm',
    '--annotate', '"org.freedesktop.realmd.Realm"', 'org.gtk.GDBus.C.Name', 'Common',
    '--c-generate-object-manager', '@INPUT@'
  ]
)
```

The generated ninja command is as follows:

```
build panels/user-accounts/um-realm-generated: CUSTOM_COMMAND ../panels/user-accounts/data/org.freedesktop.realmd.xml | /home/imartinez/jhbuild/install/bin/gdbus-codegen
 COMMAND = /home/imartinez/jhbuild/install/bin/gdbus-codegen --interface-prefix org.freedesktop.realmd. --generate-c-code panels/user-accounts/um-realm-generated --c-namespace UmRealm --annotate '"org.freedesktop.realmd.Realm"' org.gtk.GDBus.C.Name Common --c-generate-object-manager ../panels/user-accounts/data/org.freedesktop.realmd.xml
 description = Generating$ um-realm-generated$ with$ a$ custom$ command.
```

This produces the following result:

```
gdbus-codegen --interface-prefix org.freedesktop.realmd. --generate-c-code panels/user-accounts/um-realm-generated --c-namespace UmRealm --annotate '"org.freedesktop.realmd.Realm"' org.gtk.GDBus.C.Name Common --c-generate-object-manager ../panels/user-accounts/data/org.freedesktop.realmd.xml
Traceback (most recent call last):
  File "gdbus-codegen", line 39, in <module>
    sys.exit(codegen_main.codegen_main())
  File "/usr/share/glib-2.0/codegen/codegen_main.py", line 179, in codegen_main
    apply_annotations(all_ifaces, opts.annotate)
  File "/usr/share/glib-2.0/codegen/codegen_main.py", line 146, in apply_annotations
    apply_annotation(iface_list, iface, None, None, None, None, key, value)
  File "/usr/share/glib-2.0/codegen/codegen_main.py", line 65, in apply_annotation
    raise RuntimeError('No interface %s'%iface)
RuntimeError: No interface "org.freedesktop.realmd.Realm"
```

I don't know why, but using them as separate parameters doesn't work. When trying to implement the proper solution, I thought of using the `ELEMENT KEY VALUE` parameter as a single parameter, to ease its implementation, but I have not made it work either.
Why are you adding extra quotes?
> Also passing this will be bit ugly no matter what I think, maybe something like this:
>
> ```
> gnome.gdbus_codegen(
>   annotations: [
>     ['element', 'key', 'val'],
>     ['foo', 'bar', 'baz'],
>  ]
> )
> ```

@TingPing I thought about this approach, but it seemed a bit more complex. The parameter should be a list of lists, and these lists should have just 3 elements for `ELEMENT KEY VALUE` parameters.

However I implemented it in the following way, which is expecting a list of lists, holding 3 strings each, but I don't control if is not a list of lists, or if the parameters are strings or not:

```python
if 'annotate' in kwargs:
    annots = kwargs.pop('annotate')
    if not isinstance(annots, list):
        raise MesonException(annots + ' values must be a list.')
    for annot in annots:
        if not isinstance(annot, list) or len(annot) != 3:
          raise MesonException(annot + ' values must be a list of 3 values.')
        cmd += ['--annotate', annot]
```

It produces the following ninja command:

```
build panels/user-accounts/um-realm-generated.c panels/user-accounts/um-realm-generated.h: CUSTOM_COMMAND ../panels/user-accounts/data/org.freedesktop.realmd.xml | /home/imartinez/jhbuild/install/bin/gdbus-codegen
 COMMAND = /home/imartinez/jhbuild/install/bin/gdbus-codegen --interface-prefix org.freedesktop.realmd. --c-namespace UmRealm --c-generate-object-manager --annotate '"org.freedesktop.realmd.Realm"' org.gtk.GDBus.C.Name Common --output-directory panels/user-accounts --generate-c-code um-realm-generated ../panels/user-accounts/data/org.freedesktop.realmd.xml
 description = Generating$ um-realm-generated-gdbus$ with$ a$ custom$ command.
```

This ends with the same problem as the [previous comment](https://github.com/mesonbuild/meson/issues/2123#issuecomment-318597923).
> Why are you adding extra quotes?

@QuLogic I don't know exactly what the program does when using the `annotate` option, but the result changes if the extra quotes are removed. However the program doesn't seem to fail.
Just in case it helps, another case of software using `annotate` option is [gnome-photos](https://github.com/GNOME/gnome-photos). It uses the [annotate option](https://github.com/GNOME/gnome-photos/blob/master/src/Makefile.am#L580) to generate [photos-dleyna-renderer-device.\[ch\]](https://github.com/GNOME/gnome-photos/blob/master/src/Makefile.am#L575) files.

Resolving patch:
diff --git a/mesonbuild/modules/gnome.py b/mesonbuild/modules/gnome.py
--- a/mesonbuild/modules/gnome.py
+++ b/mesonbuild/modules/gnome.py
@@ -827,7 +827,7 @@ def _unpack_args(arg, kwarg_name, kwargs, expend_file_state=None):
 
         return []
 
-    @permittedKwargs({'interface_prefix', 'namespace', 'object_manager', 'build_by_default'})
+    @permittedKwargs({'interface_prefix', 'namespace', 'object_manager', 'build_by_default', 'annotations'})
     def gdbus_codegen(self, state, args, kwargs):
         if len(args) != 2:
             raise MesonException('Gdbus_codegen takes two arguments, name and xml file.')
@@ -842,6 +842,18 @@ def gdbus_codegen(self, state, args, kwargs):
         if kwargs.get('object_manager', False):
             cmd += ['--c-generate-object-manager']
 
+        # Annotations are a bit ugly in that they are a list of lists of strings...
+        annotations = kwargs.pop('annotations', [])
+        if not isinstance(annotations, list):
+            raise MesonException('annotations takes a list')
+        if annotations and isinstance(annotations, list) and not isinstance(annotations[0], list):
+            annotations = [annotations]
+
+        for annotation in annotations:
+            if len(annotation) != 3 or not all(isinstance(i, str) for i in annotation):
+                raise MesonException('Annotations must be made up of 3 strings for ELEMENT, KEY, and VALUE')
+            cmd += ['--annotate'] + annotation
+
         # https://git.gnome.org/browse/glib/commit/?id=ee09bb704fe9ccb24d92dd86696a0e6bb8f0dc1a
         if mesonlib.version_compare(self._get_native_glib_version(state), '>= 2.51.3'):
             cmd += ['--output-directory', '@OUTDIR@', '--generate-c-code', namebase, '@INPUT@']


Test patch:
diff --git a/test cases/frameworks/7 gnome/gdbus/meson.build b/test cases/frameworks/7 gnome/gdbus/meson.build
--- a/test cases/frameworks/7 gnome/gdbus/meson.build	
+++ b/test cases/frameworks/7 gnome/gdbus/meson.build	
@@ -1,6 +1,10 @@
 gdbus_src = gnome.gdbus_codegen('generated-gdbus', 'com.example.Sample.xml',
   interface_prefix : 'com.example.',
-  namespace : 'Sample')
+  namespace : 'Sample',
+  annotations : [
+    ['com.example.Hello()', 'org.freedesktop.DBus.Deprecated', 'true']
+  ]
+)
 
 gdbus_exe = executable('gdbus-test', 'gdbusprog.c',
   gdbus_src,