glib/gio/gdbus-2.0/codegen/codegen_main.py
Philip Withnall 68d8f721f3
codegen: Trivially add SPDX-License-Identifier to codegen Python files
The license and copyright are already stated in human-readable form in
these files, so this should be uncontroversial.

Signed-off-by: Philip Withnall <pwithnall@gnome.org>

Helps: #1415
2024-04-17 15:31:50 +01:00

533 lines
17 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# -*- Mode: Python -*-
# coding=utf-8
# GDBus - GLib D-Bus Library
#
# Copyright (C) 2008-2011 Red Hat, Inc.
# Copyright (C) 2018 Iñigo Martínez <inigomartinez@gmail.com>
#
# SPDX-License-Identifier: LGPL-2.1-or-later
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General
# Public License along with this library; if not, see <http://www.gnu.org/licenses/>.
#
# Author: David Zeuthen <davidz@redhat.com>
import argparse
import os
import sys
from contextlib import contextmanager
from . import config
from . import dbustypes
from . import parser
from . import codegen
from . import codegen_docbook
from . import codegen_md
from . import codegen_rst
from .utils import print_error, print_warning
def find_arg(arg_list, arg_name):
for a in arg_list:
if a.name == arg_name:
return a
return None
def find_method(iface, method):
for m in iface.methods:
if m.name == method:
return m
return None
def find_signal(iface, signal):
for m in iface.signals:
if m.name == signal:
return m
return None
def find_prop(iface, prop):
for m in iface.properties:
if m.name == prop:
return m
return None
@contextmanager
def file_or_stdout(filename):
if filename is None or filename == "-":
yield sys.stdout
else:
with open(filename, "w") as outfile:
yield outfile
def apply_annotation(iface_list, iface, method, signal, prop, arg, key, value):
iface_obj = None
for i in iface_list:
if i.name == iface:
iface_obj = i
break
if iface_obj is None:
print_error('No interface "{}"'.format(iface))
target_obj = None
if method:
method_obj = find_method(iface_obj, method)
if method_obj is None:
print_error('No method "{}" on interface "{}"'.format(method, iface))
if arg:
arg_obj = find_arg(method_obj.in_args, arg)
if arg_obj is None:
arg_obj = find_arg(method_obj.out_args, arg)
if arg_obj is None:
print_error(
'No arg "{}" on method "{}" on interface "{}"'.format(
arg, method, iface
)
)
target_obj = arg_obj
else:
target_obj = method_obj
elif signal:
signal_obj = find_signal(iface_obj, signal)
if signal_obj is None:
print_error('No signal "{}" on interface "{}"'.format(signal, iface))
if arg:
arg_obj = find_arg(signal_obj.args, arg)
if arg_obj is None:
print_error(
'No arg "{}" on signal "{}" on interface "{}"'.format(
arg, signal, iface
)
)
target_obj = arg_obj
else:
target_obj = signal_obj
elif prop:
prop_obj = find_prop(iface_obj, prop)
if prop_obj is None:
print_error('No property "{}" on interface "{}"'.format(prop, iface))
target_obj = prop_obj
else:
target_obj = iface_obj
target_obj.annotations.insert(0, dbustypes.Annotation(key, value))
def apply_annotations(iface_list, annotation_list):
# apply annotations given on the command line
for what, key, value in annotation_list:
pos = what.find("::")
if pos != -1:
# signal
iface = what[0:pos]
signal = what[pos + 2 :]
pos = signal.find("[")
if pos != -1:
arg = signal[pos + 1 :]
signal = signal[0:pos]
pos = arg.find("]")
arg = arg[0:pos]
apply_annotation(iface_list, iface, None, signal, None, arg, key, value)
else:
apply_annotation(
iface_list, iface, None, signal, None, None, key, value
)
else:
pos = what.find(":")
if pos != -1:
# property
iface = what[0:pos]
prop = what[pos + 1 :]
apply_annotation(iface_list, iface, None, None, prop, None, key, value)
else:
pos = what.find("()")
if pos != -1:
# method
combined = what[0:pos]
pos = combined.rfind(".")
iface = combined[0:pos]
method = combined[pos + 1 :]
pos = what.find("[")
if pos != -1:
arg = what[pos + 1 :]
pos = arg.find("]")
arg = arg[0:pos]
apply_annotation(
iface_list, iface, method, None, None, arg, key, value
)
else:
apply_annotation(
iface_list, iface, method, None, None, None, key, value
)
else:
# must be an interface
iface = what
apply_annotation(
iface_list, iface, None, None, None, None, key, value
)
def codegen_main():
arg_parser = argparse.ArgumentParser(
description="D-Bus code and documentation generator"
)
arg_parser.add_argument(
"files", metavar="FILE", nargs="+", help="D-Bus introspection XML file"
)
arg_parser.add_argument(
"--xml-files",
metavar="FILE",
action="append",
default=[],
help=argparse.SUPPRESS,
)
arg_parser.add_argument(
"--interface-prefix",
metavar="PREFIX",
default="",
help="String to strip from D-Bus interface names for code and docs",
)
arg_parser.add_argument(
"--c-namespace",
metavar="NAMESPACE",
default="",
help="The namespace to use for generated C code",
)
arg_parser.add_argument(
"--c-generate-object-manager",
action="store_true",
help="Generate a GDBusObjectManagerClient subclass when generating C code",
)
arg_parser.add_argument(
"--c-generate-autocleanup",
choices=["none", "objects", "all"],
default="objects",
help="Generate autocleanup support",
)
arg_parser.add_argument(
"--generate-docbook",
metavar="OUTFILES",
help="Generate Docbook in OUTFILES-org.Project.IFace.xml",
)
arg_parser.add_argument(
"--generate-md",
metavar="OUTFILES",
help="Generate Markdown in OUTFILES-org.Project.IFace.md",
)
arg_parser.add_argument(
"--generate-rst",
metavar="OUTFILES",
help="Generate reStructuredText in OUTFILES-org.Project.IFace.rst",
)
arg_parser.add_argument(
"--pragma-once",
action="store_true",
help='Use "pragma once" as the inclusion guard',
)
arg_parser.add_argument(
"--annotate",
nargs=3,
action="append",
metavar="WHAT KEY VALUE",
help="Add annotation (may be used several times)",
)
arg_parser.add_argument(
"--glib-min-required",
metavar="VERSION",
help="Minimum version of GLib to be supported by the outputted code "
"(default: 2.30)",
)
arg_parser.add_argument(
"--glib-max-allowed",
metavar="VERSION",
help="Maximum version of GLib to be used by the outputted code "
"(default: current GLib version)",
)
arg_parser.add_argument(
"--symbol-decorator",
help="Macro used to decorate a symbol in the outputted header, "
"possibly to export symbols",
)
arg_parser.add_argument(
"--symbol-decorator-header",
help="Additional header required for decorator specified by "
"--symbol-decorator",
)
arg_parser.add_argument(
"--symbol-decorator-define",
help="Additional define required for decorator specified by "
"--symbol-decorator",
)
group = arg_parser.add_mutually_exclusive_group()
group.add_argument(
"--generate-c-code", metavar="OUTFILES", help="Generate C code in OUTFILES.[ch]"
)
group.add_argument("--header", action="store_true", help="Generate C headers")
group.add_argument("--body", action="store_true", help="Generate C code")
group.add_argument(
"--interface-info-header",
action="store_true",
help="Generate GDBusInterfaceInfo C header",
)
group.add_argument(
"--interface-info-body",
action="store_true",
help="Generate GDBusInterfaceInfo C code",
)
group = arg_parser.add_mutually_exclusive_group()
group.add_argument(
"--output", metavar="FILE", help="Write output into the specified file"
)
group.add_argument(
"--output-directory",
metavar="OUTDIR",
default="",
help="Location to output generated files",
)
args = arg_parser.parse_args()
if len(args.xml_files) > 0:
print_warning(
'The "--xml-files" option is deprecated; use positional arguments instead'
)
if (
args.generate_c_code is not None
or args.generate_docbook is not None
or args.generate_md is not None
or args.generate_rst is not None
) and args.output is not None:
print_error(
"Using --generate-c-code or --generate-{docbook,md,rst} and "
"--output at the same time is not allowed"
)
if args.generate_c_code:
header_name = args.generate_c_code + ".h"
h_file = os.path.join(args.output_directory, header_name)
args.header = True
c_file = os.path.join(args.output_directory, args.generate_c_code + ".c")
args.body = True
elif args.header:
if args.output is None:
print_error("Using --header requires --output")
h_file = args.output
header_name = os.path.basename(h_file)
elif args.body:
if args.output is None:
print_error("Using --body requires --output")
c_file = args.output
if c_file == "-":
header_name = ""
else:
header_name = os.path.splitext(os.path.basename(c_file))[0] + ".h"
elif args.interface_info_header:
if args.output is None:
print_error("Using --interface-info-header requires --output")
if args.c_generate_object_manager:
print_error(
"--c-generate-object-manager is incompatible with "
"--interface-info-header"
)
h_file = args.output
header_name = os.path.basename(h_file)
elif args.interface_info_body:
if args.output is None:
print_error("Using --interface-info-body requires --output")
if args.c_generate_object_manager:
print_error(
"--c-generate-object-manager is incompatible with "
"--interface-info-body"
)
c_file = args.output
if c_file == "-":
header_name = ""
else:
header_name = os.path.splitext(os.path.basename(c_file))[0] + ".h"
# Check the minimum GLib version. The minimum --glib-min-required is 2.30,
# because thats when gdbus-codegen was introduced. Support 1, 2 or 3
# component versions, but ignore the micro component if its present.
if args.glib_min_required:
try:
parts = args.glib_min_required.split(".", 3)
glib_min_required = (int(parts[0]), int(parts[1] if len(parts) > 1 else 0))
# Ignore micro component, but still validate it:
_ = int(parts[2] if len(parts) > 2 else 0) # noqa: F841
except (ValueError, IndexError):
print_error(
"Unrecognized --glib-min-required string {}".format(
args.glib_min_required
)
)
if glib_min_required < (2, 30):
print_error(
"Invalid --glib-min-required string {}: minimum "
"version is 2.30".format(args.glib_min_required)
)
else:
glib_min_required = (2, 30)
# And the maximum GLib version.
if args.glib_max_allowed:
try:
parts = args.glib_max_allowed.split(".", 3)
glib_max_allowed = (int(parts[0]), int(parts[1] if len(parts) > 1 else 0))
# Ignore micro component, but still validate it:
_ = int(parts[2] if len(parts) > 2 else 0) # noqa: F841
except (ValueError, IndexError):
print_error(
"Unrecognized --glib-max-allowed string {}".format(
args.glib_max_allowed
)
)
else:
glib_max_allowed = (config.MAJOR_VERSION, config.MINOR_VERSION)
# Only allow --symbol-decorator-define and --symbol-decorator-header if
# --symbol-decorator is used
if args.symbol_decorator is None:
if args.symbol_decorator_header or args.symbol_decorator_define:
print_error(
"--symbol-decorator-define and --symbol-decorator-header must "
"be used with --symbol-decorator"
)
# Round --glib-max-allowed up to the next stable release.
glib_max_allowed = (
glib_max_allowed[0],
glib_max_allowed[1] + (glib_max_allowed[1] % 2),
)
if glib_max_allowed < glib_min_required:
print_error(
"Invalid versions: --glib-min-required ({}) must be "
"less than or equal to --glib-max-allowed ({})".format(
glib_min_required, glib_max_allowed
)
)
all_ifaces = []
input_files_basenames = []
for fname in sorted(args.files + args.xml_files):
with open(fname, "rb") as f:
xml_data = f.read()
parsed_ifaces = parser.parse_dbus_xml(
xml_data, h_type_implies_unix_fd=(glib_min_required >= (2, 64))
)
all_ifaces.extend(parsed_ifaces)
input_files_basenames.append(os.path.basename(fname))
if args.annotate is not None:
apply_annotations(all_ifaces, args.annotate)
for i in all_ifaces:
i.post_process(args.interface_prefix, args.c_namespace)
docbook = args.generate_docbook
docbook_gen = codegen_docbook.DocbookCodeGenerator(all_ifaces)
if docbook:
docbook_gen.generate(docbook, args.output_directory)
md = args.generate_md
md_gen = codegen_md.MdCodeGenerator(all_ifaces)
if md:
md_gen.generate(md, args.output_directory)
rst = args.generate_rst
rst_gen = codegen_rst.RstCodeGenerator(all_ifaces)
if rst:
rst_gen.generate(rst, args.output_directory)
if args.header:
with file_or_stdout(h_file) as outfile:
gen = codegen.HeaderCodeGenerator(
all_ifaces,
args.c_namespace,
args.c_generate_object_manager,
args.c_generate_autocleanup,
header_name,
input_files_basenames,
args.pragma_once,
glib_min_required,
args.symbol_decorator,
args.symbol_decorator_header,
outfile,
)
gen.generate()
if args.body:
with file_or_stdout(c_file) as outfile:
gen = codegen.CodeGenerator(
all_ifaces,
args.c_namespace,
args.c_generate_object_manager,
header_name,
input_files_basenames,
docbook_gen,
glib_min_required,
args.symbol_decorator_define,
outfile,
)
gen.generate()
if args.interface_info_header:
with file_or_stdout(h_file) as outfile:
gen = codegen.InterfaceInfoHeaderCodeGenerator(
all_ifaces,
args.c_namespace,
header_name,
input_files_basenames,
args.pragma_once,
glib_min_required,
args.symbol_decorator,
args.symbol_decorator_header,
outfile,
)
gen.generate()
if args.interface_info_body:
with file_or_stdout(c_file) as outfile:
gen = codegen.InterfaceInfoBodyCodeGenerator(
all_ifaces,
args.c_namespace,
header_name,
input_files_basenames,
glib_min_required,
args.symbol_decorator_define,
outfile,
)
gen.generate()
sys.exit(0)
if __name__ == "__main__":
codegen_main()