Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .github/workflows/build_release.sh
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,24 @@ fi

bazel build "${bazel_flags[@]}" -c opt \
//cmd/darwin-actiond:darwin-actiond_macos_arm64 \
//cmd/linux-actiond:linux-actiond_linux_arm64 \
//cmd/linux-actiond:linux-actiond_linux_x86_64 \
//cmd/windows-actiond:windows-actiond_windows_arm64 \
//cmd/windows-actiond:windows-actiond_windows_x86_64

cp -f \
bazel-bin/cmd/darwin-actiond/darwin-actiond_macos_arm64/darwin-actiond_macos_arm64 \
bazel-bin/cmd/linux-actiond/linux-actiond_linux_arm64/linux-actiond_linux_arm64 \
bazel-bin/cmd/linux-actiond/linux-actiond_linux_x86_64/linux-actiond_linux_x86_64 \
bazel-bin/cmd/windows-actiond/windows-actiond_windows_arm64/windows-actiond_windows_arm64.exe \
bazel-bin/cmd/windows-actiond/windows-actiond_windows_x86_64/windows-actiond_windows_x86_64.exe \
"${artifact_dir}/"

cd "${artifact_dir}"
shasum -a 256 \
darwin-actiond_macos_arm64 \
linux-actiond_linux_arm64 \
linux-actiond_linux_x86_64 \
windows-actiond_windows_arm64.exe \
windows-actiond_windows_x86_64.exe \
> SHA256.txt
63 changes: 63 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ on:
- main
workflow_dispatch:

concurrency:
group: build-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true

jobs:
release-binaries:
name: Release binaries
Expand All @@ -24,6 +28,65 @@ jobs:
BUILDBUDDY_API_KEY: ${{ secrets.BUILDBUDDY_API_KEY }}
run: .github/workflows/build_release.sh release

linux-qemu-llvm-smoke:
name: Linux QEMU ${{ matrix.mode }} (${{ matrix.architecture }})
strategy:
fail-fast: false
matrix:
include:
- architecture: arm64
mode: build-only
runner: ubuntu-24.04-arm
- architecture: x86_64
mode: llvm-smoke
runner: ubuntu-24.04
runs-on: ${{ matrix.runner }}
timeout-minutes: 240

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Enable KVM and vhost-vsock
if: matrix.mode == 'llvm-smoke'
run: |
test -c /dev/kvm
sudo chmod a+rw /dev/kvm
if [[ ! -e /dev/vhost-vsock ]]; then
sudo modprobe vhost_vsock
fi
test -c /dev/vhost-vsock
sudo chmod a+rw /dev/vhost-vsock

- name: Build Linux ARM64 release
if: matrix.mode == 'build-only'
env:
BUILDBUDDY_API_KEY: ${{ secrets.BUILDBUDDY_API_KEY }}
run: |
bazel build --config=remote \
--remote_header="x-buildbuddy-api-key=$BUILDBUDDY_API_KEY" \
-c opt \
//cmd/linux-actiond:linux-actiond_linux_arm64

- name: Compare actiond and Linux host
if: matrix.mode == 'llvm-smoke'
env:
BUILDBUDDY_API_KEY: ${{ secrets.BUILDBUDDY_API_KEY }}
ACTIOND_REVISION: ${{ github.event.pull_request.head.sha || github.sha }}
ACTIOND_VM_CAS_IMAGE_SIZE_MIB: 8192
ACTIOND_VM_MEMORY_MIB: 4096
run: e2e/run_llvm_linux_vm_smoke.sh "$RUNNER_TEMP/actiond-linux-llvm-${{ matrix.architecture }}"

