[PATCH 09/13] qemu: split into verify, setup and a build chain

Daniel Gomez <[email protected]> Fri, 12 Jun 2026 14:36:47 +0200
Newsgroups dev.linux.lists.kdevops
Message-ID <[email protected]>
From: Daniel Gomez <[email protected]>

Building QEMU was one privileged, expensive step wired into a bare make
through LOCALHOST_SETUP_WORK and DEFAULT_DEPS. Split it into a controller
setup step and a chain of build targets, none on the default make path.

make qemu-controller-setup installs the build and runtime dependencies so
the controller can fetch, build and run QEMU. It is the privileged
preparation, may escalate, and is the only qemu step that installs system
packages; it is registered with the controller-setup umbrella.

The build is a chain of independent targets, each depending on the
previous: qemu-verify, qemu-fetch, qemu-configure, qemu-build and
qemu-install, with make qemu running the whole chain. It starts with
verify because you cannot fetch without git: verify is read-only and
sudoless and checks the build toolchain (git, meson, ninja, cc), failing
with a diagnostic that points at make qemu-controller-setup when a tool is
missing. Because qemu-fetch depends on qemu-verify and so on, any target
pulls in the steps before it, so make qemu-build verifies, fetches and
configures first. The install escalates only for the libvirt /usr/local
prefix; the qemu-destdir prefix used by other backends needs no root.

A top-level make controller-setup aggregates the migrated
CONTROLLER_SETUP_WORK and the grandfathered LOCALHOST_SETUP_WORK so all
controller setup can be driven from one umbrella. Only qemu is migrated
here to avoid regressing the contributors still on the default path.

Generated-by: Claude AI
Signed-off-by: Daniel Gomez <[email protected]>
---
 Makefile                               |  5 +++
 docs/qemu.md                           | 16 ++++---
 playbooks/roles/qemu/README.md         |  6 ---
 playbooks/roles/qemu/defaults/main.yml |  2 -
 playbooks/roles/qemu/tasks/main.yml    | 80 +++++++++++++---------------------
 scripts/qemu.Makefile                  | 48 +++++++++++++-------
 6 files changed, 79 insertions(+), 78 deletions(-)

diff --git a/Makefile b/Makefile
index 0d6f7c4f..d43d9611 100644
--- a/Makefile
+++ b/Makefile
@@ -138,6 +138,8 @@ endif # CONFIG_NEEDS_LOCAL_DEVELOPMENT_PATH
 # make with no arguments.
 LOCALHOST_SETUP_WORK :=
 
+CONTROLLER_SETUP_WORK :=
+
 ANSIBLE_EXTRA_ARGS += $(LOCAL_DEVELOPMENT_ARGS)
 
 # We may not need the extra_args.yaml file all the time.  If this file is empty
