diff mbox series

[RFC,06/21] qapi-gen: New common driver for code and doc generators

Message ID 20180202130336.24719-7-armbru@redhat.com
State New
Headers show
Series Modularize generated QAPI code | expand

Commit Message

Markus Armbruster Feb. 2, 2018, 1:03 p.m. UTC
Whenever qapi-schema.json changes, we run six programs eleven times to
update eleven files.  This is silly.  Replace the six programs by a
single program that spits out all eleven files.

Signed-off-by: Markus Armbruster <armbru@redhat.com>
---
 Makefile                                           | 86 ++++++++++------------
 scripts/qapi-gen.py                                | 41 +++++++++++
 scripts/qapi/__init__.py                           |  0
 scripts/{qapi-commands.py => qapi/commands.py}     | 23 ++----
 scripts/{qapi.py => qapi/common.py}                |  0
 scripts/{qapi2texi.py => qapi/doc.py}              | 29 ++------
 scripts/{qapi-event.py => qapi/events.py}          | 23 ++----
 scripts/{qapi-introspect.py => qapi/introspect.py} | 32 ++------
 scripts/{qapi-types.py => qapi/types.py}           | 34 ++-------
 scripts/{qapi-visit.py => qapi/visit.py}           | 34 ++-------
 tests/Makefile.include                             | 56 +++++++-------
 tests/qapi-schema/test-qapi.py                     |  2 +-
 12 files changed, 140 insertions(+), 220 deletions(-)
 create mode 100755 scripts/qapi-gen.py
 create mode 100644 scripts/qapi/__init__.py
 rename scripts/{qapi-commands.py => qapi/commands.py} (94%)
 rename scripts/{qapi.py => qapi/common.py} (100%)
 rename scripts/{qapi2texi.py => qapi/doc.py} (92%)
 mode change 100755 => 100644
 rename scripts/{qapi-event.py => qapi/events.py} (92%)
 rename scripts/{qapi-introspect.py => qapi/introspect.py} (90%)
 rename scripts/{qapi-types.py => qapi/types.py} (90%)
 rename scripts/{qapi-visit.py => qapi/visit.py} (92%)

Comments

Eric Blake Feb. 2, 2018, 7:27 p.m. UTC | #1
On 02/02/2018 07:03 AM, Markus Armbruster wrote:
> Whenever qapi-schema.json changes, we run six programs eleven times to
> update eleven files.  This is silly.  Replace the six programs by a
> single program that spits out all eleven files.

Yay, about time!

One program, but still invoked multiple times:

> 
> Signed-off-by: Markus Armbruster <armbru@redhat.com>
> ---
>  Makefile                                           | 86 ++++++++++------------
>  scripts/qapi-gen.py                                | 41 +++++++++++
>  scripts/qapi/__init__.py                           |  0
>  scripts/{qapi-commands.py => qapi/commands.py}     | 23 ++----
>  scripts/{qapi.py => qapi/common.py}                |  0
>  scripts/{qapi2texi.py => qapi/doc.py}              | 29 ++------
>  scripts/{qapi-event.py => qapi/events.py}          | 23 ++----
>  scripts/{qapi-introspect.py => qapi/introspect.py} | 32 ++------
>  scripts/{qapi-types.py => qapi/types.py}           | 34 ++-------
>  scripts/{qapi-visit.py => qapi/visit.py}           | 34 ++-------

Maybe the commit message should mention:

This requires moving some files around for proper use in python.

>  tests/Makefile.include                             | 56 +++++++-------
>  tests/qapi-schema/test-qapi.py                     |  2 +-
>  12 files changed, 140 insertions(+), 220 deletions(-)
>  create mode 100755 scripts/qapi-gen.py
>  create mode 100644 scripts/qapi/__init__.py
>  rename scripts/{qapi-commands.py => qapi/commands.py} (94%)
>  rename scripts/{qapi.py => qapi/common.py} (100%)
>  rename scripts/{qapi2texi.py => qapi/doc.py} (92%)
>  mode change 100755 => 100644

Unintinentional?  We're inconsistent on which of our *.py files are
executable in git.  Does it matter whether a file that is designed for
use as a module is marked executable (if so, perhaps 5/21 should be
adding the x attribute on other files, rather than this patch removing
it on the doc generator).

> +++ b/Makefile

> +qga/qapi-generated/qga-qapi-types.c qga/qapi-generated/qga-qapi-types.h \
> +qga/qapi-generated/qga-qapi-visit.c qga/qapi-generated/qga-qapi-visit.h \
> +qga/qapi-generated/qga-qmp-commands.h qga/qapi-generated/qga-qmp-marshal.c \
> +qga/qapi-generated/qga-qapi.texi: \
> +qga/qapi-generated/qapi-gen-timestamp ;
> +qga/qapi-generated/qapi-gen-timestamp: $(SRC_PATH)/qga/qapi-schema.json $(qapi-py)
> +	$(call quiet-command,$(PYTHON) $(SRC_PATH)/scripts/qapi-gen.py \
> +		-o qga/qapi-generated -p "qga-" $<, \
> +		"GEN","$(@:%-timestamp=%)")
> +	@>$@

once for qga,


> +qapi-types.c qapi-types.h \
> +qapi-visit.c qapi-visit.h \
> +qmp-commands.h qmp-marshal.c \
> +qapi-event.c qapi-event.h \
> +qmp-introspect.h qmp-introspect.c \
> +qapi.texi: \
> +qapi-gen-timestamp ;
> +qapi-gen-timestamp: $(qapi-modules) $(qapi-py)
> +	$(call quiet-command,$(PYTHON) $(SRC_PATH)/scripts/qapi-gen.py \
> +		-o "." -b $<, \
> +		"GEN","$(@:%-timestamp=%)")
> +	@>$@

and again for the main level.  Still, a definite improvement!

> +++ b/scripts/qapi-gen.py

> +def main(argv):
> +    (input_file, output_dir, do_c, do_h, prefix, opts) = \
> +        parse_command_line('bu', ['builtins', 'unmask-non-abi-names'])
> +
> +    opt_builtins = False
> +    opt_unmask = False
> +
> +    for o, a in opts:
> +        if o in ('-b', '--builtins'):
> +            opt_builtins = True
> +        if o in ('-u', '--unmask-non-abi-names'):
> +            opt_unmask = True
> +
> +    schema = QAPISchema(input_file)
> +
> +    gen_types(schema, output_dir, prefix, opt_builtins)
> +    gen_visit(schema, output_dir, prefix, opt_builtins)
> +    gen_commands(schema, output_dir, prefix)
> +    gen_events(schema, output_dir, prefix)
> +    gen_introspect(schema, output_dir, prefix, opt_unmask)
> +    gen_doc(schema, output_dir, prefix)

Rather simple, but definitely nicer all in one python file than as a
series of make rules.

> @@ -255,13 +255,8 @@ class QAPISchemaGenCommandVisitor(QAPISchemaVisitor):
>          self._regy += gen_register_command(name, success_response)
>  
>  
> -def main(argv):
> -    (input_file, output_dir, do_c, do_h, prefix, opts) = parse_command_line()
> -
> -    blurb = '''
> - * Schema-defined QAPI/QMP commands
> -'''
> -
> +def gen_commands(schema, output_dir, prefix):
> +    blurb = ' * Schema-defined QAPI/QMP commands'

Some churn on the definition of blurb; worth tidying this in the
introduction earlier in the series?

> rename to scripts/qapi/introspect.py
> index 09e7d1f140..2153060f2c 100644
> --- a/scripts/qapi-introspect.py
> +++ b/scripts/qapi/introspect.py
> @@ -10,7 +10,7 @@ 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 *
> +from qapi.common import *
>  
>  
>  # Caveman's json.dumps() replacement (we're stuck at Python 2.4)
> @@ -168,22 +168,8 @@ const char %(c_name)s[] = %(c_string)s;
>          self._gen_json(name, 'event', {'arg-type': self._use_type(arg_type)})
>  
>  
> -def main(argv):
> -    # Debugging aid: unmask QAPI schema's type names
> -    # We normally mask them, because they're not QMP wire ABI
> -    opt_unmask = False
> -
> -    (input_file, output_dir, do_c, do_h, prefix, opts) = \
> -        parse_command_line('u', ['unmask-non-abi-names'])

Hmm - we didn't have any docs about this hidden command line option; I
see you still preserved it, but it may be worth mentioning in your
pending doc updates for the series respin.

> --- a/tests/Makefile.include
> +++ b/tests/Makefile.include
> @@ -23,7 +23,16 @@ check-help:
>  ifneq ($(wildcard config-host.mak),)
>  export SRC_PATH
>  
> -qapi-py = $(SRC_PATH)/scripts/qapi.py $(SRC_PATH)/scripts/ordereddict.py
> +# TODO don't duplicate $(SRC_PATH)/Makefile's qapi-py here
> +qapi-py = $(SRC_PATH)/scripts/qapi/commands.py \
> +$(SRC_PATH)/scripts/qapi/events.py \
> +$(SRC_PATH)/scripts/qapi/introspect.py \
> +$(SRC_PATH)/scripts/qapi/types.py \
> +$(SRC_PATH)/scripts/qapi/visit.py \
> +$(SRC_PATH)/scripts/qapi/common.py \
> +$(SRC_PATH)/scripts/qapi/doc.py \
> +$(SRC_PATH)/scripts/ordereddict.py \
> +$(SRC_PATH)/scripts/qapi-gen.py

So, were you counting these in the 11 generated files mentioned in the
commit message? :)
Markus Armbruster Feb. 3, 2018, 9:03 a.m. UTC | #2
Eric Blake <eblake@redhat.com> writes:

> On 02/02/2018 07:03 AM, Markus Armbruster wrote:
>> Whenever qapi-schema.json changes, we run six programs eleven times to
>> update eleven files.  This is silly.  Replace the six programs by a
>> single program that spits out all eleven files.
>
> Yay, about time!
>
> One program, but still invoked multiple times:
>
>> 
>> Signed-off-by: Markus Armbruster <armbru@redhat.com>
>> ---
>>  Makefile                                           | 86 ++++++++++------------
>>  scripts/qapi-gen.py                                | 41 +++++++++++
>>  scripts/qapi/__init__.py                           |  0
>>  scripts/{qapi-commands.py => qapi/commands.py}     | 23 ++----
>>  scripts/{qapi.py => qapi/common.py}                |  0
>>  scripts/{qapi2texi.py => qapi/doc.py}              | 29 ++------
>>  scripts/{qapi-event.py => qapi/events.py}          | 23 ++----
>>  scripts/{qapi-introspect.py => qapi/introspect.py} | 32 ++------
>>  scripts/{qapi-types.py => qapi/types.py}           | 34 ++-------
>>  scripts/{qapi-visit.py => qapi/visit.py}           | 34 ++-------
>
> Maybe the commit message should mention:
>
> This requires moving some files around for proper use in python.

Yes, I should mention that I wrap the QAPI modules in a Python package.

>>  tests/Makefile.include                             | 56 +++++++-------
>>  tests/qapi-schema/test-qapi.py                     |  2 +-
>>  12 files changed, 140 insertions(+), 220 deletions(-)
>>  create mode 100755 scripts/qapi-gen.py
>>  create mode 100644 scripts/qapi/__init__.py
>>  rename scripts/{qapi-commands.py => qapi/commands.py} (94%)
>>  rename scripts/{qapi.py => qapi/common.py} (100%)
>>  rename scripts/{qapi2texi.py => qapi/doc.py} (92%)
>>  mode change 100755 => 100644
>
> Unintinentional?  We're inconsistent on which of our *.py files are
> executable in git.  Does it matter whether a file that is designed for
> use as a module is marked executable (if so, perhaps 5/21 should be
> adding the x attribute on other files, rather than this patch removing
> it on the doc generator).

qapi2texi.py is no longer runnable as a standalone program after this
patch.

So are qapi-{commands,events,introspect,types,visit}.py, but they never
had the executable bit set.

>> +++ b/Makefile
>
>> +qga/qapi-generated/qga-qapi-types.c qga/qapi-generated/qga-qapi-types.h \
>> +qga/qapi-generated/qga-qapi-visit.c qga/qapi-generated/qga-qapi-visit.h \
>> +qga/qapi-generated/qga-qmp-commands.h qga/qapi-generated/qga-qmp-marshal.c \
>> +qga/qapi-generated/qga-qapi.texi: \
>> +qga/qapi-generated/qapi-gen-timestamp ;
>> +qga/qapi-generated/qapi-gen-timestamp: $(SRC_PATH)/qga/qapi-schema.json $(qapi-py)
>> +	$(call quiet-command,$(PYTHON) $(SRC_PATH)/scripts/qapi-gen.py \
>> +		-o qga/qapi-generated -p "qga-" $<, \
>> +		"GEN","$(@:%-timestamp=%)")
>> +	@>$@
>
> once for qga,

That's the QAPI/QGA schema.  The commit message talks about the QAPI/QMP
schema.  I wish the two weren't named the same.

Modularization might make fusing them possible.  Whether that's a good
idea I don't know.

>> +qapi-types.c qapi-types.h \
>> +qapi-visit.c qapi-visit.h \
>> +qmp-commands.h qmp-marshal.c \
>> +qapi-event.c qapi-event.h \
>> +qmp-introspect.h qmp-introspect.c \
>> +qapi.texi: \
>> +qapi-gen-timestamp ;
>> +qapi-gen-timestamp: $(qapi-modules) $(qapi-py)
>> +	$(call quiet-command,$(PYTHON) $(SRC_PATH)/scripts/qapi-gen.py \
>> +		-o "." -b $<, \
>> +		"GEN","$(@:%-timestamp=%)")
>> +	@>$@
>
> and again for the main level.  Still, a definite improvement!

Perhaps I can find a way to clarify the commit message.

>> +++ b/scripts/qapi-gen.py
>
>> +def main(argv):
>> +    (input_file, output_dir, do_c, do_h, prefix, opts) = \
>> +        parse_command_line('bu', ['builtins', 'unmask-non-abi-names'])
>> +
>> +    opt_builtins = False
>> +    opt_unmask = False
>> +
>> +    for o, a in opts:
>> +        if o in ('-b', '--builtins'):
>> +            opt_builtins = True
>> +        if o in ('-u', '--unmask-non-abi-names'):
>> +            opt_unmask = True
>> +
>> +    schema = QAPISchema(input_file)
>> +
>> +    gen_types(schema, output_dir, prefix, opt_builtins)
>> +    gen_visit(schema, output_dir, prefix, opt_builtins)
>> +    gen_commands(schema, output_dir, prefix)
>> +    gen_events(schema, output_dir, prefix)
>> +    gen_introspect(schema, output_dir, prefix, opt_unmask)
>> +    gen_doc(schema, output_dir, prefix)
>
> Rather simple, but definitely nicer all in one python file than as a
> series of make rules.
>
>> @@ -255,13 +255,8 @@ class QAPISchemaGenCommandVisitor(QAPISchemaVisitor):
>>          self._regy += gen_register_command(name, success_response)
>>  
>>  
>> -def main(argv):
>> -    (input_file, output_dir, do_c, do_h, prefix, opts) = parse_command_line()
>> -
>> -    blurb = '''
>> - * Schema-defined QAPI/QMP commands
>> -'''
>> -
>> +def gen_commands(schema, output_dir, prefix):
>> +    blurb = ' * Schema-defined QAPI/QMP commands'
>
> Some churn on the definition of blurb; worth tidying this in the
> introduction earlier in the series?

Doesn't seem worth a separate patch, but I can try to reshuffle things
to reduce churn.

>> rename to scripts/qapi/introspect.py
>> index 09e7d1f140..2153060f2c 100644
>> --- a/scripts/qapi-introspect.py
>> +++ b/scripts/qapi/introspect.py
>> @@ -10,7 +10,7 @@ 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 *
>> +from qapi.common import *
>>  
>>  
>>  # Caveman's json.dumps() replacement (we're stuck at Python 2.4)
>> @@ -168,22 +168,8 @@ const char %(c_name)s[] = %(c_string)s;
>>          self._gen_json(name, 'event', {'arg-type': self._use_type(arg_type)})
>>  
>>  
>> -def main(argv):
>> -    # Debugging aid: unmask QAPI schema's type names
>> -    # We normally mask them, because they're not QMP wire ABI
>> -    opt_unmask = False
>> -
>> -    (input_file, output_dir, do_c, do_h, prefix, opts) = \
>> -        parse_command_line('u', ['unmask-non-abi-names'])
>
> Hmm - we didn't have any docs about this hidden command line option; I
> see you still preserved it, but it may be worth mentioning in your
> pending doc updates for the series respin.

Maybe.

Providing --help would probably be more useful.  We should convert
qapi-gen.py to argparse.

