Bazel Build System

Last updated on Dec 18, 2025 (Commit: 9ef596e)

Overview

Relevant Files
  • src/main/java/com/google/devtools/build/lib/bazel/Bazel.java
  • src/main/java/com/google/devtools/build/lib/runtime/BlazeRuntime.java
  • src/main/java/com/google/devtools/build/lib/runtime/BlazeModule.java
  • README.md

Bazel is a fast, scalable build system designed to handle projects of any size across multiple programming languages and platforms. The codebase implements a sophisticated client-server architecture that maintains state between builds for optimal performance.

Core Architecture

Bazel operates as a client-server system where a long-running server process stays in RAM between builds, allowing the system to maintain state and avoid redundant work. The entry point is the Bazel class, which initializes the runtime with a carefully ordered list of modules and services.

The BlazeRuntime class encapsulates the immutable configuration of a Bazel instance. It manages:

  • The file system abstraction layer
  • Module lifecycle and initialization
  • Command registration and dispatching
  • Package factory and rule class providers
  • Query environment and output formatters
  • Build event streaming and profiling infrastructure

Module System

Bazel uses a pluggable module architecture where functionality is organized into discrete BlazeModule implementations. Each module can:

  • Provide startup options and global initialization
  • Hook into the server lifecycle (startup, shutdown, command execution)
  • Register custom commands and execution strategies
  • Contribute precomputed values to the Skyframe dependency graph

Module order is critical—later modules override earlier ones for conflicting strategies. For example, the StandaloneModule is placed after WorkerModule and SandboxModule to make standalone execution the default strategy.

Execution Flow

Loading diagram...

Key Components

Modules (30+ in total) handle concerns like:

  • Profiling and metrics collection
  • Remote execution and caching
  • Sandbox and worker process management
  • Starlark debugging and repository management
  • Build event service integration

Services provide native functionality:

  • File system event monitoring
  • Platform-specific operations
  • Network statistics collection
  • Process utilities

Commands are registered by modules and dispatched by CommandDispatcher, which parses options, applies invocation policies, and executes the appropriate command implementation.

Build Execution

When a command executes, it operates within a CommandEnvironment that provides access to:

  • The workspace and package factory
  • The Skyframe graph for incremental builds
  • Event handlers for progress reporting
  • Execution strategies and caching layers

The system rebuilds only what is necessary through advanced dependency analysis and maintains both local and distributed caches for optimal performance.

Architecture & Build Phases

Relevant Files
  • src/main/java/com/google/devtools/build/lib/buildtool/BuildTool.java
  • src/main/java/com/google/devtools/build/lib/buildtool/AnalysisPhaseRunner.java
  • src/main/java/com/google/devtools/build/lib/buildtool/ExecutionTool.java
  • src/main/java/com/google/devtools/build/lib/analysis/BuildView.java

Bazel's build process is divided into distinct phases, each with specific responsibilities. Understanding this architecture is essential for working with the codebase.

Build Phases Overview

The build system executes in three main phases:

  1. Loading Phase – Parses BUILD files and resolves target patterns
  2. Analysis Phase – Constructs the action graph and validates configurations
  3. Execution Phase – Runs actions to produce build outputs

Phase Orchestration

The BuildTool class (entry point: buildTargets()) orchestrates the entire build process. It delegates to specialized runners:

  • AnalysisPhaseRunner – Handles loading and analysis
  • ExecutionTool – Manages execution and resource allocation

The build supports two execution modes:

  • Standard Mode – Sequential analysis then execution
  • Skymeld Mode – Merged analysis and execution for faster incremental builds

Analysis Phase Details

BuildView.update() performs semantic analysis:

  • Creates build configurations from command-line options
  • Constructs ConfiguredTarget graph (target + configuration pairs)
  • Generates action graph representing work to be done
  • Validates dependencies and detects cycles
  • Handles aspects and test filtering

Key outputs: AnalysisResult containing targets, actions, and artifacts.

Execution Phase Details

