diff --git a/Makefile b/Makefile
index d6c5c9fdef..b22e5b272e 100644
--- a/Makefile
+++ b/Makefile
@@ -80,6 +80,12 @@ ifneq ($(MESON),)
 Makefile.mtest: build.ninja scripts/mtest2make.py
 	$(MESON) introspect --tests --benchmarks | $(PYTHON) scripts/mtest2make.py > $@
 -include Makefile.mtest
+
+# jq is used to keep the result stable
+meson-buildoptions.json: meson_options.txt
+	$(MESON) introspect --buildoptions . | \
+	    jq 'map(select(.section == "user")) | sort_by(.name)' \
+	    > $(SRC_PATH)/meson-buildoptions.json
 endif
 
 Makefile: .git-submodule-status
diff --git a/configure b/configure
index b4f5d2e272..54958bfc17 100755
--- a/configure
+++ b/configure
@@ -396,16 +396,10 @@ docs=""
 fdt=""
 netmap="no"
 sdl="auto"
-sdl_image="auto"
 virtfs=""
 mpath=""
-vnc="enabled"
 sparse="no"
 vde=""
-vnc_sasl="auto"
-vnc_jpeg="auto"
-vnc_png="auto"
-xkbcommon="auto"
 xen=""
 xen_ctrl_version=""
 xen_pci_passthrough=""
@@ -466,7 +460,6 @@ trace_file="trace"
 spice=""
 rbd=""
 smartcard=""
-u2f="auto"
 libusb=""
 usb_redir=""
 opengl=""
@@ -539,6 +532,7 @@ rng_none="no"
 secret_keyring=""
 libdaxctl=""
 meson=""
+meson_args=""
 ninja=""
 skip_meson=no
 gettext=""
@@ -1100,10 +1094,6 @@ for opt do
   ;;
   --enable-sdl) sdl="enabled"
   ;;
-  --disable-sdl-image) sdl_image="disabled"
-  ;;
-  --enable-sdl-image) sdl_image="enabled"
-  ;;
   --disable-qom-cast-debug) qom_cast_debug="no"
   ;;
   --enable-qom-cast-debug) qom_cast_debug="yes"
@@ -1116,10 +1106,6 @@ for opt do
   ;;
   --enable-mpath) mpath="yes"
   ;;
-  --disable-vnc) vnc="disabled"
-  ;;
-  --enable-vnc) vnc="enabled"
-  ;;
   --disable-gettext) gettext="false"
   ;;
   --enable-gettext) gettext="true"
@@ -1158,18 +1144,6 @@ for opt do
   ;;
   --disable-strip) strip_opt="no"
   ;;
-  --disable-vnc-sasl) vnc_sasl="disabled"
-  ;;
-  --enable-vnc-sasl) vnc_sasl="enabled"
-  ;;
-  --disable-vnc-jpeg) vnc_jpeg="disabled"
-  ;;
-  --enable-vnc-jpeg) vnc_jpeg="enabled"
-  ;;
-  --disable-vnc-png) vnc_png="disabled"
-  ;;
-  --enable-vnc-png) vnc_png="enabled"
-  ;;
   --disable-slirp) slirp="no"
   ;;
   --enable-slirp=git) slirp="git"
@@ -1369,10 +1343,6 @@ for opt do
   ;;
   --enable-smartcard) smartcard="yes"
   ;;
-  --disable-u2f) u2f="disabled"
-  ;;
-  --enable-u2f) u2f="enabled"
-  ;;
   --disable-libusb) libusb="no"
   ;;
   --enable-libusb) libusb="yes"
@@ -1593,10 +1563,6 @@ for opt do
   ;;
   --disable-libpmem) libpmem=no
   ;;
-  --enable-xkbcommon) xkbcommon="enabled"
-  ;;
-  --disable-xkbcommon) xkbcommon="disabled"
-  ;;
   --enable-plugins) plugins="yes"
   ;;
   --disable-plugins) plugins="no"
@@ -1623,6 +1589,9 @@ for opt do
   ;;
   --disable-libdaxctl) libdaxctl=no
   ;;