>> --- a/tests/Makefile.include
>> +++ b/tests/Makefile.include
>> @@ -23,7 +23,16 @@ check-help:
>>  ifneq ($(wildcard config-host.mak),)
>>  export SRC_PATH
>>  
>> -qapi-py = $(SRC_PATH)/scripts/qapi.py $(SRC_PATH)/scripts/ordereddict.py
>> +# TODO don't duplicate $(SRC_PATH)/Makefile's qapi-py here
>> +qapi-py = $(SRC_PATH)/scripts/qapi/commands.py \
>> +$(SRC_PATH)/scripts/qapi/events.py \
>> +$(SRC_PATH)/scripts/qapi/introspect.py \
>> +$(SRC_PATH)/scripts/qapi/types.py \
>> +$(SRC_PATH)/scripts/qapi/visit.py \
>> +$(SRC_PATH)/scripts/qapi/common.py \
>> +$(SRC_PATH)/scripts/qapi/doc.py \
>> +$(SRC_PATH)/scripts/ordereddict.py \
>> +$(SRC_PATH)/scripts/qapi-gen.py
>
> So, were you counting these in the 11 generated files mentioned in the
> commit message? :)

I'm not sure I understand what you mean here...
Marc-Andre Lureau Feb. 5, 2018, 1:44 p.m. UTC | #3
On Fri, Feb 2, 2018 at 2:03 PM, Markus Armbruster <armbru@redhat.com> wrote:
> Whenever qapi-schema.json changes, we run six programs eleven times to
> update eleven files.  This is silly.  Replace the six programs by a
> single program that spits out all eleven files.
>

Now we will need documentation update.

Also greping for the renamed files:

git grep -E 'qapi-commands|qapi2texi|qapi-event.py|qapi-intros|qapi-types.py|qapi-visit.py'
docs/devel/qapi-code-gen.txt:=== scripts/qapi-types.py ===
docs/devel/qapi-code-gen.txt:    $ python scripts/qapi-types.py
--output-dir="qapi-generated" \
docs/devel/qapi-code-gen.txt:=== scripts/qapi-visit.py ===
docs/devel/qapi-code-gen.txt:    $ python scripts/qapi-visit.py
--output-dir="qapi-generated"
docs/devel/qapi-code-gen.txt:=== scripts/qapi-commands.py ===
docs/devel/qapi-code-gen.txt:                        generated by
qapi-visit.py are used to
docs/devel/qapi-code-gen.txt:    $ python scripts/qapi-commands.py
--output-dir="qapi-generated"
docs/devel/qapi-code-gen.txt:=== scripts/qapi-event.py ===
docs/devel/qapi-code-gen.txt:    $ python scripts/qapi-event.py
--output-dir="qapi-generated"
docs/devel/qapi-code-gen.txt:=== scripts/qapi-introspect.py ===
docs/devel/qapi-code-gen.txt:    $ python scripts/qapi-introspect.py
--output-dir="qapi-generated"
monitor.c: * qapi-introspect.py's output actually conforms to the schema.
qapi-schema.json:# Documentation generated with qapi2texi.py is in
source order, with

