The One Line Where Bazel Hands Android Kernel Builds to Kbuild

Bazel logo
Table of Content
  • LOADING HEADINGS...

Kleaf runs the main Make/Kbuild build inside one Bazel action. The detailed compiler commands remain nested inside that action. This creates a candidate process-level integration boundary that still requires kernel-scale validation.

The practical problem

Bazel remote execution distributes Bazel actions. Native Bazel C++ rules often expose one compile action per translation unit, giving the scheduler many independent units of work. Kleaf’s main kernel build is coarser: Bazel schedules the enclosing KernelBuild action, while Make and Kbuild perform a larger compilation fan-out inside the environment executing that action.

The practical consequence is specific: adding Bazel remote workers does not automatically create more independently schedulable kernel compilation actions. Other Kleaf actions can still run independently, and a Bazel cache hit can reuse the enclosing action; the limitation applies to commands nested inside the main KernelBuild action.

The one-line Bazel-to-Kbuild handoff

In the pinned Kleaf revision examined here, the main build is registered as a Bazel action named KernelBuild:

ctx.actions.run_shell(

    mnemonic = “KernelBuild”,

    …

    command = command,

)

The command executed by that action contains the handoff:

# Actual kernel build

make -C ${KERNEL_DIR} ${TOOL_ARGS} O=${OUT_DIR} {make_goals}

Bazel analyzes the rule and registers the outer action. When the action runs, its shell command launches GNU Make. Make and Kbuild then determine and launch the compilation, assembly, archive, link, BTF, and module-related commands required by the selected configuration.

ers, “must Bazel re-evaluate this graph node?” The local action cache lives under the output base and answers, “must this command run again on this machine?” The disk or remote cache answers, “has anyone already produced an equivalent result?”

Figure 1. Bazel schedules one KernelBuild action. Make/Kbuild launches the child commands inside it. This boundary creates a candidate point for process-level evaluation.

Kleaf creates other targets and actions for configuration, modules, ABI work, signing, packaging, and distribution. The boundary discussed here is narrower: the main Make/Kbuild invocation is one coarse KernelBuild action.

What Bazel schedules independently

A Bazel target may produce several actions, and one action may launch several child processes. Those child processes remain part of the enclosing Bazel action. In the Kleaf path examined here, Bazel can schedule the main KernelBuild action on one execution worker, and Make can use the resources available there, but the compiler commands remain part of the enclosing action.

Inside that action, Kbuild reads .config, applies architecture and feature rules, selects built-in objects and modules, and drives generated files, compilation, archives, links, BTF, and module tooling. It also records commands and dependencies in .cmd files for its own incremental logic.

There are therefore three distinct reuse layers: Bazel may reuse the whole outer action; a persistent local OUT_DIR may preserve Kbuild’s internal state; and a process-level system may separately evaluate qualified commands. These mechanisms are complementary, but they have different correctness and invalidation boundaries.

What the controlled experiment established

We reproduced the structural pattern using one generated C source built in two ways. The small experiment ran on an Islo-hosted Linux machine. Islo, developed by Incredibuild and available at islo.dev, provided the execution layer for the Bazel, Make, and tracing commands. The native version used Bazel C++ rules, and aquery reported a CppCompile action. The wrapped version used one genrule that launched Make; aquery reported the outer Genrule, while strace observed Make launching the compiler beneath it.

Native:

