[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[Qemu-devel] [RFC PATCH 23/32] qapi-options: Command line option backend
From: |
Markus Armbruster |
Subject: |
[Qemu-devel] [RFC PATCH 23/32] qapi-options: Command line option backend |
Date: |
Mon, 2 Oct 2017 17:25:43 +0200 |
New qapi generator qapi-options.py generates code for parsing command
line options into an array of QAPIOption.
TODO negative tests
Signed-off-by: Markus Armbruster <address@hidden>
---
Makefile | 11 ++-
Makefile.objs | 1 +
scripts/qapi-options.py | 199 ++++++++++++++++++++++++++++++++++++++++++++++
tests/Makefile.include | 16 +++-
tests/test-qapi-options.c | 74 +++++++++++++++++
5 files changed, 297 insertions(+), 4 deletions(-)
create mode 100644 scripts/qapi-options.py
create mode 100644 tests/test-qapi-options.c
diff --git a/Makefile b/Makefile
index 421e65d833..5e858f7295 100644
--- a/Makefile
+++ b/Makefile
@@ -56,8 +56,8 @@ GENERATED_FILES += builtin-qapi-types.h builtin-qapi-types.c
GENERATED_FILES += builtin-qapi-visit.h builtin-qapi-visit.c
GENERATED_FILES += qmp-commands.h qapi-types.h qapi-visit.h qapi-event.h
GENERATED_FILES += qmp-marshal.c qapi-types.c qapi-visit.c qapi-event.c
-GENERATED_FILES += qmp-introspect.h
-GENERATED_FILES += qmp-introspect.c
+GENERATED_FILES += qapi-options.h qmp-introspect.h
+GENERATED_FILES += qapi-options.c qmp-introspect.c
GENERATED_FILES += trace/generated-tcg-tracers.h
@@ -470,6 +470,13 @@ qapi-commands-gen: $(qapi-modules)
$(SRC_PATH)/scripts/qapi-commands.py $(qapi-p
$<, \
"GEN","$@")
+.INTERMEDIATE: qapi-options-gen
+qapi-options.h qapi-options.c: qapi-options-gen
+qapi-options-gen: $(qapi-modules) $(SRC_PATH)/scripts/qapi-options.py
$(qapi-py)
+ $(call quiet-command,$(PYTHON) $(SRC_PATH)/scripts/qapi-options.py \
+ $<, \
+ "GEN","$@")
+
.INTERMEDIATE: qapi-introspect-gen
qmp-introspect.h qmp-introspect.c: qapi-introspect-gen
qapi-introspect-gen: $(qapi-modules) $(SRC_PATH)/scripts/qapi-introspect.py
$(qapi-py)
diff --git a/Makefile.objs b/Makefile.objs
index c2c62cb462..908ec053fe 100644
--- a/Makefile.objs
+++ b/Makefile.objs
@@ -80,6 +80,7 @@ common-obj-$(CONFIG_FDT) += device_tree.o
# qapi
common-obj-y += qmp-marshal.o
+common-obj-y += qapi-options.o
common-obj-y += qmp-introspect.o
common-obj-y += qmp.o hmp.o
endif
diff --git a/scripts/qapi-options.py b/scripts/qapi-options.py
new file mode 100644
index 0000000000..240c9021c7
--- /dev/null
+++ b/scripts/qapi-options.py
@@ -0,0 +1,199 @@
+#
+# QAPI option generator
+#
+# Copyright (C) 2017 Red Hat, Inc.
+#
+# Authors:
+# Markus Armbruster <address@hidden>
+#
+# This work is licensed under the terms of the GNU GPL, version 2.
+# See the COPYING file in the top-level directory.
+
+from qapi import *
+
+
+class QAPISchemaGenOptionVisitor(QAPISchemaVisitor):
+ def __init__(self):
+ self.decl = None
+ self.defn = None
+ self._shortopts = None
+ self._longopts = None
+ self._cases = None
+
+ def visit_begin(self, schema):
+ self.decl = ''
+ self.defn = ''
+ self._shortopts = ''
+ self._longopts = ''
+ self._cases = ''
+
+ def visit_end(self):
+ if self._cases:
+ c_max = c_enum_const(args.prefix + 'QAPIOptionKind', '_MAX')
+ self.decl += mcgen('''
+%(c_prefix)sQAPIOption *%(c_prefix)sqapi_options_parse(int argc, char *argv[]);
+''',
+ c_prefix=c_name(args.prefix, protect=False))
+ self.defn += mcgen('''
+%(c_prefix)sQAPIOption *%(c_prefix)sqapi_options_parse(int argc, char *argv[])
+{
+ static const struct option longopts[] = {
+%(longopts)s {0}
+ };
+ %(c_prefix)sQAPIOption *opt = g_new(%(c_prefix)sQAPIOption, argc);
+ int nopt, longidx, ret;
+ Visitor *v;
+
+ optind = 0;
+
+ for (nopt = 0, opt[nopt].idx = 1;
+ (ret = getopt_long_only(argc, argv, %(c_shortopts)s,
+ longopts, &longidx)) >= 0;
+ opt[++nopt].idx = optind) {
+ if (ret > 255) {
+ opt[nopt].type = longopts[longidx].val - 256;
+ }
+ opt[nopt].cnt = optind - opt[nopt].idx;
+ loc_set_cmdline(argv, opt[nopt].idx, opt[nopt].cnt);
+
+ switch(ret) {
+%(cases)s
+ case '?':
+ exit(1);
+ default:
+ abort();
+ }
+ }
+
+ opt[nopt].type = %(c_max)s;
+ opt[nopt].cnt = 0;
+
+ return g_renew(%(c_prefix)sQAPIOption, opt, nopt + 1);
+}
+''',
+ c_prefix=c_name(args.prefix, protect=False),
+ c_shortopts=c_string(self._shortopts),
+ longopts=self._longopts,
+ cases=self._cases,
+ c_max=c_max)
+ self._shortopts = None
+ self._longopts = None
+ self._cases = None
+
+ def visit_option(self, name, info, arg_type, short, implied_key,
+ boxed, help_):
+ name = name[2:]
+ enum_val = c_enum_const(args.prefix + 'QAPIOptionKind', name)
+
+ push_indent(8)
+
+ self._longopts += mcgen('''
+{
+ .name = "%(name)s",
+ .has_arg = %(has_arg)s,
+ .val = %(val)s
+},
+''',
+ name=name,
+ has_arg='required_argument'
+ if arg_type else 'no_argument',
+ val='256 + ' + enum_val)
+ if short:
+ self._shortopts += short
+ if arg_type:
+ self._shortopts += ':'
+ self._cases += mcgen('''
+case '%(char)s':
+ opt[nopt].type = %(case)s;
+ /* fall through */
+''',
+ char=short, case=enum_val)
+
+ self._cases += mcgen('''
+case 256 + %(case)s:
+''',
+ case=enum_val)
+ if arg_type is None:
+ pass
+ elif isinstance(arg_type, QAPISchemaObjectType):
+ self._cases += mcgen('''
+ v = qobject_input_visitor_new_str(optarg, %(c_implied_key)s, &error_fatal);
+ visit_start_struct(v, NULL, NULL, 0, &error_fatal);
+ visit_type_%(c_type)s_members(v, &opt[nopt].u.%(c_name)s, &error_fatal);
+ visit_check_struct(v, &error_fatal);
+ visit_end_struct(v, NULL);
+ visit_free(v);
+''',
+ c_name=c_name(name), c_type=arg_type.c_name(),
+ c_implied_key=c_string(implied_key))
+ else:
+ assert isinstance(arg_type,
+ (QAPISchemaBuiltinType, QAPISchemaEnumType))
+ self._cases += mcgen('''
+ v = qobject_input_visitor_new_keyval(QOBJECT(qstring_from_str(optarg)));
+ visit_type_%(c_type)s(v, NULL, &opt[nopt].u.%(c_name)s.data, &error_fatal);
+ visit_free(v);
+''',
+ c_name=c_name(name), c_type=arg_type.c_name())
+ self._cases += mcgen('''
+ break;
+''')
+
+ pop_indent(8)
+
+
+args = common_argument_parser().parse_args()
+
+c_comment = '''
+/*
+ * QAPI command line options
+ *
+ * Copyright (C) 2017 Red Hat, Inc.
+ *
+ * This work is licensed under the terms of the GNU LGPL, version 2.1 or later.
+ * See the COPYING.LIB file in the top-level directory.
+ *
+ */
+'''
+h_comment = '''
+/*
+ * QAPI command line options
+ *
+ * Copyright (C) 2017 Red Hat, Inc.
+ *
+ * This work is licensed under the terms of the GNU LGPL, version 2.1 or later.
+ * See the COPYING.LIB file in the top-level directory.
+ *
+ */
+'''
+
+(fdef, fdecl) = open_output(args.output_dir, args.prefix,
+ 'qapi-options.c', 'qapi-options.h',
+ c_comment, h_comment)
+
+fdef.write(mcgen('''
+#include "qemu/osdep.h"
+#include <getopt.h>
+#include "%(prefix)sqapi-options.h"
+#include "%(prefix)sqapi-visit.h"
+#include "qapi/error.h"
+#include "qapi/qmp/qstring.h"
+#include "qapi/qobject-input-visitor.h"
+#include "qemu/error-report.h"
+
+''',
+ prefix=args.prefix))
+
+fdecl.write(mcgen('''
+#include "%(prefix)sqapi-types.h"
+
+''',
+ prefix=args.prefix))
+
+schema = QAPISchema(args.schema, args.prefix)
+gen = QAPISchemaGenOptionVisitor()
+schema.visit(gen)
+fdef.write(gen.defn)
+fdecl.write(gen.decl)
+
+close_output(fdef, fdecl)
diff --git a/tests/Makefile.include b/tests/Makefile.include
index 2ef5dc51f1..442de4e0aa 100644
--- a/tests/Makefile.include
+++ b/tests/Makefile.include
@@ -59,6 +59,7 @@ check-unit-y += tests/test-string-output-visitor$(EXESUF)
gcov-files-test-string-output-visitor-y = qapi/string-output-visitor.c
check-unit-y += tests/test-qmp-event$(EXESUF)
gcov-files-test-qmp-event-y += qapi/qmp-event.c
+check-unit-y += tests/test-qapi-options(EXESUF)
check-unit-y += tests/test-opts-visitor$(EXESUF)
gcov-files-test-opts-visitor-y = qapi/opts-visitor.c
check-unit-y += tests/test-coroutine$(EXESUF)
@@ -542,7 +543,7 @@ check-qapi-schema-y := $(addprefix tests/qapi-schema/,
$(qapi-schema))
GENERATED_FILES += tests/test-qapi-types.h tests/test-qapi-visit.h \
tests/test-qmp-commands.h tests/test-qapi-event.h \
- tests/test-qmp-introspect.h
+ tests/test-qapi-options.h tests/test-qmp-introspect.h
test-obj-y = tests/check-qnum.o tests/check-qstring.o tests/check-qdict.o \
tests/check-qlist.o tests/check-qnull.o \
@@ -567,7 +568,8 @@ QEMU_CFLAGS += -I$(SRC_PATH)/tests
test-util-obj-y = libqemuutil.a
test-qom-obj-y = $(qom-obj-y) $(test-util-obj-y)
test-qapi-obj-y = tests/test-qapi-visit.o tests/test-qapi-types.o \
- tests/test-qapi-event.o tests/test-qmp-introspect.o \
+ tests/test-qapi-event.o tests/test-qapi-options.o \
+ tests/test-qmp-introspect.o \
$(test-qom-obj-y)
benchmark-crypto-obj-y = $(crypto-obj-y) $(test-qom-obj-y)
test-crypto-obj-y = $(crypto-obj-y) $(test-qom-obj-y)
@@ -651,6 +653,15 @@ $(SRC_PATH)/tests/qapi-schema/qapi-schema-test.json
$(SRC_PATH)/scripts/qapi-com
-o tests -p "test-" $<, \
"GEN","$@")
+.INTERMEDIATE: tests/test-qapi-options-gen
+tests/test-qapi-options.c tests/test-qapi-options.h:
tests/test-qapi-options-gen
+tests/test-qapi-options-gen: \
+$(SRC_PATH)/tests/qapi-schema/qapi-schema-test.json
$(SRC_PATH)/scripts/qapi-options.py $(qapi-py)
+ $(call quiet-command,$(PYTHON) $(SRC_PATH)/scripts/qapi-options.py \
+ -o tests -p "test-" $<, \
+ "GEN","$@")
+tests/test-qmp-introspect.c tests/test-qmp-introspect.h :\
+
.INTERMEDIATE: tests/test-qapi-event-gen
tests/test-qapi-event.c tests/test-qapi-event.h: tests/test-qapi-event-gen ;
tests/test-qapi-event-gen: \
@@ -673,6 +684,7 @@ tests/qapi-schema/doc-good.test.texi:
$(SRC_PATH)/tests/qapi-schema/doc-good.jso
tests/test-string-output-visitor$(EXESUF): tests/test-string-output-visitor.o
$(test-qapi-obj-y)
tests/test-string-input-visitor$(EXESUF): tests/test-string-input-visitor.o
$(test-qapi-obj-y)
tests/test-qmp-event$(EXESUF): tests/test-qmp-event.o $(test-qapi-obj-y)
+tests/test-qapi-options(EXESUF): tests/test-qapi-options.o $(test-qapi-obj-y)
tests/test-qobject-output-visitor$(EXESUF):
tests/test-qobject-output-visitor.o $(test-qapi-obj-y)
tests/test-clone-visitor$(EXESUF): tests/test-clone-visitor.o
$(test-qapi-obj-y)
tests/test-qobject-input-visitor$(EXESUF): tests/test-qobject-input-visitor.o
$(test-qapi-obj-y) qmp-introspect.o
diff --git a/tests/test-qapi-options.c b/tests/test-qapi-options.c
new file mode 100644
index 0000000000..aa4394abdb
--- /dev/null
+++ b/tests/test-qapi-options.c
@@ -0,0 +1,74 @@
+#include "qemu/osdep.h"
+#include "qapi/qmp/qlit.h"
+#include "test-qapi-options.h"
+
+static void test_qapi_options_parse(void)
+{
+ static const char *argv[] = {
+ "progname",
+ "--help",
+ "-h",
+ "--opt-str", "hello",
+ "--opt-int", "123",
+ "--opt-enum", "value1",
+ "--opt-any", "hello",
+ "--opt-any", "123",
+ "--opt-struct", "sval,i=1",
+ "--opt-boxed", "integer=42",
+ NULL
+ };
+ static QLitObject qlit_hello = QLIT_QSTR("hello");
+ static QLitObject qlit_123 = QLIT_QSTR("123");
+ test_QAPIOption *opt;
+
+ opt = test_qapi_options_parse(ARRAY_SIZE(argv) - 1, (char **)argv);
+ g_assert_cmpint(opt[0].type, ==, TEST_QAPI_OPTION_KIND_HELP);
+ g_assert_cmpint(opt[0].idx, ==, 1);
+ g_assert_cmpint(opt[0].cnt, ==, 1);
+ g_assert_cmpint(opt[1].type, ==, TEST_QAPI_OPTION_KIND_HELP);
+ g_assert_cmpint(opt[1].idx, ==, 2);
+ g_assert_cmpint(opt[1].cnt, ==, 1);
+ g_assert_cmpint(opt[2].type, ==, TEST_QAPI_OPTION_KIND_OPT_STR);
+ g_assert_cmpint(opt[2].idx, ==, 3);
+ g_assert_cmpint(opt[2].cnt, ==, 2);
+ g_assert_cmpstr(opt[2].u.opt_str.data, ==, "hello");
+ g_assert_cmpint(opt[3].type, ==, TEST_QAPI_OPTION_KIND_OPT_INT);
+ g_assert_cmpint(opt[3].idx, ==, 5);
+ g_assert_cmpint(opt[3].cnt, ==, 2);
+ g_assert_cmpint(opt[3].u.opt_int.data, ==, 123);
+ g_assert_cmpint(opt[4].type, ==, TEST_QAPI_OPTION_KIND_OPT_ENUM);
+ g_assert_cmpint(opt[4].idx, ==, 7);
+ g_assert_cmpint(opt[4].cnt, ==, 2);
+ g_assert_cmpint(opt[4].u.opt_enum.data, ==, ENUM_ONE_VALUE1);
+ g_assert_cmpint(opt[5].type, ==, TEST_QAPI_OPTION_KIND_OPT_ANY);
+ g_assert_cmpint(opt[5].idx, ==, 9);
+ g_assert_cmpint(opt[5].cnt, ==, 2);
+ g_assert(qlit_equal_qobject(&qlit_hello, opt[5].u.opt_any.data));
+ g_assert_cmpint(opt[6].type, ==, TEST_QAPI_OPTION_KIND_OPT_ANY);
+ g_assert_cmpint(opt[6].idx, ==, 11);
+ g_assert_cmpint(opt[6].cnt, ==, 2);
+ g_assert(qlit_equal_qobject(&qlit_123, opt[6].u.opt_any.data));
+ g_assert_cmpint(opt[7].type, ==, TEST_QAPI_OPTION_KIND_OPT_STRUCT);
+ g_assert_cmpint(opt[7].idx, ==, 13);
+ g_assert_cmpint(opt[7].cnt, ==, 2);
+ g_assert_cmpstr(opt[7].u.opt_struct.s, ==, "sval");
+ g_assert_cmpint(opt[8].type, ==, TEST_QAPI_OPTION_KIND_OPT_BOXED);
+ g_assert_cmpint(opt[8].idx, ==, 15);
+ g_assert_cmpint(opt[8].cnt, ==, 2);
+ g_assert_cmpint(opt[8].u.opt_boxed.integer, ==, 42);
+ g_assert_cmpint(opt[9].type, ==, TEST_QAPI_OPTION_KIND__MAX);
+ g_assert_cmpint(opt[9].idx, ==, 17);
+ g_assert_cmpint(opt[9].cnt, ==, 0);
+ g_free(opt);
+}
+
+int main(int argc, char **argv)
+{
+ g_test_init(&argc, &argv, NULL);
+
+ g_test_add_func("/qapi/options-parse", test_qapi_options_parse);
+
+ g_test_run();
+
+ return 0;
+}
--
2.13.6
- [Qemu-devel] [RFC PATCH 22/32] qapi: New helper c_string(), (continued)
- [Qemu-devel] [RFC PATCH 22/32] qapi: New helper c_string(), Markus Armbruster, 2017/10/02
- [Qemu-devel] [RFC PATCH 30/32] qapi/options: QAPIfy --watchdog-action argument type, Markus Armbruster, 2017/10/02
- [Qemu-devel] [RFC PATCH 08/32] qapi: Simplify check_name() parameters, Markus Armbruster, 2017/10/02
- [Qemu-devel] [RFC PATCH 10/32] qapi: Don't run generators twice, Markus Armbruster, 2017/10/02
- [Qemu-devel] [RFC PATCH 31/32] qapi/options: QAPIfy --blockdev argument type, Markus Armbruster, 2017/10/02
- [Qemu-devel] [RFC PATCH 05/32] qapi2texi: Provide access to Texinfo markup, Markus Armbruster, 2017/10/02
- [Qemu-devel] [RFC PATCH 21/32] qapi: Define QAPIOptionKind and QAPIOption automatically, Markus Armbruster, 2017/10/02
- [Qemu-devel] [RFC PATCH 23/32] qapi-options: Command line option backend,
Markus Armbruster <=
- [Qemu-devel] [RFC PATCH 25/32] qapi-introspect: Include command line options information, Markus Armbruster, 2017/10/02
- [Qemu-devel] [RFC PATCH 20/32] qapi: Frontend for defining command line options, Markus Armbruster, 2017/10/02
- [Qemu-devel] [RFC PATCH 19/32] qapi: Accept double-quoted strings, Markus Armbruster, 2017/10/02
[Qemu-devel] [RFC PATCH 12/32] qapi: Use argparse to parse command line arguments, Markus Armbruster, 2017/10/02