ExecutionTool.executeBuild() executes the action graph:

  • Prepares execution environment (symlink forest, action cache)
  • Configures resource management and spawn strategies
  • Runs actions via SkyframeBuilder
  • Tracks action cache statistics
  • Handles test execution and result reporting

Skyframe Integration

Both phases use Skyframe (incremental evaluation framework):

  • Caches intermediate results across builds
  • Detects changed files and invalidates affected nodes
  • Enables efficient incremental builds
  • Supports remote analysis caching for distributed builds
Loading diagram...

Error Handling

The build system uses DetailedExitCode and FailureDetail for structured error reporting. Errors are categorized by phase and propagated through the build with --keep_going support for partial builds.

Skyframe Incremental Evaluation

Relevant Files
  • src/main/java/com/google/devtools/build/skyframe/AbstractInMemoryMemoizingEvaluator.java
  • src/main/java/com/google/devtools/build/skyframe/InMemoryMemoizingEvaluator.java
  • src/main/java/com/google/devtools/build/skyframe/ParallelEvaluator.java
  • src/main/java/com/google/devtools/build/skyframe/DirtyBuildingState.java
  • src/main/java/com/google/devtools/build/skyframe/IncrementalInMemoryNodeEntry.java

Skyframe's incremental evaluation system enables fast rebuilds by reusing computation results from previous builds. The system tracks which nodes have changed and only re-evaluates affected nodes, while leveraging change pruning to resurrect nodes whose values remain unchanged despite dependency modifications.

Core Evaluation Flow

The evaluation process begins with the AbstractInMemoryMemoizingEvaluator, which orchestrates incremental builds:

  1. Differencing: Before evaluation, a Differencer compares the current state against the previous build, identifying changed inputs and injecting new values.
  2. Invalidation: Changed nodes are marked dirty using bottom-up invalidation, which propagates changes upward through the dependency graph.
  3. Parallel Evaluation: The ParallelEvaluator re-evaluates dirty nodes in parallel, respecting dependencies between nodes.
// Simplified evaluation flow
Version graphVersion = getNextGraphVersion();
Diff diff = differencer.getDiff(lastGraphVersion, graphVersion);
invalidate(diff.changedKeysWithoutNewValues());
injectValues(diff.changedKeysWithNewValues());
result = evaluator.eval(roots);

Dependency Checking and Change Pruning

When a node is marked dirty, the DirtyBuildingState tracks its lifecycle through several states:

  • CHECK_DEPENDENCIES: The node checks if its dependencies have changed since the last build.
  • VERIFIED_CLEAN: All dependencies are unchanged, so the node can skip re-evaluation.
  • NEEDS_REBUILDING: At least one dependency changed, requiring re-evaluation.
  • REBUILDING: The node is actively being re-evaluated.

The key optimization is change pruning: if a node is re-evaluated and produces the same value as before, its dependents are not invalidated. This is detected in IncrementalInMemoryNodeEntry.setValue():

if (dirtyBuildingState.unchangedFromLastBuild(value)) {
  // Value unchanged: preserve old version, don't propagate change
  version = NodeVersion.of(lastChanged, graphVersion);
  this.value = dirtyBuildingState.getLastBuildValue();
} else {
  // Value changed: update version and propagate to dependents
  version = graphVersion;
  this.value = value;
}

Version Tracking

Each node maintains a NodeVersion with two components:

  • lastChanged: The graph version when the node's value last changed.
  • lastEvaluated: The graph version when the node was last evaluated.

During dependency checking, if a child's lastChanged version is at most the parent's lastEvaluated version, the child is considered unchanged. This enables efficient pruning without re-evaluating the child.

Incremental State Management

The evaluator maintains state across builds:

  • keepEdges: When true, the graph stores dependency edges, enabling incremental evaluation. When false, all evaluations use Version.constant() (non-incremental).
  • lastGraphVersion: Tracks the previous build's version for differencing.
  • valuesToDirty/valuesToDelete: Queued invalidations applied before the next evaluation.

