treewide: replace hardcoded runfiles paths

We hardcoded some of the runfiles paths to find specific files. This replaces the hardcoded paths by a call to rlocationpath. This prevents running a target without the correct dependencies at build time instead of at runtime

Change-Id: I7ce56935ac80be6b28b824ccb0781ab401bd6521
Reviewed-on: https://review.monogon.dev/c/monogon/+/3301
Reviewed-by: Serge Bazanski <serge@monogon.tech>
Tested-by: Jenkins CI
diff --git a/cloud/agent/e2e/BUILD.bazel b/cloud/agent/e2e/BUILD.bazel
index 0fbc1ba..39dd031 100644
--- a/cloud/agent/e2e/BUILD.bazel
+++ b/cloud/agent/e2e/BUILD.bazel
@@ -6,9 +6,17 @@
     data = [
         "//cloud/agent/takeover:initramfs",
         "//metropolis/installer/test/testos:testos_bundle",
-        "//third_party/edk2:firmware",
+        "//third_party/edk2:OVMF_CODE.fd",
+        "//third_party/edk2:OVMF_VARS.fd",
         "//third_party/linux",
     ],
+    x_defs = {
+        "xBundleFilePath": "$(rlocationpath //metropolis/installer/test/testos:testos_bundle )",
+        "xOvmfVarsPath": "$(rlocationpath //third_party/edk2:OVMF_VARS.fd )",
+        "xOvmfCodePath": "$(rlocationpath //third_party/edk2:OVMF_CODE.fd )",
+        "xKernelPath": "$(rlocationpath //third_party/linux )",
+        "xInitramfsOrigPath": "$(rlocationpath //cloud/agent/takeover:initramfs )",
+    },
     deps = [
         "//cloud/agent/api",
         "//cloud/bmaas/server/api",
diff --git a/cloud/agent/e2e/main_test.go b/cloud/agent/e2e/main_test.go
index ec1ee0c..dcde625 100644
--- a/cloud/agent/e2e/main_test.go
+++ b/cloud/agent/e2e/main_test.go
@@ -34,6 +34,30 @@
 	"source.monogon.dev/osbase/pki"
 )
 
