Gradle
Posts
-
Digging into Gradle's Source 1: How I Read It with AI, and the Map
The first post of a series on reading Gradle's source, starting with diagrams and a 3-minute hands-on run that show what Gradle is, how it works and what it is for. It covers which version is read (master, a 9.10.0 development build), how to get the same commit and build Gradle with Gradle inside Docker, a platform map of the roughly one-million-line codebase, the main road one gradle command travels through the client and the daemon to your task action, and how AI-written notes are re-checked against the source.
-
Digging into Gradle's Source 2: The Big Picture — Client and Daemon, Service Scopes, and the Three Build Phases
A map of the Gradle source for intermediate developers opening it for the first time. We check against the real source and examples we ran ourselves which four processes take part in one gradle command, how the daemon's state is layered (process, session, build tree, build, project) and matched one-to-one with service scopes, which class and which state the initialization, configuration and execution phases really are, and how the model (Gradle, Settings, Project, Task) and the execution side (execution plan, nodes, execution engine) fit together — then lay out which part of this map each of parts 3 to 12 reads.
-
Digging into Gradle's Source 3: From the gradle Command to the Daemon — the Launcher, the Wrapper and the Daemon Protocol
When you type gradle in a terminal, the build is not done by that process but by a daemon JVM hiding behind it. This post follows the 64 MiB client the shell script starts, the three paths that decide whether a daemon is used and what --no-daemon really means, how registry.bin is used to find a daemon and judge compatibility, the order in which a daemon is started and connected to, the 16-step command chain and the Idle/Busy states inside the daemon, the conditions under which a daemon stops itself, and how gradlew and IDEs reach the same daemon — all checked against the source at a fixed commit and with Docker examples.
-
Digging into Gradle's Source 4: The Service Registry — the DI Container Inside Gradle
Who puts a FileSystemOperations into your task when you mark its constructor with @Inject? This post reads DefaultServiceRegistry, one per scope, which looks services up through its parents; the rules for declaring services with @Provides methods and decorators; lazy singletons created on first lookup; ServiceScopeValidator, which keeps services in their proper scope; GradleModuleServices, through which 57 modules add services to every scope; and the _Decorated classes that fill in abstract getters. We use the registry directly outside a build and print the stack of a task being created to check it all against the source.
-
Digging into Gradle's Source 5: The Build Lifecycle — Where Initialization, Configuration and Execution Split
We follow, in the source of the reference commit, how a build request received by the daemon travels down a chain of executors to the build lifecycle, how StateTransitionController guards state transitions, how initialization reads init scripts and settings, how the configuration phase configures projects, where the configuration time barrier and the task graph sit, and how the build wraps up with buildFinished and the Flow API. We hook every lifecycle callback to print their order, read the build operation tree, and fail the build on purpose in each of the three phases.
-
Digging into Gradle's Source 6: Projects and Build Scripts — How build.gradle.kts Gets Run
What does one build.gradle.kts file turn into inside Gradle? We follow the code that builds DefaultProject from the descriptors settings left behind, the evaluator chain that runs under a project lock with its beforeEvaluate and afterEvaluate states, the selector that picks Groovy or Kotlin by the end of the file name, the two DSLs that cut out plugins {} and compile twice, the generated accessors that make tasks.compileJava possible, and the class loader scopes that run from settings down to subprojects, running the same script in both DSLs and printing a stack trace to check each step against the source.
-
Digging into Gradle's Source 7: Applying Plugins — From the plugins {} Block to apply()
What does a single plugins { java } line turn into in the source? DefaultPluginRequestApplicator resolves every request first and only then applies them all; a resolver chain asks core plugins, buildSrc, included builds and the Plugin Portal in turn, using marker artifacts; META-INF/gradle-plugins descriptors turn an id into a class; DefaultPluginManager runs a plugin only once even if you apply it twice; withPlugin fires at a precise moment; extensions get attached; java pulls in java-base and base; and buildSrc and precompiled script plugins fit in — all checked with a plugin we write ourselves.
-
Digging into Gradle's Source 8: Tasks and Lazy Configuration — DefaultTask, TaskContainer and Provider
A single tasks.register line does not create a task. We follow when the TaskCreatingProvider that only holds a name and a configuration block becomes a real task, how configureEach reaches tasks that exist now and tasks that come later, which APIs like withType { } quietly create every task, how TaskFactory and DefaultTask hold the action list, why a Provider that carries "the task that makes this value" gives you ordering without dependsOn, and the three ways a Property gets locked — checked against the source with three runnable examples.
-
Digging into Gradle's Source 9: The Task Graph and the Execution Plan — What, in What Order, How Many at Once
How does the pA in gradle pA become packageApp? What do dependsOn, mustRunAfter, shouldRunAfter and finalizedBy turn into inside the execution plan, and who decides the order? Who picks the next node while the build runs, what exactly do --parallel and --max-workers limit, and how does the Worker API split up the work inside a single task? We answer each question from the source at a fixed commit and from examples we actually ran (whenReady, --dry-run, thread names, jstack).
-
Digging into Gradle's Source 10: The Execution Engine and Incremental Builds — How UP-TO-DATE Is Decided
How does the single word UP-TO-DATE on your console get decided in the source? We follow DefaultTaskNodeExecutor, which filters tasks before the engine, TaskExecution, which wraps a task as a unit of work, the execution engine built from twenty-five nested steps, ResolveChangesStep and SkipUpToDateStep, which compare the last execution record with the current state, the file fingerprints that @PathSensitive changes, the VFS that remembers file state between builds, and InputChanges, which hands a task only the files that changed — all checked against the source with examples we actually ran.
-
Digging into Gradle's Source 11: The Build Cache and the Configuration Cache — Two Ways to Reuse Work
Where in the source do FROM-CACHE and "Reusing configuration cache." come from? This post covers ResolveMutableCachingStateStep and BuildCacheStep in the execution engine's step chain, what goes into the MD5 cache key, @CacheableTask and the list of reasons not to cache, the local directory cache where the key is the file name and each entry is a tar.gz, the configuration cache that serializes the whole work graph and reloads it even on the very first build, the fingerprint that catches environment variable reads by rewriting bytecode, and the problem report — all checked against the source at a fixed commit and with Docker examples.
-
Digging into Gradle's Source 12: Dependency Resolution — From One implementation Line to Jar Files, and Wrapping Up the Series
What does the single implementation line in build.gradle.kts become in the source? This post follows the DependencyHandler.add() call behind the generated accessor, configurations whose roles are split into declare, resolve and consume, resolution that waits until someone asks for the file list, the loop in which DependencyGraphBuilder grows the graph, the LatestModuleConflictResolver that picks the higher of two versions of the same library and the places where strictly and force change that result, and the two-pass lookup that checks the cache before the repositories, together with --offline and the read-only cache — all checked against the source at a fixed commit and with Docker examples. It then wraps up the 12-part series.