This design ensures that Bazel can skip re-evaluation of unchanged nodes while correctly propagating changes through the dependency graph, achieving both correctness and performance in incremental builds.

Starlark & Package Loading

Relevant Files
  • src/main/java/com/google/devtools/build/lib/skyframe/BzlLoadFunction.java
  • src/main/java/com/google/devtools/build/lib/skyframe/PackageFunction.java
  • src/main/java/com/google/devtools/build/lib/packages/PackageFactory.java
  • src/main/java/com/google/devtools/build/lib/packages/BazelStarlarkEnvironment.java

Bazel's loading phase executes Starlark code from .bzl (and .scl) files and BUILD files. This process is orchestrated through Skyframe and involves careful environment setup, module loading, and execution.

Overview

The loading phase has two main components:

  1. BzlLoadFunction – Loads and executes .bzl modules
  2. PackageFunction – Loads and executes BUILD files

Both use Skyframe for dependency tracking and caching. The Starlark environment is initialized by BazelStarlarkEnvironment, which manages predeclared symbols, the native module, and builtins injection.

.bzl Module Loading (BzlLoadFunction)

When a .bzl file is loaded, BzlLoadFunction performs these steps:

  1. Obtain builtins – Fetch StarlarkBuiltinsValue containing the Starlark semantics and injected symbols
  2. Compile the file – Use BzlCompileFunction to parse and compile the .bzl into a Program
  3. Resolve load statements – Extract and validate load() statements to identify dependencies
  4. Load dependencies – Recursively load all .bzl modules referenced by load() statements
  5. Build the environment – Construct the predeclared environment with symbols from BazelStarlarkEnvironment
  6. Execute the program – Run the compiled program in a StarlarkThread with the loaded modules available
  7. Return BzlLoadValue – Cache the resulting Module and transitive digest
Loading diagram...

BUILD File Execution (PackageFunction)

PackageFunction handles BUILD files similarly but with additional steps:

  1. Get builtins – Obtain StarlarkBuiltinsValue (may be inlined for performance)
  2. Compile BUILD file – Parse and compile the BUILD file into a Program
  3. Load .bzl dependencies – Resolve and load all .bzl files referenced in the BUILD file
  4. Execute BUILD file – Run the program via PackageFactory.executeBuildFile()
  5. Create Package – Construct a Package object containing all defined rules and targets

The BUILD file execution happens in PackageFactory.executeBuildFileImpl(), which creates a StarlarkThread with the predeclared environment and loaded modules, then executes the program.

Starlark Environment Setup

BazelStarlarkEnvironment manages the predeclared symbols available in different contexts:

  • BUILD filesuninjectedBuildEnv plus optional prelude bindings
  • BUILD-loaded .bzl filesuninjectedBuildBzlEnv with native module
  • MODULE-loaded .bzl filesuninjectedModuleBzlEnv with different native bindings
  • @_builtins .bzl filesbuiltinsBzlEnv for internal builtins

Builtins injection (controlled by EXPERIMENTAL_BUILTINS_BZL_PATH) allows overriding symbols via the @_builtins pseudo-repository. The final environment is computed by StarlarkBuiltinsFunction and cached in StarlarkBuiltinsValue.

Inlining Optimization

For performance, BzlLoadFunction supports “inlining” mode where .bzl loading happens without Skyframe calls. This is used by PackageFunction and StarlarkBuiltinsFunction to avoid Skyframe overhead during the loading phase. An InliningState object maintains a local cache of successfully loaded modules to ensure consistency within a single calling context.

Key Concepts

  • Module – A Starlark module representing a loaded .bzl file, containing exported symbols
  • Program – Compiled Starlark code ready for execution
  • StarlarkThread – Execution context for running Starlark code, holds thread-local state
  • Transitive Digest – Hash of a module and all its transitive dependencies, used for caching
  • Load Visibility – Restrictions on which files can load a given .bzl module

Action Execution & Strategies