> Signed-off-by: Markus Armbruster <armbru@redhat.com>
> ---
>  Makefile                                           | 86 ++++++++++------------
>  scripts/qapi-gen.py                                | 41 +++++++++++
>  scripts/qapi/__init__.py                           |  0
>  scripts/{qapi-commands.py => qapi/commands.py}     | 23 ++----
>  scripts/{qapi.py => qapi/common.py}                |  0
>  scripts/{qapi2texi.py => qapi/doc.py}              | 29 ++------
>  scripts/{qapi-event.py => qapi/events.py}          | 23 ++----
>  scripts/{qapi-introspect.py => qapi/introspect.py} | 32 ++------
>  scripts/{qapi-types.py => qapi/types.py}           | 34 ++-------
>  scripts/{qapi-visit.py => qapi/visit.py}           | 34 ++-------
>  tests/Makefile.include                             | 56 +++++++-------
>  tests/qapi-schema/test-qapi.py                     |  2 +-
>  12 files changed, 140 insertions(+), 220 deletions(-)
>  create mode 100755 scripts/qapi-gen.py
>  create mode 100644 scripts/qapi/__init__.py
>  rename scripts/{qapi-commands.py => qapi/commands.py} (94%)
>  rename scripts/{qapi.py => qapi/common.py} (100%)
>  rename scripts/{qapi2texi.py => qapi/doc.py} (92%)
>  mode change 100755 => 100644
>  rename scripts/{qapi-event.py => qapi/events.py} (92%)
>  rename scripts/{qapi-introspect.py => qapi/introspect.py} (90%)
>  rename scripts/{qapi-types.py => qapi/types.py} (90%)
>  rename scripts/{qapi-visit.py => qapi/visit.py} (92%)
>
> diff --git a/Makefile b/Makefile
> index af31e8981f..e02f0c13ef 100644
> --- a/Makefile
> +++ b/Makefile
> @@ -92,6 +92,7 @@ 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.texi
>
>  GENERATED_FILES += trace/generated-tcg-tracers.h
>
> @@ -477,25 +478,26 @@ qemu-ga$(EXESUF): QEMU_CFLAGS += -I qga/qapi-generated
>  qemu-keymap$(EXESUF): LIBS += $(XKBCOMMON_LIBS)
>  qemu-keymap$(EXESUF): QEMU_CFLAGS += $(XKBCOMMON_CFLAGS)
>
> -gen-out-type = $(subst .,-,$(suffix $@))
> +qapi-py = $(SRC_PATH)/scripts/qapi/commands.py \
> +$(SRC_PATH)/scripts/qapi/events.py \
> +$(SRC_PATH)/scripts/qapi/introspect.py \
> +$(SRC_PATH)/scripts/qapi/types.py \
> +$(SRC_PATH)/scripts/qapi/visit.py \
> +$(SRC_PATH)/scripts/qapi/common.py \
> +$(SRC_PATH)/scripts/qapi/doc.py \
> +$(SRC_PATH)/scripts/ordereddict.py \
> +$(SRC_PATH)/scripts/qapi-gen.py
>
> -qapi-py = $(SRC_PATH)/scripts/qapi.py $(SRC_PATH)/scripts/ordereddict.py
> -
> -qga/qapi-generated/qga-qapi-types.c qga/qapi-generated/qga-qapi-types.h :\
> -$(SRC_PATH)/qga/qapi-schema.json $(SRC_PATH)/scripts/qapi-types.py $(qapi-py)
> -       $(call quiet-command,$(PYTHON) $(SRC_PATH)/scripts/qapi-types.py \
> -               $(gen-out-type) -o qga/qapi-generated -p "qga-" $<, \
> -               "GEN","$@")
> -qga/qapi-generated/qga-qapi-visit.c qga/qapi-generated/qga-qapi-visit.h :\
> -$(SRC_PATH)/qga/qapi-schema.json $(SRC_PATH)/scripts/qapi-visit.py $(qapi-py)
> -       $(call quiet-command,$(PYTHON) $(SRC_PATH)/scripts/qapi-visit.py \
> -               $(gen-out-type) -o qga/qapi-generated -p "qga-" $<, \
> -               "GEN","$@")
> -qga/qapi-generated/qga-qmp-commands.h qga/qapi-generated/qga-qmp-marshal.c :\
> -$(SRC_PATH)/qga/qapi-schema.json $(SRC_PATH)/scripts/qapi-commands.py $(qapi-py)
> -       $(call quiet-command,$(PYTHON) $(SRC_PATH)/scripts/qapi-commands.py \
> -               $(gen-out-type) -o qga/qapi-generated -p "qga-" $<, \
> -               "GEN","$@")
> +qga/qapi-generated/qga-qapi-types.c qga/qapi-generated/qga-qapi-types.h \
> +qga/qapi-generated/qga-qapi-visit.c qga/qapi-generated/qga-qapi-visit.h \
> +qga/qapi-generated/qga-qmp-commands.h qga/qapi-generated/qga-qmp-marshal.c \
> +qga/qapi-generated/qga-qapi.texi: \
> +qga/qapi-generated/qapi-gen-timestamp ;
> +qga/qapi-generated/qapi-gen-timestamp: $(SRC_PATH)/qga/qapi-schema.json $(qapi-py)
> +       $(call quiet-command,$(PYTHON) $(SRC_PATH)/scripts/qapi-gen.py \
> +               -o qga/qapi-generated -p "qga-" $<, \
> +               "GEN","$(@:%-timestamp=%)")
> +       @>$@
>
>  qapi-modules = $(SRC_PATH)/qapi-schema.json $(SRC_PATH)/qapi/common.json \
>                 $(SRC_PATH)/qapi/block.json $(SRC_PATH)/qapi/block-core.json \
> @@ -512,31 +514,18 @@ qapi-modules = $(SRC_PATH)/qapi-schema.json $(SRC_PATH)/qapi/common.json \
>                 $(SRC_PATH)/qapi/transaction.json \
>                 $(SRC_PATH)/qapi/ui.json
>
> -qapi-types.c qapi-types.h :\
> -$(qapi-modules) $(SRC_PATH)/scripts/qapi-types.py $(qapi-py)
> -       $(call quiet-command,$(PYTHON) $(SRC_PATH)/scripts/qapi-types.py \
> -               $(gen-out-type) -o "." -b $<, \
> -               "GEN","$@")
> -qapi-visit.c qapi-visit.h :\
> -$(qapi-modules) $(SRC_PATH)/scripts/qapi-visit.py $(qapi-py)
> -       $(call quiet-command,$(PYTHON) $(SRC_PATH)/scripts/qapi-visit.py \
> -               $(gen-out-type) -o "." -b $<, \
> -               "GEN","$@")
> -qapi-event.c qapi-event.h :\
> -$(qapi-modules) $(SRC_PATH)/scripts/qapi-event.py $(qapi-py)
> -       $(call quiet-command,$(PYTHON) $(SRC_PATH)/scripts/qapi-event.py \
> -               $(gen-out-type) -o "." $<, \
> -               "GEN","$@")
> -qmp-commands.h qmp-marshal.c :\
> -$(qapi-modules) $(SRC_PATH)/scripts/qapi-commands.py $(qapi-py)
> -       $(call quiet-command,$(PYTHON) $(SRC_PATH)/scripts/qapi-commands.py \
> -               $(gen-out-type) -o "." $<, \
> -               "GEN","$@")
> -qmp-introspect.h qmp-introspect.c :\
> -$(qapi-modules) $(SRC_PATH)/scripts/qapi-introspect.py $(qapi-py)
> -       $(call quiet-command,$(PYTHON) $(SRC_PATH)/scripts/qapi-introspect.py \
> -               $(gen-out-type) -o "." $<, \
> -               "GEN","$@")
> +qapi-types.c qapi-types.h \
> +qapi-visit.c qapi-visit.h \
> +qmp-commands.h qmp-marshal.c \
> +qapi-event.c qapi-event.h \
> +qmp-introspect.h qmp-introspect.c \
> +qapi.texi: \
> +qapi-gen-timestamp ;
> +qapi-gen-timestamp: $(qapi-modules) $(qapi-py)
> +       $(call quiet-command,$(PYTHON) $(SRC_PATH)/scripts/qapi-gen.py \
> +               -o "." -b $<, \
> +               "GEN","$(@:%-timestamp=%)")
> +       @>$@
>
>  QGALIB_GEN=$(addprefix qga/qapi-generated/, qga-qapi-types.h qga-qapi-visit.h qga-qmp-commands.h)
>  $(qga-obj-y): $(QGALIB_GEN)
> @@ -596,6 +585,7 @@ clean:
>         rm -f trace/generated-tracers-dtrace.dtrace*
>         rm -f trace/generated-tracers-dtrace.h*
>         rm -f $(foreach f,$(GENERATED_FILES),$(f) $(f)-timestamp)
> +       rm -f qapi-gen-timestamp
>         rm -rf qapi-generated
>         rm -rf qga/qapi-generated
>         for d in $(ALL_SUBDIRS); do \
> @@ -803,13 +793,11 @@ qemu-monitor-info.texi: $(SRC_PATH)/hmp-commands-info.hx $(SRC_PATH)/scripts/hxt
>  qemu-img-cmds.texi: $(SRC_PATH)/qemu-img-cmds.hx $(SRC_PATH)/scripts/hxtool
>         $(call quiet-command,sh $(SRC_PATH)/scripts/hxtool -t < $< > $@,"GEN","$@")
>
> -docs/interop/qemu-qmp-qapi.texi docs/interop/qemu-ga-qapi.texi: $(SRC_PATH)/scripts/qapi2texi.py $(qapi-py)
> +docs/interop/qemu-qmp-qapi.texi: qapi.texi
> +       @cp -p $< $@
>
> -docs/interop/qemu-qmp-qapi.texi: $(qapi-modules)
> -       $(call quiet-command,$(PYTHON) $(SRC_PATH)/scripts/qapi2texi.py $< > $@,"GEN","$@")
> -
> -docs/interop/qemu-ga-qapi.texi: $(SRC_PATH)/qga/qapi-schema.json
> -       $(call quiet-command,$(PYTHON) $(SRC_PATH)/scripts/qapi2texi.py $< > $@,"GEN","$@")
> +docs/interop/qemu-ga-qapi.texi: qga/qapi-generated/qga-qapi.texi
> +       @cp -p $< $@
>
>  qemu.1: qemu-doc.texi qemu-options.texi qemu-monitor.texi qemu-monitor-info.texi
>  qemu.1: qemu-option-trace.texi
> diff --git a/scripts/qapi-gen.py b/scripts/qapi-gen.py
> new file mode 100755
> index 0000000000..575c938a1b
> --- /dev/null
> +++ b/scripts/qapi-gen.py
> @@ -0,0 +1,41 @@
> +#!/usr/bin/env python
> +# QAPI generator
> +#
> +# This work is licensed under the terms of the GNU GPL, version 2 or later.
> +# See the COPYING file in the top-level directory.
> +
> +import sys
> +from qapi.common import parse_command_line, QAPISchema
> +from qapi.types import gen_types
> +from qapi.visit import gen_visit
> +from qapi.commands import gen_commands
> +from qapi.events import gen_events
> +from qapi.introspect import gen_introspect
> +from qapi.doc import gen_doc
> +
> +
> +def main(argv):
> +    (input_file, output_dir, do_c, do_h, prefix, opts) = \
> +        parse_command_line('bu', ['builtins', 'unmask-non-abi-names'])
> +
> +    opt_builtins = False
> +    opt_unmask = False
> +
> +    for o, a in opts:
> +        if o in ('-b', '--builtins'):
> +            opt_builtins = True
> +        if o in ('-u', '--unmask-non-abi-names'):
> +            opt_unmask = True
> +
> +    schema = QAPISchema(input_file)
> +
> +    gen_types(schema, output_dir, prefix, opt_builtins)
> +    gen_visit(schema, output_dir, prefix, opt_builtins)
> +    gen_commands(schema, output_dir, prefix)
> +    gen_events(schema, output_dir, prefix)
> +    gen_introspect(schema, output_dir, prefix, opt_unmask)
> +    gen_doc(schema, output_dir, prefix)
> +
> +
> +if __name__ == '__main__':
> +    main(sys.argv)
> diff --git a/scripts/qapi/__init__.py b/scripts/qapi/__init__.py
> new file mode 100644
> index 0000000000..e69de29bb2
> diff --git a/scripts/qapi-commands.py b/scripts/qapi/commands.py
> similarity index 94%
> rename from scripts/qapi-commands.py
> rename to scripts/qapi/commands.py
> index 331b58670e..383a4dd426 100644
> --- a/scripts/qapi-commands.py
> +++ b/scripts/qapi/commands.py
> @@ -13,7 +13,7 @@ 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 *
> +from qapi.common import *
>
>
>  def gen_command_decl(name, arg_type, boxed, ret_type):
> @@ -255,13 +255,8 @@ class QAPISchemaGenCommandVisitor(QAPISchemaVisitor):
>          self._regy += gen_register_command(name, success_response)
>
>
> -def main(argv):
> -    (input_file, output_dir, do_c, do_h, prefix, opts) = parse_command_line()
> -
> -    blurb = '''
> - * Schema-defined QAPI/QMP commands
> -'''
> -
> +def gen_commands(schema, output_dir, prefix):
> +    blurb = ' * Schema-defined QAPI/QMP commands'
>      genc = QAPIGenC(blurb, __doc__)
>      genh = QAPIGenH(blurb, __doc__)
>
> @@ -290,17 +285,9 @@ void %(c_prefix)sqmp_init_marshal(QmpCommandList *cmds);
>  ''',
>                      prefix=prefix, c_prefix=c_name(prefix, protect=False)))
>
> -    schema = QAPISchema(input_file)
>      vis = QAPISchemaGenCommandVisitor(prefix)
>      schema.visit(vis)
>      genc.body(vis.defn)
>      genh.body(vis.decl)
> -
> -    if do_c:
> -        genc.write(output_dir, prefix + 'qmp-marshal.c')
> -    if do_h:
> -        genh.write(output_dir, prefix + 'qmp-commands.h')
> -
> -
> -if __name__ == '__main__':
> -    main(sys.argv)
> +    genc.write(output_dir, prefix + 'qmp-marshal.c')
> +    genh.write(output_dir, prefix + 'qmp-commands.h')
> diff --git a/scripts/qapi.py b/scripts/qapi/common.py
> similarity index 100%
> rename from scripts/qapi.py
> rename to scripts/qapi/common.py
> diff --git a/scripts/qapi2texi.py b/scripts/qapi/doc.py
> old mode 100755
> new mode 100644
> similarity index 92%
> rename from scripts/qapi2texi.py
> rename to scripts/qapi/doc.py
> index 924b374cd3..1f57f6e1c2
> --- a/scripts/qapi2texi.py
> +++ b/scripts/qapi/doc.py
> @@ -4,10 +4,9 @@
>  # This work is licensed under the terms of the GNU LGPL, version 2+.
>  # See the COPYING file in the top-level directory.
>  """This script produces the documentation of a qapi schema in texinfo format"""
> +
>  import re
> -import sys
> -
> -import qapi
> +import qapi.common
>
>  MSG_FMT = """
>  @deftypefn {type} {{}} {name}
> @@ -196,7 +195,7 @@ def texi_entity(doc, what, base=None, variants=None,
>              + texi_sections(doc))
>
>
> -class QAPISchemaGenDocVisitor(qapi.QAPISchemaVisitor):
> +class QAPISchemaGenDocVisitor(qapi.common.QAPISchemaVisitor):

Would be a bit easier to read and more consitent with a top-level
"from qapi.common import QAPISchemaVisitor"

>      def __init__(self):
>          self.out = None
>          self.cur_doc = None
> @@ -271,20 +270,8 @@ def texi_schema(schema):
>      return gen.out
>
>
> -def main(argv):
> -    """Takes schema argument, prints result to stdout"""
> -    if len(argv) != 2:
> -        print >>sys.stderr, "%s: need exactly 1 argument: SCHEMA" % argv[0]
> -        sys.exit(1)
> -
> -    schema = qapi.QAPISchema(argv[1])
> -    if not qapi.doc_required:
> -        print >>sys.stderr, ("%s: need pragma 'doc-required' "
> -                             "to generate documentation" % argv[0])
> -        sys.exit(1)
> -    print '@c AUTOMATICALLY GENERATED, DO NOT MODIFY\n'
> -    print texi_schema(schema),
> -
> -
> -if __name__ == '__main__':
> -    main(sys.argv)
> +def gen_doc(schema, output_dir, prefix):
> +    if qapi.common.doc_required:
> +        gen = qapi.common.QAPIGenDoc()
> +        gen.body(texi_schema(schema))
> +        gen.write(output_dir, prefix + 'qapi.texi')
> diff --git a/scripts/qapi-event.py b/scripts/qapi/events.py
> similarity index 92%
> rename from scripts/qapi-event.py
> rename to scripts/qapi/events.py
> index 5b33c694d4..1f267686db 100644
> --- a/scripts/qapi-event.py
> +++ b/scripts/qapi/events.py
> @@ -12,7 +12,7 @@ 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 *
> +from qapi.common import *
>
>
>  def build_event_send_proto(name, arg_type, boxed):
> @@ -171,13 +171,8 @@ class QAPISchemaGenEventVisitor(QAPISchemaVisitor):
>          self._event_names.append(name)
>
>
> -def main(argv):
> -    (input_file, output_dir, do_c, do_h, prefix, dummy) = parse_command_line()
> -
> -    blurb = '''
> - * Schema-defined QAPI/QMP events
> -'''
> -
> +def gen_events(schema, output_dir, prefix):
> +    blurb = ' * Schema-defined QAPI/QMP events'
>      genc = QAPIGenC(blurb, __doc__)
>      genh = QAPIGenH(blurb, __doc__)
>
> @@ -201,17 +196,9 @@ def main(argv):
>  ''',
>                      prefix=prefix))
>
> -    schema = QAPISchema(input_file)
>      vis = QAPISchemaGenEventVisitor(prefix)
>      schema.visit(vis)
>      genc.body(vis.defn)
>      genh.body(vis.decl)
> -
> -    if do_c:
> -        genc.write(output_dir, prefix + 'qapi-event.c')
> -    if do_h:
> -        genh.write(output_dir, prefix + 'qapi-event.h')
> -
> -
> -if __name__ == '__main__':
> -    main(sys.argv)
> +    genc.write(output_dir, prefix + 'qapi-event.c')
> +    genh.write(output_dir, prefix + 'qapi-event.h')
> diff --git a/scripts/qapi-introspect.py b/scripts/qapi/introspect.py
> similarity index 90%
> rename from scripts/qapi-introspect.py
> rename to scripts/qapi/introspect.py
> index 09e7d1f140..2153060f2c 100644
> --- a/scripts/qapi-introspect.py
> +++ b/scripts/qapi/introspect.py
> @@ -10,7 +10,7 @@ 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 *
> +from qapi.common import *
>
>
>  # Caveman's json.dumps() replacement (we're stuck at Python 2.4)
> @@ -168,22 +168,8 @@ const char %(c_name)s[] = %(c_string)s;
>          self._gen_json(name, 'event', {'arg-type': self._use_type(arg_type)})
>
>
> -def main(argv):
> -    # Debugging aid: unmask QAPI schema's type names
> -    # We normally mask them, because they're not QMP wire ABI
> -    opt_unmask = False
> -
> -    (input_file, output_dir, do_c, do_h, prefix, opts) = \
> -        parse_command_line('u', ['unmask-non-abi-names'])
> -
> -    for o, a in opts:
> -        if o in ('-u', '--unmask-non-abi-names'):
> -            opt_unmask = True
> -
> -    blurb = '''
> - * QAPI/QMP schema introspection
> -'''
> -
> +def gen_introspect(schema, output_dir, prefix, opt_unmask):
> +    blurb = ' * QAPI/QMP schema introspection'
>      genc = QAPIGenC(blurb, __doc__)
>      genh = QAPIGenH(blurb, __doc__)
>
> @@ -194,17 +180,9 @@ def main(argv):
>  ''',
>                      prefix=prefix))
>
> -    schema = QAPISchema(input_file)
>      vis = QAPISchemaGenIntrospectVisitor(prefix, opt_unmask)
>      schema.visit(vis)
>      genc.body(vis.defn)
>      genh.body(vis.decl)
> -
> -    if do_c:
> -        genc.write(output_dir, prefix + 'qmp-introspect.c')
> -    if do_h:
> -        genh.write(output_dir, prefix + 'qmp-introspect.h')
> -
> -
> -if __name__ == '__main__':
> -    main(sys.argv)
> +    genc.write(output_dir, prefix + 'qmp-introspect.c')
> +    genh.write(output_dir, prefix + 'qmp-introspect.h')
> diff --git a/scripts/qapi-types.py b/scripts/qapi/types.py
> similarity index 90%
> rename from scripts/qapi-types.py
> rename to scripts/qapi/types.py
> index f2ddde94b1..b2095120e0 100644
> --- a/scripts/qapi-types.py
> +++ b/scripts/qapi/types.py
> @@ -13,7 +13,7 @@ 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 *
> +from qapi.common import *
>
>
>  # variants must be emitted before their container; track what has already
> @@ -241,24 +241,8 @@ class QAPISchemaGenTypeVisitor(QAPISchemaVisitor):
>          self._gen_type_cleanup(name)
>
>
> -def main(argv):
> -    # If you link code generated from multiple schemata, you want only one
> -    # instance of the code for built-in types.  Generate it only when
> -    # opt_builtins, enabled by command line option -b.  See also
> -    # QAPISchemaGenTypeVisitor.visit_end().
> -    opt_builtins = False
> -
> -    (input_file, output_dir, do_c, do_h, prefix, opts) = \
> -        parse_command_line('b', ['builtins'])
> -
> -    for o, a in opts:
> -        if o in ('-b', '--builtins'):
> -            opt_builtins = True
> -
> -    blurb = '''
> - * Schema-defined QAPI types
> -'''
> -
> +def gen_types(schema, output_dir, prefix, opt_builtins):
> +    blurb = ' * Schema-defined QAPI types'
>      genc = QAPIGenC(blurb, __doc__)
>      genh = QAPIGenH(blurb, __doc__)
>
> @@ -274,17 +258,9 @@ def main(argv):
>  #include "qapi/util.h"
>  '''))
>
> -    schema = QAPISchema(input_file)
>      vis = QAPISchemaGenTypeVisitor(opt_builtins)
>      schema.visit(vis)
>      genc.body(vis.defn)
>      genh.body(vis.decl)
> -
> -    if do_c:
> -        genc.write(output_dir, prefix + 'qapi-types.c')
> -    if do_h:
> -        genh.write(output_dir, prefix + 'qapi-types.h')
> -
> -
> -if __name__ == '__main__':
> -    main(sys.argv)
> +    genc.write(output_dir, prefix + 'qapi-types.c')
> +    genh.write(output_dir, prefix + 'qapi-types.h')
> diff --git a/scripts/qapi-visit.py b/scripts/qapi/visit.py
> similarity index 92%
> rename from scripts/qapi-visit.py
> rename to scripts/qapi/visit.py
> index 473fa72574..80c0b85f8c 100644
> --- a/scripts/qapi-visit.py
> +++ b/scripts/qapi/visit.py
> @@ -13,7 +13,7 @@ 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 *
> +from qapi.common import *
>
>
>  def gen_visit_decl(name, scalar=False):
> @@ -324,24 +324,8 @@ class QAPISchemaGenVisitVisitor(QAPISchemaVisitor):
>          self.defn += gen_visit_alternate(name, variants)
>
>
> -def main(argv):
> -    # If you link code generated from multiple schemata, you want only one
> -    # instance of the code for built-in types.  Generate it only when
> -    # opt_builtins, enabled by command line option -b.  See also
> -    # QAPISchemaGenVisitVisitor.visit_end().
> -    opt_builtins = False
> -
> -    (input_file, output_dir, do_c, do_h, prefix, opts) = \
> -        parse_command_line('b', ['builtins'])
> -
> -    for o, a in opts:
> -        if o in ('-b', '--builtins'):
> -            opt_builtins = True
> -
> -    blurb = '''
> - * Schema-defined QAPI visitors
> -'''
> -
> +def gen_visit(schema, output_dir, prefix, opt_builtins):
> +    blurb = ' * Schema-defined QAPI visitors'
>      genc = QAPIGenC(blurb, __doc__)
>      genh = QAPIGenH(blurb, __doc__)
>
> @@ -361,17 +345,9 @@ def main(argv):
>  ''',
>                      prefix=prefix))
>
> -    schema = QAPISchema(input_file)
>      vis = QAPISchemaGenVisitVisitor(opt_builtins)
>      schema.visit(vis)
>      genc.body(vis.defn)
>      genh.body(vis.decl)
> -
> -    if do_c:
> -        genc.write(output_dir, prefix + 'qapi-visit.c')
> -    if do_h:
> -        genh.write(output_dir, prefix + 'qapi-visit.h')
> -
> -
> -if __name__ == '__main__':
> -    main(sys.argv)
> +    genc.write(output_dir, prefix + 'qapi-visit.c')
> +    genh.write(output_dir, prefix + 'qapi-visit.h')
> diff --git a/tests/Makefile.include b/tests/Makefile.include
> index 851aafe9d1..768655a810 100644
> --- a/tests/Makefile.include
> +++ b/tests/Makefile.include
> @@ -23,7 +23,16 @@ check-help:
>  ifneq ($(wildcard config-host.mak),)
>  export SRC_PATH
>
> -qapi-py = $(SRC_PATH)/scripts/qapi.py $(SRC_PATH)/scripts/ordereddict.py
> +# TODO don't duplicate $(SRC_PATH)/Makefile's qapi-py here

hmm, it doesn't look necessary, since the makefile is included from
there. Right?

> +qapi-py = $(SRC_PATH)/scripts/qapi/commands.py \
> +$(SRC_PATH)/scripts/qapi/events.py \
> +$(SRC_PATH)/scripts/qapi/introspect.py \
> +$(SRC_PATH)/scripts/qapi/types.py \
> +$(SRC_PATH)/scripts/qapi/visit.py \
> +$(SRC_PATH)/scripts/qapi/common.py \
> +$(SRC_PATH)/scripts/qapi/doc.py \
> +$(SRC_PATH)/scripts/ordereddict.py \
> +$(SRC_PATH)/scripts/qapi-gen.py
>
>  # Get the list of all supported sysemu targets
>  SYSEMU_TARGET_LIST := $(subst -softmmu.mak,,$(notdir \
> @@ -642,34 +651,24 @@ tests/test-logging$(EXESUF): tests/test-logging.o $(test-util-obj-y)
>  tests/test-replication$(EXESUF): tests/test-replication.o $(test-util-obj-y) \
>         $(test-block-obj-y)
>
> -tests/test-qapi-types.c tests/test-qapi-types.h :\
> -$(SRC_PATH)/tests/qapi-schema/qapi-schema-test.json $(SRC_PATH)/scripts/qapi-types.py $(qapi-py)
> -       $(call quiet-command,$(PYTHON) $(SRC_PATH)/scripts/qapi-types.py \
> -               $(gen-out-type) -o tests -p "test-" $<, \
> -               "GEN","$@")
> -tests/test-qapi-visit.c tests/test-qapi-visit.h :\
> -$(SRC_PATH)/tests/qapi-schema/qapi-schema-test.json $(SRC_PATH)/scripts/qapi-visit.py $(qapi-py)
> -       $(call quiet-command,$(PYTHON) $(SRC_PATH)/scripts/qapi-visit.py \
> -               $(gen-out-type) -o tests -p "test-" $<, \
> -               "GEN","$@")
> -tests/test-qmp-commands.h tests/test-qmp-marshal.c :\
> -$(SRC_PATH)/tests/qapi-schema/qapi-schema-test.json $(SRC_PATH)/scripts/qapi-commands.py $(qapi-py)
> -       $(call quiet-command,$(PYTHON) $(SRC_PATH)/scripts/qapi-commands.py \
> -               $(gen-out-type) -o tests -p "test-" $<, \
> -               "GEN","$@")
> -tests/test-qapi-event.c tests/test-qapi-event.h :\
> -$(SRC_PATH)/tests/qapi-schema/qapi-schema-test.json $(SRC_PATH)/scripts/qapi-event.py $(qapi-py)
> -       $(call quiet-command,$(PYTHON) $(SRC_PATH)/scripts/qapi-event.py \
> -               $(gen-out-type) -o tests -p "test-" $<, \
> -               "GEN","$@")
> -tests/test-qmp-introspect.c tests/test-qmp-introspect.h :\
> -$(SRC_PATH)/tests/qapi-schema/qapi-schema-test.json $(SRC_PATH)/scripts/qapi-introspect.py $(qapi-py)
> -       $(call quiet-command,$(PYTHON) $(SRC_PATH)/scripts/qapi-introspect.py \
> -               $(gen-out-type) -o tests -p "test-" $<, \
> -               "GEN","$@")
> +tests/test-qapi-types.c tests/test-qapi-types.h \
> +tests/test-qapi-visit.c tests/test-qapi-visit.h \
> +tests/test-qmp-commands.h tests/test-qmp-marshal.c \
> +tests/test-qapi-event.c tests/test-qapi-event.h \
> +tests/test-qmp-introspect.c tests/test-qmp-introspect.h: \
> +tests/test-qapi-gen-timestamp ;
> +tests/test-qapi-gen-timestamp: $(SRC_PATH)/tests/qapi-schema/qapi-schema-test.json $(qapi-py)
> +       $(call quiet-command,$(PYTHON) $(SRC_PATH)/scripts/qapi-gen.py \
> +               -o tests -p "test-" $<, \
> +               "GEN","$(@:%-timestamp=%)")
> +       @>$@
>
> -tests/qapi-schema/doc-good.test.texi: $(SRC_PATH)/tests/qapi-schema/doc-good.json $(SRC_PATH)/scripts/qapi2texi.py $(qapi-py)
> -       $(call quiet-command,$(PYTHON) $(SRC_PATH)/scripts/qapi2texi.py $< > $@,"GEN","$@")
> +tests/qapi-schema/doc-good.test.texi: $(SRC_PATH)/tests/qapi-schema/doc-good.json $(qapi-py)
> +       $(call quiet-command,$(PYTHON) $(SRC_PATH)/scripts/qapi-gen.py \
> +               -o tests/qapi-schema -p "doc-good-" $<, \
> +               "GEN","$@")
> +       @mv tests/qapi-schema/doc-good-qapi.texi $@
> +       @rm -f tests/qapi-schema/doc-good-qapi-*.[ch] tests/qapi-schema/doc-good-qmp-*.[ch]
>
>  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)
> @@ -937,6 +936,7 @@ check-clean:
>         $(MAKE) -C tests/tcg clean
>         rm -rf $(check-unit-y) tests/*.o $(QEMU_IOTESTS_HELPERS-y)
>         rm -rf $(sort $(foreach target,$(SYSEMU_TARGET_LIST), $(check-qtest-$(target)-y)) $(check-qtest-generic-y))
> +       rm -f tests/test-qapi-gen-timestamp
>
>  clean: check-clean
>
> diff --git a/tests/qapi-schema/test-qapi.py b/tests/qapi-schema/test-qapi.py
> index fe0ca08d78..7772d09919 100644
> --- a/tests/qapi-schema/test-qapi.py
> +++ b/tests/qapi-schema/test-qapi.py
> @@ -10,7 +10,7 @@
>  # See the COPYING file in the top-level directory.
>  #
>
> -from qapi import *
> +from qapi.common import *
>  from pprint import pprint
>  import os
>  import sys
> --
> 2.13.6
>

good to me otherwise,
Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Markus Armbruster Feb. 5, 2018, 3:36 p.m. UTC | #4
Marc-Andre Lureau <mlureau@redhat.com> writes:

> On Fri, Feb 2, 2018 at 2:03 PM, Markus Armbruster <armbru@redhat.com> wrote:
>> Whenever qapi-schema.json changes, we run six programs eleven times to
>> update eleven files.  This is silly.  Replace the six programs by a
>> single program that spits out all eleven files.
>>
>
> Now we will need documentation update.

Absolutely!  One reason this is RFC.

> Also greping for the renamed files:
>
> git grep -E 'qapi-commands|qapi2texi|qapi-event.py|qapi-intros|qapi-types.py|qapi-visit.py'
> docs/devel/qapi-code-gen.txt:=== scripts/qapi-types.py ===
> docs/devel/qapi-code-gen.txt:    $ python scripts/qapi-types.py
> --output-dir="qapi-generated" \
> docs/devel/qapi-code-gen.txt:=== scripts/qapi-visit.py ===
> docs/devel/qapi-code-gen.txt:    $ python scripts/qapi-visit.py
> --output-dir="qapi-generated"
> docs/devel/qapi-code-gen.txt:=== scripts/qapi-commands.py ===
> docs/devel/qapi-code-gen.txt:                        generated by
> qapi-visit.py are used to
> docs/devel/qapi-code-gen.txt:    $ python scripts/qapi-commands.py
> --output-dir="qapi-generated"
> docs/devel/qapi-code-gen.txt:=== scripts/qapi-event.py ===
> docs/devel/qapi-code-gen.txt:    $ python scripts/qapi-event.py
> --output-dir="qapi-generated"
> docs/devel/qapi-code-gen.txt:=== scripts/qapi-introspect.py ===
> docs/devel/qapi-code-gen.txt:    $ python scripts/qapi-introspect.py
> --output-dir="qapi-generated"
> monitor.c: * qapi-introspect.py's output actually conforms to the schema.
> qapi-schema.json:# Documentation generated with qapi2texi.py is in
> source order, with
>
>> Signed-off-by: Markus Armbruster <armbru@redhat.com>
>> ---
>>  Makefile                                           | 86 ++++++++++------------
>>  scripts/qapi-gen.py                                | 41 +++++++++++
>>  scripts/qapi/__init__.py                           |  0
>>  scripts/{qapi-commands.py => qapi/commands.py}     | 23 ++----
>>  scripts/{qapi.py => qapi/common.py}                |  0
>>  scripts/{qapi2texi.py => qapi/doc.py}              | 29 ++------
>>  scripts/{qapi-event.py => qapi/events.py}          | 23 ++----
>>  scripts/{qapi-introspect.py => qapi/introspect.py} | 32 ++------
>>  scripts/{qapi-types.py => qapi/types.py}           | 34 ++-------
>>  scripts/{qapi-visit.py => qapi/visit.py}           | 34 ++-------
>>  tests/Makefile.include                             | 56 +++++++-------
>>  tests/qapi-schema/test-qapi.py                     |  2 +-
>>  12 files changed, 140 insertions(+), 220 deletions(-)
>>  create mode 100755 scripts/qapi-gen.py
>>  create mode 100644 scripts/qapi/__init__.py
>>  rename scripts/{qapi-commands.py => qapi/commands.py} (94%)
>>  rename scripts/{qapi.py => qapi/common.py} (100%)
>>  rename scripts/{qapi2texi.py => qapi/doc.py} (92%)
>>  mode change 100755 => 100644
>>  rename scripts/{qapi-event.py => qapi/events.py} (92%)
>>  rename scripts/{qapi-introspect.py => qapi/introspect.py} (90%)
>>  rename scripts/{qapi-types.py => qapi/types.py} (90%)
>>  rename scripts/{qapi-visit.py => qapi/visit.py} (92%)
>>
>> diff --git a/Makefile b/Makefile
>> index af31e8981f..e02f0c13ef 100644
>> --- a/Makefile
>> +++ b/Makefile
>> @@ -92,6 +92,7 @@ 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.texi
>>
>>  GENERATED_FILES += trace/generated-tcg-tracers.h
>>
>> @@ -477,25 +478,26 @@ qemu-ga$(EXESUF): QEMU_CFLAGS += -I qga/qapi-generated
>>  qemu-keymap$(EXESUF): LIBS += $(XKBCOMMON_LIBS)
>>  qemu-keymap$(EXESUF): QEMU_CFLAGS += $(XKBCOMMON_CFLAGS)
>>
>> -gen-out-type = $(subst .,-,$(suffix $@))
>> +qapi-py = $(SRC_PATH)/scripts/qapi/commands.py \
>> +$(SRC_PATH)/scripts/qapi/events.py \
>> +$(SRC_PATH)/scripts/qapi/introspect.py \
>> +$(SRC_PATH)/scripts/qapi/types.py \
>> +$(SRC_PATH)/scripts/qapi/visit.py \
>> +$(SRC_PATH)/scripts/qapi/common.py \
>> +$(SRC_PATH)/scripts/qapi/doc.py \
>> +$(SRC_PATH)/scripts/ordereddict.py \
>> +$(SRC_PATH)/scripts/qapi-gen.py
>>
>> -qapi-py = $(SRC_PATH)/scripts/qapi.py $(SRC_PATH)/scripts/ordereddict.py
>> -
>> -qga/qapi-generated/qga-qapi-types.c qga/qapi-generated/qga-qapi-types.h :\
>> -$(SRC_PATH)/qga/qapi-schema.json $(SRC_PATH)/scripts/qapi-types.py $(qapi-py)
>> -       $(call quiet-command,$(PYTHON) $(SRC_PATH)/scripts/qapi-types.py \
>> -               $(gen-out-type) -o qga/qapi-generated -p "qga-" $<, \
>> -               "GEN","$@")
>> -qga/qapi-generated/qga-qapi-visit.c qga/qapi-generated/qga-qapi-visit.h :\
>> -$(SRC_PATH)/qga/qapi-schema.json $(SRC_PATH)/scripts/qapi-visit.py $(qapi-py)
>> -       $(call quiet-command,$(PYTHON) $(SRC_PATH)/scripts/qapi-visit.py \
>> -               $(gen-out-type) -o qga/qapi-generated -p "qga-" $<, \
>> -               "GEN","$@")
>> -qga/qapi-generated/qga-qmp-commands.h qga/qapi-generated/qga-qmp-marshal.c :\
>> -$(SRC_PATH)/qga/qapi-schema.json $(SRC_PATH)/scripts/qapi-commands.py $(qapi-py)
>> -       $(call quiet-command,$(PYTHON) $(SRC_PATH)/scripts/qapi-commands.py \
>> -               $(gen-out-type) -o qga/qapi-generated -p "qga-" $<, \
>> -               "GEN","$@")
>> +qga/qapi-generated/qga-qapi-types.c qga/qapi-generated/qga-qapi-types.h \
>> +qga/qapi-generated/qga-qapi-visit.c qga/qapi-generated/qga-qapi-visit.h \
>> +qga/qapi-generated/qga-qmp-commands.h qga/qapi-generated/qga-qmp-marshal.c \
>> +qga/qapi-generated/qga-qapi.texi: \
>> +qga/qapi-generated/qapi-gen-timestamp ;
>> +qga/qapi-generated/qapi-gen-timestamp: $(SRC_PATH)/qga/qapi-schema.json $(qapi-py)
>> +       $(call quiet-command,$(PYTHON) $(SRC_PATH)/scripts/qapi-gen.py \
>> +               -o qga/qapi-generated -p "qga-" $<, \
>> +               "GEN","$(@:%-timestamp=%)")
>> +       @>$@
>>
>>  qapi-modules = $(SRC_PATH)/qapi-schema.json $(SRC_PATH)/qapi/common.json \
>>                 $(SRC_PATH)/qapi/block.json $(SRC_PATH)/qapi/block-core.json \
>> @@ -512,31 +514,18 @@ qapi-modules = $(SRC_PATH)/qapi-schema.json $(SRC_PATH)/qapi/common.json \
>>                 $(SRC_PATH)/qapi/transaction.json \
>>                 $(SRC_PATH)/qapi/ui.json
>>
>> -qapi-types.c qapi-types.h :\
>> -$(qapi-modules) $(SRC_PATH)/scripts/qapi-types.py $(qapi-py)
>> -       $(call quiet-command,$(PYTHON) $(SRC_PATH)/scripts/qapi-types.py \
>> -               $(gen-out-type) -o "." -b $<, \
>> -               "GEN","$@")
>> -qapi-visit.c qapi-visit.h :\
>> -$(qapi-modules) $(SRC_PATH)/scripts/qapi-visit.py $(qapi-py)
>> -       $(call quiet-command,$(PYTHON) $(SRC_PATH)/scripts/qapi-visit.py \
>> -               $(gen-out-type) -o "." -b $<, \
>> -               "GEN","$@")
>> -qapi-event.c qapi-event.h :\
>> -$(qapi-modules) $(SRC_PATH)/scripts/qapi-event.py $(qapi-py)
>> -       $(call quiet-command,$(PYTHON) $(SRC_PATH)/scripts/qapi-event.py \
>> -               $(gen-out-type) -o "." $<, \
>> -               "GEN","$@")
>> -qmp-commands.h qmp-marshal.c :\
>> -$(qapi-modules) $(SRC_PATH)/scripts/qapi-commands.py $(qapi-py)
>> -       $(call quiet-command,$(PYTHON) $(SRC_PATH)/scripts/qapi-commands.py \
>> -               $(gen-out-type) -o "." $<, \
>> -               "GEN","$@")
>> -qmp-introspect.h qmp-introspect.c :\
>> -$(qapi-modules) $(SRC_PATH)/scripts/qapi-introspect.py $(qapi-py)
>> -       $(call quiet-command,$(PYTHON) $(SRC_PATH)/scripts/qapi-introspect.py \
>> -               $(gen-out-type) -o "." $<, \
>> -               "GEN","$@")
>> +qapi-types.c qapi-types.h \
>> +qapi-visit.c qapi-visit.h \
>> +qmp-commands.h qmp-marshal.c \
>> +qapi-event.c qapi-event.h \
>> +qmp-introspect.h qmp-introspect.c \
>> +qapi.texi: \
>> +qapi-gen-timestamp ;
>> +qapi-gen-timestamp: $(qapi-modules) $(qapi-py)
>> +       $(call quiet-command,$(PYTHON) $(SRC_PATH)/scripts/qapi-gen.py \
>> +               -o "." -b $<, \
>> +               "GEN","$(@:%-timestamp=%)")
>> +       @>$@
>>
>>  QGALIB_GEN=$(addprefix qga/qapi-generated/, qga-qapi-types.h qga-qapi-visit.h qga-qmp-commands.h)
>>  $(qga-obj-y): $(QGALIB_GEN)
>> @@ -596,6 +585,7 @@ clean:
>>         rm -f trace/generated-tracers-dtrace.dtrace*
>>         rm -f trace/generated-tracers-dtrace.h*
>>         rm -f $(foreach f,$(GENERATED_FILES),$(f) $(f)-timestamp)
>> +       rm -f qapi-gen-timestamp
>>         rm -rf qapi-generated
>>         rm -rf qga/qapi-generated
>>         for d in $(ALL_SUBDIRS); do \
>> @@ -803,13 +793,11 @@ qemu-monitor-info.texi: $(SRC_PATH)/hmp-commands-info.hx $(SRC_PATH)/scripts/hxt
>>  qemu-img-cmds.texi: $(SRC_PATH)/qemu-img-cmds.hx $(SRC_PATH)/scripts/hxtool
>>         $(call quiet-command,sh $(SRC_PATH)/scripts/hxtool -t < $< > $@,"GEN","$@")
>>
>> -docs/interop/qemu-qmp-qapi.texi docs/interop/qemu-ga-qapi.texi: $(SRC_PATH)/scripts/qapi2texi.py $(qapi-py)
>> +docs/interop/qemu-qmp-qapi.texi: qapi.texi
>> +       @cp -p $< $@
>>
>> -docs/interop/qemu-qmp-qapi.texi: $(qapi-modules)
>> -       $(call quiet-command,$(PYTHON) $(SRC_PATH)/scripts/qapi2texi.py $< > $@,"GEN","$@")
>> -
>> -docs/interop/qemu-ga-qapi.texi: $(SRC_PATH)/qga/qapi-schema.json
>> -       $(call quiet-command,$(PYTHON) $(SRC_PATH)/scripts/qapi2texi.py $< > $@,"GEN","$@")
>> +docs/interop/qemu-ga-qapi.texi: qga/qapi-generated/qga-qapi.texi
>> +       @cp -p $< $@
>>
>>  qemu.1: qemu-doc.texi qemu-options.texi qemu-monitor.texi qemu-monitor-info.texi
>>  qemu.1: qemu-option-trace.texi
>> diff --git a/scripts/qapi-gen.py b/scripts/qapi-gen.py
>> new file mode 100755
>> index 0000000000..575c938a1b
>> --- /dev/null
>> +++ b/scripts/qapi-gen.py
>> @@ -0,0 +1,41 @@
>> +#!/usr/bin/env python
>> +# QAPI generator
>> +#
>> +# This work is licensed under the terms of the GNU GPL, version 2 or later.
>> +# See the COPYING file in the top-level directory.
>> +
>> +import sys
>> +from qapi.common import parse_command_line, QAPISchema
>> +from qapi.types import gen_types
>> +from qapi.visit import gen_visit
>> +from qapi.commands import gen_commands
>> +from qapi.events import gen_events
>> +from qapi.introspect import gen_introspect
>> +from qapi.doc import gen_doc
>> +
>> +
>> +def main(argv):
>> +    (input_file, output_dir, do_c, do_h, prefix, opts) = \
>> +        parse_command_line('bu', ['builtins', 'unmask-non-abi-names'])
>> +
>> +    opt_builtins = False
>> +    opt_unmask = False
>> +
>> +    for o, a in opts:
>> +        if o in ('-b', '--builtins'):
>> +            opt_builtins = True
>> +        if o in ('-u', '--unmask-non-abi-names'):
>> +            opt_unmask = True
>> +
>> +    schema = QAPISchema(input_file)
>> +
>> +    gen_types(schema, output_dir, prefix, opt_builtins)
>> +    gen_visit(schema, output_dir, prefix, opt_builtins)
>> +    gen_commands(schema, output_dir, prefix)
>> +    gen_events(schema, output_dir, prefix)
>> +    gen_introspect(schema, output_dir, prefix, opt_unmask)
>> +    gen_doc(schema, output_dir, prefix)
>> +
>> +
>> +if __name__ == '__main__':
>> +    main(sys.argv)
>> diff --git a/scripts/qapi/__init__.py b/scripts/qapi/__init__.py
>> new file mode 100644
>> index 0000000000..e69de29bb2
>> diff --git a/scripts/qapi-commands.py b/scripts/qapi/commands.py
>> similarity index 94%
>> rename from scripts/qapi-commands.py
>> rename to scripts/qapi/commands.py
>> index 331b58670e..383a4dd426 100644
>> --- a/scripts/qapi-commands.py
>> +++ b/scripts/qapi/commands.py
>> @@ -13,7 +13,7 @@ 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 *
>> +from qapi.common import *
>>
>>
>>  def gen_command_decl(name, arg_type, boxed, ret_type):
>> @@ -255,13 +255,8 @@ class QAPISchemaGenCommandVisitor(QAPISchemaVisitor):
>>          self._regy += gen_register_command(name, success_response)
>>
>>
>> -def main(argv):
>> -    (input_file, output_dir, do_c, do_h, prefix, opts) = parse_command_line()
>> -
>> -    blurb = '''
>> - * Schema-defined QAPI/QMP commands
>> -'''
>> -
>> +def gen_commands(schema, output_dir, prefix):
>> +    blurb = ' * Schema-defined QAPI/QMP commands'
>>      genc = QAPIGenC(blurb, __doc__)
>>      genh = QAPIGenH(blurb, __doc__)
>>
>> @@ -290,17 +285,9 @@ void %(c_prefix)sqmp_init_marshal(QmpCommandList *cmds);
>>  ''',
>>                      prefix=prefix, c_prefix=c_name(prefix, protect=False)))
>>
>> -    schema = QAPISchema(input_file)
>>      vis = QAPISchemaGenCommandVisitor(prefix)
>>      schema.visit(vis)
>>      genc.body(vis.defn)
>>      genh.body(vis.decl)
>> -
>> -    if do_c:
>> -        genc.write(output_dir, prefix + 'qmp-marshal.c')
>> -    if do_h:
>> -        genh.write(output_dir, prefix + 'qmp-commands.h')
>> -
>> -
>> -if __name__ == '__main__':
>> -    main(sys.argv)
>> +    genc.write(output_dir, prefix + 'qmp-marshal.c')
>> +    genh.write(output_dir, prefix + 'qmp-commands.h')
>> diff --git a/scripts/qapi.py b/scripts/qapi/common.py
>> similarity index 100%
>> rename from scripts/qapi.py
>> rename to scripts/qapi/common.py
>> diff --git a/scripts/qapi2texi.py b/scripts/qapi/doc.py
>> old mode 100755
>> new mode 100644
>> similarity index 92%
>> rename from scripts/qapi2texi.py
>> rename to scripts/qapi/doc.py
>> index 924b374cd3..1f57f6e1c2
>> --- a/scripts/qapi2texi.py
>> +++ b/scripts/qapi/doc.py
>> @@ -4,10 +4,9 @@
>>  # This work is licensed under the terms of the GNU LGPL, version 2+.
>>  # See the COPYING file in the top-level directory.
>>  """This script produces the documentation of a qapi schema in texinfo format"""
>> +
>>  import re
>> -import sys
>> -
>> -import qapi
>> +import qapi.common
>>
>>  MSG_FMT = """
>>  @deftypefn {type} {{}} {name}
>> @@ -196,7 +195,7 @@ def texi_entity(doc, what, base=None, variants=None,
>>              + texi_sections(doc))
>>
>>
>> -class QAPISchemaGenDocVisitor(qapi.QAPISchemaVisitor):
>> +class QAPISchemaGenDocVisitor(qapi.common.QAPISchemaVisitor):
>
> Would be a bit easier to read and more consitent with a top-level
> "from qapi.common import QAPISchemaVisitor"

Can do.

>>      def __init__(self):
>>          self.out = None
>>          self.cur_doc = None
>> @@ -271,20 +270,8 @@ def texi_schema(schema):
>>      return gen.out
>>
>>
>> -def main(argv):
>> -    """Takes schema argument, prints result to stdout"""
>> -    if len(argv) != 2:
>> -        print >>sys.stderr, "%s: need exactly 1 argument: SCHEMA" % argv[0]
>> -        sys.exit(1)
>> -
>> -    schema = qapi.QAPISchema(argv[1])
>> -    if not qapi.doc_required:
>> -        print >>sys.stderr, ("%s: need pragma 'doc-required' "
>> -                             "to generate documentation" % argv[0])
>> -        sys.exit(1)
>> -    print '@c AUTOMATICALLY GENERATED, DO NOT MODIFY\n'
>> -    print texi_schema(schema),
>> -
>> -
>> -if __name__ == '__main__':
>> -    main(sys.argv)
>> +def gen_doc(schema, output_dir, prefix):
>> +    if qapi.common.doc_required:
>> +        gen = qapi.common.QAPIGenDoc()
>> +        gen.body(texi_schema(schema))
>> +        gen.write(output_dir, prefix + 'qapi.texi')
>> diff --git a/scripts/qapi-event.py b/scripts/qapi/events.py
>> similarity index 92%
>> rename from scripts/qapi-event.py
>> rename to scripts/qapi/events.py
>> index 5b33c694d4..1f267686db 100644
>> --- a/scripts/qapi-event.py
>> +++ b/scripts/qapi/events.py
>> @@ -12,7 +12,7 @@ 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 *
>> +from qapi.common import *
>>
>>
>>  def build_event_send_proto(name, arg_type, boxed):
>> @@ -171,13 +171,8 @@ class QAPISchemaGenEventVisitor(QAPISchemaVisitor):
>>          self._event_names.append(name)
>>
>>
>> -def main(argv):
>> -    (input_file, output_dir, do_c, do_h, prefix, dummy) = parse_command_line()
>> -
>> -    blurb = '''
>> - * Schema-defined QAPI/QMP events
>> -'''
>> -
>> +def gen_events(schema, output_dir, prefix):
>> +    blurb = ' * Schema-defined QAPI/QMP events'
>>      genc = QAPIGenC(blurb, __doc__)
>>      genh = QAPIGenH(blurb, __doc__)
>>
>> @@ -201,17 +196,9 @@ def main(argv):
>>  ''',
>>                      prefix=prefix))
>>
>> -    schema = QAPISchema(input_file)
>>      vis = QAPISchemaGenEventVisitor(prefix)
>>      schema.visit(vis)
>>      genc.body(vis.defn)
>>      genh.body(vis.decl)
>> -
>> -    if do_c:
>> -        genc.write(output_dir, prefix + 'qapi-event.c')
>> -    if do_h:
>> -        genh.write(output_dir, prefix + 'qapi-event.h')
>> -
>> -
>> -if __name__ == '__main__':
>> -    main(sys.argv)
>> +    genc.write(output_dir, prefix + 'qapi-event.c')
>> +    genh.write(output_dir, prefix + 'qapi-event.h')
>> diff --git a/scripts/qapi-introspect.py b/scripts/qapi/introspect.py
>> similarity index 90%
>> rename from scripts/qapi-introspect.py
>> rename to scripts/qapi/introspect.py
>> index 09e7d1f140..2153060f2c 100644
>> --- a/scripts/qapi-introspect.py
>> +++ b/scripts/qapi/introspect.py
>> @@ -10,7 +10,7 @@ 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 *
>> +from qapi.common import *
>>
>>
>>  # Caveman's json.dumps() replacement (we're stuck at Python 2.4)
>> @@ -168,22 +168,8 @@ const char %(c_name)s[] = %(c_string)s;
>>          self._gen_json(name, 'event', {'arg-type': self._use_type(arg_type)})
>>
>>
>> -def main(argv):
>> -    # Debugging aid: unmask QAPI schema's type names
>> -    # We normally mask them, because they're not QMP wire ABI
>> -    opt_unmask = False
>> -
>> -    (input_file, output_dir, do_c, do_h, prefix, opts) = \
>> -        parse_command_line('u', ['unmask-non-abi-names'])
>> -
>> -    for o, a in opts:
>> -        if o in ('-u', '--unmask-non-abi-names'):
>> -            opt_unmask = True
>> -
>> -    blurb = '''
>> - * QAPI/QMP schema introspection
>> -'''
>> -
>> +def gen_introspect(schema, output_dir, prefix, opt_unmask):
>> +    blurb = ' * QAPI/QMP schema introspection'
>>      genc = QAPIGenC(blurb, __doc__)
>>      genh = QAPIGenH(blurb, __doc__)
>>
>> @@ -194,17 +180,9 @@ def main(argv):
>>  ''',
>>                      prefix=prefix))
>>
>> -    schema = QAPISchema(input_file)
>>      vis = QAPISchemaGenIntrospectVisitor(prefix, opt_unmask)
>>      schema.visit(vis)
>>      genc.body(vis.defn)
>>      genh.body(vis.decl)
>> -
>> -    if do_c:
>> -        genc.write(output_dir, prefix + 'qmp-introspect.c')
>> -    if do_h:
>> -        genh.write(output_dir, prefix + 'qmp-introspect.h')
>> -
>> -
>> -if __name__ == '__main__':
>> -    main(sys.argv)
>> +    genc.write(output_dir, prefix + 'qmp-introspect.c')
>> +    genh.write(output_dir, prefix + 'qmp-introspect.h')
>> diff --git a/scripts/qapi-types.py b/scripts/qapi/types.py
>> similarity index 90%
>> rename from scripts/qapi-types.py
>> rename to scripts/qapi/types.py
>> index f2ddde94b1..b2095120e0 100644
>> --- a/scripts/qapi-types.py
>> +++ b/scripts/qapi/types.py
>> @@ -13,7 +13,7 @@ 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 *
>> +from qapi.common import *
>>
>>
>>  # variants must be emitted before their container; track what has already
>> @@ -241,24 +241,8 @@ class QAPISchemaGenTypeVisitor(QAPISchemaVisitor):
>>          self._gen_type_cleanup(name)
>>
>>
>> -def main(argv):
>> -    # If you link code generated from multiple schemata, you want only one
>> -    # instance of the code for built-in types.  Generate it only when
>> -    # opt_builtins, enabled by command line option -b.  See also
>> -    # QAPISchemaGenTypeVisitor.visit_end().
>> -    opt_builtins = False
>> -
>> -    (input_file, output_dir, do_c, do_h, prefix, opts) = \
>> -        parse_command_line('b', ['builtins'])
>> -
>> -    for o, a in opts:
>> -        if o in ('-b', '--builtins'):
>> -            opt_builtins = True
>> -
>> -    blurb = '''
>> - * Schema-defined QAPI types
>> -'''
>> -
>> +def gen_types(schema, output_dir, prefix, opt_builtins):
>> +    blurb = ' * Schema-defined QAPI types'
>>      genc = QAPIGenC(blurb, __doc__)
>>      genh = QAPIGenH(blurb, __doc__)
>>
>> @@ -274,17 +258,9 @@ def main(argv):
>>  #include "qapi/util.h"
>>  '''))
>>
>> -    schema = QAPISchema(input_file)
>>      vis = QAPISchemaGenTypeVisitor(opt_builtins)
>>      schema.visit(vis)
>>      genc.body(vis.defn)
>>      genh.body(vis.decl)
>> -
>> -    if do_c:
>> -        genc.write(output_dir, prefix + 'qapi-types.c')
>> -    if do_h:
>> -        genh.write(output_dir, prefix + 'qapi-types.h')
>> -
>> -
>> -if __name__ == '__main__':
>> -    main(sys.argv)
>> +    genc.write(output_dir, prefix + 'qapi-types.c')
>> +    genh.write(output_dir, prefix + 'qapi-types.h')
>> diff --git a/scripts/qapi-visit.py b/scripts/qapi/visit.py
>> similarity index 92%
>> rename from scripts/qapi-visit.py
>> rename to scripts/qapi/visit.py
>> index 473fa72574..80c0b85f8c 100644
>> --- a/scripts/qapi-visit.py
>> +++ b/scripts/qapi/visit.py
>> @@ -13,7 +13,7 @@ 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 *
>> +from qapi.common import *
>>
>>
>>  def gen_visit_decl(name, scalar=False):
>> @@ -324,24 +324,8 @@ class QAPISchemaGenVisitVisitor(QAPISchemaVisitor):
>>          self.defn += gen_visit_alternate(name, variants)
>>
>>
>> -def main(argv):
>> -    # If you link code generated from multiple schemata, you want only one
>> -    # instance of the code for built-in types.  Generate it only when
>> -    # opt_builtins, enabled by command line option -b.  See also
>> -    # QAPISchemaGenVisitVisitor.visit_end().
>> -    opt_builtins = False
>> -
>> -    (input_file, output_dir, do_c, do_h, prefix, opts) = \
>> -        parse_command_line('b', ['builtins'])
>> -
>> -    for o, a in opts:
>> -        if o in ('-b', '--builtins'):
>> -            opt_builtins = True
>> -
>> -    blurb = '''
>> - * Schema-defined QAPI visitors
>> -'''
>> -
>> +def gen_visit(schema, output_dir, prefix, opt_builtins):
>> +    blurb = ' * Schema-defined QAPI visitors'
>>      genc = QAPIGenC(blurb, __doc__)
>>      genh = QAPIGenH(blurb, __doc__)
>>
>> @@ -361,17 +345,9 @@ def main(argv):
>>  ''',
>>                      prefix=prefix))
>>
>> -    schema = QAPISchema(input_file)
>>      vis = QAPISchemaGenVisitVisitor(opt_builtins)
>>      schema.visit(vis)
>>      genc.body(vis.defn)
>>      genh.body(vis.decl)
>> -
>> -    if do_c:
>> -        genc.write(output_dir, prefix + 'qapi-visit.c')
>> -    if do_h:
>> -        genh.write(output_dir, prefix + 'qapi-visit.h')
>> -
>> -
>> -if __name__ == '__main__':
>> -    main(sys.argv)
>> +    genc.write(output_dir, prefix + 'qapi-visit.c')
>> +    genh.write(output_dir, prefix + 'qapi-visit.h')
>> diff --git a/tests/Makefile.include b/tests/Makefile.include
>> index 851aafe9d1..768655a810 100644
>> --- a/tests/Makefile.include
>> +++ b/tests/Makefile.include
>> @@ -23,7 +23,16 @@ check-help:
>>  ifneq ($(wildcard config-host.mak),)
>>  export SRC_PATH
>>
>> -qapi-py = $(SRC_PATH)/scripts/qapi.py $(SRC_PATH)/scripts/ordereddict.py
>> +# TODO don't duplicate $(SRC_PATH)/Makefile's qapi-py here
>
> hmm, it doesn't look necessary, since the makefile is included from
> there. Right?

That's what I thought, so I deleted it, breaking my build.  I still
don't quite understand what's happening here.

>> +qapi-py = $(SRC_PATH)/scripts/qapi/commands.py \
>> +$(SRC_PATH)/scripts/qapi/events.py \
>> +$(SRC_PATH)/scripts/qapi/introspect.py \
>> +$(SRC_PATH)/scripts/qapi/types.py \
>> +$(SRC_PATH)/scripts/qapi/visit.py \
>> +$(SRC_PATH)/scripts/qapi/common.py \
>> +$(SRC_PATH)/scripts/qapi/doc.py \
>> +$(SRC_PATH)/scripts/ordereddict.py \
>> +$(SRC_PATH)/scripts/qapi-gen.py
>>
>>  # Get the list of all supported sysemu targets
>>  SYSEMU_TARGET_LIST := $(subst -softmmu.mak,,$(notdir \
>> @@ -642,34 +651,24 @@ tests/test-logging$(EXESUF): tests/test-logging.o $(test-util-obj-y)
>>  tests/test-replication$(EXESUF): tests/test-replication.o $(test-util-obj-y) \
>>         $(test-block-obj-y)
>>
>> -tests/test-qapi-types.c tests/test-qapi-types.h :\
>> -$(SRC_PATH)/tests/qapi-schema/qapi-schema-test.json $(SRC_PATH)/scripts/qapi-types.py $(qapi-py)
>> -       $(call quiet-command,$(PYTHON) $(SRC_PATH)/scripts/qapi-types.py \
>> -               $(gen-out-type) -o tests -p "test-" $<, \
>> -               "GEN","$@")
>> -tests/test-qapi-visit.c tests/test-qapi-visit.h :\
>> -$(SRC_PATH)/tests/qapi-schema/qapi-schema-test.json $(SRC_PATH)/scripts/qapi-visit.py $(qapi-py)
>> -       $(call quiet-command,$(PYTHON) $(SRC_PATH)/scripts/qapi-visit.py \
>> -               $(gen-out-type) -o tests -p "test-" $<, \
>> -               "GEN","$@")
>> -tests/test-qmp-commands.h tests/test-qmp-marshal.c :\
>> -$(SRC_PATH)/tests/qapi-schema/qapi-schema-test.json $(SRC_PATH)/scripts/qapi-commands.py $(qapi-py)
>> -       $(call quiet-command,$(PYTHON) $(SRC_PATH)/scripts/qapi-commands.py \
>> -               $(gen-out-type) -o tests -p "test-" $<, \
>> -               "GEN","$@")
>> -tests/test-qapi-event.c tests/test-qapi-event.h :\
>> -$(SRC_PATH)/tests/qapi-schema/qapi-schema-test.json $(SRC_PATH)/scripts/qapi-event.py $(qapi-py)
>> -       $(call quiet-command,$(PYTHON) $(SRC_PATH)/scripts/qapi-event.py \
>> -               $(gen-out-type) -o tests -p "test-" $<, \
>> -               "GEN","$@")
>> -tests/test-qmp-introspect.c tests/test-qmp-introspect.h :\
>> -$(SRC_PATH)/tests/qapi-schema/qapi-schema-test.json $(SRC_PATH)/scripts/qapi-introspect.py $(qapi-py)
>> -       $(call quiet-command,$(PYTHON) $(SRC_PATH)/scripts/qapi-introspect.py \
>> -               $(gen-out-type) -o tests -p "test-" $<, \
>> -               "GEN","$@")
>> +tests/test-qapi-types.c tests/test-qapi-types.h \
>> +tests/test-qapi-visit.c tests/test-qapi-visit.h \
>> +tests/test-qmp-commands.h tests/test-qmp-marshal.c \
>> +tests/test-qapi-event.c tests/test-qapi-event.h \
>> +tests/test-qmp-introspect.c tests/test-qmp-introspect.h: \
>> +tests/test-qapi-gen-timestamp ;
>> +tests/test-qapi-gen-timestamp: $(SRC_PATH)/tests/qapi-schema/qapi-schema-test.json $(qapi-py)
>> +       $(call quiet-command,$(PYTHON) $(SRC_PATH)/scripts/qapi-gen.py \
>> +               -o tests -p "test-" $<, \
>> +               "GEN","$(@:%-timestamp=%)")
>> +       @>$@
>>
>> -tests/qapi-schema/doc-good.test.texi: $(SRC_PATH)/tests/qapi-schema/doc-good.json $(SRC_PATH)/scripts/qapi2texi.py $(qapi-py)
>> -       $(call quiet-command,$(PYTHON) $(SRC_PATH)/scripts/qapi2texi.py $< > $@,"GEN","$@")
>> +tests/qapi-schema/doc-good.test.texi: $(SRC_PATH)/tests/qapi-schema/doc-good.json $(qapi-py)
>> +       $(call quiet-command,$(PYTHON) $(SRC_PATH)/scripts/qapi-gen.py \
>> +               -o tests/qapi-schema -p "doc-good-" $<, \
>> +               "GEN","$@")
>> +       @mv tests/qapi-schema/doc-good-qapi.texi $@
>> +       @rm -f tests/qapi-schema/doc-good-qapi-*.[ch] tests/qapi-schema/doc-good-qmp-*.[ch]
>>
>>  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)
>> @@ -937,6 +936,7 @@ check-clean:
>>         $(MAKE) -C tests/tcg clean
>>         rm -rf $(check-unit-y) tests/*.o $(QEMU_IOTESTS_HELPERS-y)
>>         rm -rf $(sort $(foreach target,$(SYSEMU_TARGET_LIST), $(check-qtest-$(target)-y)) $(check-qtest-generic-y))
>> +       rm -f tests/test-qapi-gen-timestamp
>>
>>  clean: check-clean
>>
>> diff --git a/tests/qapi-schema/test-qapi.py b/tests/qapi-schema/test-qapi.py
>> index fe0ca08d78..7772d09919 100644
>> --- a/tests/qapi-schema/test-qapi.py
>> +++ b/tests/qapi-schema/test-qapi.py
>> @@ -10,7 +10,7 @@
>>  # See the COPYING file in the top-level directory.
>>  #
>>
>> -from qapi import *
>> +from qapi.common import *
>>  from pprint import pprint
>>  import os
>>  import sys
>> --
>> 2.13.6
>>
>
> good to me otherwise,
> Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>

Thanks!
Eric Blake Feb. 5, 2018, 3:52 p.m. UTC | #5
On 02/03/2018 03:03 AM, Markus Armbruster wrote:
> Eric Blake <eblake@redhat.com> writes:
> 
>> On 02/02/2018 07:03 AM, Markus Armbruster wrote:
>>> Whenever qapi-schema.json changes, we run six programs eleven times to
>>> update eleven files.  This is silly.  Replace the six programs by a
>>> single program that spits out all eleven files.
>>
>> Yay, about time!
>>
>> One program, but still invoked multiple times:
>>

>>>  rename scripts/{qapi2texi.py => qapi/doc.py} (92%)
>>>  mode change 100755 => 100644
>>
>> Unintinentional?  We're inconsistent on which of our *.py files are
>> executable in git.  Does it matter whether a file that is designed for
>> use as a module is marked executable (if so, perhaps 5/21 should be
>> adding the x attribute on other files, rather than this patch removing
>> it on the doc generator).
> 
> qapi2texi.py is no longer runnable as a standalone program after this
> patch.
> 
> So are qapi-{commands,events,introspect,types,visit}.py, but they never
> had the executable bit set.

Okay, so dropping the bit consistently makes sense; still, a mention in
the commit message wouldn't hurt.

> 
>>> +++ b/Makefile
>>
>>> +qga/qapi-generated/qga-qapi-types.c qga/qapi-generated/qga-qapi-types.h \
>>> +qga/qapi-generated/qga-qapi-visit.c qga/qapi-generated/qga-qapi-visit.h \
>>> +qga/qapi-generated/qga-qmp-commands.h qga/qapi-generated/qga-qmp-marshal.c \
>>> +qga/qapi-generated/qga-qapi.texi: \
>>> +qga/qapi-generated/qapi-gen-timestamp ;
>>> +qga/qapi-generated/qapi-gen-timestamp: $(SRC_PATH)/qga/qapi-schema.json $(qapi-py)
>>> +	$(call quiet-command,$(PYTHON) $(SRC_PATH)/scripts/qapi-gen.py \
>>> +		-o qga/qapi-generated -p "qga-" $<, \
>>> +		"GEN","$(@:%-timestamp=%)")
>>> +	@>$@
>>
>> once for qga,
> 
> That's the QAPI/QGA schema.  The commit message talks about the QAPI/QMP
> schema.  I wish the two weren't named the same.

7 files here,...

> 
> Modularization might make fusing them possible.  Whether that's a good
> idea I don't know.
> 
>>> +qapi-types.c qapi-types.h \
>>> +qapi-visit.c qapi-visit.h \
>>> +qmp-commands.h qmp-marshal.c \
>>> +qapi-event.c qapi-event.h \
>>> +qmp-introspect.h qmp-introspect.c \
>>> +qapi.texi: \
>>> +qapi-gen-timestamp ;
>>> +qapi-gen-timestamp: $(qapi-modules) $(qapi-py)
>>> +	$(call quiet-command,$(PYTHON) $(SRC_PATH)/scripts/qapi-gen.py \
>>> +		-o "." -b $<, \
>>> +		"GEN","$(@:%-timestamp=%)")
>>> +	@>$@
>>
>> and again for the main level.  Still, a definite improvement!

11 files here,...

> 
> Perhaps I can find a way to clarify the commit message.
> 

[1]


>>> -def main(argv):
>>> -    (input_file, output_dir, do_c, do_h, prefix, opts) = parse_command_line()
>>> -
>>> -    blurb = '''
>>> - * Schema-defined QAPI/QMP commands
>>> -'''
>>> -
>>> +def gen_commands(schema, output_dir, prefix):
>>> +    blurb = ' * Schema-defined QAPI/QMP commands'
>>
>> Some churn on the definition of blurb; worth tidying this in the
>> introduction earlier in the series?
> 
> Doesn't seem worth a separate patch, but I can try to reshuffle things
> to reduce churn.

Yeah, my question was more about the conversion between multiline
'''...''' to single-line '...'; why not just use the single-line
construct earlier in the series when introducing the blurb variable.
You are right that creating blurb didn't need a separate patch, just
less churn over the series.

>>>  
>>> -qapi-py = $(SRC_PATH)/scripts/qapi.py $(SRC_PATH)/scripts/ordereddict.py
>>> +# TODO don't duplicate $(SRC_PATH)/Makefile's qapi-py here
>>> +qapi-py = $(SRC_PATH)/scripts/qapi/commands.py \
>>> +$(SRC_PATH)/scripts/qapi/events.py \
>>> +$(SRC_PATH)/scripts/qapi/introspect.py \
>>> +$(SRC_PATH)/scripts/qapi/types.py \
>>> +$(SRC_PATH)/scripts/qapi/visit.py \
>>> +$(SRC_PATH)/scripts/qapi/common.py \
>>> +$(SRC_PATH)/scripts/qapi/doc.py \
>>> +$(SRC_PATH)/scripts/ordereddict.py \
>>> +$(SRC_PATH)/scripts/qapi-gen.py
>>
>> So, were you counting these in the 11 generated files mentioned in the
>> commit message? :)
> 
> I'm not sure I understand what you mean here...

[1] and 9 more files.  So, the commit message only mentioned the 11 QMP
files, rather than all 27 (if I counted right) QAPI generated files.  My
comments were trying to point out that you simplified more than just the
QMP code generation into fewer script invocations, and the counts looked
off since out of the three spots converted, only one of the spots had 11
files.
Markus Armbruster Feb. 6, 2018, 7:45 a.m. UTC | #6
Eric Blake <eblake@redhat.com> writes:

> On 02/03/2018 03:03 AM, Markus Armbruster wrote:
>> Eric Blake <eblake@redhat.com> writes:
>> 
>>> On 02/02/2018 07:03 AM, Markus Armbruster wrote:
>>>> Whenever qapi-schema.json changes, we run six programs eleven times to
>>>> update eleven files.  This is silly.  Replace the six programs by a
>>>> single program that spits out all eleven files.
>>>
>>> Yay, about time!
>>>
>>> One program, but still invoked multiple times:
>>>
>
>>>>  rename scripts/{qapi2texi.py => qapi/doc.py} (92%)
>>>>  mode change 100755 => 100644
>>>
>>> Unintinentional?  We're inconsistent on which of our *.py files are
>>> executable in git.  Does it matter whether a file that is designed for
>>> use as a module is marked executable (if so, perhaps 5/21 should be
>>> adding the x attribute on other files, rather than this patch removing
>>> it on the doc generator).
>> 
>> qapi2texi.py is no longer runnable as a standalone program after this
>> patch.
>> 
>> So are qapi-{commands,events,introspect,types,visit}.py, but they never
>> had the executable bit set.
>
> Okay, so dropping the bit consistently makes sense; still, a mention in
> the commit message wouldn't hurt.

Can do.

>>>> +++ b/Makefile
>>>
>>>> +qga/qapi-generated/qga-qapi-types.c qga/qapi-generated/qga-qapi-types.h \
>>>> +qga/qapi-generated/qga-qapi-visit.c qga/qapi-generated/qga-qapi-visit.h \
>>>> +qga/qapi-generated/qga-qmp-commands.h qga/qapi-generated/qga-qmp-marshal.c \
>>>> +qga/qapi-generated/qga-qapi.texi: \
>>>> +qga/qapi-generated/qapi-gen-timestamp ;
>>>> +qga/qapi-generated/qapi-gen-timestamp: $(SRC_PATH)/qga/qapi-schema.json $(qapi-py)
>>>> +	$(call quiet-command,$(PYTHON) $(SRC_PATH)/scripts/qapi-gen.py \
>>>> +		-o qga/qapi-generated -p "qga-" $<, \
>>>> +		"GEN","$(@:%-timestamp=%)")
>>>> +	@>$@
>>>
>>> once for qga,
>> 
>> That's the QAPI/QGA schema.  The commit message talks about the QAPI/QMP
>> schema.  I wish the two weren't named the same.
>
> 7 files here,...
>
>> 
>> Modularization might make fusing them possible.  Whether that's a good
>> idea I don't know.
>> 
>>>> +qapi-types.c qapi-types.h \
>>>> +qapi-visit.c qapi-visit.h \
>>>> +qmp-commands.h qmp-marshal.c \
>>>> +qapi-event.c qapi-event.h \
>>>> +qmp-introspect.h qmp-introspect.c \
>>>> +qapi.texi: \
>>>> +qapi-gen-timestamp ;
>>>> +qapi-gen-timestamp: $(qapi-modules) $(qapi-py)
>>>> +	$(call quiet-command,$(PYTHON) $(SRC_PATH)/scripts/qapi-gen.py \
>>>> +		-o "." -b $<, \
>>>> +		"GEN","$(@:%-timestamp=%)")
>>>> +	@>$@
>>>
>>> and again for the main level.  Still, a definite improvement!
>
> 11 files here,...
>
>> 
>> Perhaps I can find a way to clarify the commit message.
>> 
>
> [1]
>
>
>>>> -def main(argv):
>>>> -    (input_file, output_dir, do_c, do_h, prefix, opts) = parse_command_line()
>>>> -
>>>> -    blurb = '''
>>>> - * Schema-defined QAPI/QMP commands
>>>> -'''
>>>> -
>>>> +def gen_commands(schema, output_dir, prefix):
>>>> +    blurb = ' * Schema-defined QAPI/QMP commands'
>>>
>>> Some churn on the definition of blurb; worth tidying this in the
>>> introduction earlier in the series?
>> 
>> Doesn't seem worth a separate patch, but I can try to reshuffle things
>> to reduce churn.
>
> Yeah, my question was more about the conversion between multiline
> '''...''' to single-line '...'; why not just use the single-line
> construct earlier in the series when introducing the blurb variable.

Introduced in PATCH 01:

    -c_comment = '''
    -/*
    - * schema-defined QMP->QAPI command dispatch
    +blurb = '''
    + * Schema-defined QAPI/QMP commands
      *
      * Copyright IBM, Corp. 2011
      *
      * Authors:
      *  Anthony Liguori   <aliguori@us.ibm.com>
    - *
    - * 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.
    - *
    - */
    -'''

Shortened in PATCH 02:

     blurb = '''
      * Schema-defined QAPI/QMP commands
    - *
    - * Copyright IBM, Corp. 2011
    - *
    - * Authors:
    - *  Anthony Liguori   <aliguori@us.ibm.com>
     '''

Reformatted in PATCH 06 (see above).

Moved in PATCH 16 to visitor's __init__ for types and visits (the rest
aren't implemented, yet):

     class QAPISchemaGenTypeVisitor(QAPISchemaVisitor):
    -    def __init__(self, opt_builtins):
    +    def __init__(self, opt_builtins, prefix):
             self._opt_builtins = opt_builtins
    -        self.decl = None
    -        self.defn = None
    -        self._fwdecl = None
    -        self._btin = None
    +        self._prefix = prefix
    +        blurb = ' * Schema-defined QAPI types'
    +        self._genc = QAPIGenC(blurb, __doc__)
    +        self._genh = QAPIGenH(blurb, __doc__)

Variable eliminated there in PATCH 17:

    -        blurb = ' * Schema-defined QAPI types'
    -        self._genc = QAPIGenC(blurb, __doc__)
    -        self._genh = QAPIGenH(blurb, __doc__)
    +        self._module = {}
    +        self._add_module(None, ' * Built-in QAPI types')

I could delay the reformatting until PATCH 16, or do it in PATCH 02.
Feels like a wash to me, but if you have a preference...

> You are right that creating blurb didn't need a separate patch, just
> less churn over the series.
>
>>>>  
>>>> -qapi-py = $(SRC_PATH)/scripts/qapi.py $(SRC_PATH)/scripts/ordereddict.py
>>>> +# TODO don't duplicate $(SRC_PATH)/Makefile's qapi-py here
>>>> +qapi-py = $(SRC_PATH)/scripts/qapi/commands.py \
>>>> +$(SRC_PATH)/scripts/qapi/events.py \
>>>> +$(SRC_PATH)/scripts/qapi/introspect.py \
>>>> +$(SRC_PATH)/scripts/qapi/types.py \
>>>> +$(SRC_PATH)/scripts/qapi/visit.py \
>>>> +$(SRC_PATH)/scripts/qapi/common.py \
>>>> +$(SRC_PATH)/scripts/qapi/doc.py \
>>>> +$(SRC_PATH)/scripts/ordereddict.py \
>>>> +$(SRC_PATH)/scripts/qapi-gen.py
>>>
>>> So, were you counting these in the 11 generated files mentioned in the
>>> commit message? :)
>> 
>> I'm not sure I understand what you mean here...
>
> [1] and 9 more files.  So, the commit message only mentioned the 11 QMP
> files, rather than all 27 (if I counted right) QAPI generated files.  My
> comments were trying to point out that you simplified more than just the
> QMP code generation into fewer script invocations, and the counts looked
> off since out of the three spots converted, only one of the spots had 11
> files.

The commit message talks only about the QAPI/QMP schema.  It's confusing
because our poor taste in file names creates ambiguity: does
qapi-schema.json refer to ./qapi-schema.json (i.e. the QAPI/QMP one),
qga/qapi-schema.json, or both?

Perhaps I should rename qapi-schema.json to qapi/schema.json.

The commit message needs a note along the lines of "same for
qga/qapi-schema.json".
Eric Blake Feb. 6, 2018, 2:56 p.m. UTC | #7
On 02/06/2018 01:45 AM, Markus Armbruster wrote:

>>>>> -def main(argv):
>>>>> -    (input_file, output_dir, do_c, do_h, prefix, opts) = parse_command_line()
>>>>> -
>>>>> -    blurb = '''
>>>>> - * Schema-defined QAPI/QMP commands
>>>>> -'''
>>>>> -
>>>>> +def gen_commands(schema, output_dir, prefix):
>>>>> +    blurb = ' * Schema-defined QAPI/QMP commands'
>>>>
>>>> Some churn on the definition of blurb; worth tidying this in the
>>>> introduction earlier in the series?
>>>
>>> Doesn't seem worth a separate patch, but I can try to reshuffle things
>>> to reduce churn.
>>
>> Yeah, my question was more about the conversion between multiline
>> '''...''' to single-line '...'; why not just use the single-line
>> construct earlier in the series when introducing the blurb variable.
> 
> Introduced in PATCH 01:
> 
>      -c_comment = '''
>      -/*
>      - * schema-defined QMP->QAPI command dispatch
>      +blurb = '''
>      + * Schema-defined QAPI/QMP commands
>        *
>        * Copyright IBM, Corp. 2011

Multiline made sense here.

> Shortened in PATCH 02:
> 
>       blurb = '''
>        * Schema-defined QAPI/QMP commands
>      - *
>      - * Copyright IBM, Corp. 2011
>      - *
>      - * Authors:
>      - *  Anthony Liguori   <aliguori@us.ibm.com>
        '''
> 

Where it is now a single line...


> Variable eliminated there in PATCH 17:
> 
>      -        blurb = ' * Schema-defined QAPI types'
>      -        self._genc = QAPIGenC(blurb, __doc__)
>      -        self._genh = QAPIGenH(blurb, __doc__)
>      +        self._module = {}
>      +        self._add_module(None, ' * Built-in QAPI types')

Oh, I didn't even notice that!

> 
> I could delay the reformatting until PATCH 16, or do it in PATCH 02.
> Feels like a wash to me, but if you have a preference...

I guess my preference is to reformat to single line in PATCH 02, then 
patch 6 doesn't have to touch it, and patch 16 can still get rid of it. 
But, as it disappears by the end of the series anyways, I'm also okay if 
you don't bother (churn is not necessarily bad, if it is still easy to 
review).


> The commit message talks only about the QAPI/QMP schema.  It's confusing
> because our poor taste in file names creates ambiguity: does
> qapi-schema.json refer to ./qapi-schema.json (i.e. the QAPI/QMP one),
> qga/qapi-schema.json, or both?
> 
> Perhaps I should rename qapi-schema.json to qapi/schema.json.

I'd be in favor of such a change.  We'd also rename qga/qapi-schema.json 
to qga/schema.json?

> 
> The commit message needs a note along the lines of "same for
> qga/qapi-schema.json".
>
Markus Armbruster Feb. 8, 2018, 9:55 a.m. UTC | #8
Markus Armbruster <armbru@redhat.com> writes:

> Marc-Andre Lureau <mlureau@redhat.com> writes:
>
>> On Fri, Feb 2, 2018 at 2:03 PM, Markus Armbruster <armbru@redhat.com> wrote:
[...]
>>> diff --git a/scripts/qapi2texi.py b/scripts/qapi/doc.py
>>> old mode 100755
>>> new mode 100644
>>> similarity index 92%
>>> rename from scripts/qapi2texi.py
>>> rename to scripts/qapi/doc.py
>>> index 924b374cd3..1f57f6e1c2
>>> --- a/scripts/qapi2texi.py
>>> +++ b/scripts/qapi/doc.py
>>> @@ -4,10 +4,9 @@
>>>  # This work is licensed under the terms of the GNU LGPL, version 2+.
>>>  # See the COPYING file in the top-level directory.
>>>  """This script produces the documentation of a qapi schema in texinfo format"""
>>> +
>>>  import re
>>> -import sys
>>> -
>>> -import qapi
>>> +import qapi.common
>>>
>>>  MSG_FMT = """
>>>  @deftypefn {type} {{}} {name}
>>> @@ -196,7 +195,7 @@ def texi_entity(doc, what, base=None, variants=None,
>>>              + texi_sections(doc))
>>>
>>>
>>> -class QAPISchemaGenDocVisitor(qapi.QAPISchemaVisitor):
>>> +class QAPISchemaGenDocVisitor(qapi.common.QAPISchemaVisitor):
>>
>> Would be a bit easier to read and more consitent with a top-level
>> "from qapi.common import QAPISchemaVisitor"
>
> Can do.

