Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions plugin-gradle/CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format (
### Changes
- Bump default `cleanthat` version `2.24` -> `2.25`. ([#2903](https://github.com/diffplug/spotless/pull/2903))
- Bump default `eclipse-jdt` version from `4.35` to `4.39`. ([#2912](https://github.com/diffplug/spotless/pull/2912))
- Make `spotlessPredeclare` visible to Gradle Kotlin DSL type-safe accessors. ([#2925](https://github.com/diffplug/spotless/pull/2925))
- Allow `spotlessPredeclare` to be used directly without enabling it first in spotless extension. ([#2925](https://github.com/diffplug/spotless/pull/2925))

## [8.4.0] - 2026-03-18
### Added
Expand Down
17 changes: 13 additions & 4 deletions plugin-gradle/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1959,17 +1959,26 @@ spotless {
By default, Spotless resolves dependencies on a per-project basis. For very large parallel builds, this can sometimes cause problems. As an alternative, Spotless can be configured to resolve all dependencies in the root project like so:

```gradle
spotless {
...
predeclareDeps()
spotlessPredeclare {
java { eclipse() }
kotlin { ktfmt('0.28') }
}
```

By default, `spotlessPredeclare` resolves dependencies from the root project's repositories. Alternatively, you can resolve dependencies from the buildscript repositories rather than the project repositories:

```gradle
buildscript {
repositories { mavenCentral() }
}
spotlessPredeclare {
fromBuildscriptRepositories()
java { eclipse() }
kotlin { ktfmt('0.28') }
}
```

Alternatively, you can also use `predeclareDepsFromBuildscript()` to resolve the dependencies from the buildscript repositories rather than the project repositories.
The older `spotless { predeclareDeps() }` and `spotless { predeclareDepsFromBuildscript() }` APIs are still supported.

If you use this feature, you will get an error if you use a formatter in a subproject which is not declared in the `spotlessPredeclare` block.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -308,21 +308,46 @@ <T extends FormatExtension> T instantiateFormatExtension(Class<T> clazz) {

protected abstract void createFormatTasks(String name, FormatExtension formatExtension);

/**
* Enables predeclared dependency resolution using the root project's {@code buildscript} repositories.
*
* @deprecated Configure the repository policy directly in {@code spotlessPredeclare} instead:
* <pre>{@code
* spotlessPredeclare {
* fromBuildscriptRepositories()
* java { googleJavaFormat("1.17.0") }
* }
* }</pre>
*/
@Deprecated
public void predeclareDepsFromBuildscript() {
if (project.getRootProject() != project) {
throw new GradleException("predeclareDepsFromBuildscript can only be called from the root project");
}
project.getLogger().info("predeclareDepsFromBuildscript() is deprecated, use 'spotlessPredeclare { fromBuildscriptRepositories() }' directly instead.");
predeclare(GradleProvisioner.Policy.ROOT_BUILDSCRIPT);
}

/**
* Enables predeclared dependency resolution using the root project's repositories.
*
* @deprecated Declare formats directly in {@code spotlessPredeclare} instead:
* <pre>{@code
* spotlessPredeclare {
* java { googleJavaFormat("1.17.0") }
* }
* }</pre>
*/
@Deprecated
public void predeclareDeps() {
if (project.getRootProject() != project) {
throw new GradleException("predeclareDeps can only be called from the root project");
}
project.getLogger().info("predeclareDeps() is deprecated, use 'spotlessPredeclare { ... }' directly instead.");
predeclare(GradleProvisioner.Policy.ROOT_PROJECT);
}

protected void predeclare(GradleProvisioner.Policy policy) {
project.getExtensions().create(SpotlessExtensionPredeclare.class, EXTENSION_PREDECLARE, SpotlessExtensionPredeclare.class, project, policy);
void predeclare(GradleProvisioner.Policy policy) {
project.getExtensions().getByType(SpotlessExtensionPredeclare.class).enablePredeclare(policy);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,43 +18,109 @@
import java.util.SortedMap;
import java.util.TreeMap;

import javax.annotation.Nullable;

import org.gradle.api.Action;
import org.gradle.api.GradleException;
import org.gradle.api.Project;
import org.gradle.api.tasks.TaskProvider;

import com.diffplug.spotless.LazyForwardingEquality;

public class SpotlessExtensionPredeclare extends SpotlessExtension {
private final SortedMap<String, FormatExtension> toSetup = new TreeMap<>();
private final RegisterDependenciesTask registerDependenciesTask;
private @Nullable GradleProvisioner.Policy policy;
private boolean policyExplicit;
private @Nullable RegisterDependenciesTask registerDependenciesTask;

public SpotlessExtensionPredeclare(Project project, GradleProvisioner.Policy policy) {
public SpotlessExtensionPredeclare(Project project) {
super(project);
this.registerDependenciesTask = findRegisterDepsTask().get();
SpotlessTaskService taskService = getSpotlessTaskService().get();
taskService.registerDependenciesTask = registerDependenciesTask;
taskService.predeclaredProvisioner = policy.dedupingProvisioner(project);
taskService.predeclaredP2Provisioner = policy.dedupingP2Provisioner(project);
project.afterEvaluate(unused -> toSetup.forEach((name, formatExtension) -> {
for (Action<FormatExtension> lazyAction : formatExtension.lazyActions) {
lazyAction.execute(formatExtension);
project.afterEvaluate(unused -> {
if (policy == null) {
return;
}
RegisterDependenciesTask task = registerDependenciesTask;
if (task == null) {
throw new IllegalStateException("spotlessPredeclare was enabled without a register dependencies task.");
}
registerDependenciesTask.steps.addAll(formatExtension.steps);
// needed to fix Deemon memory leaks (#1194), but this line came from https://github.com/diffplug/spotless/pull/1206
LazyForwardingEquality.unlazy(registerDependenciesTask.steps);
}));
toSetup.forEach((name, formatExtension) -> {
for (Action<FormatExtension> lazyAction : formatExtension.lazyActions) {
lazyAction.execute(formatExtension);
}
task.steps.addAll(formatExtension.steps);
// needed to fix Deemon memory leaks (#1194), but this line came from https://github.com/diffplug/spotless/pull/1206
LazyForwardingEquality.unlazy(task.steps);
});
});
}

@Override
protected void createFormatTasks(String name, FormatExtension formatExtension) {
enablePredeclareIfAbsent(GradleProvisioner.Policy.ROOT_PROJECT);
toSetup.put(name, formatExtension);
}

@Override
protected void predeclare(GradleProvisioner.Policy policy) {
void predeclare(GradleProvisioner.Policy policy) {
throw new UnsupportedOperationException("predeclare can't be called from within `" + EXTENSION_PREDECLARE + "`");
}

/**
* Resolves predeclared dependencies from the root project's repositories.
* <p>
* This is the default behavior when any format is declared in {@code spotlessPredeclare}.
*
* @see SpotlessExtension#predeclareDeps()
*/
public void fromProjectRepositories() {
enablePredeclare(GradleProvisioner.Policy.ROOT_PROJECT);
}

/**
* Resolves predeclared dependencies from the root project's {@code buildscript} repositories.
* <p>
* Use this when formatter dependencies should be resolved from {@code buildscript { repositories { ... } }}
* instead of the root project's normal repositories.
*
* @see SpotlessExtension#predeclareDepsFromBuildscript()
*/
public void fromBuildscriptRepositories() {
enablePredeclare(GradleProvisioner.Policy.ROOT_BUILDSCRIPT);
}

void enablePredeclare(GradleProvisioner.Policy policy) {
enablePredeclare(policy, true);
}

private void enablePredeclareIfAbsent(GradleProvisioner.Policy policy) {
enablePredeclare(policy, false);
}

private void enablePredeclare(GradleProvisioner.Policy policy, boolean explicit) {
if (this.policy != null) {
if (!explicit || !policyExplicit) {
if (explicit) {
this.policy = policy;
this.policyExplicit = true;
configureProvisioners(policy);
}
return;
}
throw new GradleException("predeclared dependency resolution can only be configured once.");
}
this.policy = policy;
this.policyExplicit = explicit;
registerDependenciesTask = findRegisterDepsTask().get();
configureProvisioners(policy);
}

private void configureProvisioners(GradleProvisioner.Policy policy) {
SpotlessTaskService taskService = getSpotlessTaskService().get();
taskService.registerDependenciesTask = registerDependenciesTask;
taskService.predeclaredProvisioner = policy.dedupingProvisioner(project);
taskService.predeclaredP2Provisioner = policy.dedupingP2Provisioner(project);
}

private TaskProvider<RegisterDependenciesTask> findRegisterDepsTask() {
try {
return findRegisterDepsTask(RegisterDependenciesTask.TASK_NAME);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2016-2025 DiffPlug
* Copyright 2016-2026 DiffPlug
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -50,6 +50,9 @@ public void apply(Project project) {

// setup the extension
project.getExtensions().create(SpotlessExtension.class, SpotlessExtension.EXTENSION, SpotlessExtensionImpl.class, project);
if (project.getRootProject() == project) {
project.getExtensions().create(SpotlessExtensionPredeclare.class, SpotlessExtension.EXTENSION_PREDECLARE, SpotlessExtensionPredeclare.class, project);
}

// clear spotless' cache when the user does a clean
// resolution for: https://github.com/diffplug/spotless/issues/243#issuecomment-564323856
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,37 +92,60 @@ public void predeclaredFails() throws IOException {
}

@Test
public void predeclaredSucceeds() throws IOException {
public void predeclaredSucceeds_deprecatedAPI() throws IOException {
setFile("build.gradle").toLines(
"plugins {",
" id 'com.diffplug.spotless'",
"}",
"repositories { mavenCentral() }",
"spotless { predeclareDeps() }",
"",
"spotlessPredeclare {",
" java { googleJavaFormat('1.17.0') }",
" java { googleJavaFormat('1.17.0') }",
"}");
createNSubprojects();
gradleRunner().withArguments("spotlessApply").build();
}

@Test
public void predeclaredFromBuildscriptSucceeds() throws IOException {
public void predeclaredFromBuildscriptSucceeds_deprecatedAPI() throws IOException {
setFile("build.gradle").toLines(
"buildscript {",
" repositories { mavenCentral() }",
"}",
"plugins {",
" id 'com.diffplug.spotless'",
"}",
"repositories { mavenCentral() }",
"spotless { predeclareDepsFromBuildscript() }",
"spotlessPredeclare {",
" java { googleJavaFormat('1.17.0') }",
" java { googleJavaFormat('1.17.0') }",
"}");
createNSubprojects();
gradleRunner().withArguments("spotlessApply").build();
}

@Test
public void predeclaredFromBuildscriptInPredeclareBlockSucceeds() throws IOException {
setFile("build.gradle").toLines(
"buildscript {",
" repositories { mavenCentral() }",
"}",
"plugins {",
" id 'com.diffplug.spotless'",
"}",
"repositories { mavenCentral() }",
"",
"spotlessPredeclare {",
" fromBuildscriptRepositories()",
" java { googleJavaFormat('1.17.0') }",
"}");
createNSubprojects();
gradleRunner().withArguments("spotlessApply").build();
}

@Test
public void predeclaredOrdering() throws IOException {
public void predeclaredOrderingIsFlexible() throws IOException {
setFile("build.gradle").toLines(
"plugins {",
" id 'com.diffplug.spotless'",
Expand All @@ -131,10 +154,9 @@ public void predeclaredOrdering() throws IOException {
"spotlessPredeclare {",
" java { googleJavaFormat('1.17.0') }",
"}",
"spotless { predeclareDepsFromBuildscript() }");
"spotless { predeclareDeps() }");
createNSubprojects();
Assertions.assertThat(gradleRunner().withArguments("spotlessApply").buildAndFail().getOutput())
.contains("Could not find method spotlessPredeclare() for arguments");
gradleRunner().withArguments("spotlessApply").build();
}

@Test
Expand Down Expand Up @@ -168,7 +190,7 @@ public void predeclaredDepsRegression() throws IOException {
}

@Test
public void predeclaredUndeclared() throws IOException {
public void predeclaredWithoutSpotlessBlockSucceeds() throws IOException {
setFile("build.gradle").toLines(
"plugins {",
" id 'com.diffplug.spotless'",
Expand All @@ -178,7 +200,6 @@ public void predeclaredUndeclared() throws IOException {
" java { googleJavaFormat('1.17.0') }",
"}");
createNSubprojects();
Assertions.assertThat(gradleRunner().withArguments("spotlessApply").buildAndFail().getOutput())
.contains("Could not find method spotlessPredeclare() for arguments");
gradleRunner().withArguments("spotlessApply").build();
}
}
Loading
Loading