skip to content

A teammate adds `release.mustRunAfter(build)` expecting that running `gradle release` will now also build. Why doesn't it, and what should they do instead?

level: middleimportance: should knowfreq 45%

answer

  1. ordering = order, not inclusion
  2. membership vs order are separate
  3. mustRunAfter won't pull build in
  4. fix: dependsOn OR request both
  5. must run before always => dependency

basics

~10 s

mustRunAfter only orders tasks that are already scheduled; it never adds build to the graph. Running gradle release alone won't build. They need a real dependency (release.dependsOn(build)) or to request both tasks.

solid answer

~40 s

Ordering rules are purely about *sequence*, not *inclusion*. `release.mustRunAfter(build)` says "if both `release` and `build` are in this build, run `build` first" — but it does nothing to put `build` into the graph. So `gradle release` runs `release` alone, and the rule is inert. To actually build before releasing, the teammate has two correct options: 1. **Declare a dependency:** `tasks.named("release") { dependsOn("build") }`. This both pulls `build` into the graph and orders it first. 2. **Request both explicitly:** `gradle build release`. Now both are scheduled, and the existing `mustRunAfter` will order them correctly. The common confusion is treating ordering rules as a lightweight dependency. They are not — they are a *conditional sequencing* mechanism that assumes the other task's presence is handled elsewhere.

code

bash · 8 lines
bash
# Ordering rule inert — build is not scheduled:
gradle release          # runs release only

# Works because both are requested:
gradle build release    # build first (mustRunAfter honored), then release

# Or change the rule to a dependency so this always builds first:
# tasks.named("release") { dependsOn("build") }

go deeper

for a junior

Recognize that the predecessor must already be scheduled for an ordering rule to do anything.

for a middle

Diagnose the bug, separate 'membership' from 'order', and propose both fixes with their tradeoffs.

for a senior

Advise when to keep the decoupled ordering design vs convert to a dependency based on standalone-runnability requirements.

for a principal

Establish team conventions so engineers don't reach for ordering rules expecting dependency semantics across modules.

## The root misconception Ordering rules answer the question *"in what order?"*, never *"does it run at all?"*. Two independent decisions drive the task graph: 1. **Membership** — which tasks are in the graph (decided by requested tasks + dependencies). 2. **Order** — the sequence among members (refined by `mustRunAfter` / `shouldRunAfter`). `mustRunAfter` touches only #2. If `build` never becomes a member, the rule has nothing to order. ## Reproducing the surprise ```kotlin tasks.named("release") { mustRunAfter("build") } ``` - `gradle release` → graph = `{release}`. `build` absent → rule inert → no build happens. **Surprise.** - `gradle build release` → graph = `{build, release}` → rule applies → `build` then `release`. Works. ## The two correct fixes ### Fix A — make it a dependency ```kotlin tasks.named("release") { dependsOn("build") // pulls build in AND orders it first } ``` Now `gradle release` always builds first. Note you usually no longer need the separate `mustRunAfter`, because `dependsOn` already implies ordering. ### Fix B — request both tasks Keep the ordering rule but always invoke `gradle build release` (e.g. in CI). The rule then guarantees order among the requested tasks. ## When the ordering rule is still the *right* choice If you *don't* want `release` to drag `build` along when invoked standalone (maybe `release` can run against a prebuilt artifact in some pipelines), then keeping `mustRunAfter` and explicitly requesting both in CI is exactly the decoupled design ordering rules exist for. The fix depends on whether `build` should be *mandatory* (→ dependency) or merely *ordered when present* (→ ordering rule). ## Mental checklist - Need the task to *always* run before? → dependency. - Need ordering *only when both already run*? → ordering rule. - Seeing 'my predecessor didn't run'? → you used an ordering rule where you needed a dependency.

  • After switching to `dependsOn("build")`, is the original `mustRunAfter("build")` still needed?
    Usually no — a dependency already implies that `build` runs before `release`. The explicit ordering rule becomes redundant, though harmless.
  • When is keeping the ordering rule (instead of a dependency) the better design?
    When `release` should be runnable standalone against a prebuilt artifact, but ordered after `build` whenever both are requested — i.e. you want order without forcing inclusion.

saying these in an interview costs you the question

  • Believing mustRunAfter implicitly schedules the predecessor.
  • Adding both dependsOn and mustRunAfter and thinking the ordering rule is what makes build run.

context