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