- name: Upload Linux LLVM smoke results
if: always() && matrix.mode == 'llvm-smoke'
uses: actions/upload-artifact@v4
with:
name: linux-qemu-${{ matrix.mode }}-${{ matrix.architecture }}
if-no-files-found: warn
path: |
${{ runner.temp }}/actiond-linux-llvm-${{ matrix.architecture }}/*.md
${{ runner.temp }}/actiond-linux-llvm-${{ matrix.architecture }}/*.log

windows-llvm-smoke:
name: Windows ${{ matrix.mode }} (${{ matrix.architecture }})
strategy:
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ jobs:
fi
gh release upload "${TAG_NAME}" \
release/darwin-actiond_macos_arm64 \
release/linux-actiond_linux_arm64 \
release/linux-actiond_linux_x86_64 \
release/windows-actiond_windows_arm64.exe \
release/windows-actiond_windows_x86_64.exe \
release/SHA256.txt \
Expand Down
25 changes: 19 additions & 6 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
# Architecture

`actiond` is a local Remote Execution API worker and cache for Bazel. Its main
mode is `darwin-actiond serve-vm` or `windows-actiond serve-vm`: the host
process owns the public gRPC listener, starts a small Linux VM, and forwards
REAPI traffic into a Linux guest over virtio-vsock or Hyper-V sockets.
mode is `darwin-actiond serve-vm`, `windows-actiond serve-vm`, or
`linux-actiond serve-vm`: the host process owns the public gRPC listener,
starts a small Linux VM, and forwards REAPI traffic into a Linux guest over
virtio-vsock or Hyper-V sockets.

The design centers on three ideas:

Expand All @@ -18,7 +19,7 @@ Bazel
|
| gRPC / REAPI
v
darwin-actiond / windows-actiond
darwin-actiond / windows-actiond / linux-actiond
|
| TCP-to-virtio-vsock / TCP-to-AF_HYPERV bridge
v
Expand All @@ -32,7 +33,9 @@ guest ext4 disk mounted at /cas
On Windows, `windows-actiond` uses Host Compute System `LinuxKernelDirect`,
Hyper-V synthetic SCSI, and `AF_HYPERV`. Guest AF_VSOCK port 5001 maps to the
standard Hyper-V socket service GUID template. The Windows guest matches the
ARM64 or x86_64 host architecture; the macOS guest is ARM64.
ARM64 or x86_64 host architecture; the macOS guest is ARM64. On Linux,
`linux-actiond` uses QEMU `virt` on ARM64 and QEMU `microvm` on x86_64. Both
QEMU machines use virtio-mmio block devices and `vhost-vsock-device`.

In VM mode, the host does not keep a second CAS mirror. Uploads, downloads,
ActionCache requests, and Execute requests are forwarded to the guest. The
Expand All @@ -52,6 +55,16 @@ the matching Linux kernel, initramfs, and runtime SquashFS. At startup it
materializes those bytes under `--root`, wraps the runtime and CAS as fixed VHD
files, and starts the VM with Host Compute System.

`linux-actiond` is released for ARM64 and x86_64. Zig `@embedFile` includes the
matching Linux kernel, initramfs, runtime SquashFS, and QEMU executable selected
by the `rules_qemu` target toolchain. The x86_64 release also includes
`qboot.rom`; QEMU `virt` direct kernel boot on ARM64 does not require firmware.
`linux-actiond` creates sealed memfds for QEMU and every immutable embedded VM
artifact, then executes QEMU with `execveat`. Only the persistent guest-owned
CAS image is stored under `--root`. QEMU uses KVM and host CPU passthrough. The
persistent CAS image uses `cache=none`, `aio=io_uring`, one IOThread, and one
virtio-blk queue per vCPU up to four queues.

`linux-actiond-guest` lives in the initramfs. It runs as guest init, mounts the
minimal guest filesystems, mounts `/cas` and `/runtimes`, then execs itself as
the guest REAPI worker.
Expand All @@ -65,7 +78,7 @@ The VM is intentionally small:
- writable virtio or Hyper-V synthetic SCSI block device for `/cas`
- read-only virtio or Hyper-V synthetic SCSI block device for `/runtimes`
- virtio-vsock or `AF_HYPERV` for gRPC
- serial stderr for logs on macOS
- serial host logs on macOS and Linux
- no guest network device, SSH, systemd, package manager, graphics, or login

The VM is long-lived. Each action gets its own Linux process sandbox inside the
Expand Down
15 changes: 15 additions & 0 deletions MODULE.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ bazel_dep(name = "squashfs-tools", version = "4.7.5")
bazel_dep(name = "zstd", version = "1.5.7.bcr.1")
bazel_dep(name = "codesign.bzl", version = "0.0.13")
bazel_dep(name = "gawk", version = "5.3.2.bcr.3")
bazel_dep(name = "rules_qemu", version = "0.3.0")

http_archive = use_repo_rule("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")

Expand Down Expand Up @@ -56,6 +57,20 @@ register_toolchains("@zig_toolchains//:all")
register_toolchains("@llvm//toolchain:all")
register_toolchains("@codesign.bzl//toolchain:all")

qemu = use_extension("@rules_qemu//qemu/extension:qemu.bzl", "qemu")
qemu.system_toolchain(
system_target = "aarch64-softmmu",
target_settings = ["//platforms:qemu_system_aarch64"],
)
qemu.system_toolchain(
machine = "microvm,acpi=off,pcie=off,x-option-roms=off,rtc=off",
system_target = "x86_64-softmmu",
target_settings = ["//platforms:qemu_system_x86_64"],
)
use_repo(qemu, "qemu_system_toolchains", "qemu_user_toolchains")

register_toolchains("@qemu_system_toolchains//:all")

osx = use_extension("@llvm//extensions:osx.bzl", "osx")
osx.frameworks(names = [
"CoreFoundation",
Expand Down
7 changes: 6 additions & 1 deletion MODULE.bazel.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

30 changes: 24 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
# actiond

`actiond` is a local Remote Execution API worker and cache for Bazel. On macOS
and Windows it starts a small Linux VM and runs Bazel actions inside that VM,
so the host can act like a local Linux remote-execution worker.
`actiond` is a local Remote Execution API worker and cache for Bazel. On macOS,
Windows, and Linux it starts a small Linux VM and runs Bazel actions inside
that VM, so the host can act like a local Linux remote-execution worker.

The macOS ARM64 and Windows ARM64/x86_64 releases include the matching VM
kernel, initramfs, and Linux runtime image.
The macOS ARM64, Windows ARM64/x86_64, and Linux ARM64/x86_64 releases include
the matching VM kernel, initramfs, and Linux runtime image.

## Why Use It?

Expand Down Expand Up @@ -61,6 +61,22 @@ Windows requires Hyper-V. `windows-actiond` wraps the runtime SquashFS and
guest-owned ext4 CAS in fixed VHD files. The default VHD paths are under
`--root`; `--cas-image` can select another CAS VHD path.

The Linux releases embed the matching QEMU executable from `rules_qemu`. The
x86_64 release also embeds `qboot.rom`. `linux-actiond` passes QEMU, the Linux
kernel, the initramfs, the runtime SquashFS, and `qboot.rom` through sealed
memfds. The guest-owned CAS remains a persistent ext4 file under `--root`. The
QEMU process uses KVM, host CPU passthrough, and `io_uring` for the CAS file.
The host kernel must support `io_uring`. `linux-actiond` requires read/write
access to `/dev/kvm` and `/dev/vhost-vsock`:

```bash
test -r /dev/kvm -a -w /dev/kvm
sudo modprobe vhost_vsock
./linux-actiond_linux_x86_64 serve-vm \
--listen=127.0.0.1:8980 \
--root="$HOME/.cache/actiond/vm"
```

## Point Bazel At actiond

Add a config like this to your workspace `.bazelrc`:
Expand Down Expand Up @@ -121,6 +137,8 @@ Most users should use releases. Source builds are mainly for development:

```bash
bazel build --config=remote -c opt //cmd/darwin-actiond
bazel build --config=remote -c opt //cmd/linux-actiond:linux-actiond_linux_arm64
bazel build --config=remote -c opt //cmd/linux-actiond:linux-actiond_linux_x86_64
```

Normal contributor checks:
Expand All @@ -130,7 +148,7 @@ bazel build --config=remote //...
bazel test --config=remote //...
```

The macOS VM e2e harness is:
The macOS and Linux VM e2e harness is:

```bash
tools/e2e.sh vm
Expand Down
53 changes: 53 additions & 0 deletions cmd/linux-actiond/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
load("@bazel_lib//lib:transitions.bzl", "platform_transition_binary")
load("@rules_zig//zig:defs.bzl", "zig_binary")
load("//tools:zig_embedded_assets.bzl", "zig_embedded_assets")
load("//tools:zig_embedded_qemu.bzl", "zig_embedded_qemu")

zig_binary(
name = "linux-actiond",
main = "main.zig",
strip_debug_symbols = True,
target_compatible_with = ["@platforms//os:linux"],
visibility = ["//visibility:public"],
deps = [
":embedded_assets",
":embedded_qemu",
"//src:actiond",
],
)

alias(
name = "embedded_initramfs",
actual = select({
"@platforms//cpu:aarch64": "//vm:initramfs_aarch64",
"@platforms//cpu:x86_64": "//vm:initramfs_x86_64",
}),
)

zig_embedded_assets(
name = "embedded_assets",
initramfs = ":embedded_initramfs",
kernel = "//vm:linux_kernel.image",
runtime_image = "//runtimes:runtimes_squashfs",
)

zig_embedded_qemu(
name = "embedded_qemu",
target_compatible_with = ["@platforms//os:linux"],
)

platform_transition_binary(
name = "linux-actiond_linux_arm64",
basename = "linux-actiond_linux_arm64",
binary = ":linux-actiond",
target_platform = "//platforms:linux_aarch64_musl",
visibility = ["//visibility:public"],
)

platform_transition_binary(
name = "linux-actiond_linux_x86_64",
basename = "linux-actiond_linux_x86_64",
binary = ":linux-actiond",
target_platform = "//platforms:linux_x86_64_musl",
visibility = ["//visibility:public"],
)
39 changes: 39 additions & 0 deletions cmd/linux-actiond/main.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
const std = @import("std");
const Io = std.Io;
const actiond = @import("actiond");
const embedded_assets = @import("actiond_embedded_assets");
const embedded_qemu = @import("actiond_embedded_qemu");

pub fn main(init: std.process.Init) !void {
const io = init.io;
const arena = init.arena.allocator();
const args = try init.minimal.args.toSlice(arena);

if (args.len > 1 and std.mem.eql(u8, args[1], actiond.qemu_vm.fexec_argument)) {
if (args.len < 3) return error.MissingQemuArguments;
try actiond.qemu_vm.fexecEmbedded(
io,
arena,
init.minimal.environ,
embedded_assets,
embedded_qemu,
args[2..],
);
}

if (args.len > 1 and std.mem.eql(u8, args[1], "serve-vm")) {
const options = try actiond.vm_host.parseServeVmArgs(args[2..]);
return actiond.linux_vm_host.serve(io, std.heap.smp_allocator, options, embedded_qemu);
}

var stdout_buffer: [256]u8 = undefined;
var stdout_writer = Io.File.stdout().writer(io, &stdout_buffer);
const stdout = &stdout_writer.interface;
try stdout.print(
\\linux-actiond zig={s} bazel={s}
\\usage:
\\ linux-actiond serve-vm [--kernel=/path/kernel] [--initramfs=/path/initramfs.cpio[.zst]] [--runtime-image=/path/runtimes.sqfs] [--cas-image=/path/cas.ext4] [--listen=127.0.0.1:8980] [--root=/tmp/actiond-vm] [--actiondfs-stats-path=/path/stats.txt]
\\
, .{ actiond.version.zig, actiond.version.bazel });
try stdout.flush();
}
Loading
Loading