Relevant Files
  • src/main/java/com/google/devtools/build/lib/skyframe/ActionExecutionFunction.java
  • src/main/java/com/google/devtools/build/lib/exec/SpawnStrategyRegistry.java
  • src/main/java/com/google/devtools/build/lib/exec/SpawnStrategyResolver.java
  • src/main/java/com/google/devtools/build/lib/worker/WorkerSpawnRunner.java
  • src/main/java/com/google/devtools/build/lib/sandbox/SandboxModule.java

Overview

Action execution in Bazel is a multi-stage process that determines whether an action needs to run, selects the appropriate execution strategy, and manages the actual spawn execution. The system balances performance through caching with flexibility through pluggable execution strategies.

Action Execution Flow

The execution pipeline starts in ActionExecutionFunction, a Skyframe function that orchestrates the entire process:

  1. Input Resolution: Gather all action inputs and their metadata
  2. Action Cache Check: Determine if outputs are already up-to-date
  3. Strategy Selection: Choose the best execution strategy for the spawn
  4. Spawn Execution: Run the action using the selected strategy
  5. Output Validation: Verify outputs and update the action cache

If the action cache returns a cache hit (null token), the action is skipped entirely. Otherwise, a non-null token indicates the action must execute.

Action Caching System

The ActionCacheChecker determines whether an action needs execution by comparing:

  • Action key: Hash of the action's command, inputs, and environment
  • Input metadata: Digests of all input files
  • Output metadata: Timestamps and digests of existing outputs

The cache stores entries keyed by the primary output path. A cache hit means all inputs and the command are unchanged, so outputs are still valid. Cache misses trigger execution and the cache is updated afterward.

Spawn Strategy Selection

SpawnStrategyRegistry maintains a registry of available execution strategies (local, worker, sandboxed, remote). During execution, SpawnStrategyResolver queries the registry to find applicable strategies for a spawn:

List<? extends SpawnStrategy> strategies =
    registry.getStrategies(spawn, eventHandler);
List<? extends SpawnStrategy> executableStrategies =
    strategies.stream()
        .filter(s -> s.canExec(spawn, context))
        .collect(Collectors.toList());

Strategies are ordered by preference. The resolver picks the first executable strategy, allowing fallback chains (e.g., remote with local fallback).

Execution Strategies

Worker Strategy (WorkerSpawnRunner): Maintains persistent worker processes that handle multiple spawns. Reduces startup overhead for tools like Java compilers.

Sandbox Strategies (SandboxModule): Isolate action execution in separate filesystem trees. Platform-specific implementations exist for Linux, macOS, Windows, and Docker. Prevents actions from interfering with each other.

Local Strategy: Direct execution on the host machine without isolation.

Remote Strategy: Executes on remote infrastructure via gRPC.

Dynamic Execution

When multiple strategies are available, Bazel can run a spawn concurrently with different strategies and use the first result. This is configured through mnemonic-specific and regex-based strategy policies in the registry.

Remote Execution & Caching

Relevant Files
  • src/main/java/com/google/devtools/build/lib/remote/RemoteExecutionService.java
  • src/main/java/com/google/devtools/build/lib/remote/RemoteSpawnRunner.java
  • src/main/java/com/google/devtools/build/lib/remote/RemoteModule.java
  • src/main/java/com/google/devtools/build/lib/remote/CombinedCache.java
  • src/main/java/com/google/devtools/build/lib/remote/RemoteExecutionCache.java

Bazel's remote execution system enables distributed build execution and caching across multiple machines. This section explains how actions are executed remotely, cached, and how results are downloaded back to the local machine.

Architecture Overview

Loading diagram...

Key Components

RemoteExecutionService orchestrates the entire remote execution workflow. It manages action building, cache lookups, input uploads, and result downloads. The service maintains cache policies that determine whether results can be read from or written to remote and disk caches.

RemoteSpawnRunner implements the SpawnRunner interface and executes individual spawns remotely. It coordinates with RemoteExecutionService to check caches, upload inputs, execute actions, and download outputs. The runner tracks detailed metrics including parse time, upload time, queue time, and execution time.