bazel aquery ‘mnemonic(“CppCompile”, //native:app)’

Wrapped:

bazel aquery //wrapped:build

strace -f -e execve -o processes.log bazel build //wrapped:build

The result established the action/process distinction: native Bazel compilation exposed a compile action, while the Make-wrapped build exposed one outer Bazel action with a child compiler process.

The experiment used a controlled sandbox with a small C build. Its scope covered action and process structure. Remote distribution, cache reuse, artifact correctness, and performance remain untested.

Where Incredibuild may fit

Incredibuild operates at the process layer. Its Linux configuration model can classify an orchestration process as intercepted, qualified child commands as allow_remote, and unsupported or stateful commands as local_only. Structurally, that model matches the process tree beneath KernelBuild: observe Make, classify its child commands, and apply remote execution only where the command is qualified.

Distribution and cache reuse must be treated separately. A qualified compiler command may be a remote-execution candidate. Cache reuse requires a narrower, operation-specific determination that the relevant inputs and execution conditions are represented and that the output is safe to reuse. Public Incredibuild documentation scopes Build Cache support for Clang/GCC-family tools to compilation. Linking remains outside that documented cache scope.

A conservative initial scope is therefore C/C++ compiler invocations, with automatic local fallback. Rust, assembly, archives, link/LTO, BTF, signing, packaging, and other commands should remain local until their exact command patterns and side effects are separately qualified.

The source and process model support a plausible integration point. A production Kleaf integration still requires validation. If Bazel executes KernelBuild on a remote worker, the process-level agent and its required filesystem and network behavior must exist and be permitted on that worker—or the action must use a compatible execution strategy. Execution topology must be validated explicitly.

How to evaluate this in your environment

Teams evaluating process-level acceleration for Kleaf should start with a narrow, reproducible test and expand only after each command class meets correctness and performance requirements:

  1. Pin the Android Common Kernel manifest, kernel and Kleaf revisions, Bazel version, JDK, LLVM toolchain, configuration, machine image, and execution topology.
  2. Trace the real KernelBuild process tree and classify each command for interception, remote execution, cache eligibility, local fallback, inputs, outputs, path sensitivity, determinism, and side effects.
  3. Start with qualified C/C++ compiler invocations and automatic local fallback. Keep Rust, assembly, archives, link/LTO, BTF, signing, packaging, and other commands local until separately qualified.
  4. Validate files, symlinks, toolchains, environment variables, response files, generated headers, temporary outputs, permissions, and GNU Make jobserver behavior across local and remote execution.
  5. Compare stock and accelerated outputs using hashes where byte identity is expected, supported by kernel/module symbols, ABI reports, package contents, boot behavior, module loading, and relevant test suites.
  6. Measure stock local Kleaf, the existing Bazel cache/RBE configuration, process distribution, and qualified cache reuse across clean and incremental scenarios. Report total wall time, transfer and materialization costs, fallback rate, cache behavior, and run-to-run variance.

This sequence gives teams a practical adoption path: qualify one command class at a time, preserve local fallback, and use artifact correctness and end-to-end build time as the acceptance criteria.

  • Pin checksums for fetched archives so tampering or unstable upstream archives fail closed.
  • Prefer BCR modules, stable releases, and maintained rulesets over live branches.
  • Use internal mirrors for release binaries, registry metadata, and source archives.
  • Keep lockfiles current and test offline or mirror-only builds in CI.
  • Restrict action network communication where possible; do not assume sandboxing alone guarantees network isolation.
  • Operate the remote cache and CAS like production systems, with capacity planning, retention policy, monitoring, and recovery drills.
  • Measure cache hit rate, remote miss cost, bandwidth, queue time, and developer wait time instead of relying on headline benchmarks.

The practical takeaway

The Make handoff marks a clear architectural boundary: Bazel schedules the enclosing KernelBuild action, while Make/Kbuild drive the detailed work inside it. Teams exploring process-level acceleration should begin with compiler-only qualification on a pinned Android Common Kernel build, retain local fallback, and expand only after correctness and end-to-end performance are demonstrated.

Sources and code references

Yossi Eliaz

Table of Content
  • LOADING HEADINGS...

Shorten Your Builds

Incredibuild empowers your teams to be productive and focus on innovating.

Share

Related Blog Posts

Uncategorized
The fun part was free. Proving it worked cost $1.96.
[25 Aug 2026]
CI/CD tools
How to Integrate Incredibuild With GitHub Actions for up to 4x Faster Pipeline
[21 Aug 2026]
CI/CD tools, Uncategorized
How Incredibuild Integrates With Your CI/CD Pipeline (Step-by-Step)
[20 Aug 2026]

Never run anything twice