+  --enable-* | --disable-*)
+      meson_args="$meson_args $(quote_sh "$opt")"
+  ;;
   *)
       echo "ERROR: unknown option $opt"
       echo "Try '$0 --help' for more information"
@@ -1648,6 +1617,16 @@ then
     fi
 fi
 
+# Convert unknown --enable/--disable arguments to Meson build options.
+meson_options=$(eval \
+    \$python \
+    \$source_path/scripts/configure-parse-buildoptions.py \
+    \< \$source_path/meson-buildoptions.json $meson_args)
+if test $? = 1; then
+    echo "Try '$0 --help' for more information"
+    exit 1
+fi
+
 firmwarepath="${firmwarepath:-$prefix/share/qemu-firmware}"
 libdir="${libdir:-$prefix/lib}"
 libexecdir="${libexecdir:-$prefix/libexec}"
@@ -1853,10 +1831,14 @@ Advanced options (experts only):
                            enable plugins via shared library loading
   --disable-containers     don't use containers for cross-building
   --gdb=GDB-path           gdb to use for gdbstub tests [$gdb_bin]
-
-Optional features, enabled with --enable-FEATURE and
-disabled with --disable-FEATURE, default is enabled if available:
-
+EOF
+  if test -z "$python"; then
+    echo
+    echo "Python not found. Use --python=/path/to/python to see complete help"
+    exit 0
+  fi
+  $python $source_path/scripts/configure-parse-buildoptions.py --print-help < $source_path/meson-buildoptions.json
+cat << EOF
   system          all system emulation targets
   user            supported user emulation targets
   linux-user      all linux usermode emulation targets
@@ -1883,10 +1865,6 @@ disabled with --disable-FEATURE, default is enabled if available:
   vte             vte support for the gtk UI
   curses          curses UI
   iconv           font glyph conversion support
-  vnc             VNC UI support
-  vnc-sasl        SASL encryption for VNC server
-  vnc-jpeg        JPEG lossy compression for VNC server
-  vnc-png         PNG compression for VNC server
   cocoa           Cocoa UI (Mac OS X only)
   virtfs          VirtFS
   mpath           Multipath persistent reservation passthrough
@@ -1920,7 +1898,6 @@ disabled with --disable-FEATURE, default is enabled if available:
   libiscsi        iscsi support
   libnfs          nfs support
   smartcard       smartcard support (libcacard)
-  u2f             U2F support (u2f-emu)
   libusb          libusb (for usb passthrough)
   live-block-migration   Block migration in the main migration stream
   usb-redir       usb network redirection support
@@ -1962,7 +1939,6 @@ disabled with --disable-FEATURE, default is enabled if available:
   capstone        capstone disassembler support
   debug-mutex     mutex debugging support
   libpmem         libpmem support
-  xkbcommon       xkbcommon support
   rng-none        dummy RNG, avoid using /dev/(u)random and getrandom()
   libdaxctl       libdaxctl support
 
@@ -8008,9 +7984,8 @@ NINJA=${ninja:-$PWD/ninjatool} $meson setup \
         -Dstrip=$(if test "$strip_opt" = yes; then echo true; else echo false; fi) \
         -Db_pie=$(if test "$pie" = yes; then echo true; else echo false; fi) \
         -Db_coverage=$(if test "$gcov" = yes; then echo true; else echo false; fi) \
-	-Dsdl=$sdl -Dsdl_image=$sdl_image \
-	-Dvnc=$vnc -Dvnc_sasl=$vnc_sasl -Dvnc_jpeg=$vnc_jpeg -Dvnc_png=$vnc_png \
-	-Dgettext=$gettext -Dxkbcommon=$xkbcommon -Du2f=$u2f\
+	$meson_args \
+	-Dsdl=$sdl -Dgettext=$gettext \
         $cross_arg \
         "$PWD" "$source_path"
 
diff --git a/docs/devel/build-system.rst b/docs/devel/build-system.rst
index 08e85c69e1..9fd321f227 100644
--- a/docs/devel/build-system.rst
+++ b/docs/devel/build-system.rst
@@ -52,12 +52,6 @@ following tasks:
 
  - Add a Meson build option to meson_options.txt.
 
