Chapter 18Lesson 05~235 minutes

Checkpoint Lab — Gradle Plugins, Core Plugins, Community Plugins, Convention Plugins, and Plugin Management

Create a multi-project build whose Java policy comes from reviewed convention logic and whose external plugin resolution is pinned and inspectable, then break and repair one plugin-resolution assumption without touching shared Gradle state.

CheckpointMulti-projectPinned pluginsConvention policyRollback

Create a multi-project build whose Java policy comes from reviewed convention logic and whose external plugin resolution is pinned and inspectable, then break and repair one plugin-resolution assumption without touching shared Gradle state.

Learning objectives

  • Create a multi-project Gradle checkpoint with repository-owned Java convention logic and one pinned external plugin.
  • Record Wrapper/JVM/plugin repository/plugin version evidence before executing build tasks.
  • Predict which projects gain Java, application, Spotless, and convention-provided model elements.
  • Prove plugin-provided tasks and project scope independently.
  • Inject and diagnose one plugin-version/repository-resolution failure using a separate isolated Gradle User Home.
  • Audit convention logic for secret/environment assumptions and cleanly restore all experimental changes.

1. Checkpoint objective and acceptance criteria

You are preparing a small repository for CI onboarding. The app and library projects share Java policy from reviewed convention logic. Spotless is a pinned external plugin applied only to app. The build must prove its plugin source/version policy and remain recoverable if plugin resolution breaks.

Acceptance: exact Wrapper/runtime evidence, exact external plugin version/repository, convention source under version control, scoped tasks/extensions, clean build/format check, preserved broken-resolution evidence, no secrets/environment-specific assumptions in convention source, and deletion of only disposable state.

2. Preflight and version assumptions

mkdir gradle-plugin-checkpoint
cd gradle-plugin-checkpoint
export GRADLE_USER_HOME="$PWD/.checkpoint-gradle-home"

# Copy the verified Gradle 9.7.1 Wrapper files from Chapter 15.
chmod +x gradlew
./gradlew --version | tee gradle-version.txt
java -version 2> java-version.txt

mkdir -p app/src/main/java/dev/academy/app
mkdir -p library/src/main/java/dev/academy/lib
mkdir -p build-logic/src/main/kotlin
mkdir -p evidence

Assumptions: Gradle 9.7.1, supported JVM runtime (JDK 21 in the course), Java 17 target, and Spotless 8.10.0 from the public Gradle Plugin Portal. If external network access is blocked, do not weaken repository policy; record that resolution cannot be exercised in that environment and use an approved mirror/cached CI environment rather than adding arbitrary repositories.

3. Create the complete repository-owned plugin model

cat > settings.gradle.kts <<'EOF'
pluginManagement {
    includeBuild("build-logic")
    repositories {
        gradlePluginPortal()
    }
    plugins {
        id("com.diffplug.spotless") version "8.10.0"
    }
}
rootProject.name = "gradle-plugin-checkpoint"
include("app", "library")
EOF

cat > build.gradle.kts <<'EOF'
plugins {
    id("com.diffplug.spotless") apply false
}
EOF

cat > build-logic/settings.gradle.kts <<'EOF'
rootProject.name = "build-logic"
EOF
cat > build-logic/build.gradle.kts <<'EOF'
plugins { `kotlin-dsl` }
repositories { mavenCentral() }
EOF

cat > build-logic/src/main/kotlin/academy.java-conventions.gradle.kts <<'EOF'
import org.gradle.api.tasks.compile.JavaCompile
import org.gradle.jvm.toolchain.JavaLanguageVersion

plugins { java }

java {
    toolchain.languageVersion.set(JavaLanguageVersion.of(17))
}

tasks.withType<JavaCompile>().configureEach {
    options.encoding = "UTF-8"
    options.release.set(17)
}

tasks.register("conventionReport") {
    group = "academy"
    doLast {
        println("plugin=academy.java-conventions")
        println("release=17")
        println("encoding=UTF-8")
    }
}
EOF

cat > app/build.gradle.kts <<'EOF'
plugins {
    id("academy.java-conventions")
    application
    id("com.diffplug.spotless")
}
application { mainClass.set("dev.academy.app.Main") }
spotless {
    format("misc") {
        target("*.md")
        trimTrailingWhitespace()
        endWithNewline()
    }
}
EOF