+var (
+	// These are filled by bazel at linking time with the canonical path of
+	// their corresponding file. Inside the init function we resolve it
+	// with the rules_go runfiles package to the real path.
+	xBundleFilePath    string
+	xOvmfVarsPath      string
+	xOvmfCodePath      string
+	xKernelPath        string
+	xInitramfsOrigPath string
+)
+
+func init() {
+	var err error
+	for _, path := range []*string{
+		&xBundleFilePath, &xOvmfVarsPath, &xOvmfCodePath,
+		&xKernelPath, &xInitramfsOrigPath,
+	} {
+		*path, err = runfiles.Rlocation(*path)
+		if err != nil {
+			panic(err)
+		}
+	}
+}
+
 type fakeServer struct {
 	hardwareReport      *bpb.AgentHardwareReport
 	installationRequest *bpb.OSInstallationRequest
@@ -139,12 +163,9 @@
 	grpcListenAddr := grpcLis.Addr().(*net.TCPAddr)
 
 	m := http.NewServeMux()
-	bundleFilePath, err := runfiles.Rlocation("_main/metropolis/installer/test/testos/testos_bundle.zip")
-	if err != nil {
-		t.Fatal(err)
-	}
+
 	m.HandleFunc("/bundle.bin", func(w http.ResponseWriter, req *http.Request) {
-		http.ServeFile(w, req, bundleFilePath)
+		http.ServeFile(w, req, xBundleFilePath)
 	})
 	blobLis, err := net.Listen("tcp", "127.0.0.1:0")
 	if err != nil {
@@ -178,23 +199,7 @@
 		t.Fatalf("ftruncate failed: %v", err)
 	}
 
-	ovmfVarsPath, err := runfiles.Rlocation("edk2/OVMF_VARS.fd")
-	if err != nil {
-		t.Fatal(err)
-	}
-	ovmfCodePath, err := runfiles.Rlocation("edk2/OVMF_CODE.fd")
-	if err != nil {
-		t.Fatal(err)
-	}
-	kernelPath, err := runfiles.Rlocation("_main/third_party/linux/bzImage")
-	if err != nil {
-		t.Fatal(err)
-	}
-	initramfsOrigPath, err := runfiles.Rlocation("_main/cloud/agent/takeover/initramfs.cpio.zst")
-	if err != nil {
-		t.Fatal(err)
-	}
-	initramfsOrigFile, err := os.Open(initramfsOrigPath)
+	initramfsOrigFile, err := os.Open(xInitramfsOrigPath)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -235,7 +240,7 @@
 	if err != nil {
 		t.Fatal(err)
 	}
-	ovmfVarsTmpl, err := os.Open(ovmfVarsPath)
+	ovmfVarsTmpl, err := os.Open(xOvmfVarsPath)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -246,7 +251,7 @@
 	qemuArgs := []string{
 		"-machine", "q35", "-accel", "kvm", "-nographic", "-nodefaults", "-m", "1024",
 		"-cpu", "host", "-smp", "sockets=1,cpus=1,cores=2,threads=2,maxcpus=4",
-		"-drive", "if=pflash,format=raw,readonly=on,file=" + ovmfCodePath,
+		"-drive", "if=pflash,format=raw,readonly=on,file=" + xOvmfCodePath,
 		"-drive", "if=pflash,format=raw,file=" + ovmfVars.Name(),
 		"-drive", "if=virtio,format=raw,cache=unsafe,file=" + rootDisk.Name(),
 		"-netdev", fmt.Sprintf("user,id=net0,net=10.42.0.0/24,dhcpstart=10.42.0.10,%s,%s", grpcGuestFwd, blobGuestFwd),
@@ -256,7 +261,7 @@
 		"-no-reboot",
 	}
 	stage1Args := append(qemuArgs,
-		"-kernel", kernelPath,
+		"-kernel", xKernelPath,
 		"-initrd", initramfsFile.Name(),
 		"-append", "console=ttyS0 quiet")
 	qemuCmdAgent := exec.Command("qemu-system-x86_64", stage1Args...)
diff --git a/cloud/agent/takeover/e2e/BUILD.bazel b/cloud/agent/takeover/e2e/BUILD.bazel
index 2b9a200..7259fc5 100644
--- a/cloud/agent/takeover/e2e/BUILD.bazel
+++ b/cloud/agent/takeover/e2e/BUILD.bazel
@@ -5,9 +5,16 @@
     srcs = ["main_test.go"],
     data = [
         "//cloud/agent/takeover",
-        "//third_party/edk2:firmware",
+        "//third_party/edk2:OVMF_CODE.fd",
+        "//third_party/edk2:OVMF_VARS.fd",
         "@debian_11_cloudimage//file",
     ],
+    x_defs = {
+        "xCloudImagePath": "$(rlocationpath @debian_11_cloudimage//file )",
+        "xOvmfVarsPath": "$(rlocationpath //third_party/edk2:OVMF_VARS.fd )",
+        "xOvmfCodePath": "$(rlocationpath //third_party/edk2:OVMF_CODE.fd )",
+        "xTakeoverPath": "$(rlocationpath //cloud/agent/takeover )",
+    },
     deps = [
         "//cloud/agent/api",
         "//osbase/fat32",
diff --git a/cloud/agent/takeover/e2e/main_test.go b/cloud/agent/takeover/e2e/main_test.go
index a8c8686..0521cc9 100644
--- a/cloud/agent/takeover/e2e/main_test.go
+++ b/cloud/agent/takeover/e2e/main_test.go
@@ -26,6 +26,29 @@
 	"source.monogon.dev/osbase/freeport"
 )
 
+var (
+	// These are filled by bazel at linking time with the canonical path of
+	// their corresponding file. Inside the init function we resolve it
+	// with the rules_go runfiles package to the real path.
+	xCloudImagePath string
+	xOvmfVarsPath   string
+	xOvmfCodePath   string
+	xTakeoverPath   string
+)
+
+func init() {
+	var err error
+	for _, path := range []*string{
+		&xCloudImagePath, &xOvmfVarsPath, &xOvmfCodePath,
+		&xTakeoverPath,
+	} {
+		*path, err = runfiles.Rlocation(*path)
+		if err != nil {
+			panic(err)
+		}
+	}
+}
+
 func TestE2E(t *testing.T) {
 	pubKey, privKey, err := ed25519.GenerateKey(rand.Reader)
 	if err != nil {
@@ -74,18 +97,6 @@
 	if err := fat32.WriteFS(cloudInitDataFile, rootInode, fat32.Options{Label: "cidata"}); err != nil {
 		t.Fatal(err)
 	}
-	cloudImagePath, err := runfiles.Rlocation("debian_11_cloudimage/file/downloaded")
-	if err != nil {
-		t.Fatal(err)
-	}
-	ovmfVarsPath, err := runfiles.Rlocation("edk2/OVMF_VARS.fd")
-	if err != nil {
-		t.Fatal(err)
-	}
-	ovmfCodePath, err := runfiles.Rlocation("edk2/OVMF_CODE.fd")
-	if err != nil {
-		t.Fatal(err)
-	}
 
 	sshPort, sshPortCloser, err := freeport.AllocateTCPPort()
 	if err != nil {
@@ -95,9 +106,9 @@
 	qemuArgs := []string{
 		"-machine", "q35", "-accel", "kvm", "-nographic", "-nodefaults", "-m", "1024",
 		"-cpu", "host", "-smp", "sockets=1,cpus=1,cores=2,threads=2,maxcpus=4",
-		"-drive", "if=pflash,format=raw,readonly=on,file=" + ovmfCodePath,
-		"-drive", "if=pflash,format=raw,snapshot=on,file=" + ovmfVarsPath,
-		"-drive", "if=virtio,format=qcow2,snapshot=on,cache=unsafe,file=" + cloudImagePath,
+		"-drive", "if=pflash,format=raw,readonly=on,file=" + xOvmfCodePath,
+		"-drive", "if=pflash,format=raw,snapshot=on,file=" + xOvmfVarsPath,
+		"-drive", "if=virtio,format=qcow2,snapshot=on,cache=unsafe,file=" + xCloudImagePath,
 		"-drive", "if=virtio,format=raw,snapshot=on,file=" + cloudInitDataFile.Name(),
 		"-netdev", fmt.Sprintf("user,id=net0,net=10.42.0.0/24,dhcpstart=10.42.0.10,hostfwd=tcp::%d-:22", sshPort),
 		"-device", "virtio-net-pci,netdev=net0,mac=22:d5:8e:76:1d:07",
@@ -158,11 +169,7 @@
 	if err := takeoverFile.Chmod(0o755); err != nil {
 		t.Fatal(err)
 	}
-	takeoverPath, err := runfiles.Rlocation("_main/cloud/agent/takeover/takeover_/takeover")
-	if err != nil {
-		t.Fatal(err)
-	}
-	takeoverSrcFile, err := os.Open(takeoverPath)
+	takeoverSrcFile, err := os.Open(xTakeoverPath)
 	if err != nil {
 		t.Fatal(err)
 	}
diff --git a/go/qcow2/BUILD.bazel b/go/qcow2/BUILD.bazel
index 07afa58..c4806eb 100644
--- a/go/qcow2/BUILD.bazel
+++ b/go/qcow2/BUILD.bazel
@@ -3,16 +3,19 @@
 go_library(
     name = "qcow2",
     srcs = ["qcow2.go"],
+    data = [
+        "@qemu//:qemu-img",
+    ],
     importpath = "source.monogon.dev/go/qcow2",
     visibility = ["//visibility:public"],
+    x_defs = {
+        "xQemuImgPath": "$(rlocationpath @qemu//:qemu-img )",
+    },
 )
 
 go_test(
     name = "qcow2_test",
     srcs = ["qcow2_test.go"],
-    data = [
-        "@qemu//:qemu-img",
-    ],
     embed = [":qcow2"],
     deps = ["@io_bazel_rules_go//go/runfiles:go_default_library"],
 )
diff --git a/go/qcow2/qcow2_test.go b/go/qcow2/qcow2_test.go
index 3b987c0..9dc230a 100644
--- a/go/qcow2/qcow2_test.go
+++ b/go/qcow2/qcow2_test.go
@@ -10,13 +10,27 @@
 	"github.com/bazelbuild/rules_go/go/runfiles"
 )
 
+var (
+	// These are filled by bazel at linking time with the canonical path of
+	// their corresponding file. Inside the init function we resolve it
+	// with the rules_go runfiles package to the real path.
+	xQemuImgPath string
+)
+
+func init() {
+	var err error
+	for _, path := range []*string{
+		&xQemuImgPath,
+	} {
+		*path, err = runfiles.Rlocation(*path)
+		if err != nil {
+			panic(err)
+		}
+	}
+}
+
 // TestGenerate exercises the Generate function for a variety of image sizes.
 func TestGenerate(t *testing.T) {
-	qemuImg, err := runfiles.Rlocation("qemu/qemu-img")
-	if err != nil {
-		t.Fatalf("Could not locate qemu-img: %v", err)
-	}
-
 	// Test all orders of magnitude from 1KiB to 1PiB.
 	for i := 20; i < 50; i++ {
 		t.Run(fmt.Sprintf("%d", 1<<i), func(t *testing.T) {
@@ -33,7 +47,7 @@
 				t.Fatalf("Close: %v", err)
 			}
 
-			cmd := exec.Command(qemuImg, "check", path)
+			cmd := exec.Command(xQemuImgPath, "check", path)
 			if err := cmd.Run(); err != nil {
 				t.Fatalf("qemu-img check failed: %v", err)
 			}
diff --git a/metropolis/cli/metroctl/test/BUILD.bazel b/metropolis/cli/metroctl/test/BUILD.bazel
index 9b9c4cc..7f5b6bd 100644
--- a/metropolis/cli/metroctl/test/BUILD.bazel
+++ b/metropolis/cli/metroctl/test/BUILD.bazel
@@ -7,10 +7,11 @@
     srcs = ["test.go"],
     data = [
         "//metropolis/cli/metroctl",
-        "//metropolis/node:image",
-        "//third_party/edk2:firmware",
     ],
     rundir = ".",
+    x_defs = {
+        "xMetroctlPath": "$(rlocationpath //metropolis/cli/metroctl )",
+    },
     deps = [
         "//metropolis/node",
         "//metropolis/test/launch",
diff --git a/metropolis/cli/metroctl/test/test.go b/metropolis/cli/metroctl/test/test.go
index f601811..17f5f75 100644
--- a/metropolis/cli/metroctl/test/test.go
+++ b/metropolis/cli/metroctl/test/test.go
@@ -21,14 +21,23 @@
 	"source.monogon.dev/version"
 )
 
-// resolveMetroctl resolves metroctl filesystem path. It will return a correct
-// path, or terminate test execution.
-func resolveMetroctl() string {
-	path, err := runfiles.Rlocation("_main/metropolis/cli/metroctl/metroctl_/metroctl")
-	if err != nil {
-		log.Fatalf("Couldn't resolve metroctl binary: %v", err)
+var (
+	// These are filled by bazel at linking time with the canonical path of
+	// their corresponding file. Inside the init function we resolve it
+	// with the rules_go runfiles package to the real path.
+	xMetroctlPath string
+)
+
+func init() {
+	var err error
+	for _, path := range []*string{
+		&xMetroctlPath,
+	} {
+		*path, err = runfiles.Rlocation(*path)
+		if err != nil {
+			panic(err)
+		}
 	}
-	return path
 }
 
 // mctlRun starts metroctl, and waits till it exits. It returns nil, if the run
@@ -36,12 +45,11 @@
 func mctlRun(t *testing.T, ctx context.Context, args []string) error {
 	t.Helper()
 
-	path := resolveMetroctl()
 	log.Printf("$ metroctl %s", strings.Join(args, " "))
 	logf := func(line string) {
 		log.Printf("metroctl: %s", line)
 	}
-	_, err := cmd.RunCommand(ctx, path, args, cmd.WaitUntilCompletion(logf))
+	_, err := cmd.RunCommand(ctx, xMetroctlPath, args, cmd.WaitUntilCompletion(logf))
 	return err
 }
 
@@ -50,13 +58,12 @@
 func mctlExpectOutput(t *testing.T, ctx context.Context, args []string, expect string) (bool, error) {
 	t.Helper()
 
-	path := resolveMetroctl()
 	log.Printf("$ metroctl %s", strings.Join(args, " "))
 	// Terminate metroctl as soon as the expected output is found.
 	logf := func(line string) {
 		log.Printf("metroctl: %s", line)
 	}
-	found, err := cmd.RunCommand(ctx, path, args, cmd.TerminateIfFound(expect, logf))
+	found, err := cmd.RunCommand(ctx, xMetroctlPath, args, cmd.TerminateIfFound(expect, logf))
 	if err != nil {
 		return false, fmt.Errorf("while running metroctl: %w", err)
 	}
diff --git a/metropolis/installer/test/BUILD.bazel b/metropolis/installer/test/BUILD.bazel
index 7f71218..3f74cde 100644
--- a/metropolis/installer/test/BUILD.bazel
+++ b/metropolis/installer/test/BUILD.bazel
@@ -1,24 +1,26 @@
-load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
+load("@io_bazel_rules_go//go:def.bzl", "go_test")
 load("//metropolis/node/build:efi.bzl", "efi_unified_kernel_image")
 
 go_test(
-    name = "installer",
+    name = "test_test",
     size = "medium",
+    srcs = ["run_test.go"],
     data = [
         ":kernel",
         "//metropolis/installer/test/testos:testos_bundle",
-        "//third_party/edk2:firmware",
+        "//third_party/edk2:OVMF_CODE.fd",
+        "//third_party/edk2:OVMF_VARS.fd",
         "@qemu//:qemu-x86_64-softmmu",
     ],
-    embed = [":test"],
-    rundir = ".",
-)
-
-go_library(
-    name = "test",
-    srcs = ["main.go"],
     importpath = "source.monogon.dev/metropolis/installer/test",
     visibility = ["//visibility:private"],
+    x_defs = {
+        "xOvmfVarsPath": "$(rlocationpath //third_party/edk2:OVMF_VARS.fd )",
+        "xOvmfCodePath": "$(rlocationpath //third_party/edk2:OVMF_CODE.fd )",
+        "xQemuPath": "$(rlocationpath @qemu//:qemu-x86_64-softmmu )",
+        "xInstallerPath": "$(rlocationpath :kernel )",
+        "xBundlePath": "$(rlocationpath //metropolis/installer/test/testos:testos_bundle )",
+    },
     deps = [
         "//metropolis/cli/metroctl/core",
         "//metropolis/node/build/mkimage/osimage",
diff --git a/metropolis/installer/test/main.go b/metropolis/installer/test/run_test.go
similarity index 92%
rename from metropolis/installer/test/main.go
rename to metropolis/installer/test/run_test.go
index f2c1f46..fef8e20 100644
--- a/metropolis/installer/test/main.go
+++ b/metropolis/installer/test/run_test.go
@@ -42,6 +42,30 @@
 	"source.monogon.dev/osbase/cmd"
 )
 
+var (
+	// These are filled by bazel at linking time with the canonical path of
+	// their corresponding file. Inside the init function we resolve it
+	// with the rules_go runfiles package to the real path.
+	xOvmfCodePath  string
+	xOvmfVarsPath  string
+	xQemuPath      string
+	xInstallerPath string
+	xBundlePath    string
+)
+
+func init() {
+	var err error
+	for _, path := range []*string{
+		&xOvmfCodePath, &xOvmfVarsPath, &xQemuPath,
+		&xInstallerPath, &xBundlePath,
+	} {
+		*path, err = runfiles.Rlocation(*path)
+		if err != nil {
+			panic(err)
+		}
+	}
+}
+
 // Each variable in this block points to either a test dependency or a side
 // effect. These variables are initialized in TestMain using Bazel.
 var (
@@ -57,31 +81,19 @@
 // QEMU is killed shortly after the string is found, or when the context is
 // cancelled.
 func runQemu(ctx context.Context, args []string, expectedOutput string) (bool, error) {
-	ovmfVarsPath, err := runfiles.Rlocation("edk2/OVMF_VARS.fd")
-	if err != nil {
-		return false, err
-	}
-	ovmfCodePath, err := runfiles.Rlocation("edk2/OVMF_CODE.fd")
-	if err != nil {
-		return false, err
-	}
-	qemuPath, err := runfiles.Rlocation("qemu/qemu-x86_64-softmmu")
-	if err != nil {
-		return false, err
-	}
 	defaultArgs := []string{
 		"-machine", "q35", "-accel", "kvm", "-nographic", "-nodefaults",
 		"-m", "512",
 		"-smp", "2",
 		"-cpu", "host",
-		"-drive", "if=pflash,format=raw,snapshot=on,file=" + ovmfCodePath,
-		"-drive", "if=pflash,format=raw,readonly=on,file=" + ovmfVarsPath,
+		"-drive", "if=pflash,format=raw,snapshot=on,file=" + xOvmfCodePath,
+		"-drive", "if=pflash,format=raw,readonly=on,file=" + xOvmfVarsPath,
 		"-serial", "stdio",
 		"-no-reboot",
 	}
 	qemuArgs := append(defaultArgs, args...)
 	pf := cmd.TerminateIfFound(expectedOutput, nil)
-	return cmd.RunCommand(ctx, qemuPath, qemuArgs, pf)
+	return cmd.RunCommand(ctx, xQemuPath, qemuArgs, pf)
 }
 
 // runQemuWithInstaller runs the Metropolis Installer in a qemu, performing the
@@ -136,20 +148,12 @@
 func TestMain(m *testing.M) {
 	installerImage = filepath.Join(os.Getenv("TEST_TMPDIR"), "installer.img")
 
-	installerPath, err := runfiles.Rlocation("_main/metropolis/installer/test/kernel.efi")
-	if err != nil {
-		log.Fatal(err)
-	}
-	installer, err := os.ReadFile(installerPath)
+	installer, err := os.ReadFile(xInstallerPath)
 	if err != nil {
 		log.Fatal(err)
 	}
 
-	bundlePath, err := runfiles.Rlocation("_main/metropolis/installer/test/testos/testos_bundle.zip")
-	if err != nil {
-		log.Fatal(err)
-	}
-	bundle, err := os.ReadFile(bundlePath)
+	bundle, err := os.ReadFile(xBundlePath)
 	if err != nil {
 		log.Fatal(err)
 	}
diff --git a/metropolis/node/core/metrics/BUILD.bazel b/metropolis/node/core/metrics/BUILD.bazel
index 30d08a7..9383afb 100644
--- a/metropolis/node/core/metrics/BUILD.bazel
+++ b/metropolis/node/core/metrics/BUILD.bazel
@@ -25,10 +25,12 @@
     name = "metrics_test",
     srcs = ["metrics_test.go"],
     data = [
-        # keep
         "//metropolis/node/core/metrics/fake_exporter",
     ],
     embed = [":metrics"],
+    x_defs = {
+        "xFakeExporterPath": "$(rlocationpath //metropolis/node/core/metrics/fake_exporter )",
+    },
     deps = [
         "//metropolis/node",
         "//metropolis/node/core/curator/proto/api",
diff --git a/metropolis/node/core/metrics/metrics_test.go b/metropolis/node/core/metrics/metrics_test.go
index 06b1d4e..fb5e3dc 100644
--- a/metropolis/node/core/metrics/metrics_test.go
+++ b/metropolis/node/core/metrics/metrics_test.go
@@ -23,9 +23,26 @@
 	"source.monogon.dev/osbase/supervisor"
 )
 
-func fakeExporter(name, value string) *Exporter {
-	path, _ := runfiles.Rlocation("_main/metropolis/node/core/metrics/fake_exporter/fake_exporter_/fake_exporter")
+var (
+	// These are filled by bazel at linking time with the canonical path of
+	// their corresponding file. Inside the init function we resolve it
+	// with the rules_go runfiles package to the real path.
+	xFakeExporterPath string
+)
 
+func init() {
+	var err error
+	for _, path := range []*string{
+		&xFakeExporterPath,
+	} {
+		*path, err = runfiles.Rlocation(*path)
+		if err != nil {
+			panic(err)
+		}
+	}
+}
+
+func fakeExporter(name, value string) *Exporter {
 	p, closer, err := freeport.AllocateTCPPort()
 	if err != nil {
 		panic(err)
@@ -36,7 +53,7 @@
 	return &Exporter{
 		Name:       name,
 		Port:       port,
-		Executable: path,
+		Executable: xFakeExporterPath,
 		Arguments: []string{
 			"-listen", "127.0.0.1:" + port.PortString(),
 			"-value", value,
diff --git a/metropolis/node/core/update/e2e/BUILD.bazel b/metropolis/node/core/update/e2e/BUILD.bazel
index b96006c..abf3ba1 100644
--- a/metropolis/node/core/update/e2e/BUILD.bazel
+++ b/metropolis/node/core/update/e2e/BUILD.bazel
@@ -5,7 +5,8 @@
     srcs = ["e2e_test.go"],
     data = [
         # For emulation
-        "//third_party/edk2:firmware",
+        "//third_party/edk2:OVMF_CODE.fd",
+        "//third_party/edk2:OVMF_VARS.fd",
         # For the initial image creation
         "//metropolis/node/core/update/e2e/testos:verity_rootfs_x",
         "//metropolis/node/core/update/e2e/testos:kernel_efi_x",
@@ -14,6 +15,16 @@
         "//metropolis/node/core/update/e2e/testos:testos_bundle_y",
         "//metropolis/node/core/update/e2e/testos:testos_bundle_z",
     ],
+    x_defs = {
+        "xBundleYPath": "$(rlocationpath //metropolis/node/core/update/e2e/testos:testos_bundle_y )",
+        "xBundleZPath": "$(rlocationpath //metropolis/node/core/update/e2e/testos:testos_bundle_z )",
+        "xOvmfVarsPath": "$(rlocationpath //third_party/edk2:OVMF_VARS.fd )",
+        "xOvmfCodePath": "$(rlocationpath //third_party/edk2:OVMF_CODE.fd )",
+        "xBootPath": "$(rlocationpath //metropolis/node/core/update/e2e/testos:kernel_efi_x )",
+        "xSystemXPath": "$(rlocationpath //metropolis/node/core/update/e2e/testos:verity_rootfs_x )",
+        # TODO(tim): Hardcoded because of https://github.com/monogon-dev/monogon/issues/316
+        "xAbloaderPath": "_main/metropolis/node/core/abloader/abloader_bin.efi",
+    },
     deps = [
         "//metropolis/node/build/mkimage/osimage",
         "//osbase/blkio",
diff --git a/metropolis/node/core/update/e2e/e2e_test.go b/metropolis/node/core/update/e2e/e2e_test.go
index a62c3d7..3df42e2 100644
--- a/metropolis/node/core/update/e2e/e2e_test.go
+++ b/metropolis/node/core/update/e2e/e2e_test.go
@@ -23,6 +23,33 @@
 	"source.monogon.dev/osbase/blockdev"
 )
 
+var (
+	// These are filled by bazel at linking time with the canonical path of
+	// their corresponding file. Inside the init function we resolve it
+	// with the rules_go runfiles package to the real path.
+	xBundleYPath  string
+	xBundleZPath  string
+	xOvmfVarsPath string
+	xOvmfCodePath string
+	xBootPath     string
+	xSystemXPath  string
+	xAbloaderPath string
+)
+
+func init() {
+	var err error
+	for _, path := range []*string{
+		&xBundleYPath, &xBundleZPath, &xOvmfVarsPath,
+		&xOvmfCodePath, &xBootPath, &xSystemXPath,
+		&xAbloaderPath,
+	} {
+		*path, err = runfiles.Rlocation(*path)
+		if err != nil {
+			panic(err)
+		}
+	}
+}
+
 const Mi = 1024 * 1024
 
 var variantRegexp = regexp.MustCompile(`TESTOS_VARIANT=([A-Z])`)
@@ -137,16 +164,8 @@
 	}
 
 	m := http.NewServeMux()
-	bundleYPath, err := runfiles.Rlocation("_main/metropolis/node/core/update/e2e/testos/testos_bundle_y.zip")
-	if err != nil {
-		t.Fatal(err)
-	}
-	b.bundlePaths["Y"] = bundleYPath
-	bundleZPath, err := runfiles.Rlocation("_main/metropolis/node/core/update/e2e/testos/testos_bundle_z.zip")
-	if err != nil {
-		t.Fatal(err)
-	}
-	b.bundlePaths["Z"] = bundleZPath
+	b.bundlePaths["Y"] = xBundleYPath
+	b.bundlePaths["Z"] = xBundleZPath
 	m.HandleFunc("/bundle.bin", func(w http.ResponseWriter, req *http.Request) {
 		b.m.Lock()
 		bundleFilePath := b.bundleFilePath
@@ -175,38 +194,18 @@
 	t.Cleanup(func() { os.Remove(rootDevPath) })
 	defer rootDisk.Close()
 
-	ovmfVarsPath, err := runfiles.Rlocation("edk2/OVMF_VARS.fd")
-	if err != nil {
-		t.Fatal(err)
-	}
-	ovmfCodePath, err := runfiles.Rlocation("edk2/OVMF_CODE.fd")
-	if err != nil {
-		t.Fatal(err)
-	}
-	bootPath, err := runfiles.Rlocation("_main/metropolis/node/core/update/e2e/testos/kernel_efi_x.efi")
-	if err != nil {
-		t.Fatal(err)
-	}
-	boot, err := blkio.NewFileReader(bootPath)
+	boot, err := blkio.NewFileReader(xBootPath)
 	if err != nil {
 		t.Fatal(err)
 	}
 	defer boot.Close()
-	systemXPath, err := runfiles.Rlocation("_main/metropolis/node/core/update/e2e/testos/verity_rootfs_x.img")
-	if err != nil {
-		t.Fatal(err)
-	}
-	system, err := os.Open(systemXPath)
+	system, err := os.Open(xSystemXPath)
 	if err != nil {
 		t.Fatal(err)
 	}
 	defer system.Close()
 
-	abloaderPath, err := runfiles.Rlocation("_main/metropolis/node/core/abloader/abloader_bin.efi")
-	if err != nil {
-		t.Fatal(err)
-	}
-	loader, err := blkio.NewFileReader(abloaderPath)
+	loader, err := blkio.NewFileReader(xAbloaderPath)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -233,7 +232,7 @@
 	}
 	defer ovmfVars.Close()
 	t.Cleanup(func() { os.Remove(ovmfVars.Name()) })
-	ovmfVarsTmpl, err := os.Open(ovmfVarsPath)
+	ovmfVarsTmpl, err := os.Open(xOvmfVarsPath)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -245,7 +244,7 @@
 	qemuArgs := []string{
 		"-machine", "q35", "-accel", "kvm", "-nographic", "-nodefaults", "-m", "1024",
 		"-cpu", "max", "-smp", "sockets=1,cpus=1,cores=2,threads=2,maxcpus=4",
-		"-drive", "if=pflash,format=raw,readonly=on,file=" + ovmfCodePath,
+		"-drive", "if=pflash,format=raw,readonly=on,file=" + xOvmfCodePath,
 		"-drive", "if=pflash,format=raw,file=" + ovmfVars.Name(),
 		"-drive", "if=virtio,format=raw,cache=unsafe,file=" + rootDevPath,
 		"-netdev", fmt.Sprintf("user,id=net0,net=10.42.0.0/24,dhcpstart=10.42.0.10,%s", blobGuestFwd),
diff --git a/metropolis/test/e2e/suites/core/BUILD.bazel b/metropolis/test/e2e/suites/core/BUILD.bazel
index e3ccc59..a8c90ad 100644
--- a/metropolis/test/e2e/suites/core/BUILD.bazel
+++ b/metropolis/test/e2e/suites/core/BUILD.bazel
@@ -4,9 +4,7 @@
     name = "core_test",
     srcs = ["run_test.go"],
     data = [
-        "//metropolis/node:image",
         "//metropolis/test/e2e:testimages_manifest",
-        "//third_party/edk2:firmware",
     ],
     tags = [
         "resources:iops:5000",
@@ -14,6 +12,9 @@
         # 2x2048 for nodes plus some extra.
         "resources:ram:4500",
     ],
+    x_defs = {
+        "xTestImagesManifestPath": "$(rlocationpath //metropolis/test/e2e:testimages_manifest )",
+    },
     deps = [
         "//metropolis/node",
         "//metropolis/node/core/rpc",
diff --git a/metropolis/test/e2e/suites/core/run_test.go b/metropolis/test/e2e/suites/core/run_test.go
index 21640a6..c8f654f 100644
--- a/metropolis/test/e2e/suites/core/run_test.go
+++ b/metropolis/test/e2e/suites/core/run_test.go
@@ -28,6 +28,25 @@
 	cpb "source.monogon.dev/metropolis/proto/common"
 )
 
+var (
+	// These are filled by bazel at linking time with the canonical path of
+	// their corresponding file. Inside the init function we resolve it
+	// with the rules_go runfiles package to the real path.
+	xTestImagesManifestPath string
+)
+
+func init() {
+	var err error
+	for _, path := range []*string{
+		&xTestImagesManifestPath,
+	} {
+		*path, err = runfiles.Rlocation(*path)
+		if err != nil {
+			panic(err)
+		}
+	}
+}
+
 const (
 	// Timeout for the global test context.
 	//
@@ -49,11 +68,7 @@
 	ctx, cancel := context.WithTimeout(context.Background(), globalTestTimeout)
 	defer cancel()
 
-	rPath, err := runfiles.Rlocation("_main/metropolis/test/e2e/testimages_manifest.prototxt")
-	if err != nil {
-		t.Fatalf("Resolving registry manifest failed: %v", err)
-	}
-	df, err := os.ReadFile(rPath)
+	df, err := os.ReadFile(xTestImagesManifestPath)
 	if err != nil {
 		t.Fatalf("Reading registry manifest failed: %v", err)
 	}
diff --git a/metropolis/test/e2e/suites/ha/BUILD.bazel b/metropolis/test/e2e/suites/ha/BUILD.bazel
index 5a2a4dd..3d9c688 100644
--- a/metropolis/test/e2e/suites/ha/BUILD.bazel
+++ b/metropolis/test/e2e/suites/ha/BUILD.bazel
@@ -4,9 +4,7 @@
     name = "ha_test",
     srcs = ["run_test.go"],
     data = [
-        "//metropolis/node:image",
         "//metropolis/test/e2e:testimages_manifest",
-        "//third_party/edk2:firmware",
     ],
     tags = [
         "resources:iops:5000",
@@ -14,6 +12,9 @@
         # 3x2048 for nodes plus some extra.
         "resources:ram:7000",
     ],
+    x_defs = {
+        "xTestImagesManifestPath": "$(rlocationpath //metropolis/test/e2e:testimages_manifest )",
+    },
     deps = [
         "//metropolis/test/launch",
         "//metropolis/test/localregistry",
diff --git a/metropolis/test/e2e/suites/ha/run_test.go b/metropolis/test/e2e/suites/ha/run_test.go
index cc02df4..f2f7cc2 100644
--- a/metropolis/test/e2e/suites/ha/run_test.go
+++ b/metropolis/test/e2e/suites/ha/run_test.go
@@ -15,6 +15,25 @@
 	"source.monogon.dev/osbase/test/launch"
 )
 
+var (
+	// These are filled by bazel at linking time with the canonical path of
+	// their corresponding file. Inside the init function we resolve it
+	// with the rules_go runfiles package to the real path.
+	xTestImagesManifestPath string
+)
+
+func init() {
+	var err error
+	for _, path := range []*string{
+		&xTestImagesManifestPath,
+	} {
+		*path, err = runfiles.Rlocation(*path)
+		if err != nil {
+			panic(err)
+		}
+	}
+}
+
 const (
 	// Timeout for the global test context.
 	//
@@ -35,11 +54,7 @@
 	ctx, cancel := context.WithTimeout(context.Background(), globalTestTimeout)
 	defer cancel()
 
-	rPath, err := runfiles.Rlocation("_main/metropolis/test/e2e/testimages_manifest.prototxt")
-	if err != nil {
-		t.Fatalf("Resolving registry manifest failed: %v", err)
-	}
-	df, err := os.ReadFile(rPath)
+	df, err := os.ReadFile(xTestImagesManifestPath)
 	if err != nil {
 		t.Fatalf("Reading registry manifest failed: %v", err)
 	}
diff --git a/metropolis/test/e2e/suites/kubernetes/BUILD.bazel b/metropolis/test/e2e/suites/kubernetes/BUILD.bazel
index 6234e94..302f3ac 100644
--- a/metropolis/test/e2e/suites/kubernetes/BUILD.bazel
+++ b/metropolis/test/e2e/suites/kubernetes/BUILD.bazel
@@ -20,9 +20,7 @@
     name = "kubernetes_test",
     srcs = ["run_test.go"],
     data = [
-        "//metropolis/node:image",
         "//metropolis/test/e2e:testimages_manifest",
-        "//third_party/edk2:firmware",
     ],
     embed = [":kubernetes"],
     tags = [
@@ -31,6 +29,9 @@
         # 2x2048 for nodes plus some extra.
         "resources:ram:4500",
     ],
+    x_defs = {
+        "xTestImagesManifestPath": "$(rlocationpath //metropolis/test/e2e:testimages_manifest )",
+    },
     deps = [
         "//metropolis/node",
         "//metropolis/test/launch",
diff --git a/metropolis/test/e2e/suites/kubernetes/run_test.go b/metropolis/test/e2e/suites/kubernetes/run_test.go
index f15fafd..3608c3c 100644
--- a/metropolis/test/e2e/suites/kubernetes/run_test.go
+++ b/metropolis/test/e2e/suites/kubernetes/run_test.go
@@ -30,6 +30,25 @@
 	common "source.monogon.dev/metropolis/node"
 )
 
+var (
+	// These are filled by bazel at linking time with the canonical path of
+	// their corresponding file. Inside the init function we resolve it
+	// with the rules_go runfiles package to the real path.
+	xTestImagesManifestPath string
+)
+
+func init() {
+	var err error
+	for _, path := range []*string{
+		&xTestImagesManifestPath,
+	} {
+		*path, err = runfiles.Rlocation(*path)
+		if err != nil {
+			panic(err)
+		}
+	}
+}
+
 const (
 	// Timeout for the global test context.
 	//
@@ -50,11 +69,7 @@
 	ctx, cancel := context.WithTimeout(context.Background(), globalTestTimeout)
 	defer cancel()
 
-	rPath, err := runfiles.Rlocation("_main/metropolis/test/e2e/testimages_manifest.prototxt")
-	if err != nil {
-		t.Fatalf("Resolving registry manifest failed: %v", err)
-	}
-	df, err := os.ReadFile(rPath)
+	df, err := os.ReadFile(xTestImagesManifestPath)
 	if err != nil {
 		t.Fatalf("Reading registry manifest failed: %v", err)
 	}
diff --git a/metropolis/test/launch/BUILD.bazel b/metropolis/test/launch/BUILD.bazel
index 1b7ee4d..d494a6b 100644
--- a/metropolis/test/launch/BUILD.bazel
+++ b/metropolis/test/launch/BUILD.bazel
@@ -5,24 +5,39 @@
     srcs = [
         "cluster.go",
         "insecure_key.go",
+        "launch.go",
         "metroctl.go",
         "prefixed_stdio.go",
         "swtpm.go",
     ],
     data = [
+        "//metropolis/cli/metroctl",
         "//metropolis/node:image",
         "//metropolis/test/nanoswitch:initramfs",
         "//metropolis/test/swtpm/certtool",
         "//metropolis/test/swtpm/swtpm_cert",
         "//osbase/test/ktest:linux-testing",
-        "//third_party/edk2:firmware",
-        "@com_github_bonzini_qboot//:qboot-bin",
+        "//third_party/edk2:OVMF_CODE.fd",
+        "//third_party/edk2:OVMF_VARS.fd",
         "@swtpm",
         "@swtpm//:swtpm_localca",
         "@swtpm//:swtpm_setup",
     ],
     importpath = "source.monogon.dev/metropolis/test/launch",
     visibility = ["//visibility:public"],
+    x_defs = {
+        "xSwtpmPath": "$(rlocationpath @swtpm )",
+        "xSwtpmSetupPath": "$(rlocationpath @swtpm//:swtpm_setup )",
+        "xSwtpmLocalCAPath": "$(rlocationpath @swtpm//:swtpm_localca )",
+        "xSwtpmCertPath": "$(rlocationpath //metropolis/test/swtpm/swtpm_cert )",
+        "xCerttoolPath": "$(rlocationpath //metropolis/test/swtpm/certtool )",
+        "xMetroctlPath": "$(rlocationpath //metropolis/cli/metroctl )",
+        "xOvmfVarsPath": "$(rlocationpath //third_party/edk2:OVMF_VARS.fd )",
+        "xOvmfCodePath": "$(rlocationpath //third_party/edk2:OVMF_CODE.fd )",
+        "xKernelPath": "$(rlocationpath //osbase/test/ktest:linux-testing )",
+        "xInitramfsPath": "$(rlocationpath //metropolis/test/nanoswitch:initramfs )",
+        "xNodeImagePath": "$(rlocationpath //metropolis/node:image )",
+    },
     deps = [
         "//go/qcow2",
         "//metropolis/cli/metroctl/core",
diff --git a/metropolis/test/launch/cli/launch-cluster/BUILD.bazel b/metropolis/test/launch/cli/launch-cluster/BUILD.bazel
index 9e48795..a952abd 100644
--- a/metropolis/test/launch/cli/launch-cluster/BUILD.bazel
+++ b/metropolis/test/launch/cli/launch-cluster/BUILD.bazel
@@ -14,9 +14,6 @@
 
 go_binary(
     name = "launch-cluster",
-    data = [
-        "//metropolis/cli/metroctl",
-    ],
     embed = [":launch-cluster_lib"],
     visibility = ["//visibility:public"],
 )
diff --git a/metropolis/test/launch/cli/launch-cluster/main.go b/metropolis/test/launch/cli/launch-cluster/main.go
index 1529396..695fcc7 100644
--- a/metropolis/test/launch/cli/launch-cluster/main.go
+++ b/metropolis/test/launch/cli/launch-cluster/main.go
@@ -20,6 +20,7 @@
 	"context"
 	"log"
 	"os"
+	"os/exec"
 	"os/signal"
 
 	metroctl "source.monogon.dev/metropolis/cli/metroctl/core"
@@ -36,10 +37,6 @@
 		log.Fatalf("LaunchCluster: %v", err)
 	}
 
-	mpath, err := mlaunch.MetroctlRunfilePath()
-	if err != nil {
-		log.Fatalf("MetroctlRunfilePath: %v", err)
-	}
 	wpath, err := cl.MakeMetroctlWrapper()
 	if err != nil {
 		log.Fatalf("MakeWrapper: %v", err)
@@ -53,8 +50,19 @@
 		log.Fatalf("Cluster has no Kubernetes controller nodes")
 	}
 
+	// If the user has metroctl in their path, use the metroctl from path as
+	// a credential plugin. Otherwise use the path to the currently-running
+	// metroctl.
+	metroctlPath := "metroctl"
+	if _, err := exec.LookPath("metroctl"); err != nil {
+		metroctlPath, err = os.Executable()
+		if err != nil {
+			log.Fatalf("Failed to create kubectl entry as metroctl is neither in PATH nor can its absolute path be determined: %v", err)
+		}
+	}
+
 	configName := "launch-cluster"
-	if err := metroctl.InstallKubeletConfig(ctx, mpath, cl.ConnectOptions(), configName, apiservers[0]); err != nil {
+	if err := metroctl.InstallKubeletConfig(ctx, metroctlPath, cl.ConnectOptions(), configName, apiservers[0]); err != nil {
 		log.Fatalf("InstallKubeletConfig: %v", err)
 	}
 
diff --git a/metropolis/test/launch/cluster.go b/metropolis/test/launch/cluster.go
index ac8a017..98ab760 100644
--- a/metropolis/test/launch/cluster.go
+++ b/metropolis/test/launch/cluster.go
@@ -26,7 +26,6 @@
 	"syscall"
 	"time"
 
-	"github.com/bazelbuild/rules_go/go/runfiles"
 	"github.com/cenkalti/backoff/v4"
 	"go.uber.org/multierr"
 	"golang.org/x/net/proxy"
@@ -140,30 +139,21 @@
 	}
 
 	// Initialize the node's storage with a prebuilt image.
-	si, err := runfiles.Rlocation("_main/metropolis/node/image.img")
-	if err != nil {
-		return nil, fmt.Errorf("while resolving a path: %w", err)
-	}
-
 	di := filepath.Join(stdp, "image.qcow2")
-	launch.Log("Cluster: generating node QCOW2 snapshot image: %s -> %s", si, di)
+	launch.Log("Cluster: generating node QCOW2 snapshot image: %s -> %s", xNodeImagePath, di)
 
 	df, err := os.Create(di)
 	if err != nil {
 		return nil, fmt.Errorf("while opening image for writing: %w", err)
 	}
 	defer df.Close()
-	if err := qcow2.Generate(df, qcow2.GenerateWithBackingFile(si)); err != nil {
+	if err := qcow2.Generate(df, qcow2.GenerateWithBackingFile(xNodeImagePath)); err != nil {
 		return nil, fmt.Errorf("while creating copy-on-write node image: %w", err)
 	}
 
 	// Initialize the OVMF firmware variables file.
-	sv, err := runfiles.Rlocation("edk2/OVMF_VARS.fd")
-	if err != nil {
-		return nil, fmt.Errorf("while resolving a path: %w", err)
-	}
-	dv := filepath.Join(stdp, filepath.Base(sv))
-	if err := copyFile(sv, dv); err != nil {
+	dv := filepath.Join(stdp, filepath.Base(xOvmfVarsPath))
+	if err := copyFile(xOvmfVarsPath, dv); err != nil {
 		return nil, fmt.Errorf("while copying firmware variables: %w", err)
 	}
 
@@ -268,18 +258,13 @@
 		options.Mac = mac
 	}
 
-	ovmfCodePath, err := runfiles.Rlocation("edk2/OVMF_CODE.fd")
-	if err != nil {
-		return err
-	}
-
 	tpmSocketPath := filepath.Join(r.sd, "tpm-socket")
 	fwVarPath := filepath.Join(r.ld, "OVMF_VARS.fd")
 	storagePath := filepath.Join(r.ld, "image.qcow2")
 	qemuArgs := []string{
 		"-machine", "q35", "-accel", "kvm", "-nographic", "-nodefaults", "-m", "2048",
 		"-cpu", "host", "-smp", "sockets=1,cpus=1,cores=2,threads=2,maxcpus=4",
-		"-drive", "if=pflash,format=raw,readonly=on,file=" + ovmfCodePath,
+		"-drive", "if=pflash,format=raw,readonly=on,file=" + xOvmfCodePath,
 		"-drive", "if=pflash,format=raw,file=" + fwVarPath,
 		"-drive", "if=virtio,format=qcow2,cache=unsafe,file=" + storagePath,
 		"-netdev", qemuNetConfig.ToOption(qemuNetType),
@@ -318,7 +303,7 @@
 
 	// Manufacture TPM if needed.
 	tpmd := filepath.Join(r.ld, "tpm")
-	err = tpmFactory.Manufacture(ctx, tpmd, &TPMPlatform{
+	err := tpmFactory.Manufacture(ctx, tpmd, &TPMPlatform{
 		Manufacturer: "Monogon",
 		Version:      "1.0",
 		Model:        "TestCluster",
@@ -328,14 +313,9 @@
 	}
 
 	// Start TPM emulator as a subprocess
-	swtpm, err := runfiles.Rlocation("swtpm/swtpm")
-	if err != nil {
-		return fmt.Errorf("could not find swtpm: %w", err)
-	}
-
 	tpmCtx, tpmCancel := context.WithCancel(options.Runtime.ctxT)
 
-	tpmEmuCmd := exec.CommandContext(tpmCtx, swtpm, "socket", "--tpm2", "--tpmstate", "dir="+tpmd, "--ctrl", "type=unixio,path="+tpmSocketPath)
+	tpmEmuCmd := exec.CommandContext(tpmCtx, xSwtpmPath, "socket", "--tpm2", "--tpmstate", "dir="+tpmd, "--ctrl", "type=unixio,path="+tpmSocketPath)
 	// Silence warnings from unsafe libtpms build (uses non-constant-time
 	// cryptographic operations).
 	tpmEmuCmd.Env = append(tpmEmuCmd.Env, "MONOGON_LIBTPMS_ACKNOWLEDGE_UNSAFE=yes")
@@ -854,18 +834,10 @@
 		} else {
 			serialPort = newPrefixedStdio(99)
 		}
-		kernelPath, err := runfiles.Rlocation("_main/osbase/test/ktest/vmlinux")
-		if err != nil {
-			launch.Fatal("Failed to resolved nanoswitch kernel: %v", err)
-		}
-		initramfsPath, err := runfiles.Rlocation("_main/metropolis/test/nanoswitch/initramfs.cpio.zst")
-		if err != nil {
-			launch.Fatal("Failed to resolved nanoswitch initramfs: %v", err)
-		}
 		if err := launch.RunMicroVM(ctxT, &launch.MicroVMOptions{
 			Name:                   "nanoswitch",
-			KernelPath:             kernelPath,
-			InitramfsPath:          initramfsPath,
+			KernelPath:             xKernelPath,
+			InitramfsPath:          xInitramfsPath,
 			ExtraNetworkInterfaces: switchPorts,
 			PortMap:                portMap,
 			GuestServiceMap:        guestSvcMap,
diff --git a/metropolis/test/launch/launch.go b/metropolis/test/launch/launch.go
new file mode 100644
index 0000000..d6c29fe
--- /dev/null
+++ b/metropolis/test/launch/launch.go
@@ -0,0 +1,37 @@
+package launch
+
+import (
+	"github.com/bazelbuild/rules_go/go/runfiles"
+)
+
+var (
+	// These are filled by bazel at linking time with the canonical path of
+	// their corresponding file. Inside the init function we resolve it
+	// with the rules_go runfiles package to the real path.
+	xSwtpmPath        string
+	xSwtpmSetupPath   string
+	xSwtpmLocalCAPath string
+	xSwtpmCertPath    string
+	xCerttoolPath     string
+	xMetroctlPath     string
+	xOvmfCodePath     string
+	xOvmfVarsPath     string
+	xKernelPath       string
+	xInitramfsPath    string
+	xNodeImagePath    string
+)
+
+func init() {
+	var err error
+	for _, path := range []*string{
+		&xSwtpmPath, &xSwtpmSetupPath, &xSwtpmLocalCAPath,
+		&xSwtpmCertPath, &xCerttoolPath, &xMetroctlPath,
+		&xOvmfCodePath, &xOvmfVarsPath, &xKernelPath,
+		&xInitramfsPath, &xNodeImagePath,
+	} {
+		*path, err = runfiles.Rlocation(*path)
+		if err != nil {
+			panic(err)
+		}
+	}
+}
diff --git a/metropolis/test/launch/metroctl.go b/metropolis/test/launch/metroctl.go
index e3196a6..711855b 100644
--- a/metropolis/test/launch/metroctl.go
+++ b/metropolis/test/launch/metroctl.go
@@ -9,25 +9,11 @@
 	"path"
 	"sort"
 
-	"github.com/bazelbuild/rules_go/go/runfiles"
 	"github.com/kballard/go-shellquote"
 
 	metroctl "source.monogon.dev/metropolis/cli/metroctl/core"
 )
 
-const metroctlRunfile = "_main/metropolis/cli/metroctl/metroctl_/metroctl"
-
-// MetroctlRunfilePath returns the absolute path to the metroctl binary available
-// if the built target depends on //metropolis/cli/metroctl. Otherwise, an error
-// is returned.
-func MetroctlRunfilePath() (string, error) {
-	path, err := runfiles.Rlocation(metroctlRunfile)
-	if err != nil {
-		return "", fmt.Errorf("//metropolis/cli/metroctl not found in runfiles, did you include it as a data dependency? error: %w", err)
-	}
-	return path, nil
-}
-
 type acceptall struct{}
 
 func (a *acceptall) Ask(ctx context.Context, _ *metroctl.ConnectOptions, _ *x509.Certificate) (bool, error) {
@@ -63,10 +49,6 @@
 // dependency of the built target) with all the required flags to connect to the
 // launched cluster.
 func (c *Cluster) MakeMetroctlWrapper() (string, error) {
-	mpath, err := MetroctlRunfilePath()
-	if err != nil {
-		return "", err
-	}
 	wpath := path.Join(c.metroctlDir, "metroctl.sh")
 
 	// Don't create wrapper if it already exists.
@@ -74,7 +56,7 @@
 		return wpath, nil
 	}
 
-	wrapper := fmt.Sprintf("#!/usr/bin/env bash\nexec %s %s \"$@\"", mpath, c.MetroctlFlags())
+	wrapper := fmt.Sprintf("#!/usr/bin/env bash\nexec %s %s \"$@\"", xMetroctlPath, c.MetroctlFlags())
 	if err := os.WriteFile(wpath, []byte(wrapper), 0555); err != nil {
 		return "", fmt.Errorf("could not write wrapper: %w", err)
 	}
diff --git a/metropolis/test/launch/swtpm.go b/metropolis/test/launch/swtpm.go
index fa5cb78..5691c86 100644
--- a/metropolis/test/launch/swtpm.go
+++ b/metropolis/test/launch/swtpm.go
@@ -10,8 +10,6 @@
 	"sort"
 	"strings"
 
-	"github.com/bazelbuild/rules_go/go/runfiles"
-
 	"source.monogon.dev/osbase/test/launch"
 )
 
@@ -111,29 +109,6 @@
 		launch.Log("Skipping manufacturing TPM for %s, already exists", path)
 		return nil
 	}
-
-	// Find all tools.
-	swtpm, err := runfiles.Rlocation("swtpm/swtpm")
-	if err != nil {
-		return fmt.Errorf("could not find swtpm: %w", err)
-	}
-	swtpmSetup, err := runfiles.Rlocation("swtpm/swtpm_setup")
-	if err != nil {
-		return fmt.Errorf("could not find swtpm_setup: %w", err)
-	}
-	swtpmLocalca, err := runfiles.Rlocation("swtpm/swtpm_localca")
-	if err != nil {
-		return fmt.Errorf("could not find swtpm_localca: %w", err)
-	}
-	swtpmCert, err := runfiles.Rlocation("_main/metropolis/test/swtpm/swtpm_cert/swtpm_cert_/swtpm_cert")
-	if err != nil {
-		return fmt.Errorf("could not find swtpm_cert: %w", err)
-	}
-	certtool, err := runfiles.Rlocation("_main/metropolis/test/swtpm/certtool/certtool_/certtool")
-	if err != nil {
-		return fmt.Errorf("could not find certtool: %w", err)
-	}
-
 	// Prepare swtpm-localca.options.
 	options := []string{
 		"--platform-manufacturer " + platform.Manufacturer,
@@ -141,14 +116,14 @@
 		"--platform-model " + platform.Model,
 		"",
 	}
-	err = os.WriteFile(f.localCAOptionsPath(), []byte(strings.Join(options, "\n")), 0600)
+	err := os.WriteFile(f.localCAOptionsPath(), []byte(strings.Join(options, "\n")), 0600)
 	if err != nil {
 		return fmt.Errorf("could not write local options: %w", err)
 	}
 
 	// Prepare swptm.conf.
 	err = writeSWTPMConfig(f.swtpmConfPath(), map[string]string{
-		"create_certs_tool":         swtpmLocalca,
+		"create_certs_tool":         xSwtpmLocalCAPath,
 		"create_certs_tool_config":  f.localCAConfPath(),
 		"create_certs_tool_options": f.localCAOptionsPath(),
 	})
@@ -159,8 +134,8 @@
 	if err := os.MkdirAll(path, 0700); err != nil {
 		return fmt.Errorf("could not make output path: %w", err)
 	}
-	cmd := exec.CommandContext(ctx, swtpmSetup,
-		"--tpm", fmt.Sprintf("%s socket", swtpm),
+	cmd := exec.CommandContext(ctx, xSwtpmSetupPath,
+		"--tpm", fmt.Sprintf("%s socket", xSwtpmPath),
 		"--tpmstate", path,
 		"--create-ek-cert",
 		"--create-platform-cert",
@@ -169,7 +144,7 @@
 		"--display",
 		"--pcr-banks", "sha1,sha256,sha384,sha512",
 		"--config", f.swtpmConfPath())
-	cmd.Env = append(cmd.Env, fmt.Sprintf("PATH=%s:%s", filepath.Dir(swtpmCert), filepath.Dir(certtool)))
+	cmd.Env = append(cmd.Env, fmt.Sprintf("PATH=%s:%s", filepath.Dir(xSwtpmCertPath), filepath.Dir(xCerttoolPath)))
 	cmd.Env = append(cmd.Env, "MONOGON_LIBTPMS_ACKNOWLEDGE_UNSAFE=yes")
 	if out, err := cmd.CombinedOutput(); err != nil {
 		log.Printf("Manufacturing TPM for %s failed: swtm_setup: %s", path, out)
diff --git a/metropolis/vm/smoketest/BUILD.bazel b/metropolis/vm/smoketest/BUILD.bazel
index 3c01a4a..7d075d8 100644
--- a/metropolis/vm/smoketest/BUILD.bazel
+++ b/metropolis/vm/smoketest/BUILD.bazel
@@ -4,8 +4,14 @@
 go_library(
     name = "smoketest_lib",
     srcs = ["main.go"],
+    data = [
+        "@qemu//:qemu-x86_64-softmmu",
+    ],
     importpath = "source.monogon.dev/metropolis/vm/smoketest",
     visibility = ["//visibility:private"],
+    x_defs = {
+        "xQemuPath": "$(rlocationpath @qemu//:qemu-x86_64-softmmu )",
+    },
     deps = ["@io_bazel_rules_go//go/runfiles:go_default_library"],
 )
 
@@ -21,11 +27,6 @@
 
 go_binary(
     name = "smoketest",
-    data = [
-        ":initramfs",
-        "//osbase/test/ktest:linux-testing",
-        "@qemu//:qemu-x86_64-softmmu",
-    ],
     embed = [":smoketest_lib"],
     pure = "on",
     visibility = ["//visibility:private"],
diff --git a/metropolis/vm/smoketest/main.go b/metropolis/vm/smoketest/main.go
index 3f0b8ca..58f1a22 100644
--- a/metropolis/vm/smoketest/main.go
+++ b/metropolis/vm/smoketest/main.go
@@ -25,10 +25,30 @@
 	"net"
 	"os"
 	"os/exec"
+	"path/filepath"
 
 	"github.com/bazelbuild/rules_go/go/runfiles"
 )
 
+var (
+	// These are filled by bazel at linking time with the canonical path of
+	// their corresponding file. Inside the init function we resolve it
+	// with the rules_go runfiles package to the real path.
+	xQemuPath string
+)
+
+func init() {
+	var err error
+	for _, path := range []*string{
+		&xQemuPath,
+	} {
+		*path, err = runfiles.Rlocation(*path)
+		if err != nil {
+			panic(err)
+		}
+	}
+}
+
 func main() {
 	testSocket, err := net.Listen("unix", "@metropolis/vm/smoketest")
 	if err != nil {
@@ -49,17 +69,9 @@
 		}
 	}()
 
-	qemuPath, err := runfiles.Rlocation("qemu/qemu-x86_64-softmmu")
-	if err != nil {
-		panic(err)
-	}
-
 	// TODO(lorenz): This explicitly doesn't use our own qboot because it cannot be built in a musl configuration.
 	// This will be fixed once we have a proper multi-target toolchain.
-	biosPath, err := runfiles.Rlocation("qemu/pc-bios/qboot.rom")
-	if err != nil {
-		panic(err)
-	}
+	biosPath := filepath.Join(filepath.Dir(xQemuPath), "pc-bios/qboot.rom")
 
 	baseArgs := []string{"-nodefaults", "-no-user-config", "-nographic", "-no-reboot",
 		"-accel", "kvm", "-cpu", "host",
@@ -74,7 +86,7 @@
 		"-chardev", "socket,id=test,path=metropolis/vm/smoketest,abstract=on",
 		"-device", "virtserialport,chardev=test",
 	}
-	qemuCmd := exec.Command(qemuPath, baseArgs...)
+	qemuCmd := exec.Command(xQemuPath, baseArgs...)
 	qemuCmd.Stdout = os.Stdout
 	qemuCmd.Stderr = os.Stderr
 	if err := qemuCmd.Run(); err != nil {
diff --git a/osbase/fat32/BUILD.bazel b/osbase/fat32/BUILD.bazel
index d9aec3d..80a6e03 100644
--- a/osbase/fat32/BUILD.bazel
+++ b/osbase/fat32/BUILD.bazel
@@ -20,8 +20,13 @@
         "linux_test.go",
         "structs_test.go",
     ],
-    data = ["@com_github_dosfstools_dosfstools//:fsck"],
+    data = [
+        "@com_github_dosfstools_dosfstools//:fsck",
+    ],
     embed = [":fat32"],
+    x_defs = {
+        "xFsckPath": "$(rlocationpath @com_github_dosfstools_dosfstools//:fsck )",
+    },
     deps = [
         "@com_github_stretchr_testify//assert",
         "@com_github_stretchr_testify//require",
diff --git a/osbase/fat32/fsck_test.go b/osbase/fat32/fsck_test.go
index 27de542..dde3978 100644
--- a/osbase/fat32/fsck_test.go
+++ b/osbase/fat32/fsck_test.go
@@ -11,12 +11,31 @@
 	"github.com/bazelbuild/rules_go/go/runfiles"
 )
 
+var (
+	// These are filled by bazel at linking time with the canonical path of
+	// their corresponding file. Inside the init function we resolve it
+	// with the rules_go runfiles package to the real path.
+	xFsckPath string
+)
+
+func init() {
+	if os.Getenv("IN_KTEST") == "true" {
+		return
+	}
+
+	var err error
+	for _, path := range []*string{
+		&xFsckPath,
+	} {
+		*path, err = runfiles.Rlocation(*path)
+		if err != nil {
+			panic(err)
+		}
+	}
+}
+
 func testWithFsck(t *testing.T, rootInode Inode, opts Options) {
 	t.Helper()
-	fsckPath, err := runfiles.Rlocation("com_github_dosfstools_dosfstools/fsck")
-	if err != nil {
-		t.Fatalf("unable to get path to fsck: %v", err)
-	}
 	testFile, err := os.CreateTemp("", "fat32-fsck-test")
 	if err != nil {
 		t.Fatal(err)
@@ -29,7 +48,7 @@
 	// as well as perform deep verification (-V)
 	// If the file system is OK (i.e. fsck does not want to fix it) it returns
 	// 0, otherwise 1.
-	fsckCmd := exec.Command(fsckPath, "-n", "-S", "-V", testFile.Name())
+	fsckCmd := exec.Command(xFsckPath, "-n", "-S", "-V", testFile.Name())
 	result, err := fsckCmd.CombinedOutput()
 	if err != nil {
 		t.Errorf("fsck failed: %v", string(result))
diff --git a/third_party/edk2/BUILD.bazel b/third_party/edk2/BUILD.bazel
index 8511359..8809591 100644
--- a/third_party/edk2/BUILD.bazel
+++ b/third_party/edk2/BUILD.bazel
@@ -3,3 +3,15 @@
     actual = "@edk2//:firmware",
     visibility = ["//visibility:public"],
 )
+
+alias(
+    name = "OVMF_CODE.fd",
+    actual = "@edk2//:OVMF_CODE.fd",
+    visibility = ["//visibility:public"],
+)
+
+alias(
+    name = "OVMF_VARS.fd",
+    actual = "@edk2//:OVMF_VARS.fd",
+    visibility = ["//visibility:public"],
+)