The obvious patch (appended) doesn't work, because doc_required is
always False in gen_doc().  WTF?!?

[...]


diff --git a/scripts/qapi/doc.py b/scripts/qapi/doc.py
index 4027722032..919e77b79e 100644
--- a/scripts/qapi/doc.py
+++ b/scripts/qapi/doc.py
@@ -7,7 +7,7 @@
 
 from __future__ import print_function
 import re
-import qapi.common
+from qapi.common import doc_required, QAPIGenDoc, QAPISchemaVisitor
 
 MSG_FMT = """
 @deftypefn {type} {{}} {name}
@@ -196,7 +196,7 @@ def texi_entity(doc, what, base=None, variants=None,
             + texi_sections(doc))
 
 
-class QAPISchemaGenDocVisitor(qapi.common.QAPISchemaVisitor):
+class QAPISchemaGenDocVisitor(QAPISchemaVisitor):
     def __init__(self):
         self.out = None
         self.cur_doc = None
@@ -272,7 +272,7 @@ def texi_schema(schema):
 
 
 def gen_doc(schema, output_dir, prefix):
-    if qapi.common.doc_required:
-        gen = qapi.common.QAPIGenDoc()
+    if doc_required:
+        gen = QAPIGenDoc()
         gen.add(texi_schema(schema))
         gen.write(output_dir, prefix + 'qapi.texi')
diff mbox series

Patch

diff --git a/Makefile b/Makefile
index af31e8981f..e02f0c13ef 100644
--- a/Makefile
+++ b/Makefile
@@ -92,6 +92,7 @@  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.texi
 
 GENERATED_FILES += trace/generated-tcg-tracers.h
 
@@ -477,25 +478,26 @@  qemu-ga$(EXESUF): QEMU_CFLAGS += -I qga/qapi-generated
 qemu-keymap$(EXESUF): LIBS += $(XKBCOMMON_LIBS)
 qemu-keymap$(EXESUF): QEMU_CFLAGS += $(XKBCOMMON_CFLAGS)
 
-gen-out-type = $(subst .,-,$(suffix $@))
+qapi-py = $(SRC_PATH)/scripts/qapi/commands.py \
+$(SRC_PATH)/scripts/qapi/events.py \
+$(SRC_PATH)/scripts/qapi/introspect.py \
+$(SRC_PATH)/scripts/qapi/types.py \
+$(SRC_PATH)/scripts/qapi/visit.py \
+$(SRC_PATH)/scripts/qapi/common.py \
+$(SRC_PATH)/scripts/qapi/doc.py \
+$(SRC_PATH)/scripts/ordereddict.py \
+$(SRC_PATH)/scripts/qapi-gen.py
 
-qapi-py = $(SRC_PATH)/scripts/qapi.py $(SRC_PATH)/scripts/ordereddict.py
-
-qga/qapi-generated/qga-qapi-types.c qga/qapi-generated/qga-qapi-types.h :\
-$(SRC_PATH)/qga/qapi-schema.json $(SRC_PATH)/scripts/qapi-types.py $(qapi-py)
-	$(call quiet-command,$(PYTHON) $(SRC_PATH)/scripts/qapi-types.py \
-		$(gen-out-type) -o qga/qapi-generated -p "qga-" $<, \
-		"GEN","$@")
-qga/qapi-generated/qga-qapi-visit.c qga/qapi-generated/qga-qapi-visit.h :\
-$(SRC_PATH)/qga/qapi-schema.json $(SRC_PATH)/scripts/qapi-visit.py $(qapi-py)
-	$(call quiet-command,$(PYTHON) $(SRC_PATH)/scripts/qapi-visit.py \
-		$(gen-out-type) -o qga/qapi-generated -p "qga-" $<, \
-		"GEN","$@")
-qga/qapi-generated/qga-qmp-commands.h qga/qapi-generated/qga-qmp-marshal.c :\
-$(SRC_PATH)/qga/qapi-schema.json $(SRC_PATH)/scripts/qapi-commands.py $(qapi-py)
-	$(call quiet-command,$(PYTHON) $(SRC_PATH)/scripts/qapi-commands.py \
-		$(gen-out-type) -o qga/qapi-generated -p "qga-" $<, \
-		"GEN","$@")
+qga/qapi-generated/qga-qapi-types.c qga/qapi-generated/qga-qapi-types.h \
+qga/qapi-generated/qga-qapi-visit.c qga/qapi-generated/qga-qapi-visit.h \
+qga/qapi-generated/qga-qmp-commands.h qga/qapi-generated/qga-qmp-marshal.c \
+qga/qapi-generated/qga-qapi.texi: \
+qga/qapi-generated/qapi-gen-timestamp ;
+qga/qapi-generated/qapi-gen-timestamp: $(SRC_PATH)/qga/qapi-schema.json $(qapi-py)
+	$(call quiet-command,$(PYTHON) $(SRC_PATH)/scripts/qapi-gen.py \
+		-o qga/qapi-generated -p "qga-" $<, \
+		"GEN","$(@:%-timestamp=%)")
+	@>$@
 
 qapi-modules = $(SRC_PATH)/qapi-schema.json $(SRC_PATH)/qapi/common.json \
                $(SRC_PATH)/qapi/block.json $(SRC_PATH)/qapi/block-core.json \
@@ -512,31 +514,18 @@  qapi-modules = $(SRC_PATH)/qapi-schema.json $(SRC_PATH)/qapi/common.json \
                $(SRC_PATH)/qapi/transaction.json \
                $(SRC_PATH)/qapi/ui.json
 
-qapi-types.c qapi-types.h :\
-$(qapi-modules) $(SRC_PATH)/scripts/qapi-types.py $(qapi-py)
-	$(call quiet-command,$(PYTHON) $(SRC_PATH)/scripts/qapi-types.py \
-		$(gen-out-type) -o "." -b $<, \
-		"GEN","$@")
-qapi-visit.c qapi-visit.h :\
-$(qapi-modules) $(SRC_PATH)/scripts/qapi-visit.py $(qapi-py)
-	$(call quiet-command,$(PYTHON) $(SRC_PATH)/scripts/qapi-visit.py \
-		$(gen-out-type) -o "." -b $<, \
-		"GEN","$@")
-qapi-event.c qapi-event.h :\
-$(qapi-modules) $(SRC_PATH)/scripts/qapi-event.py $(qapi-py)
-	$(call quiet-command,$(PYTHON) $(SRC_PATH)/scripts/qapi-event.py \
-		$(gen-out-type) -o "." $<, \
-		"GEN","$@")
-qmp-commands.h qmp-marshal.c :\
-$(qapi-modules) $(SRC_PATH)/scripts/qapi-commands.py $(qapi-py)
-	$(call quiet-command,$(PYTHON) $(SRC_PATH)/scripts/qapi-commands.py \
-		$(gen-out-type) -o "." $<, \
-		"GEN","$@")
-qmp-introspect.h qmp-introspect.c :\
-$(qapi-modules) $(SRC_PATH)/scripts/qapi-introspect.py $(qapi-py)
-	$(call quiet-command,$(PYTHON) $(SRC_PATH)/scripts/qapi-introspect.py \
-		$(gen-out-type) -o "." $<, \
-		"GEN","$@")
+qapi-types.c qapi-types.h \
+qapi-visit.c qapi-visit.h \
+qmp-commands.h qmp-marshal.c \
+qapi-event.c qapi-event.h \
+qmp-introspect.h qmp-introspect.c \
+qapi.texi: \
+qapi-gen-timestamp ;
+qapi-gen-timestamp: $(qapi-modules) $(qapi-py)
+	$(call quiet-command,$(PYTHON) $(SRC_PATH)/scripts/qapi-gen.py \
+		-o "." -b $<, \
+		"GEN","$(@:%-timestamp=%)")
+	@>$@
 
 QGALIB_GEN=$(addprefix qga/qapi-generated/, qga-qapi-types.h qga-qapi-visit.h qga-qmp-commands.h)
 $(qga-obj-y): $(QGALIB_GEN)
@@ -596,6 +585,7 @@  clean:
 	rm -f trace/generated-tracers-dtrace.dtrace*
 	rm -f trace/generated-tracers-dtrace.h*
 	rm -f $(foreach f,$(GENERATED_FILES),$(f) $(f)-timestamp)
+	rm -f qapi-gen-timestamp
 	rm -rf qapi-generated
 	rm -rf qga/qapi-generated
 	for d in $(ALL_SUBDIRS); do \
@@ -803,13 +793,11 @@  qemu-monitor-info.texi: $(SRC_PATH)/hmp-commands-info.hx $(SRC_PATH)/scripts/hxt
 qemu-img-cmds.texi: $(SRC_PATH)/qemu-img-cmds.hx $(SRC_PATH)/scripts/hxtool
 	$(call quiet-command,sh $(SRC_PATH)/scripts/hxtool -t < $< > $@,"GEN","$@")
 
-docs/interop/qemu-qmp-qapi.texi docs/interop/qemu-ga-qapi.texi: $(SRC_PATH)/scripts/qapi2texi.py $(qapi-py)
+docs/interop/qemu-qmp-qapi.texi: qapi.texi
+	@cp -p $< $@
 
-docs/interop/qemu-qmp-qapi.texi: $(qapi-modules)
-	$(call quiet-command,$(PYTHON) $(SRC_PATH)/scripts/qapi2texi.py $< > $@,"GEN","$@")
-
-docs/interop/qemu-ga-qapi.texi: $(SRC_PATH)/qga/qapi-schema.json
-	$(call quiet-command,$(PYTHON) $(SRC_PATH)/scripts/qapi2texi.py $< > $@,"GEN","$@")
+docs/interop/qemu-ga-qapi.texi: qga/qapi-generated/qga-qapi.texi
+	@cp -p $< $@
 
 qemu.1: qemu-doc.texi qemu-options.texi qemu-monitor.texi qemu-monitor-info.texi
 qemu.1: qemu-option-trace.texi
diff --git a/scripts/qapi-gen.py b/scripts/qapi-gen.py
new file mode 100755
index 0000000000..575c938a1b
--- /dev/null
+++ b/scripts/qapi-gen.py
@@ -0,0 +1,41 @@ 
+#!/usr/bin/env python
+# QAPI generator
+#
+# This work is licensed under the terms of the GNU GPL, version 2 or later.
+# See the COPYING file in the top-level directory.
+
+import sys
+from qapi.common import parse_command_line, QAPISchema
+from qapi.types import gen_types
+from qapi.visit import gen_visit
+from qapi.commands import gen_commands
+from qapi.events import gen_events
+from qapi.introspect import gen_introspect
+from qapi.doc import gen_doc
+
+
+def main(argv):
+    (input_file, output_dir, do_c, do_h, prefix, opts) = \
+        parse_command_line('bu', ['builtins', 'unmask-non-abi-names'])
+
+    opt_builtins = False
+    opt_unmask = False
+
+    for o, a in opts:
+        if o in ('-b', '--builtins'):
+            opt_builtins = True
+        if o in ('-u', '--unmask-non-abi-names'):
+            opt_unmask = True
+
+    schema = QAPISchema(input_file)
+
+    gen_types(schema, output_dir, prefix, opt_builtins)
+    gen_visit(schema, output_dir, prefix, opt_builtins)
+    gen_commands(schema, output_dir, prefix)
+    gen_events(schema, output_dir, prefix)
+    gen_introspect(schema, output_dir, prefix, opt_unmask)
+    gen_doc(schema, output_dir, prefix)
+
+
+if __name__ == '__main__':
+    main(sys.argv)
diff --git a/scripts/qapi/__init__.py b/scripts/qapi/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/scripts/qapi-commands.py b/scripts/qapi/commands.py
similarity index 94%
rename from scripts/qapi-commands.py
rename to scripts/qapi/commands.py
index 331b58670e..383a4dd426 100644
--- a/scripts/qapi-commands.py
+++ b/scripts/qapi/commands.py
@@ -13,7 +13,7 @@  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 *
+from qapi.common import *
 
 
 def gen_command_decl(name, arg_type, boxed, ret_type):
@@ -255,13 +255,8 @@  class QAPISchemaGenCommandVisitor(QAPISchemaVisitor):
         self._regy += gen_register_command(name, success_response)
 
 
-def main(argv):
-    (input_file, output_dir, do_c, do_h, prefix, opts) = parse_command_line()
-
-    blurb = '''
- * Schema-defined QAPI/QMP commands
-'''
-
+def gen_commands(schema, output_dir, prefix):
+    blurb = ' * Schema-defined QAPI/QMP commands'
     genc = QAPIGenC(blurb, __doc__)
     genh = QAPIGenH(blurb, __doc__)
 
@@ -290,17 +285,9 @@  void %(c_prefix)sqmp_init_marshal(QmpCommandList *cmds);
 ''',
                     prefix=prefix, c_prefix=c_name(prefix, protect=False)))
 
-    schema = QAPISchema(input_file)
     vis = QAPISchemaGenCommandVisitor(prefix)
     schema.visit(vis)
     genc.body(vis.defn)
     genh.body(vis.decl)
-
-    if do_c:
-        genc.write(output_dir, prefix + 'qmp-marshal.c')
-    if do_h:
-        genh.write(output_dir, prefix + 'qmp-commands.h')
-
-
-if __name__ == '__main__':
-    main(sys.argv)
+    genc.write(output_dir, prefix + 'qmp-marshal.c')
+    genh.write(output_dir, prefix + 'qmp-commands.h')
diff --git a/scripts/qapi.py b/scripts/qapi/common.py
similarity index 100%
rename from scripts/qapi.py
rename to scripts/qapi/common.py
diff --git a/scripts/qapi2texi.py b/scripts/qapi/doc.py
old mode 100755
new mode 100644
similarity index 92%
rename from scripts/qapi2texi.py
rename to scripts/qapi/doc.py
index 924b374cd3..1f57f6e1c2
--- a/scripts/qapi2texi.py
+++ b/scripts/qapi/doc.py
@@ -4,10 +4,9 @@ 
 # This work is licensed under the terms of the GNU LGPL, version 2+.
 # See the COPYING file in the top-level directory.
 """This script produces the documentation of a qapi schema in texinfo format"""
+
 import re
-import sys
-
-import qapi
+import qapi.common
 
 MSG_FMT = """
 @deftypefn {type} {{}} {name}