CombinedCache provides unified access to both disk and remote caches. It handles cache lookups and uploads with a two-tier strategy: check disk cache first, then fall back to remote cache. Results from remote cache are automatically written to disk cache for future hits.

Execution Flow

  1. Action Building: buildRemoteAction() creates a RemoteAction containing the command, input files, and Merkle tree digest. This step is semaphore-limited to prevent memory exhaustion during high parallelism.

  2. Cache Lookup: lookupCache() queries the action cache using the action key. If a cached result exists and the action succeeded with all outputs present, execution is skipped.

  3. Input Upload: uploadInputsIfNotPresent() uploads the Merkle tree and command to the remote cache. The system checks which digests are missing before uploading to avoid redundant transfers.

  4. Remote Execution: executeRemotely() sends an ExecuteRequest to the remote executor via gRPC. The executor returns an ExecuteResponse containing output digests and exit codes.

  5. Output Download: Results are downloaded from the remote cache and written to the local filesystem. Failed actions are treated as cache misses to avoid caching flaky tests.

Cache Policies

Cache behavior is controlled by CachePolicy objects that specify read and write permissions:

  • Read Policy: Determines if cached results can be accepted. Controlled by --remote_accept_cached flag and spawn properties.
  • Write Policy: Determines if results should be uploaded. Controlled by execution info and remoteActionCacheSupportsUpdate().

Both policies support disk and remote cache independently, enabling hybrid caching strategies.

Error Handling & Retries

The system uses RemoteRetrier to handle transient failures. Failed cache lookups trigger re-execution. If remote cache outputs are missing or corrupted, the action is retried with acceptCachedResult=false to force fresh execution.

The --experimental_remote_require_cached flag enforces that actions must be cached, returning EXECUTION_DENIED if no cache hit occurs.

Module System & Extensibility

Relevant Files
  • src/main/java/com/google/devtools/build/lib/runtime/BlazeModule.java
  • src/main/java/com/google/devtools/build/lib/runtime/ServerBuilder.java
  • src/main/java/com/google/devtools/build/lib/runtime/WorkspaceBuilder.java
  • src/main/java/com/google/devtools/build/lib/bazel/BazelRepositoryModule.java

Bazel's module system provides a plugin architecture that allows extending the build system's functionality at well-defined lifecycle points. Modules are the primary mechanism for customizing Bazel's behavior without modifying core code.

Core Concepts

BlazeModule is an abstract base class that modules extend to hook into Bazel's initialization and execution pipeline. Each module must have a no-argument constructor, with all initialization logic deferred to lifecycle methods. Modules are registered at startup and remain fixed for the server's lifetime, allowing them to maintain state across multiple builds.

The module system enforces single-responsibility constraints: exactly one module must provide the file system, at most one can provide an output service, and at most one can post build metrics. This prevents conflicts and ensures predictable behavior.

Lifecycle Hooks

// Startup phase (server initialization)
globalInit(OptionsParsingResult)           // First hook, before filesystem setup
getFileSystem(OptionsParsingResult)        // Provide filesystem implementation
blazeStartup(...)                          // After filesystem, before server init
serverInit(OptionsParsingResult, ServerBuilder)  // Configure server

// Workspace phase (per-workspace initialization)
workspaceInit(BlazeRuntime, BlazeDirectories, WorkspaceBuilder)

// Command phase (per-command execution)
beforeCommand(CommandEnvironment)
getOutputListener()                        // Console output interception
getOutputService()                         // Custom output handling
afterAnalysis(...)                         // Post-analysis hooks
executorInit(...)                          // Action execution setup
registerActionContexts(...)                // Register execution strategies
registerSpawnStrategies(...)               // Register spawn handling

// Shutdown phase
afterCommand()
commandComplete()
blazeShutdown()
blazeShutdownOnCrash(DetailedExitCode)

Extension Points

ServerBuilder allows modules to customize server-level configuration during serverInit():

  • Add custom commands (addCommands())
  • Register info items for the info command
  • Add query functions and output formatters
  • Configure invocation policies
  • Set repository helpers factory

