glib (2.79.0)

(root)/
share/
glib-2.0/
codegen/
codegen_main.py
       1  # -*- Mode: Python -*-
       2  # coding=utf-8
       3  
       4  # GDBus - GLib D-Bus Library
       5  #
       6  # Copyright (C) 2008-2011 Red Hat, Inc.
       7  # Copyright (C) 2018 Iñigo Martínez <inigomartinez@gmail.com>
       8  #
       9  # This library is free software; you can redistribute it and/or
      10  # modify it under the terms of the GNU Lesser General Public
      11  # License as published by the Free Software Foundation; either
      12  # version 2.1 of the License, or (at your option) any later version.
      13  #
      14  # This library is distributed in the hope that it will be useful,
      15  # but WITHOUT ANY WARRANTY; without even the implied warranty of
      16  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
      17  # Lesser General Public License for more details.
      18  #
      19  # You should have received a copy of the GNU Lesser General
      20  # Public License along with this library; if not, see <http://www.gnu.org/licenses/>.
      21  #
      22  # Author: David Zeuthen <davidz@redhat.com>
      23  
      24  import argparse
      25  import os
      26  import sys
      27  
      28  from . import config
      29  from . import dbustypes
      30  from . import parser
      31  from . import codegen
      32  from . import codegen_docbook
      33  from . import codegen_md
      34  from . import codegen_rst
      35  from .utils import print_error, print_warning
      36  
      37  
      38  def find_arg(arg_list, arg_name):
      39      for a in arg_list:
      40          if a.name == arg_name:
      41              return a
      42      return None
      43  
      44  
      45  def find_method(iface, method):
      46      for m in iface.methods:
      47          if m.name == method:
      48              return m
      49      return None
      50  
      51  
      52  def find_signal(iface, signal):
      53      for m in iface.signals:
      54          if m.name == signal:
      55              return m
      56      return None
      57  
      58  
      59  def find_prop(iface, prop):
      60      for m in iface.properties:
      61          if m.name == prop:
      62              return m
      63      return None
      64  
      65  
      66  def apply_annotation(iface_list, iface, method, signal, prop, arg, key, value):
      67      iface_obj = None
      68      for i in iface_list:
      69          if i.name == iface:
      70              iface_obj = i
      71              break
      72  
      73      if iface_obj is None:
      74          print_error('No interface "{}"'.format(iface))
      75  
      76      target_obj = None
      77  
      78      if method:
      79          method_obj = find_method(iface_obj, method)
      80          if method_obj is None:
      81              print_error('No method "{}" on interface "{}"'.format(method, iface))
      82          if arg:
      83              arg_obj = find_arg(method_obj.in_args, arg)
      84              if arg_obj is None:
      85                  arg_obj = find_arg(method_obj.out_args, arg)
      86                  if arg_obj is None:
      87                      print_error(
      88                          'No arg "{}" on method "{}" on interface "{}"'.format(
      89                              arg, method, iface
      90                          )
      91                      )
      92              target_obj = arg_obj
      93          else:
      94              target_obj = method_obj
      95      elif signal:
      96          signal_obj = find_signal(iface_obj, signal)
      97          if signal_obj is None:
      98              print_error('No signal "{}" on interface "{}"'.format(signal, iface))
      99          if arg:
     100              arg_obj = find_arg(signal_obj.args, arg)
     101              if arg_obj is None:
     102                  print_error(
     103                      'No arg "{}" on signal "{}" on interface "{}"'.format(
     104                          arg, signal, iface
     105                      )
     106                  )
     107              target_obj = arg_obj
     108          else:
     109              target_obj = signal_obj
     110      elif prop:
     111          prop_obj = find_prop(iface_obj, prop)
     112          if prop_obj is None:
     113              print_error('No property "{}" on interface "{}"'.format(prop, iface))
     114          target_obj = prop_obj
     115      else:
     116          target_obj = iface_obj
     117      target_obj.annotations.insert(0, dbustypes.Annotation(key, value))
     118  
     119  
     120  def apply_annotations(iface_list, annotation_list):
     121      # apply annotations given on the command line
     122      for what, key, value in annotation_list:
     123          pos = what.find("::")
     124          if pos != -1:
     125              # signal
     126              iface = what[0:pos]
     127              signal = what[pos + 2 :]
     128              pos = signal.find("[")
     129              if pos != -1:
     130                  arg = signal[pos + 1 :]
     131                  signal = signal[0:pos]
     132                  pos = arg.find("]")
     133                  arg = arg[0:pos]
     134                  apply_annotation(iface_list, iface, None, signal, None, arg, key, value)
     135              else:
     136                  apply_annotation(
     137                      iface_list, iface, None, signal, None, None, key, value
     138                  )
     139          else:
     140              pos = what.find(":")
     141              if pos != -1:
     142                  # property
     143                  iface = what[0:pos]
     144                  prop = what[pos + 1 :]
     145                  apply_annotation(iface_list, iface, None, None, prop, None, key, value)
     146              else:
     147                  pos = what.find("()")
     148                  if pos != -1:
     149                      # method
     150                      combined = what[0:pos]
     151                      pos = combined.rfind(".")
     152                      iface = combined[0:pos]
     153                      method = combined[pos + 1 :]
     154                      pos = what.find("[")
     155                      if pos != -1:
     156                          arg = what[pos + 1 :]
     157                          pos = arg.find("]")
     158                          arg = arg[0:pos]
     159                          apply_annotation(
     160                              iface_list, iface, method, None, None, arg, key, value
     161                          )
     162                      else:
     163                          apply_annotation(
     164                              iface_list, iface, method, None, None, None, key, value
     165                          )
     166                  else:
     167                      # must be an interface
     168                      iface = what
     169                      apply_annotation(
     170                          iface_list, iface, None, None, None, None, key, value
     171                      )
     172  
     173  
     174  def codegen_main():
     175      arg_parser = argparse.ArgumentParser(
     176          description="D-Bus code and documentation generator"
     177      )
     178      arg_parser.add_argument(
     179          "files", metavar="FILE", nargs="+", help="D-Bus introspection XML file"
     180      )
     181      arg_parser.add_argument(
     182          "--xml-files",
     183          metavar="FILE",
     184          action="append",
     185          default=[],
     186          help=argparse.SUPPRESS,
     187      )
     188      arg_parser.add_argument(
     189          "--interface-prefix",
     190          metavar="PREFIX",
     191          default="",
     192          help="String to strip from D-Bus interface names for code and docs",
     193      )
     194      arg_parser.add_argument(
     195          "--c-namespace",
     196          metavar="NAMESPACE",
     197          default="",
     198          help="The namespace to use for generated C code",
     199      )
     200      arg_parser.add_argument(
     201          "--c-generate-object-manager",
     202          action="store_true",
     203          help="Generate a GDBusObjectManagerClient subclass when generating C code",
     204      )
     205      arg_parser.add_argument(
     206          "--c-generate-autocleanup",
     207          choices=["none", "objects", "all"],
     208          default="objects",
     209          help="Generate autocleanup support",
     210      )
     211      arg_parser.add_argument(
     212          "--generate-docbook",
     213          metavar="OUTFILES",
     214          help="Generate Docbook in OUTFILES-org.Project.IFace.xml",
     215      )
     216      arg_parser.add_argument(
     217          "--generate-md",
     218          metavar="OUTFILES",
     219          help="Generate Markdown in OUTFILES-org.Project.IFace.md",
     220      )
     221      arg_parser.add_argument(
     222          "--generate-rst",
     223          metavar="OUTFILES",
     224          help="Generate reStructuredText in OUTFILES-org.Project.IFace.rst",
     225      )
     226      arg_parser.add_argument(
     227          "--pragma-once",
     228          action="store_true",
     229          help='Use "pragma once" as the inclusion guard',
     230      )
     231      arg_parser.add_argument(
     232          "--annotate",
     233          nargs=3,
     234          action="append",
     235          metavar="WHAT KEY VALUE",
     236          help="Add annotation (may be used several times)",
     237      )
     238      arg_parser.add_argument(
     239          "--glib-min-required",
     240          metavar="VERSION",
     241          help="Minimum version of GLib to be supported by the outputted code "
     242          "(default: 2.30)",
     243      )
     244      arg_parser.add_argument(
     245          "--glib-max-allowed",
     246          metavar="VERSION",
     247          help="Maximum version of GLib to be used by the outputted code "
     248          "(default: current GLib version)",
     249      )
     250      arg_parser.add_argument(
     251          "--symbol-decorator",
     252          help="Macro used to decorate a symbol in the outputted header, "
     253          "possibly to export symbols",
     254      )
     255      arg_parser.add_argument(
     256          "--symbol-decorator-header",
     257          help="Additional header required for decorator specified by "
     258          "--symbol-decorator",
     259      )
     260      arg_parser.add_argument(
     261          "--symbol-decorator-define",
     262          help="Additional define required for decorator specified by "
     263          "--symbol-decorator",
     264      )
     265  
     266      group = arg_parser.add_mutually_exclusive_group()
     267      group.add_argument(
     268          "--generate-c-code", metavar="OUTFILES", help="Generate C code in OUTFILES.[ch]"
     269      )
     270      group.add_argument("--header", action="store_true", help="Generate C headers")
     271      group.add_argument("--body", action="store_true", help="Generate C code")
     272      group.add_argument(
     273          "--interface-info-header",
     274          action="store_true",
     275          help="Generate GDBusInterfaceInfo C header",
     276      )
     277      group.add_argument(
     278          "--interface-info-body",
     279          action="store_true",
     280          help="Generate GDBusInterfaceInfo C code",
     281      )
     282  
     283      group = arg_parser.add_mutually_exclusive_group()
     284      group.add_argument(
     285          "--output", metavar="FILE", help="Write output into the specified file"
     286      )
     287      group.add_argument(
     288          "--output-directory",
     289          metavar="OUTDIR",
     290          default="",
     291          help="Location to output generated files",
     292      )
     293  
     294      args = arg_parser.parse_args()
     295  
     296      if len(args.xml_files) > 0:
     297          print_warning(
     298              'The "--xml-files" option is deprecated; use positional arguments instead'
     299          )
     300  
     301      if (
     302          args.generate_c_code is not None
     303          or args.generate_docbook is not None
     304          or args.generate_md is not None
     305          or args.generate_rst is not None
     306      ) and args.output is not None:
     307          print_error(
     308              "Using --generate-c-code or --generate-{docbook,md,rst} and "
     309              "--output at the same time is not allowed"
     310          )
     311  
     312      if args.generate_c_code:
     313          header_name = args.generate_c_code + ".h"
     314          h_file = os.path.join(args.output_directory, header_name)
     315          args.header = True
     316          c_file = os.path.join(args.output_directory, args.generate_c_code + ".c")
     317          args.body = True
     318      elif args.header:
     319          if args.output is None:
     320              print_error("Using --header requires --output")
     321  
     322          h_file = args.output
     323          header_name = os.path.basename(h_file)
     324      elif args.body:
     325          if args.output is None:
     326              print_error("Using --body requires --output")
     327  
     328          c_file = args.output
     329          header_name = os.path.splitext(os.path.basename(c_file))[0] + ".h"
     330      elif args.interface_info_header:
     331          if args.output is None:
     332              print_error("Using --interface-info-header requires --output")
     333          if args.c_generate_object_manager:
     334              print_error(
     335                  "--c-generate-object-manager is incompatible with "
     336                  "--interface-info-header"
     337              )
     338  
     339          h_file = args.output
     340          header_name = os.path.basename(h_file)
     341      elif args.interface_info_body:
     342          if args.output is None:
     343              print_error("Using --interface-info-body requires --output")
     344          if args.c_generate_object_manager:
     345              print_error(
     346                  "--c-generate-object-manager is incompatible with "
     347                  "--interface-info-body"
     348              )
     349  
     350          c_file = args.output
     351          header_name = os.path.splitext(os.path.basename(c_file))[0] + ".h"
     352  
     353      # Check the minimum GLib version. The minimum --glib-min-required is 2.30,
     354      # because that’s when gdbus-codegen was introduced. Support 1, 2 or 3
     355      # component versions, but ignore the micro component if it’s present.
     356      if args.glib_min_required:
     357          try:
     358              parts = args.glib_min_required.split(".", 3)
     359              glib_min_required = (int(parts[0]), int(parts[1] if len(parts) > 1 else 0))
     360              # Ignore micro component, but still validate it:
     361              _ = int(parts[2] if len(parts) > 2 else 0)  # noqa: F841
     362          except (ValueError, IndexError):
     363              print_error(
     364                  "Unrecognized --glib-min-required string ‘{}".format(
     365                      args.glib_min_required
     366                  )
     367              )
     368  
     369          if glib_min_required < (2, 30):
     370              print_error(
     371                  "Invalid --glib-min-required string ‘{}’: minimum "
     372                  "version is 2.30".format(args.glib_min_required)
     373              )
     374      else:
     375          glib_min_required = (2, 30)
     376  
     377      # And the maximum GLib version.
     378      if args.glib_max_allowed:
     379          try:
     380              parts = args.glib_max_allowed.split(".", 3)
     381              glib_max_allowed = (int(parts[0]), int(parts[1] if len(parts) > 1 else 0))
     382              # Ignore micro component, but still validate it:
     383              _ = int(parts[2] if len(parts) > 2 else 0)  # noqa: F841
     384          except (ValueError, IndexError):
     385              print_error(
     386                  "Unrecognized --glib-max-allowed string ‘{}".format(
     387                      args.glib_max_allowed
     388                  )
     389              )
     390      else:
     391          glib_max_allowed = (config.MAJOR_VERSION, config.MINOR_VERSION)
     392  
     393      # Only allow --symbol-decorator-define and --symbol-decorator-header if
     394      # --symbol-decorator is used
     395      if args.symbol_decorator is None:
     396          if args.symbol_decorator_header or args.symbol_decorator_define:
     397              print_error(
     398                  "--symbol-decorator-define and --symbol-decorator-header must "
     399                  "be used with --symbol-decorator"
     400              )
     401  
     402      # Round --glib-max-allowed up to the next stable release.
     403      glib_max_allowed = (
     404          glib_max_allowed[0],
     405          glib_max_allowed[1] + (glib_max_allowed[1] % 2),
     406      )
     407  
     408      if glib_max_allowed < glib_min_required:
     409          print_error(
     410              "Invalid versions: --glib-min-required ({}) must be "
     411              "less than or equal to --glib-max-allowed ({})".format(
     412                  glib_min_required, glib_max_allowed
     413              )
     414          )
     415  
     416      all_ifaces = []
     417      input_files_basenames = []
     418      for fname in sorted(args.files + args.xml_files):
     419          with open(fname, "rb") as f:
     420              xml_data = f.read()
     421          parsed_ifaces = parser.parse_dbus_xml(
     422              xml_data, h_type_implies_unix_fd=(glib_min_required >= (2, 64))
     423          )
     424          all_ifaces.extend(parsed_ifaces)
     425          input_files_basenames.append(os.path.basename(fname))
     426  
     427      if args.annotate is not None:
     428          apply_annotations(all_ifaces, args.annotate)
     429  
     430      for i in all_ifaces:
     431          i.post_process(args.interface_prefix, args.c_namespace)
     432  
     433      docbook = args.generate_docbook
     434      docbook_gen = codegen_docbook.DocbookCodeGenerator(all_ifaces)
     435      if docbook:
     436          docbook_gen.generate(docbook, args.output_directory)
     437  
     438      md = args.generate_md
     439      md_gen = codegen_md.MdCodeGenerator(all_ifaces)
     440      if md:
     441          md_gen.generate(md, args.output_directory)
     442  
     443      rst = args.generate_rst
     444      rst_gen = codegen_rst.RstCodeGenerator(all_ifaces)
     445      if rst:
     446          rst_gen.generate(rst, args.output_directory)
     447  
     448      if args.header:
     449          with open(h_file, "w") as outfile:
     450              gen = codegen.HeaderCodeGenerator(
     451                  all_ifaces,
     452                  args.c_namespace,
     453                  args.c_generate_object_manager,
     454                  args.c_generate_autocleanup,
     455                  header_name,
     456                  input_files_basenames,
     457                  args.pragma_once,
     458                  glib_min_required,
     459                  args.symbol_decorator,
     460                  args.symbol_decorator_header,
     461                  outfile,
     462              )
     463              gen.generate()
     464  
     465      if args.body:
     466          with open(c_file, "w") as outfile:
     467              gen = codegen.CodeGenerator(
     468                  all_ifaces,
     469                  args.c_namespace,
     470                  args.c_generate_object_manager,
     471                  header_name,
     472                  input_files_basenames,
     473                  docbook_gen,
     474                  glib_min_required,
     475                  args.symbol_decorator_define,
     476                  outfile,
     477              )
     478              gen.generate()
     479  
     480      if args.interface_info_header:
     481          with open(h_file, "w") as outfile:
     482              gen = codegen.InterfaceInfoHeaderCodeGenerator(
     483                  all_ifaces,
     484                  args.c_namespace,
     485                  header_name,
     486                  input_files_basenames,
     487                  args.pragma_once,
     488                  glib_min_required,
     489                  args.symbol_decorator,
     490                  args.symbol_decorator_header,
     491                  outfile,
     492              )
     493              gen.generate()
     494  
     495      if args.interface_info_body:
     496          with open(c_file, "w") as outfile:
     497              gen = codegen.InterfaceInfoBodyCodeGenerator(
     498                  all_ifaces,
     499                  args.c_namespace,
     500                  header_name,
     501                  input_files_basenames,
     502                  glib_min_required,
     503                  args.symbol_decorator_define,
     504                  outfile,
     505              )
     506              gen.generate()
     507  
     508      sys.exit(0)
     509  
     510  
     511  if __name__ == "__main__":
     512      codegen_main()