@@ -196,7 +195,7 @@  def texi_entity(doc, what, base=None, variants=None,
             + texi_sections(doc))
 
 
-class QAPISchemaGenDocVisitor(qapi.QAPISchemaVisitor):
+class QAPISchemaGenDocVisitor(qapi.common.QAPISchemaVisitor):
     def __init__(self):
         self.out = None
         self.cur_doc = None
@@ -271,20 +270,8 @@  def texi_schema(schema):
     return gen.out
 
 
-def main(argv):
-    """Takes schema argument, prints result to stdout"""
-    if len(argv) != 2:
-        print >>sys.stderr, "%s: need exactly 1 argument: SCHEMA" % argv[0]
-        sys.exit(1)
-
-    schema = qapi.QAPISchema(argv[1])
-    if not qapi.doc_required:
-        print >>sys.stderr, ("%s: need pragma 'doc-required' "
-                             "to generate documentation" % argv[0])
-        sys.exit(1)
-    print '@c AUTOMATICALLY GENERATED, DO NOT MODIFY\n'
-    print texi_schema(schema),
-
-
-if __name__ == '__main__':
-    main(sys.argv)
+def gen_doc(schema, output_dir, prefix):
+    if qapi.common.doc_required:
+        gen = qapi.common.QAPIGenDoc()
+        gen.body(texi_schema(schema))
+        gen.write(output_dir, prefix + 'qapi.texi')
diff --git a/scripts/qapi-event.py b/scripts/qapi/events.py
similarity index 92%
rename from scripts/qapi-event.py
rename to scripts/qapi/events.py
index 5b33c694d4..1f267686db 100644
--- a/scripts/qapi-event.py
+++ b/scripts/qapi/events.py
@@ -12,7 +12,7 @@  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 *
+from qapi.common import *
 
 
 def build_event_send_proto(name, arg_type, boxed):
