Checkpoint Lab — Groovy DSL and Kotlin DSL, Properties, Providers, Lazy Configuration, and Build Authoring
Author a small Gradle build, port one focused section between Kotlin and Groovy DSL, replace eager APIs with Providers and task registration, then prove configuration/execution behavior and missing-property handling from observable evidence.
Author a small Gradle build, port one focused section between Kotlin and Groovy DSL, replace eager APIs with Providers and task registration, then prove configuration/execution behavior and missing-property handling from observable evidence.
Learning objectives
- Build a disposable Kotlin-DSL project through the verified Gradle 9.7.1 Wrapper and isolated Gradle User Home.
-
Predict which configuration/task/Provider/execution messages
should appear for
helpversus a selected custom task. - Port one focused Provider/task section to Groovy DSL and verify equivalent output.
-
Replace one eager Provider/task lookup with
orElse/mapandtasks.register/named. - Implement both a safe fallback property and a clear task-specific missing-property error.
- Verify outputs, optional configuration-cache reuse, and precise cleanup without touching normal Gradle state.
1. Checkpoint acceptance contract
Success is not “both files compile.” Your dossier must prove:
- Gradle 9.7.1 Wrapper/runtime identity and isolated User Home.
- Configuration messages are distinguishable from selected-task configuration and execution messages.
- An unrelated task does not realize the custom task or require its strict property.
- A focused Kotlin task and Groovy port produce equivalent output from the same property source.
- The eager version causes observable extra coupling; the repaired version removes it.
- Missing values have explicit fallback/error behavior without printing secrets.
- Cleanup removes only the synthetic lab.
2. Setup and preflight
Start from the verified Wrapper files created in Chapter 15. Use no production credentials and no global init scripts.
mkdir gradle-authoring-checkpoint
cd gradle-authoring-checkpoint
export GRADLE_USER_HOME="$PWD/.checkpoint-gradle-home"
# Copy the already-verified Gradle 9.7.1 Wrapper files here.
chmod +x gradlew
./gradlew --version | tee wrapper-version.txt
java -version 2>&1 | tee java-version.txt
mkdir -p kotlin-build groovy-port
Expected chapter baseline: Gradle 9.7.1, JDK 21 runtime (or another supported runtime), Java 17 if you later add Java source. This checkpoint needs only Gradle core APIs, so it has no external plugin dependency.
4. Predict two state changes before running anything
Write your predictions into predictions.txt before
running Gradle:
Prediction A — ./gradlew -p kotlin-build help
- CONFIG message: yes on a normal non-cached configuration
- TASK-CONFIG render: no
- PROVIDER audience realized: no
- build/checkpoint/message.txt: absent
Prediction B — ./gradlew -p kotlin-build verifyRender
- render task must be configured and executed
- audience Provider must be realized when render action needs it
- message.txt must exist before verifyRender completes
The point is causal accountability: if reality differs, investigate which API forced realization.
5. Prove configuration versus execution behavior
./gradlew -p kotlin-build help --console=plain | tee help.log
./gradlew -p kotlin-build verifyRender --console=plain | tee verify.log
printf '%s
' '--- help phase messages ---'
grep -E 'CONFIG:|TASK-CONFIG:|PROVIDER:|EXECUTION:' help.log || true
printf '%s
' '--- verify phase messages ---'
grep -E 'CONFIG:|TASK-CONFIG:|PROVIDER:|EXECUTION:' verify.log || true
cat kotlin-build/build/checkpoint/message.txt
Expected message content is
audience=repository-default unless a higher-priority
source was supplied. help should not create the output
file or need the strict deployment property.
6. Override the fallback/default through a controlled high-priority source
./gradlew -p kotlin-build render --rerun-tasks -PacademyAudience=checkpoint-cli --console=plain
cat kotlin-build/build/checkpoint/message.txt
# Expected: audience=checkpoint-cli
This verifies property wiring without editing the build script. Record the command as evidence because the command-line override is part of the effective build input.
7. Port the focused render behavior to Groovy DSL
Create a separate Groovy project. Port only the property + render task, not the entire checkpoint:
cat > groovy-port/settings.gradle <<'EOF'
rootProject.name = 'authoring-checkpoint-groovy'
EOF
cat > groovy-port/gradle.properties <<'EOF'
academyAudience=repository-default
EOF
cat > groovy-port/build.gradle <<'EOF'
println 'CONFIG: Groovy build script'
def audience = providers.gradleProperty('academyAudience')
.orElse('local-fallback')
.map {
println 'PROVIDER: Groovy audience realized'
it.trim()
}
tasks.register('render') {
println 'TASK-CONFIG: Groovy render'
def output = layout.buildDirectory.file('checkpoint/message.txt')
outputs.file(output)
doLast {
def file = output.get().asFile
file.parentFile.mkdirs()
file.text = "audience=${audience.get()}" + System.lineSeparator()
println 'EXECUTION: Groovy render wrote message.txt'
}
}
EOF
./gradlew -p groovy-port render -PacademyAudience=checkpoint-cli --console=plain | tee groovy.log
cat groovy-port/build/checkpoint/message.txt
diff -u kotlin-build/build/checkpoint/message.txt groovy-port/build/checkpoint/message.txt
A zero diff is stronger evidence than “the Groovy code looks equivalent.” Both builds used the same Wrapper and effective project-property value and produced the same focused output.
8. Inject one eager lookup and diagnose the coupling
Temporarily add this to the top of
kotlin-build/build.gradle.kts:
// INTENTIONALLY BROKEN FOR THE CHECKPOINT
val eagerStrictTier = providers.gradleProperty("deploymentTier").get()
val eagerRender = tasks.getByName("render")
./gradlew -p kotlin-build help --console=plain | tee eager-help.log || true
Expected: help can now fail for missing
deploymentTier, and
getByName("render") forces the render task to realize
even though help does not need it. Preserve
eager-help.log, then remove both lines.
The repair is not “set deploymentTier globally.” The repair is the
Provider + tasks.register design already present in the
good version.
9. Inject a missing property and prove narrow error handling
./gradlew -p kotlin-build help --console=plain
# Must succeed without deploymentTier.
./gradlew -p kotlin-build strictDeployCheck --console=plain || true
# Expected clear task-specific error: property required.
./gradlew -p kotlin-build strictDeployCheck -PdeploymentTier=stage --console=plain
# Expected: validation succeeds without echoing the value.
This is the required distinction between a fallback and an error.
academyAudience has a documented safe fallback.
deploymentTier has no fallback because silently
choosing a deployment tier would be dangerous; it fails only at the
task that needs it.
10. Optional configuration-cache evidence
After the eager lines are removed, run:
./gradlew -p kotlin-build help --configuration-cache --console=plain
./gradlew -p kotlin-build help --configuration-cache --console=plain
If the second run reuses the configuration cache, top-level configuration output may not repeat. That is expected: replaying cached configured state is not the same as re-evaluating the build script. If it does not reuse, inspect Gradle’s stated reason rather than forcing reuse.
11. Verification checklist
- Wrapper/runtime identity was captured.
-
All mutable Gradle user state stayed under
.checkpoint-gradle-home. -
helpdid not needrender, its Provider, ordeploymentTierafter repair. - Kotlin and Groovy focused outputs matched exactly.
-
-PacademyAudience=...overrode the committed root default. - The intentionally eager variant produced preserved diagnostic evidence.
- The strict property failed only its consuming task and validated allowed values without printing the value.
- No normal user cache or production secret was touched.
12. Cleanup and rollback
./gradlew --stop || true
cd ..
rm -rf gradle-authoring-checkpoint
The experiment leaves no global property, init script, cache deletion, or Wrapper upgrade behind. In a real repository, rollback would mean reverting the reviewed build-script change while preserving logs/evidence from CI.
13. What Chapter 16 adds to the production build-engineering model
You can now trace Gradle build authoring from script scope to model object to Provider/task handle to execution and output. You can also distinguish repository defaults from user/CI overrides, explain Kotlin accessor timing, and diagnose when configuration-time work creates unnecessary coupling.
Chapter 17 builds directly on this foundation: tasks become a graph with dependencies and ordering rules, inputs and outputs become explicit incremental-work contracts, and task outcomes become evidence you can reason about rather than merely commands you run.
Knowledge check
Why is the Kotlin/Groovy diff useful?
It verifies a focused observable output is equivalent under the same Wrapper/property input instead of relying on visual code similarity.
What does the eager
deploymentTier.get() demonstrate?
A task-specific required value becomes a global configuration requirement, causing unrelated tasks such as help to fail.
Why is tasks.getByName("render") included in the
broken variant?
It demonstrates eager task realization;
tasks.named/register preserve a lazy
handle.
Why does academyAudience get a fallback while
deploymentTier does not?
A harmless audience label can have a documented default; silently defaulting a deployment tier could select unsafe behavior, so the consuming task must require/validate it.
If Configuration Cache reuses the second
help invocation and the CONFIG print disappears, is
the build broken?
No. Reuse skips the configuration phase; absence of that print is evidence that the build script was not re-evaluated.
What conceptual bridge leads to Chapter 17?
Provider/task handles now become explicit task dependencies, ordering, inputs, outputs, and incremental work contracts.
Official references and version notes
- Gradle 9.7.1 Release Notes — pinned build-engine baseline for this chapter.
- Build Lifecycle — initialization, configuration, execution, and task graph construction.
- Build File Basics and Writing Build Scripts — Groovy/Kotlin DSL scripts and Project model.
- Gradle Kotlin DSL Primer — type-safe model accessors and their timing limitations.
- Properties and Providers and Lazy Configuration.
- Build Environment Configuration — project/system/Gradle/environment property mechanisms and precedence.
-
Task Configuration Avoidance
—
register(),named(),configureEach(), and eager APIs to avoid. - Configuration Cache Requirements — external information sources, environment/system/file/process access, and Provider/ValueSource guidance.
Version snapshot: Generated for August 24, 2026 with Gradle 9.7.1, JDK 21 as the Gradle runtime, and Java 17 as the small JVM-project target. Re-check current Gradle documentation before carrying version-sensitive recommendations into future production builds.
Keep the academy open
Support free, practical DevOps education.
Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0
Send only Ethereum/ERC-20 compatible assets to this
address.