P.2.1 Task-Based vs Artifact-Based
Many build tools ask, "Which steps should I run?" Bazel asks, "Which result do
you want, and what does it depend on?" In Ant, Maven, Gradle, Grunt, and similar
tools, users commonly compose tasks or lifecycle steps.1 In Bazel, a BUILD
file usually names targets, their inputs, and their dependencies, then Bazel
chooses the work needed to
produce the requested result.2 That shift is where the speed and correctness
benefits from P.1.1 Key Benefits begin.3
This beginner's model abstracts away Bazel's internal commands. Rules still create actions that invoke compilers, linkers, and scripts. BUILD-file authors normally describe the result and its requirements instead of scheduling those actions themselves.
What a Task-Based Build System Sees
In a task-based system, the build definition is primarily a recipe. The author tells the tool which named tasks exist and in what order they depend on each other.1
<target name="init">
<mkdir dir="${build}"/>
</target>
<target name="compile" depends="init">
<javac srcdir="${src}" destdir="${build}"/>
</target>
<target name="dist" depends="compile">
<jar jarfile="${dist}/lib/MyProject-${DSTAMP}.jar" basedir="${build}"/>
</target>
This is already better than a pile of ad-hoc shell scripts, but the build system still sees mostly opaque commands.1 If task B and task C both precede task A, the graph tells the system about ordering, not about the real files, tools, or hidden state each task might touch. That is why task-based systems struggle to answer questions like "is it safe to run these two steps in parallel?" or "can I reuse the last result without rerunning this task?"1,3
The limitation is not that task runners are badly implemented. It is that they hand too much control to the engineer writing the task and too little to the build system.1 A task can read undeclared files, depend on environment variables, download something from the network, or write a timestamp into the output. Once arbitrary behavior is allowed at the task level, the tool has to be conservative if it wants to stay correct.1
What Bazel Asks You to Declare Instead
Bazel keeps build definitions much narrower. Instead of writing a general-purpose script, you declare buildable targets, the source files they use, and the dependencies they need.2,4
java_binary(
name = "MyBinary",
srcs = ["MyBinary.java"],
deps = [":mylib"],
)
java_library(
name = "mylib",
srcs = ["MyLibrary.java", "MyHelper.java"],
deps = ["//java/com/example/common"],
)
Here MyBinary depends on :mylib, :mylib depends on //java/com/example/common, and each target has declared inputs.2 Bazel parses those declarations into a dependency graph, follows everything the requested target depends on, and then chooses the actions needed to produce the requested result.2 You ask which target produces the artifact you want.
That stricter model can feel less flexible at first, especially if you are used to task graphs. But the trade is deliberate: BUILD files describe the shape of the build, while Bazel decides how to schedule work, invoke tools, and reuse results. Which rule types exist and why language support lives outside the core comes next in P.2.3 Core vs Rulesets.2
Why the Distinction Matters
Once the system knows the declared inputs and dependency edges, it can make stronger general guarantees than it can around opaque, user-orchestrated steps.2,5 It can safely parallelize independent work, rebuild only the part of the graph affected by a change, and later share results across machines through remote caching and remote execution.5 P.2.2 Consequences of Artifact-Based shows those practical effects. 2.2 Three Phases of a Build later explains how Bazel turns declarations into actions, and 2.4 Caching & Incrementality explains how it reuses their results.
A good shortcut is to think of task-based builds as imperative: "first do this, then do that." Artifact-based builds are closer to a functional description: "this output is computed from these inputs under these rules."2 The analogy is not perfect, but it explains why Bazel can reason about the build instead of merely replaying scripts.
If you come from Maven or Gradle, that is the real migration hurdle previewed by M2 Maven & Gradle → Bazel. You are not just learning new files. You are handing more control to the build system so it can give you stronger guarantees back.1,2
The mental model to keep is simple: task-based tools organize commands, while Bazel organizes declared artifacts and their dependencies. Bazel is stricter on purpose. The reduced freedom in build definitions is what later enables safe parallelism, precise incremental rebuilds, and shared caching.
Check your understanding · 3 questions
1.What is the core conceptual shift from task-based tools (Ant, Maven, Gradle) to Bazel?
Select one answer
2.Why is it harder for a build tool to safely parallelize tasks or reuse their previous results?
Select one answer
3.True or false: the artifact-based model and its trade-offs.
Choose True or False for each sentence
Footnotes
-
Task-Based Build Systems — tasks as the primary unit of work, the Ant example, and why opaque scripts limit safe parallelism and incrementality ↩1 ↩2 ↩3 ↩4 ↩5 ↩6 ↩7
-
Artifact-Based Build Systems — declarative
BUILDmanifests, "what vs how", and why Bazel can own scheduling and reuse ↩1 ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 -
Software Engineering at Google — Ch.18: Build Systems and Build Philosophy — the broader argument that modern build systems shift power from ad-hoc scripts to the system to gain speed and correctness ↩1 ↩2
-
Dependency Management — the distinction between dependencies on tasks and dependencies on artifacts, plus the role of explicit modules and edges ↩
-
Distributed Builds — remote caching and remote execution depend on reproducible, input-addressed artifacts ↩1 ↩2