@@ -171,13 +171,8 @@  class QAPISchemaGenEventVisitor(QAPISchemaVisitor):
         self._event_names.append(name)
 
 
-def main(argv):
-    (input_file, output_dir, do_c, do_h, prefix, dummy) = parse_command_line()
-
-    blurb = '''
- * Schema-defined QAPI/QMP events
-'''
-
+def gen_events(schema, output_dir, prefix):
+    blurb = ' * Schema-defined QAPI/QMP events'
     genc = QAPIGenC(blurb, __doc__)
     genh = QAPIGenH(blurb, __doc__)
 
@@ -201,17 +196,9 @@  def main(argv):
 ''',
                     prefix=prefix))
 
-    schema = QAPISchema(input_file)
     vis = QAPISchemaGenEventVisitor(prefix)
     schema.visit(vis)
     genc.body(vis.defn)
     genh.body(vis.decl)
-
-    if do_c:
-        genc.write(output_dir, prefix + 'qapi-event.c')
-    if do_h:
-        genh.write(output_dir, prefix + 'qapi-event.h')
-
-
-if __name__ == '__main__':
-    main(sys.argv)
+    genc.write(output_dir, prefix + 'qapi-event.c')
+    genh.write(output_dir, prefix + 'qapi-event.h')
diff --git a/scripts/qapi-introspect.py b/scripts/qapi/introspect.py
similarity index 90%
rename from scripts/qapi-introspect.py
rename to scripts/qapi/introspect.py
index 09e7d1f140..2153060f2c 100644
--- a/scripts/qapi-introspect.py
+++ b/scripts/qapi/introspect.py
@@ -10,7 +10,7 @@  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 *
+from qapi.common import *
 
 
 # Caveman's json.dumps() replacement (we're stuck at Python 2.4)
@@ -168,22 +168,8 @@  const char %(c_name)s[] = %(c_string)s;
         self._gen_json(name, 'event', {'arg-type': self._use_type(arg_type)})
 
 
-def main(argv):
-    # Debugging aid: unmask QAPI schema's type names
-    # We normally mask them, because they're not QMP wire ABI
-    opt_unmask = False
-
-    (input_file, output_dir, do_c, do_h, prefix, opts) = \
-        parse_command_line('u', ['unmask-non-abi-names'])
-
-    for o, a in opts:
-        if o in ('-u', '--unmask-non-abi-names'):
-            opt_unmask = True
-
-    blurb = '''
- * QAPI/QMP schema introspection
-'''
-
+def gen_introspect(schema, output_dir, prefix, opt_unmask):
+    blurb = ' * QAPI/QMP schema introspection'
     genc = QAPIGenC(blurb, __doc__)
     genh = QAPIGenH(blurb, __doc__)
 
@@ -194,17 +180,9 @@  def main(argv):
 ''',
                     prefix=prefix))
 