cat > library/build.gradle.kts <<'EOF'
plugins {
    id("academy.java-conventions")
    `java-library`
}
EOF

cat > app/src/main/java/dev/academy/app/Main.java <<'EOF'
package dev.academy.app;
public final class Main {
    public static void main(String[] args) {
        System.out.println("checkpoint ok");
    }
}
EOF
cat > library/src/main/java/dev/academy/lib/LibraryValue.java <<'EOF'
package dev.academy.lib;
public final class LibraryValue {
    private LibraryValue() {}
    public static int answer() { return 42; }
}
EOF
printf '%s
' '# Plugin Checkpoint' '' 'Reviewed plugin policy.' > app/README.md

4. Predict model changes before the first build

Write these predictions before running Gradle:

Prediction A — both app and library:
  academy.java-conventions is resolvable from build-logic
  Java plugin/model exists
  compileJava and conventionReport tasks exist
  Java release convention is 17

Prediction B — app only:
  Application plugin contributes run/distribution-related model
  Spotless plugin contributes spotlessCheck/spotlessApply-style tasks

Prediction C — root project:
  Spotless is resolved with apply false, but root should not gain app Spotless configuration/tasks from application

Prediction D — plugin resolution:
  com.diffplug.spotless uses exact version 8.10.0 from configured plugin repository policy

5. Record plugin source/version policy before execution

grep -n -E 'pluginManagement|includeBuild|gradlePluginPortal|com.diffplug.spotless|8.10.0'   settings.gradle.kts build.gradle.kts app/build.gradle.kts library/build.gradle.kts   > evidence/plugin-policy.txt
cat evidence/plugin-policy.txt

This file is a review artifact: it shows what the repository declares. Pair it with runtime identity and execution evidence; do not treat logs alone as the source of truth.

6. Prove plugin-provided tasks and application scope

./gradlew :app:tasks --all > evidence/app-tasks.txt
./gradlew :library:tasks --all > evidence/library-tasks.txt

grep -E 'conventionReport|compileJava|spotless|run' evidence/app-tasks.txt || true
grep -E 'conventionReport|compileJava|spotless|run' evidence/library-tasks.txt || true

./gradlew :app:conventionReport :library:conventionReport --console=plain   | tee evidence/convention-report.log

Verify the predictions independently: convention and Java tasks appear in both projects; Spotless/application tasks belong only to app. If library unexpectedly gains formatting tasks, inspect its plugin declarations and convention logic before continuing.

7. Execute the controlled plugin work and capture evidence

./gradlew :app:spotlessCheck --info --console=plain   > evidence/spotless-info.log 2>&1
./gradlew clean build --console=plain | tee evidence/build.log
./gradlew :app:run --console=plain | tee evidence/run.log

grep -E 'com.diffplug.spotless|spotless' evidence/spotless-info.log | head -40 || true

Expected application output: checkpoint ok. A clean build is useful after plugin resolution succeeds because it proves plugin/configuration compatibility with the JVM project, but it is not provenance proof by itself.

8. Audit convention logic for secrets and environment coupling

Repository-owned convention logic should not depend on workstation-specific paths or secrets. Scan its source:

grep -R -n -E 'System\.getenv|environmentVariable|password|token|secret|/Users/|/home/|[A-Za-z]:\\'   build-logic/src || true

Expected result for this lab: no matches. A match would require review, not automatic deletion—some environment access can be intentional when modeled through Providers, but secret values must never be printed or committed.

9. Inject a plugin-version failure without polluting good state

Preserve the known-good settings, change only the external plugin version, and execute with a separate User Home:

cp settings.gradle.kts evidence/settings.good.gradle.kts
python - <<'PY'
from pathlib import Path
p=Path('settings.gradle.kts')
s=p.read_text()
s=s.replace('version "8.10.0"', 'version "99.99.99"')
p.write_text(s)
PY

set +e
GRADLE_USER_HOME="$PWD/.checkpoint-broken-home"   ./gradlew :app:spotlessCheck --info --console=plain   > evidence/broken-plugin-resolution.log 2>&1
rc=$?
set -e
printf 'broken_exit=%s
' "$rc" | tee evidence/broken-exit.txt
grep -E 'Plugin|com.diffplug.spotless|99.99.99|not found|could not'   evidence/broken-plugin-resolution.log | head -100 || true