WorkspaceBuilder enables workspace-level customization during workspaceInit():

  • Register Skyframe functions for custom evaluation logic
  • Configure workspace status actions
  • Set up diff awareness factories
  • Customize syscall caching

Options Integration modules implement OptionsSupplier to contribute command-line options:

  • getStartupOptions() for server startup flags
  • getCommandOptions(String) for command-specific flags
  • getCommonCommandOptions() for flags shared across commands

Real-World Example: BazelRepositoryModule

The BazelRepositoryModule demonstrates comprehensive module usage:

  • serverInit(): Adds FetchCommand, ModCommand, and VendorCommand
  • workspaceInit(): Registers Skyframe functions for module resolution, repository fetching, and extension evaluation
  • beforeCommand(): Configures download managers, environment variables, and repository caches
  • getPrecomputedValues(): Injects repository overrides and configuration into Skyframe
  • getCommonCommandOptions(): Provides RepositoryOptions for all commands

Module Ordering

Module order matters when multiple modules provide the same functionality. The last module wins, becoming the default implementation. For example, execution strategy modules should be ordered so that the desired default strategy appears last in the module list.

Best Practices

  1. Minimize constructor work — defer all initialization to lifecycle methods
  2. Use buildersServerBuilder and WorkspaceBuilder provide fluent APIs for configuration
  3. Handle errors gracefully — throw AbruptExitException to signal fatal errors
  4. Respect constraints — follow single-responsibility rules for filesystem, output service, and metrics
  5. Leverage precomputed values — use getPrecomputedValues() to inject data into Skyframe for efficient access during builds

Command Implementation

Relevant Files
  • src/main/java/com/google/devtools/build/lib/runtime/BlazeCommand.java
  • src/main/java/com/google/devtools/build/lib/runtime/Command.java
  • src/main/java/com/google/devtools/build/lib/runtime/BlazeCommandDispatcher.java
  • src/main/java/com/google/devtools/build/lib/runtime/commands/BuildCommand.java
  • src/main/java/com/google/devtools/build/lib/runtime/commands/TestCommand.java
  • src/main/java/com/google/devtools/build/lib/runtime/commands/QueryCommand.java
  • src/main/java/com/google/devtools/build/lib/bazel/commands/FetchCommand.java

Command Architecture

Bazel commands follow a plugin-based architecture where each command implements the BlazeCommand interface and is annotated with the @Command metadata annotation. The BlazeCommandDispatcher routes user invocations to the appropriate command implementation.

Core Interfaces and Annotations

BlazeCommand Interface: The BlazeCommand interface defines two key methods:

  • exec(CommandEnvironment env, OptionsParsingResult options) - The main execution method that performs the command's work and returns a BlazeCommandResult
  • editOptions(OptionsParser optionsParser) - Optional hook to modify parsed options before execution

@Command Annotation: Commands are registered via the @Command annotation, which specifies:

  • name - The command name as typed by users (e.g., "build", "test")
  • options - Array of option classes defining command-specific flags
  • buildPhase - Lifecycle phase: LOADS (package loading), ANALYZES (analysis), or EXECUTES (action execution)
  • inheritsOptionsFrom - Parent commands whose options are inherited
  • shortDescription - Help text for bazel help
  • usesConfigurationOptions - Whether configuration-specific options apply
  • allowResidue - Whether to accept positional arguments beyond options

Command Execution Flow

Loading diagram...

Command Registration

Commands are registered through BlazeModule.serverInit(). The BuiltinCommandModule registers core commands like BuildCommand, TestCommand, and QueryCommand. Custom commands can be added by implementing a BlazeModule and calling builder.addCommands().

Example: BuildCommand

The BuildCommand demonstrates the standard pattern:

@Command(
    name = "build",
    buildPhase = EXECUTES,
    options = {BuildRequestOptions.class, ExecutionOptions.class, ...},
    usesConfigurationOptions = true,
    shortDescription = "Builds the specified targets.",
    allowResidue = true,
    completion = "label",
    help = "resource:build.txt")