@@ -285,6 +287,9 @@ $(KDEVOPS_NODES): .config $(ANSIBLE_CFG_FILE) $(KDEVOPS_NODES_TEMPLATE) $(KDEVOP
 
 DEFAULT_DEPS += $(LOCALHOST_SETUP_WORK)
 
+PHONY += controller-setup
+controller-setup: $(CONTROLLER_SETUP_WORK)
+
 include scripts/tests.Makefile
 include scripts/linux-ab-testing.Makefile
 include scripts/ci.Makefile
diff --git a/docs/qemu.md b/docs/qemu.md
index ce4899ce..396c589c 100644
--- a/docs/qemu.md
+++ b/docs/qemu.md
@@ -67,10 +67,14 @@ it for the systemd unit `ExecStart`.
 
 ## Make targets
 
-QEMU is built as part of the localhost setup performed during `make` and
-`make bringup`. The build can also be driven explicitly:
-
-- `make qemu` — fetch, build, and install QEMU on localhost
-- `make qemu-configure` — run QEMU's configure step
+QEMU builds only when you run its targets, never during `make` or
+`make bringup`. The build targets form a chain; each pulls in the ones
+before it, and `make qemu` runs the whole chain:
+
+- `make qemu-controller-setup` — install the build and runtime dependencies (may sudo)
+- `make qemu-verify` — verify the build toolchain is present (read-only)
+- `make qemu-fetch` — fetch the QEMU git tree
+- `make qemu-configure` — configure the QEMU build
 - `make qemu-build` — build QEMU
-- `make qemu-install` — install the built QEMU on localhost
+- `make qemu-install` — install QEMU on localhost
+- `make qemu` — verify, fetch, configure, build, and install QEMU
diff --git a/playbooks/roles/qemu/README.md b/playbooks/roles/qemu/README.md
index f439bdca..56f646b9 100644
--- a/playbooks/roles/qemu/README.md
+++ b/playbooks/roles/qemu/README.md
@@ -14,12 +14,6 @@ Run a supported OS/distribution:
   * Red Hat / Fedora
   * Debian / Ubuntu
 
-Role Variables
---------------
-
-  * qemu_force_install_if_present: set to False by default, set this to True to
-    force building even if you have /usr/local/bin/qemu-system-x86_64
-
 Dependencies
 ------------
 
diff --git a/playbooks/roles/qemu/defaults/main.yml b/playbooks/roles/qemu/defaults/main.yml
index 31671d78..5fe2f375 100644
--- a/playbooks/roles/qemu/defaults/main.yml
+++ b/playbooks/roles/qemu/defaults/main.yml
@@ -4,8 +4,6 @@
 qemu_install_dir: "{{ qemu_install_dir | default('/usr/local') }}"
 qemu_install_needs_sudo: false
 
-# Forces to build and install even if the binary is already present
-qemu_force_install_if_present: false
 qemu_bin_path: "{{ qemu_bin_path | default(qemu_install_dir + '/bin/qemu-system-x86_64') }}"
 qemu_data: "{{ qemu_git_data_path | default(kdevops_controller_data_path + '/qemu') }}"
 qemu_git: "{{ qemu_git | default('https://github.com/qemu/qemu.git') }}"
diff --git a/playbooks/roles/qemu/tasks/main.yml b/playbooks/roles/qemu/tasks/main.yml
index 015c4d15..8298adcf 100644
--- a/playbooks/roles/qemu/tasks/main.yml
+++ b/playbooks/roles/qemu/tasks/main.yml
@@ -11,39 +11,40 @@
       skip: true
   tags: vars
 
-- name: Verify local build QEMU installation
-  ansible.builtin.stat:
-    path: "{{ qemu_bin_path }}"
-  register: qemu_present
+- name: Check the QEMU build toolchain is present
+  ansible.builtin.shell: |
+    missing=
+    for tool in git meson ninja cc; do
+      command -v "$tool" >/dev/null 2>&1 || missing="$missing $tool"
+    done
+    [ -z "$missing" ] || { echo "$missing"; exit 1; }
+  register: qemu_toolchain
   changed_when: false
-  failed_when: qemu_present.stat.exists and not qemu_present.stat.executable
-  tags: ["qemu", "verify"]
+  failed_when: false
+  tags: ["qemu_verify"]
 
-- name: Install build-deps for QEMU as per each Linux distribution
-  ansible.builtin.include_tasks: install-deps/main.yml
+- name: Fail when the QEMU build toolchain is missing
+  ansible.builtin.fail:
+    msg: |
+      Missing QEMU build tools:{{ qemu_toolchain.stdout }}.
+      Run make qemu-controller-setup to install the build and runtime
+      dependencies, then retry.
   when:
-    - qemu_force_install_if_present|bool or not qemu_present.stat.exists
+    - qemu_toolchain.rc != 0
+  tags: ["qemu_verify"]
 
-- name: Assume we won't build QEMU first
-  ansible.builtin.set_fact:
-    qemu_build_now: false
-  tags: vars
-
-- name: Annotate when we are building QEMU
-  ansible.builtin.set_fact:
-    qemu_build_now: true
+- name: Install build and runtime deps for QEMU per Linux distribution
+  ansible.builtin.include_tasks: install-deps/main.yml
   when:
-    - qemu_force_install_if_present|bool or not qemu_present.stat.exists
-  tags: vars
+    - "'qemu_controller_setup' in ansible_run_tags"
+  tags: ["qemu_controller_setup"]
 
 - name: Ensure the controller data path exists
   ansible.builtin.file:
     path: "{{ kdevops_controller_data_path }}"
     state: directory
     mode: "0755"
-  tags: ["qemu", "build-deps"]
-  when:
-    - qemu_build_now|bool
+  tags: ["qemu_fetch"]
 
 - name: Fetch the QEMU git tree
   environment:
@@ -52,34 +53,21 @@
     repo: "{{ qemu_git }}"
     dest: "{{ qemu_data }}"
     version: "{{ qemu_version }}"
-  when:
-    - qemu_build_now|bool
-
-- name: Nuke old build directory
-  ansible.builtin.file:
-    path: "{{ qemu_build_dir }}"
-    state: absent
-  tags: ["qemu", "build-deps"]
-  when:
-    - qemu_build_now|bool
+  tags: ["qemu_fetch"]
 
 - name: Create the out-of-tree build directory
   ansible.builtin.file:
     path: "{{ qemu_build_dir }}"
     state: directory
     mode: "0755"
-  tags: ["qemu", "build-deps"]
-  when:
-    - qemu_build_now|bool
+  tags: ["qemu_configure"]
 
 - name: Pre-fetch QEMU subprojects so configure can disable downloads
   ansible.builtin.command: "meson subprojects download"
   changed_when: true
-  tags: ["qemu", "configure"]
+  tags: ["qemu_configure"]
   args:
     chdir: "{{ qemu_data }}"
-  when:
-    - qemu_build_now|bool
 
 - name: Run configure for QEMU
   ansible.builtin.command: >-
@@ -88,27 +76,21 @@
     --prefix={{ qemu_install_dir }}
     --disable-download
   changed_when: true
-  tags: ["qemu", "configure"]
+  tags: ["qemu_configure"]
   args:
     chdir: "{{ qemu_build_dir }}"
-  when:
-    - qemu_build_now|bool
 
 - name: Get the number of build jobs
   ansible.builtin.command: "{{ num_jobs }}"
   changed_when: false
-  tags: ["qemu", "configure", "build"]
+  tags: ["qemu_build"]
   register: qemu_nproc
-  when:
-    - qemu_build_now|bool
 
 - name: Build QEMU
   community.general.make:
     chdir: "{{ qemu_build_dir }}"
     jobs: "{{ qemu_nproc.stdout }}"
-  tags: ["qemu", "build"]
-  when:
-    - qemu_build_now|bool
+  tags: ["qemu_build"]
 
 - name: Install QEMU
   become: "{{ qemu_install_needs_sudo | bool }}"
@@ -117,6 +99,6 @@
   changed_when: true
   args:
     chdir: "{{ qemu_build_dir }}"
-  tags: ["qemu", "install"]
   when:
-    - qemu_build_now|bool
+    - "'qemu_install' in ansible_run_tags"
+  tags: ["qemu_install"]
diff --git a/scripts/qemu.Makefile b/scripts/qemu.Makefile
index 8d7c446e..a7bdfb72 100644
--- a/scripts/qemu.Makefile
+++ b/scripts/qemu.Makefile
@@ -1,38 +1,56 @@
 # SPDX-License-Identifier: copyleft-next-0.3.1
 
-qemu: $(KDEVOPS_EXTRA_VARS)
+qemu-controller-setup: $(KDEVOPS_EXTRA_VARS)
 	$(Q)ansible-playbook \
 		$(KDEVOPS_PLAYBOOKS_DIR)/qemu.yml \
-		--extra-vars=@./extra_vars.yaml
-PHONY += qemu
+		--extra-vars=@./extra_vars.yaml --tags vars,qemu_controller_setup
+PHONY += qemu-controller-setup
 
-qemu-install: $(KDEVOPS_EXTRA_VARS)
+qemu-verify: $(KDEVOPS_EXTRA_VARS)
 	$(Q)ansible-playbook \
 		$(KDEVOPS_PLAYBOOKS_DIR)/qemu.yml \
-		--extra-vars=@./extra_vars.yaml --tags vars,install
-PHONY += qemu-install
+		--extra-vars=@./extra_vars.yaml --tags vars,qemu_verify
+PHONY += qemu-verify
+
+qemu-fetch: qemu-verify
+	$(Q)ansible-playbook \
+		$(KDEVOPS_PLAYBOOKS_DIR)/qemu.yml \
+		--extra-vars=@./extra_vars.yaml --tags vars,qemu_fetch
+PHONY += qemu-fetch
 
-qemu-configure: $(KDEVOPS_EXTRA_VARS)
+qemu-configure: qemu-fetch
 	$(Q)ansible-playbook \
 		$(KDEVOPS_PLAYBOOKS_DIR)/qemu.yml \
-		--extra-vars=@./extra_vars.yaml --tags vars,configure
+		--extra-vars=@./extra_vars.yaml --tags vars,qemu_configure
 PHONY += qemu-configure
 
-qemu-build: $(KDEVOPS_EXTRA_VARS)
+qemu-build: qemu-configure
 	$(Q)ansible-playbook \
 		$(KDEVOPS_PLAYBOOKS_DIR)/qemu.yml \
-		--extra-vars=@./extra_vars.yaml --tags vars,build
+		--extra-vars=@./extra_vars.yaml --tags vars,qemu_build
 PHONY += qemu-build
 
+qemu-install: qemu-build
+	$(Q)ansible-playbook \
+		$(KDEVOPS_PLAYBOOKS_DIR)/qemu.yml \
+		--extra-vars=@./extra_vars.yaml --tags vars,qemu_install
+PHONY += qemu-install
+
+qemu: qemu-install
+PHONY += qemu
 
 qemu-help-menu:
 	@echo "qemu options:"
-	@echo "qemu                                 - Git fetches QEMU, builds and install it on localhost"
-	@echo "qemu-configure                       - Configure QEMU build"
-	@echo "qemu-build                           - Build QEMU"
-	@echo "qemu-install                         - Do the install of the QEMU build on localhost"
+	@echo "qemu-controller-setup                - Install QEMU build and runtime deps (controller setup, may sudo)"
+	@echo "qemu                                 - Verify, fetch, configure, build and install QEMU"
+	@echo "qemu-verify                          - Verify the build toolchain is present (read-only)"
+	@echo "qemu-fetch                           - Fetch the QEMU git tree (after verify)"
+	@echo "qemu-configure                       - Configure the QEMU build (after fetch)"
+	@echo "qemu-build                           - Build QEMU (after configure)"
+	@echo "qemu-install                         - Install QEMU (after build)"
+	@echo "controller-setup                     - Runs qemu-controller-setup as part of opt-in controller setup"
 	@echo ""
 
 HELP_TARGETS += qemu-help-menu
 
-LOCALHOST_SETUP_WORK += qemu
+CONTROLLER_SETUP_WORK += qemu-controller-setup

-- 
2.54.0