diff --git a/Makefile b/Makefile
index 8ba5990504..6bbfacd5c4 100644
--- a/Makefile
+++ b/Makefile
@@ -91,7 +91,7 @@ build.ninja: meson-private/coredata.dat
 meson-private/coredata.dat: config-host.mak
 config-host.mak: $(SRC_PATH)/configure $(SRC_PATH)/pc-bios $(SRC_PATH)/VERSION
 	@echo $@ is out-of-date, running configure
-	@./config.status
+	@./config.status --skip-meson
 
 # Force configure to re-run if the API symbols are updated
 ifeq ($(CONFIG_PLUGIN),y)
diff --git a/configure b/configure
index 51d75db20e..1d7f0d07da 100755
--- a/configure
+++ b/configure
@@ -8134,9 +8134,11 @@ meson_quote() {
 }
 
 echo "# Automatically generated by configure - do not modify" > $cross
+echo "[properties]" >> $cross
+test -z "$cxx" && echo "link_language = 'c'" >> $cross
 echo "[binaries]" >> $cross
 echo "c = $(meson_quote $cc)" >> $cross
-echo "cpp = $(meson_quote $cxx)" >> $cross
+test -n "$cxx" && echo "cpp = $(meson_quote $cxx)" >> $cross
 echo "ar = $(meson_quote $ar)" >> $cross
 echo "nm = $(meson_quote $nm)" >> $cross
 echo "pkgconfig = $(meson_quote $pkg_config_exe)" >> $cross
@@ -8263,7 +8265,9 @@ preserve_env STRIP
 preserve_env WINDRES
 
 printf "exec" >>config.status
-printf " '%s'" "$0" --skip-meson "$@" >>config.status
+for i in "$0" "$@"; do
+  test "$i" = --skip-meson || printf " '%s'" "$i" >>config.status
+done
 echo ' "$@"' >>config.status
 chmod +x config.status
 
diff --git a/meson.build b/meson.build
index 362064f059..d307d2d989 100644
--- a/meson.build
+++ b/meson.build
@@ -31,7 +31,10 @@ add_project_arguments(config_host['QEMU_INCLUDES'].split(),
 
 python = import('python').find_installation()
 
-add_languages('cpp', required: false, native: false)
+link_language = meson.get_external_property('link_language', 'cpp')
+if link_language == 'cpp'
+  add_languages('cpp', required: true, native: false)
+endif
 if host_machine.system() == 'darwin'
   add_languages('objc', required: false, native: false)
 endif
@@ -1018,7 +1021,7 @@ foreach target : target_dirs
                c_args: c_args,
                dependencies: arch_deps + deps + exe['dependencies'],
                objects: lib.extract_all_objects(recursive: true),
-               link_language: 'cpp',
+               link_language: link_language,
                link_depends: [block_syms, qemu_syms] + exe.get('link_depends', []),
                link_args: link_args,
                gui_app: exe['gui'])
@@ -1227,7 +1230,11 @@ summary_info += {'GIT binary':        config_host['GIT']}
 summary_info += {'GIT submodules':    config_host['GIT_SUBMODULES']}
 summary_info += {'C compiler':        meson.get_compiler('c').cmd_array()[0]}
 summary_info += {'Host C compiler':   meson.get_compiler('c', native: true).cmd_array()[0]}
-summary_info += {'C++ compiler':      meson.get_compiler('cpp').cmd_array()[0]}
+if link_language == 'cpp'
+  summary_info += {'C++ compiler':      meson.get_compiler('cpp').cmd_array()[0]}
+else
+  summary_info += {'C++ compiler':      false}
+endif
 if targetos == 'darwin'
   summary_info += {'Objective-C compiler': meson.get_compiler('objc').cmd_array()[0]}
 endif
diff --git a/util/meson.build b/util/meson.build
index 38d31223d0..23b8ad459b 100644
--- a/util/meson.build
+++ b/util/meson.build
@@ -1,9 +1,5 @@
 util_ss.add(dependency('threads'))
 util_ss.add(files('osdep.c', 'cutils.c', 'unicode.c', 'qemu-timer-common.c'))
-util_ss.add(files('bufferiszero.c'))
-util_ss.add(files('lockcnt.c'))
-util_ss.add(files('aiocb.c', 'async.c', 'aio-wait.c', 'thread-pool.c', 'qemu-timer.c'))
-util_ss.add(files('main-loop.c'))
 util_ss.add(when: 'CONFIG_ATOMIC64', if_false: files('atomic64.c'))
 util_ss.add(when: 'CONFIG_POSIX', if_true: files('aio-posix.c'))
 util_ss.add(when: 'CONFIG_POSIX', if_true: files('fdmon-poll.c'))