-    schema = QAPISchema(input_file)
     vis = QAPISchemaGenIntrospectVisitor(prefix, opt_unmask)
     schema.visit(vis)
     genc.body(vis.defn)
     genh.body(vis.decl)
-
-    if do_c:
-        genc.write(output_dir, prefix + 'qmp-introspect.c')
-    if do_h:
-        genh.write(output_dir, prefix + 'qmp-introspect.h')
-
-
-if __name__ == '__main__':
-    main(sys.argv)
+    genc.write(output_dir, prefix + 'qmp-introspect.c')
+    genh.write(output_dir, prefix + 'qmp-introspect.h')
diff --git a/scripts/qapi-types.py b/scripts/qapi/types.py
similarity index 90%
rename from scripts/qapi-types.py
rename to scripts/qapi/types.py
index f2ddde94b1..b2095120e0 100644
--- a/scripts/qapi-types.py
+++ b/scripts/qapi/types.py
@@ -13,7 +13,7 @@  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 *
+from qapi.common import *
 
 
 # variants must be emitted before their container; track what has already
@@ -241,24 +241,8 @@  class QAPISchemaGenTypeVisitor(QAPISchemaVisitor):
         self._gen_type_cleanup(name)
 
 
-def main(argv):
-    # If you link code generated from multiple schemata, you want only one
-    # instance of the code for built-in types.  Generate it only when
-    # opt_builtins, enabled by command line option -b.  See also
-    # QAPISchemaGenTypeVisitor.visit_end().
-    opt_builtins = False
-
-    (input_file, output_dir, do_c, do_h, prefix, opts) = \
-        parse_command_line('b', ['builtins'])
-
-    for o, a in opts:
-        if o in ('-b', '--builtins'):
-            opt_builtins = True
-
-    blurb = '''
- * Schema-defined QAPI types
-'''
-
+def gen_types(schema, output_dir, prefix, opt_builtins):
+    blurb = ' * Schema-defined QAPI types'
     genc = QAPIGenC(blurb, __doc__)
     genh = QAPIGenH(blurb, __doc__)
 
