blob: 2b67ad81c3c3de16ac21c383531925e481cdb472 [file] [log] [blame]
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build go1.16
// +build go1.16
// Binary runqemubuildlet runs a single VM-based buildlet in a loop.
package main
import (
var (
windows10Path = flag.String("windows-10-path", defaultWindowsDir(), "Path to Windows image and QEMU dependencies.")
healthzURL = flag.String("buildlet-healthz-url", "http://localhost:8080/healthz", "URL to buildlet /healthz endpoint.")
func main() {
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt)
defer stop()
for ctx.Err() == nil {
if err := runWindows10(ctx); err != nil {
log.Printf("runWindows10() = %v. Retrying in 10 seconds.", err)
time.Sleep(10 * time.Second)
func runWindows10(ctx context.Context) error {
cmd := windows10Cmd(*windows10Path)
log.Printf("Starting VM: %s", cmd.String())
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Start(); err != nil {
return fmt.Errorf("cmd.Start() = %w", err)
ctx, cancel := heartbeatContext(ctx, 30*time.Second, 10*time.Minute, func(ctx context.Context) error {
return checkBuildletHealth(ctx, *healthzURL)
defer cancel()
if err := internal.WaitOrStop(ctx, cmd, os.Interrupt, time.Minute); err != nil {
return fmt.Errorf("WaitOrStop(_, %v, %v, %v) = %w", cmd, os.Interrupt, time.Minute, err)
return nil
// defaultWindowsDir returns a default path for a Windows VM.
// The directory should contain the Windows VM image, and UTM
// components ( and sysroot-macos-arm64).
func defaultWindowsDir() string {
home, err := os.UserHomeDir()
if err != nil {
log.Printf("os.UserHomeDir() = %q, %v", home, err)
return ""
return filepath.Join(home, "macmini-windows")
// windows10Cmd returns a qemu command for running a Windows VM, ready
// to be started.
func windows10Cmd(base string) *exec.Cmd {
c := exec.Command(filepath.Join(base, "sysroot-macos-arm64/bin/qemu-system-aarch64"),
"-L", filepath.Join(base, ""),
"-cpu", "max",
"-smp", "cpus=8,sockets=1,cores=8,threads=1", // This works well with M1 Mac Minis.
"-machine", "virt,highmem=off",
"-accel", "hvf",
"-accel", "tcg,tb-size=1536",
"-boot", "menu=on",
"-m", "12288",
"-name", "Virtual Machine",
"-device", "qemu-xhci,id=usb-bus",
"-device", "ramfb",
"-device", "usb-tablet,bus=usb-bus.0",
"-device", "usb-mouse,bus=usb-bus.0",
"-device", "usb-kbd,bus=usb-bus.0",
"-device", "virtio-net-pci,netdev=net0",
"-netdev", "user,id=net0,hostfwd=tcp:",
"-bios", filepath.Join(base, "Images/QEMU_EFI.fd"),
"-device", "nvme,drive=drive0,serial=drive0,bootindex=0",
"-drive", fmt.Sprintf("if=none,media=disk,id=drive0,file=%s,cache=writethrough", filepath.Join(base, "Images/win10.qcow2")),
"-device", "usb-storage,drive=drive2,removable=true,bootindex=1",
"-drive", fmt.Sprintf("if=none,media=cdrom,id=drive2,file=%s,cache=writethrough", filepath.Join(base, "Images/virtio.iso")),
"-snapshot", // critical to avoid saving state between runs.
"-vnc", ":3",
c.Env = append(os.Environ(),
fmt.Sprintf("DYLD_LIBRARY_PATH=%s", filepath.Join(base, "sysroot-macos-arm64/lib")),
return c