@@ -23,31 +19,20 @@ util_ss.add(when: 'CONFIG_WIN32', if_true: files('qemu-thread-win32.c'))
 util_ss.add(when: 'CONFIG_WIN32', if_true: winmm)
 util_ss.add(files('envlist.c', 'path.c', 'module.c'))
 util_ss.add(files('host-utils.c'))
-util_ss.add(files('bitmap.c', 'bitops.c', 'hbitmap.c'))
+util_ss.add(files('bitmap.c', 'bitops.c'))
 util_ss.add(files('fifo8.c'))
-util_ss.add(files('nvdimm-utils.c'))
 util_ss.add(files('cacheinfo.c'))
 util_ss.add(files('error.c', 'qemu-error.c'))
 util_ss.add(files('qemu-print.c'))
 util_ss.add(files('id.c'))
-util_ss.add(files('iov.c', 'qemu-config.c', 'qemu-sockets.c', 'uri.c', 'notify.c'))
+util_ss.add(files('qemu-config.c', 'notify.c'))
 util_ss.add(files('qemu-option.c', 'qemu-progress.c'))
 util_ss.add(files('keyval.c'))
-util_ss.add(files('hexdump.c'))
 util_ss.add(files('crc32c.c'))
 util_ss.add(files('uuid.c'))
-util_ss.add(files('throttle.c'))
 util_ss.add(files('getauxval.c'))
-util_ss.add(files('readline.c'))
 util_ss.add(files('rcu.c'))
 util_ss.add(when: 'CONFIG_MEMBARRIER', if_true: files('sys_membarrier.c'))
-util_ss.add(files('qemu-coroutine.c', 'qemu-coroutine-lock.c', 'qemu-coroutine-io.c'))
-util_ss.add(files('qemu-coroutine-sleep.c'))
-util_ss.add(files('qemu-co-shared-resource.c'))
-util_ss.add(files('coroutine-@0@.c'.format(config_host['CONFIG_COROUTINE_BACKEND'])))
-util_ss.add(files('buffer.c'))
-util_ss.add(files('timed-average.c'))
-util_ss.add(files('base64.c'))
 util_ss.add(files('log.c'))
 util_ss.add(files('pagesize.c'))
 util_ss.add(files('qdist.c'))
@@ -56,11 +41,38 @@ util_ss.add(files('qsp.c'))
 util_ss.add(files('range.c'))
 util_ss.add(files('stats64.c'))
 util_ss.add(files('systemd.c'))
-util_ss.add(files('iova-tree.c'))
-util_ss.add(when: 'CONFIG_INOTIFY1', if_true: files('filemonitor-inotify.c'),
-                                      if_false: files('filemonitor-stub.c'))
-util_ss.add(when: 'CONFIG_LINUX', if_true: files('vfio-helpers.c'))
 util_ss.add(when: 'CONFIG_POSIX', if_true: files('drm.c'))
 util_ss.add(files('guest-random.c'))
-util_ss.add(when: 'CONFIG_GIO', if_true: [files('dbus.c'), gio])
-util_ss.add(when: 'CONFIG_USER_ONLY', if_true: [files('selfmap.c')])
+
+if have_user
+  util_ss.add(files('selfmap.c'))
+endif
+
+if have_system
+  util_ss.add(when: 'CONFIG_GIO', if_true: [files('dbus.c'), gio])
+endif
+
+if have_block
+  util_ss.add(files('aiocb.c', 'async.c', 'aio-wait.c'))
+  util_ss.add(files('base64.c'))
+  util_ss.add(files('buffer.c'))
+  util_ss.add(files('bufferiszero.c'))
+  util_ss.add(files('coroutine-@0@.c'.format(config_host['CONFIG_COROUTINE_BACKEND'])))
+  util_ss.add(files('hbitmap.c'))
+  util_ss.add(files('hexdump.c'))
+  util_ss.add(files('iova-tree.c'))
+  util_ss.add(files('iov.c', 'qemu-sockets.c', 'uri.c'))
+  util_ss.add(files('lockcnt.c'))
+  util_ss.add(files('main-loop.c'))
+  util_ss.add(files('nvdimm-utils.c'))
+  util_ss.add(files('qemu-coroutine.c', 'qemu-coroutine-lock.c', 'qemu-coroutine-io.c'))
+  util_ss.add(files('qemu-coroutine-sleep.c'))
+  util_ss.add(files('qemu-co-shared-resource.c'))
+  util_ss.add(files('thread-pool.c', 'qemu-timer.c'))
+  util_ss.add(files('readline.c'))
+  util_ss.add(files('throttle.c'))
+  util_ss.add(files('timed-average.c'))
+  util_ss.add(when: 'CONFIG_INOTIFY1', if_true: files('filemonitor-inotify.c'),
+                                        if_false: files('filemonitor-stub.c'))
+  util_ss.add(when: 'CONFIG_LINUX', if_true: files('vfio-helpers.c'))
+endif