@@ -274,17 +258,9 @@  def main(argv):
 #include "qapi/util.h"
 '''))
 
-    schema = QAPISchema(input_file)
     vis = QAPISchemaGenTypeVisitor(opt_builtins)
     schema.visit(vis)
     genc.body(vis.defn)
     genh.body(vis.decl)
-
-    if do_c:
-        genc.write(output_dir, prefix + 'qapi-types.c')
-    if do_h:
-        genh.write(output_dir, prefix + 'qapi-types.h')
-
-
-if __name__ == '__main__':
-    main(sys.argv)
+    genc.write(output_dir, prefix + 'qapi-types.c')
+    genh.write(output_dir, prefix + 'qapi-types.h')
diff --git a/scripts/qapi-visit.py b/scripts/qapi/visit.py
similarity index 92%
rename from scripts/qapi-visit.py
rename to scripts/qapi/visit.py
index 473fa72574..80c0b85f8c 100644
--- a/scripts/qapi-visit.py
+++ b/scripts/qapi/visit.py
@@ -13,7 +13,7 @@  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 *
+from qapi.common import *
 
 
 def gen_visit_decl(name, scalar=False):
@@ -324,24 +324,8 @@  class QAPISchemaGenVisitVisitor(QAPISchemaVisitor):
         self.defn += gen_visit_alternate(name, variants)
 
 
-def main(argv):
-    # If you link code generated from multiple schemata, you want only one
-    # instance of the code for built-in types.  Generate it only when
-    # opt_builtins, enabled by command line option -b.  See also
-    # QAPISchemaGenVisitVisitor.visit_end().
-    opt_builtins = False
-
-    (input_file, output_dir, do_c, do_h, prefix, opts) = \
-        parse_command_line('b', ['builtins'])
-
-    for o, a in opts:
-        if o in ('-b', '--builtins'):
-            opt_builtins = True
-
-    blurb = '''
- * Schema-defined QAPI visitors
-'''
-
+def gen_visit(schema, output_dir, prefix, opt_builtins):
+    blurb = ' * Schema-defined QAPI visitors'
     genc = QAPIGenC(blurb, __doc__)
     genh = QAPIGenH(blurb, __doc__)
 
@@ -361,17 +345,9 @@  def main(argv):
 ''',
                     prefix=prefix))
 
-    schema = QAPISchema(input_file)
     vis = QAPISchemaGenVisitVisitor(opt_builtins)
     schema.visit(vis)
     genc.body(vis.defn)
     genh.body(vis.decl)
-
-    if do_c:
-        genc.write(output_dir, prefix + 'qapi-visit.c')
-    if do_h:
-        genh.write(output_dir, prefix + 'qapi-visit.h')
-
-
-if __name__ == '__main__':
-    main(sys.argv)
+    genc.write(output_dir, prefix + 'qapi-visit.c')
+    genh.write(output_dir, prefix + 'qapi-visit.h')
diff --git a/tests/Makefile.include b/tests/Makefile.include
index 851aafe9d1..768655a810 100644
--- a/tests/Makefile.include
+++ b/tests/Makefile.include
@@ -23,7 +23,16 @@  check-help:
 ifneq ($(wildcard config-host.mak),)
 export SRC_PATH
 
-qapi-py = $(SRC_PATH)/scripts/qapi.py $(SRC_PATH)/scripts/ordereddict.py
+# TODO don't duplicate $(SRC_PATH)/Makefile's qapi-py here
+qapi-py = $(SRC_PATH)/scripts/qapi/commands.py \
+$(SRC_PATH)/scripts/qapi/events.py \
+$(SRC_PATH)/scripts/qapi/introspect.py \
+$(SRC_PATH)/scripts/qapi/types.py \
+$(SRC_PATH)/scripts/qapi/visit.py \
+$(SRC_PATH)/scripts/qapi/common.py \
+$(SRC_PATH)/scripts/qapi/doc.py \
+$(SRC_PATH)/scripts/ordereddict.py \
+$(SRC_PATH)/scripts/qapi-gen.py
 
 # Get the list of all supported sysemu targets
 SYSEMU_TARGET_LIST := $(subst -softmmu.mak,,$(notdir \
@@ -642,34 +651,24 @@  tests/test-logging$(EXESUF): tests/test-logging.o $(test-util-obj-y)
 tests/test-replication$(EXESUF): tests/test-replication.o $(test-util-obj-y) \
 	$(test-block-obj-y)
 
-tests/test-qapi-types.c tests/test-qapi-types.h :\
-$(SRC_PATH)/tests/qapi-schema/qapi-schema-test.json $(SRC_PATH)/scripts/qapi-types.py $(qapi-py)
-	$(call quiet-command,$(PYTHON) $(SRC_PATH)/scripts/qapi-types.py \
-		$(gen-out-type) -o tests -p "test-" $<, \
-		"GEN","$@")
-tests/test-qapi-visit.c tests/test-qapi-visit.h :\
-$(SRC_PATH)/tests/qapi-schema/qapi-schema-test.json $(SRC_PATH)/scripts/qapi-visit.py $(qapi-py)
-	$(call quiet-command,$(PYTHON) $(SRC_PATH)/scripts/qapi-visit.py \
-		$(gen-out-type) -o tests -p "test-" $<, \
-		"GEN","$@")
-tests/test-qmp-commands.h tests/test-qmp-marshal.c :\
-$(SRC_PATH)/tests/qapi-schema/qapi-schema-test.json $(SRC_PATH)/scripts/qapi-commands.py $(qapi-py)
-	$(call quiet-command,$(PYTHON) $(SRC_PATH)/scripts/qapi-commands.py \
-		$(gen-out-type) -o tests -p "test-" $<, \
-		"GEN","$@")
-tests/test-qapi-event.c tests/test-qapi-event.h :\
-$(SRC_PATH)/tests/qapi-schema/qapi-schema-test.json $(SRC_PATH)/scripts/qapi-event.py $(qapi-py)
-	$(call quiet-command,$(PYTHON) $(SRC_PATH)/scripts/qapi-event.py \
-		$(gen-out-type) -o tests -p "test-" $<, \
-		"GEN","$@")
-tests/test-qmp-introspect.c tests/test-qmp-introspect.h :\
-$(SRC_PATH)/tests/qapi-schema/qapi-schema-test.json $(SRC_PATH)/scripts/qapi-introspect.py $(qapi-py)
-	$(call quiet-command,$(PYTHON) $(SRC_PATH)/scripts/qapi-introspect.py \
-		$(gen-out-type) -o tests -p "test-" $<, \
-		"GEN","$@")
+tests/test-qapi-types.c tests/test-qapi-types.h \
+tests/test-qapi-visit.c tests/test-qapi-visit.h \
+tests/test-qmp-commands.h tests/test-qmp-marshal.c \
+tests/test-qapi-event.c tests/test-qapi-event.h \
+tests/test-qmp-introspect.c tests/test-qmp-introspect.h: \
+tests/test-qapi-gen-timestamp ;
+tests/test-qapi-gen-timestamp: $(SRC_PATH)/tests/qapi-schema/qapi-schema-test.json $(qapi-py)
+	$(call quiet-command,$(PYTHON) $(SRC_PATH)/scripts/qapi-gen.py \
+		-o tests -p "test-" $<, \
+		"GEN","$(@:%-timestamp=%)")
+	@>$@
 
-tests/qapi-schema/doc-good.test.texi: $(SRC_PATH)/tests/qapi-schema/doc-good.json $(SRC_PATH)/scripts/qapi2texi.py $(qapi-py)
-	$(call quiet-command,$(PYTHON) $(SRC_PATH)/scripts/qapi2texi.py $< > $@,"GEN","$@")
+tests/qapi-schema/doc-good.test.texi: $(SRC_PATH)/tests/qapi-schema/doc-good.json $(qapi-py)
+	$(call quiet-command,$(PYTHON) $(SRC_PATH)/scripts/qapi-gen.py \
+		-o tests/qapi-schema -p "doc-good-" $<, \
+		"GEN","$@")
+	@mv tests/qapi-schema/doc-good-qapi.texi $@
+	@rm -f tests/qapi-schema/doc-good-qapi-*.[ch] tests/qapi-schema/doc-good-qmp-*.[ch]
 
 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)
@@ -937,6 +936,7 @@  check-clean:
 	$(MAKE) -C tests/tcg clean
 	rm -rf $(check-unit-y) tests/*.o $(QEMU_IOTESTS_HELPERS-y)
 	rm -rf $(sort $(foreach target,$(SYSEMU_TARGET_LIST), $(check-qtest-$(target)-y)) $(check-qtest-generic-y))
+	rm -f tests/test-qapi-gen-timestamp
 
 clean: check-clean
 
diff --git a/tests/qapi-schema/test-qapi.py b/tests/qapi-schema/test-qapi.py
index fe0ca08d78..7772d09919 100644
--- a/tests/qapi-schema/test-qapi.py
+++ b/tests/qapi-schema/test-qapi.py
@@ -10,7 +10,7 @@ 
 # See the COPYING file in the top-level directory.
 #
 
-from qapi import *
+from qapi.common import *
 from pprint import pprint
 import os
 import sys