- - Add support to the command line arg parser to handle any new
-   `--enable-XXX`/`--disable-XXX` flags required by the feature.
-
- - Add information to the help output message to report on the new
-   feature flag.
-
  - Add code to perform the actual feature check.
 
  - Add code to include the feature status in `config-host.h`
@@ -66,30 +60,6 @@ following tasks:
    upon completion.
 
 
-Taking the probe for SDL2_Image as an example, we have the following pieces
-in configure::
-
-  # Initial variable state
-  sdl_image=auto
-
-  ..snip..
-
-  # Configure flag processing
-  --disable-sdl-image) sdl_image=disabled
-  ;;
-  --enable-sdl-image) sdl_image=enabled
-  ;;
-
-  ..snip..
-
-  # Help output feature message
-  sdl-image         SDL Image support for icons
-
-  ..snip..
-
-  # Meson invocation
-  -Dsdl_image=$sdl_image
-
 In meson_options.txt::
 
   option('sdl', type : 'feature', value : 'auto',
@@ -108,6 +78,11 @@ In meson.build::
   # Summary
   summary_info += {'SDL image support': sdl_image.found()}
 
+The configure script automatically converts command line options to
+Meson options as long as the `meson-buildoptions.json` file in the
+root source directory is up-to-date.  You can simply type
+`make meson-buildoptions.json` to update it; the `jq` program
+is needed.
 
 
 Helper functions
diff --git a/meson-buildoptions.json b/meson-buildoptions.json
new file mode 100644
index 0000000000..dbaf5bc71e
--- /dev/null
+++ b/meson-buildoptions.json
@@ -0,0 +1,130 @@
+[
+  {
+    "name": "docdir",
+    "value": "doc",
+    "section": "user",
+    "machine": "any",
+    "type": "string",
+    "description": "Base directory for documentation installation (can be empty)"
+  },
+  {
+    "name": "gettext",
+    "value": true,
+    "section": "user",
+    "machine": "any",
+    "type": "boolean",
+    "description": "Localization of the GTK+ user interface"
+  },
+  {
+    "name": "qemu_suffix",
+    "value": "qemu",
+    "section": "user",
+    "machine": "any",
+    "type": "string",
+    "description": "Suffix for QEMU data/modules/config directories (can be empty)"
+  },
+  {
+    "name": "sdl",
+    "value": "auto",
+    "section": "user",
+    "machine": "any",
+    "choices": [
+      "enabled",
+      "disabled",
+      "auto"
+    ],
+    "type": "combo",
+    "description": "SDL user interface"
+  },
+  {
+    "name": "sdl_image",
+    "value": "auto",
+    "section": "user",
+    "machine": "any",
+    "choices": [
+      "enabled",
+      "disabled",
+      "auto"
+    ],
+    "type": "combo",
+    "description": "SDL Image support for icons"
+  },
+  {
+    "name": "u2f",
+    "value": "auto",
+    "section": "user",
+    "machine": "any",
+    "choices": [
+      "enabled",
+      "disabled",
+      "auto"
+    ],
+    "type": "combo",
+    "description": "U2F emulation support"
+  },
+  {
+    "name": "vnc",
+    "value": "enabled",
+    "section": "user",
+    "machine": "any",
+    "choices": [
+      "enabled",
+      "disabled",
+      "auto"
+    ],
+    "type": "combo",
+    "description": "VNC server"
+  },
+  {
+    "name": "vnc_jpeg",
+    "value": "auto",
+    "section": "user",
+    "machine": "any",
+    "choices": [
+      "enabled",
+      "disabled",
+      "auto"
+    ],
+    "type": "combo",
+    "description": "JPEG lossy compression for VNC server"
+  },
+  {
+    "name": "vnc_png",
+    "value": "auto",
+    "section": "user",
+    "machine": "any",
+    "choices": [
+      "enabled",
+      "disabled",
+      "auto"
+    ],
+    "type": "combo",
+    "description": "PNG compression for VNC server"
+  },
+  {
+    "name": "vnc_sasl",
+    "value": "auto",
+    "section": "user",
+    "machine": "any",
+    "choices": [
+      "enabled",
+      "disabled",
+      "auto"
+    ],
+    "type": "combo",
+    "description": "SASL authentication for VNC server"
+  },
+  {
+    "name": "xkbcommon",
+    "value": "auto",
+    "section": "user",
+    "machine": "any",
+    "choices": [
+      "enabled",
+      "disabled",
+      "auto"
+    ],
+    "type": "combo",
+    "description": "xkbcommon support"
+  }
+]
diff --git a/scripts/configure-parse-buildoptions.py b/scripts/configure-parse-buildoptions.py
new file mode 100644
index 0000000000..4171312d75
--- /dev/null
+++ b/scripts/configure-parse-buildoptions.py
@@ -0,0 +1,94 @@
+#! /usr/bin/env python3
+
+# Parse configure command line options based on Meson's user build options
+# introspection data (passed on stdin).
+#
+# Copyright (C) 2020 Red Hat, Inc.
+#
+# Author: Paolo Bonzini <pbonzini@redhat.com>
+
+import json
+import re
+import shlex
+import sys
+import textwrap
+
+SKIP_OPTIONS = [ 'docdir', 'qemu_suffix' ]
+FEATURE_CHOICES = [ 'auto', 'disabled', 'enabled' ]
+
+options = { x['name']: x
+            for x in json.load(sys.stdin)
+            if x['section'] == 'user' and x['name'] not in SKIP_OPTIONS }
+
+for x in options.values():
+    if x['type'] == 'combo' and sorted(x['choices']) == FEATURE_CHOICES:
+        x['type'] = 'feature'
+
+def value_to_help(x):
+    if x == True:
+        return 'enabled'
+    if x == False:
+        return 'disabled'
+    return str(x)
+
+def print_help_line(key, opt, indent):
+    key = '  ' + key
+    value = '%s [%s]' % (opt['description'], value_to_help(opt['value']))
+    if len(key) >= indent:
+        print(key)
+        key = ''
+    spaces = ' ' * indent
+    key = (key + spaces)[0:indent]
+    print(textwrap.fill(value, initial_indent=key, subsequent_indent=spaces))
+
+def print_help():
+    for o, x in options.items():
+        if x['type'] not in ('boolean', 'feature'):
+            print_help_line('--enable-' + o, x, 24)
+
+    print()
+    print('Optional features, enabled with --enable-FEATURE and')
+    print('disabled with --disable-FEATURE:')
+    for o, x in options.items():
+        if x['type'] in ('boolean', 'feature'):
+            print_help_line(o, x, 18)
+
+def error(s, *args):
+    print('ERROR:', s % args, file=sys.stderr)
+    sys.exit(1)
+
+def main(argv):
+    if not argv:
+        return
+
+    if argv[0] == '--print-help':
+        print_help()
+        return
+
+    args = []
+    for arg in sys.argv[1:]:
+        m = re.search('--(enable|disable)-([^=]*)(?:=(.*))?', arg)
+        if not m:
+            error('internal error parsing command line')
+        opt = m.group(2).replace('-', '_')
+        if opt not in options:
+            error('Unknown option --%s-%s', m.group(1), m.group(2))
+        opt_type = options[opt]['type']
+        if opt_type in ('boolean', 'feature'):
+            if m.group(3) is not None:
+                error('option --%s-%s does not take an argument', m.group(1), m.group(2))
+            if opt_type == 'feature':
+                value = m.group(1) + 'd'
+            else:
+                value = 'true' if m.group(1) == 'enable' else 'false'
+        else:
+            if m.group(1) == 'disable':
+                error('Unknown option --disable-%s', m.group(2))
+            if m.group(3) is None:
+                error('option --enable-%s takes an argument', m.group(2))
+
+        args.append(shlex.quote('-D%s=%s' % (opt, value)))
+    print(' '.join(args))
+
+if __name__ == "__main__":
+    main(sys.argv[1:])