public final class BuildCommand implements BlazeCommand {
  @Override
  public BlazeCommandResult exec(CommandEnvironment env, OptionsParsingResult options) {
    List&lt;String&gt; targets = TargetPatternsHelper.readFrom(env, options);
    BuildRequest request = BuildRequest.builder()
        .setCommandName("build")
        .setTargets(targets)
        .build();
    DetailedExitCode exitCode = new BuildTool(env).processRequest(request, null, options)
        .getDetailedExitCode();
    return BlazeCommandResult.detailedExitCode(exitCode);
  }
}

Return Values

Commands return BlazeCommandResult which encapsulates:

  • DetailedExitCode - Exit code with optional failure details
  • ExecRequest - For bazel run, instruction to execute a binary
  • shutdown - Flag to shut down the server
  • idleTasks - Background tasks to run during idle periods

Option Inheritance

Commands can inherit options from parent commands via inheritsOptionsFrom. For example, TestCommand inherits from BuildCommand, gaining all build-related options. This enables consistent option handling across related commands.

C++ Launcher & Startup

Relevant Files
  • src/main/cpp/main.cc
  • src/main/cpp/blaze.cc
  • src/main/cpp/blaze.h
  • src/main/cpp/bazel_startup_options.h
  • src/main/cpp/option_processor.cc
  • src/main/cpp/startup_options.h
  • src/main/cpp/workspace_layout.h

The C++ launcher is the entry point for Bazel, responsible for bootstrapping the system and managing the client-server architecture. It handles startup option parsing, workspace detection, server lifecycle management, and command execution.

Entry Point

The launcher begins in main.cc, which calls main_impl() to initialize the startup sequence. On Windows, wmain() converts Unicode arguments to UTF-8 strings before delegating to main_impl(). The function records the start time and creates three key components:

  1. WorkspaceLayout - Detects and manages workspace boundaries
  2. BazelStartupOptions - Parses Bazel-specific startup flags
  3. OptionProcessor - Processes command-line arguments and RC files

These are passed to blaze::Main(), which orchestrates the entire startup flow.

Startup Sequence

The Main() function in blaze.cc follows this sequence:

  1. Initialize Logging - Sets up the logging handler before any log statements
  2. Determine Self Path - Locates the Bazel binary itself
  3. Parse Options - Processes startup options from command line and RC files (.bazelrc)
  4. Detect Workspace - Finds the workspace root by searching for MODULE.bazel or WORKSPACE files
  5. Extract Archive - Unpacks embedded Python, C++, and Java components into the install base
  6. Prepare Directories - Creates and validates output base and install base directories
  7. Run Launcher - Delegates to the appropriate execution mode

Execution Modes

The launcher supports three execution modes:

  • Server Mode - Starts a persistent server that listens for commands (used with --nohup)
  • Client-Server Mode - Default mode; client connects to an existing server or starts one
  • Batch Mode - Runs the command directly without a persistent server (used with --batch or outside a workspace)

Lock Management

The launcher uses filesystem locks to coordinate between multiple client invocations:

  • Install Base Lock (shared) - Multiple clients can read the install base simultaneously
  • Output Base Lock (exclusive) - Only one client can use an output base at a time

The BlazeServer::AcquireLocks() method handles lock acquisition with optional blocking behavior controlled by --noblock_for_lock.

Server Lifecycle

Loading diagram...

Communication Protocol

The client communicates with the server via gRPC using the CommandServer service. The BlazeServer::Communicate() method sends a RunRequest containing the command, arguments, and startup options. The server streams back RunResponse messages containing stdout, stderr, and the final exit code. The client releases its locks after sending the command, allowing the server to outlive the client process.

Key Responsibilities

  • Version Management - Detects version mismatches and restarts the server if needed
  • Signal Handling - Installs signal handlers to gracefully cancel running commands
  • Process Synchronization - Uses PID files to track server processes and prevent stale instances
  • Environment Setup - Prepares JVM environment variables and scheduling parameters
  • Error Handling - Validates directories, handles lock timeouts, and manages server failures