Expected: non-zero exit and a plugin-resolution error. This is a deliberate supply-chain/configuration failure, not a formatting/test failure. Preserve the log before repair.

10. Restore reviewed policy and verify recovery

cp evidence/settings.good.gradle.kts settings.gradle.kts
rm -rf .checkpoint-broken-home
export GRADLE_USER_HOME="$PWD/.checkpoint-gradle-home"

./gradlew :app:spotlessCheck build --console=plain   | tee evidence/repaired-build.log

Recovery proves that the smallest policy correction—restoring the reviewed version—repairs the build. No normal Gradle cache was deleted, no extra plugin repository was added, and no version loosened to a dynamic selector.

11. Optional second fault: missing plugin repository

If you want one additional diagnostic exercise, create another disposable copy of settings and remove gradlePluginPortal(), then use a fresh isolated User Home. The expected failure is inability to resolve the external community plugin. Restore the repository allow-list afterward. Do not add arbitrary repositories until one happens to work.

12. Produce a small plugin manifest for code review

Record a human-readable manifest:

Gradle engine: 9.7.1 (project Wrapper)
Gradle runtime JVM: JDK 21 course baseline
Local convention plugin: academy.java-conventions
  source: build-logic/src/main/kotlin/academy.java-conventions.gradle.kts
  external version: none; repository-owned source in this build
Core plugins:
  java / application / java-library -> supplied by Gradle 9.7.1
Community plugin:
  id: com.diffplug.spotless
  version: 8.10.0
  source policy: gradlePluginPortal()
Scope:
  app -> convention + application + Spotless
  library -> convention + java-library

In a production repository, this information may live in architecture/build-governance documentation or be generated by policy tooling. The important property is that reviewers can answer “what executable build logic do we trust?” without reverse-engineering every build script.

13. Verification checklist

  • Exactly one verified Gradle 9.7.1 Wrapper is used.
  • All checkpoint Gradle state is under project-local User Homes.
  • Spotless ID/version and plugin repository are exact and reviewable.
  • academy.java-conventions source is repository-owned and reviewable.
  • Both JVM subprojects expose the convention task and Java model.
  • Only app exposes Spotless/application-specific tasks.
  • Convention source contains no secret/workstation assumption in this lab.
  • Broken 99.99.99 resolution produced a preserved non-zero failure.
  • Repair restored exact 8.10.0 rather than loosening repository/version policy.
  • Clean build and formatting gate succeed after repair.

14. Cleanup and rollback

./gradlew --stop || true
cd ..
rm -rf gradle-plugin-checkpoint

This removes only disposable checkpoint state. In a real repository, rollback means restoring reviewed settings/build-logic commits and preserving CI logs—not deleting shared caches or hiding plugin-resolution evidence.

15. What Chapter 18 adds to the production build-engineering model

You can now treat plugins as a formal trust graph: the Gradle engine supplies core plugins; settings defines where non-core plugins may come from and which versions are acceptable; projects decide application scope; convention plugins package repository-owned policy; and task/model evidence proves what those plugins contribute.

Chapter 19 moves from plugin code to dependency resolution semantics: configurations, variants, attributes, capabilities, and metadata rules. The same discipline continues—inspect the declared model, resolved graph, source metadata, and trust boundary before changing resolution behavior.

Knowledge check

What two independent pins protect this checkpoint?

Why is the broken test run under a separate Gradle User Home?

What evidence proves local convention logic is applied to both modules?

Why does a successful clean build not prove plugin provenance?

What is the correct repair for version 99.99.99?

What is the bridge to Chapter 19?

Official references and version notes

Version snapshot: Generated August 24, 2026 with Gradle 9.7.1, JDK 21 as the Gradle runtime, Java 17 as the course JVM target, and com.diffplug.spotless 8.10.0 as the one external community-plugin example. Re-check plugin versions and compatibility before adopting them in production.

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.

Ethereum / ERC-20
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0 Send only Ethereum/ERC-20 compatible assets to this address.