Skip to content

Add support for predeclared dependencies. #1039

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
Dec 23, 2021
Merged
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
18 changes: 9 additions & 9 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
@@ -108,14 +108,14 @@ jobs:
- *restore_cache_wrapper
- *restore_cache_deps
- run:
name: gradlew npmTest
command: export SPOTLESS_EXCLUDE_MAVEN=true && ./gradlew npmTest --build-cache
name: gradlew testNpm
command: export SPOTLESS_EXCLUDE_MAVEN=true && ./gradlew testNpm --build-cache
- store_test_results:
path: testlib/build/test-results/NpmTest
path: testlib/build/test-results/testNpm
- store_test_results:
path: plugin-maven/build/test-results/NpmTest
path: plugin-maven/build/test-results/testNpm
- store_test_results:
path: plugin-gradle/build/test-results/NpmTest
path: plugin-gradle/build/test-results/testNpm
- run:
name: gradlew test
command: export SPOTLESS_EXCLUDE_MAVEN=true && ./gradlew test --build-cache
@@ -141,12 +141,12 @@ jobs:
- store_test_results:
path: plugin-gradle/build/test-results/test
- run:
name: gradlew npmTest
command: gradlew npmTest --build-cache -PSPOTLESS_EXCLUDE_MAVEN=true
name: gradlew testNpm
command: gradlew testNpm --build-cache -PSPOTLESS_EXCLUDE_MAVEN=true
- store_test_results:
path: testlib/build/test-results/NpmTest
path: testlib/build/test-results/testNpm
- store_test_results:
path: plugin-gradle/build/test-results/NpmTest
path: plugin-gradle/build/test-results/testNpm
changelog_print:
<< : *env_gradle
steps:
4 changes: 2 additions & 2 deletions gradle/special-tests.gradle
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
apply plugin: 'org.gradle.test-retry'

apply plugin: 'com.adarshr.test-logger'
def special = [
'Npm',
'Black',
@@ -21,7 +21,7 @@ tasks.named('test') {
}

special.forEach { tag ->
tasks.register("${tag}Test", Test) {
tasks.register("test${tag}", Test) {
useJUnitPlatform { includeTags tag }
}
}
19 changes: 18 additions & 1 deletion plugin-gradle/CHANGES.md
Original file line number Diff line number Diff line change
@@ -3,6 +3,23 @@
We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format (starting after version `3.27.0`).

## [Unreleased]
### Added
* You can now predeclare formatter dependencies in the root project.
* specify one of:
* `spotless { predeclareDeps() }` to resolve all deps from the root project, which will show up in dependency reports.
* `spotless { predeclareDepsFromBuildscript() }` to resolve all deps from `buildscript { repositories {`, which will not show up in dependency reports ([see #1027](https://github.com/diffplug/spotless/issues/1027)).
* and then below that you have a block where you simply declare each formatter which you are using, e.g.
* ```
spotless {
...
predeclareDepsFromBuildscript()
}
spotlessPredeclare {
java { eclipse() }
kotlin { ktfmt('0.28') }
}
```
* By default, Spotless resolves all dependencies per-project, and the predeclaration above is unnecessary in the vast majority of cases.
## [6.0.5] - 2021-12-16
### Fixed
@@ -42,7 +59,7 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format (
* To make this daemon-restriction obsolete, please see and upvote [#987](https://github.com/diffplug/spotless/issues/987).
### Changed
* **BREAKING** Previously, many projects required `buildscript { repositories { mavenCentral() }}` at the top of their root project, because Spotless resolved its dependencies using the buildscript repositories. Spotless now resolves its dependencies from the normal project repositories of each project with a `spotless {...}` block. This means that you can remove the `buildscript {}` block, but you still need a `repositories { mavenCentral() }` (or similar) in each project which is using Spotless. ([#980](https://github.com/diffplug/spotless/pull/980), [#983](https://github.com/diffplug/spotless/pull/983))
* If you prefer the old behavior, we are open to adding that back as a new feature, see [#984 predeclared dependencies](https://github.com/diffplug/spotless/issues/984).
* If you prefer the old behavior, it is available via [`predeclareDepsFromBuildscript()` starting in `6.1.0`](../README.md#dependency-resolution-modes).
* **BREAKING** `createIndepentApplyTask(String taskName)` now requires that `taskName` does not end with `Apply`
* Bump minimum required Gradle from `6.1` to `6.1.1`.
* Bump default formatter versions ([#989](https://github.com/diffplug/spotless/pull/989))
21 changes: 21 additions & 0 deletions plugin-gradle/README.md
Original file line number Diff line number Diff line change
@@ -86,6 +86,7 @@ Spotless supports all of Gradle's built-in performance features (incremental bui
- [Multiple (or custom) language-specific blocks](#multiple-or-custom-language-specific-blocks)
- [Inception (languages within languages within...)](#inception-languages-within-languages-within)
- [Disabling warnings and error messages](#disabling-warnings-and-error-messages)
- [Dependency resolution modes](#dependency-resolution-modes)
- [How do I preview what `spotlessApply` will do?](#how-do-i-preview-what-spotlessapply-will-do)
- [Example configurations (from real-world projects)](#example-configurations-from-real-world-projects)

@@ -910,6 +911,26 @@ spotless {
ignoreErrorForPath('path/to/file.java') // ignore errors by all steps on this specific file
```
<a name="dependency-resolution-modes"></a>
## Dependency resolution modes
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') }
}
```
Alternatively, you can also use `predeclareDepsFromBuildscript()` to resolve the dependencies from the buildscript repositories rather than the project repositories.
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.
<a name="preview"></a>
## How do I preview what `spotlessApply` will do?
Original file line number Diff line number Diff line change
@@ -73,7 +73,7 @@ public FormatExtension(SpotlessExtension spotless) {
}

protected final Provisioner provisioner() {
return spotless.getRegisterDependenciesTask().getTaskService().get().provisionerFor(spotless.project);
return spotless.getRegisterDependenciesTask().getTaskService().get().provisionerFor(spotless);
}

private String formatName() {
@@ -757,16 +757,6 @@ protected void setupTask(SpotlessTask task) {
} else {
steps = this.steps;
}
// <IMPORTANT>
// By calling .hashCode, we are triggering all steps to evaluate their state,
// which triggers dependency resolution. It's important to do that here, because
// otherwise it won't happen until Gradle starts checking for task up-to-date-ness.
// For a large parallel build, the task up-to-dateness might get called on a different
// thread than the thread where task configuration happens, which will trigger a
// java.util.ConcurrentModificationException
// See https://github.com/diffplug/spotless/issues/1015 for details.
steps.hashCode();
// </IMPORTANT>
task.setSteps(steps);
task.setLineEndingsPolicy(getLineEndings().createPolicy(getProject().getProjectDir(), () -> totalTarget));
spotless.getRegisterDependenciesTask().hookSubprojectTask(task);
Original file line number Diff line number Diff line change
@@ -19,61 +19,102 @@
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.gradle.api.GradleException;
import org.gradle.api.Project;
import org.gradle.api.artifacts.Configuration;
import org.gradle.api.artifacts.ConfigurationContainer;
import org.gradle.api.artifacts.dsl.DependencyHandler;
import org.gradle.api.attributes.Bundling;
import org.gradle.api.initialization.dsl.ScriptHandler;

import com.diffplug.common.base.Unhandled;
import com.diffplug.common.collect.ImmutableList;
import com.diffplug.spotless.Provisioner;

/** Should be package-private. */
class GradleProvisioner {
private GradleProvisioner() {}

static Provisioner newDedupingProvisioner(Project project) {
return new DedupingProvisioner(project);
enum Policy {
INDEPENDENT, ROOT_PROJECT, ROOT_BUILDSCRIPT;

public DedupingProvisioner dedupingProvisioner(Project project) {
switch (this) {
case ROOT_PROJECT:
return new DedupingProvisioner(forProject(project));
case ROOT_BUILDSCRIPT:
return new DedupingProvisioner(forRootProjectBuildscript(project));
case INDEPENDENT:
default:
throw Unhandled.enumException(this);
}
}
}

static class DedupingProvisioner implements Provisioner {
private final Project project;
private final Provisioner provisioner;
private final Map<Request, Set<File>> cache = new HashMap<>();

DedupingProvisioner(Project project) {
this.project = project;
DedupingProvisioner(Provisioner provisioner) {
this.provisioner = provisioner;
}

@Override
public Set<File> provisionWithTransitives(boolean withTransitives, Collection<String> mavenCoordinates) {
Request req = new Request(withTransitives, mavenCoordinates);
Set<File> result = cache.get(req);
Set<File> result;
synchronized (cache) {
result = cache.get(req);
}
if (result != null) {
return result;
} else {
result = cache.get(req);
if (result != null) {
return result;
} else {
result = forProject(project).provisionWithTransitives(req.withTransitives, req.mavenCoords);
cache.put(req, result);
synchronized (cache) {
result = cache.get(req);
if (result == null) {
result = provisioner.provisionWithTransitives(req.withTransitives, req.mavenCoords);
cache.put(req, result);
}
return result;
}
}
}

/** A child Provisioner which retries cached elements only. */
final Provisioner cachedOnly = (withTransitives, mavenCoordinates) -> {
Request req = new Request(withTransitives, mavenCoordinates);
Set<File> result;
synchronized (cache) {
result = cache.get(req);
}
if (result != null) {
return result;
}
throw new GradleException("Add a step with " + req.mavenCoords + " into the `spotlessPredeclare` block in the root project.");
};
}

static Provisioner forProject(Project project) {
return forConfigurationContainer(project, project.getConfigurations(), project.getDependencies());
}

static Provisioner forRootProjectBuildscript(Project project) {
Project rootProject = project.getRootProject();
ScriptHandler buildscript = rootProject.getBuildscript();
return forConfigurationContainer(rootProject, buildscript.getConfigurations(), buildscript.getDependencies());
}

private static Provisioner forProject(Project project) {
Objects.requireNonNull(project);
private static Provisioner forConfigurationContainer(Project project, ConfigurationContainer configurations, DependencyHandler dependencies) {
return (withTransitives, mavenCoords) -> {
try {
Configuration config = project.getConfigurations().create("spotless"
Configuration config = configurations.create("spotless"
+ new Request(withTransitives, mavenCoords).hashCode());
mavenCoords.stream()
.map(project.getDependencies()::create)
.map(dependencies::create)
.forEach(config.getDependencies()::add);
config.setDescription(mavenCoords.toString());
config.setTransitive(withTransitives);
Original file line number Diff line number Diff line change
@@ -15,16 +15,26 @@
*/
package com.diffplug.gradle.spotless;

import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;

import javax.inject.Inject;

import org.gradle.api.DefaultTask;
import org.gradle.api.provider.Property;
import org.gradle.api.services.BuildServiceRegistry;
import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.Internal;
import org.gradle.api.tasks.OutputFile;
import org.gradle.api.tasks.TaskAction;
import org.gradle.build.event.BuildEventsListenerRegistry;

import com.diffplug.common.base.Preconditions;
import com.diffplug.common.io.Files;
import com.diffplug.spotless.FormatterStep;

/**
* NOT AN END-USER TASK, DO NOT USE FOR ANYTHING!
@@ -40,11 +50,10 @@ public abstract class RegisterDependenciesTask extends DefaultTask {
static final String TASK_NAME = "spotlessInternalRegisterDependencies";

void hookSubprojectTask(SpotlessTask task) {
// TODO: in the future, we might use this hook to implement #984
// spotlessSetup {
// java { googleJavaFormat('1.2') }
// ...etc
// }
// this ensures that if a user is using predeclared dependencies,
// those predeclared deps will be resolved before they are needed
// by the child tasks
//
// it's also needed to make sure that jvmLocalCache gets set
// in the SpotlessTaskService before any spotless tasks run
task.dependsOn(this);
@@ -56,11 +65,27 @@ void setup() {
BuildServiceRegistry buildServices = getProject().getGradle().getSharedServices();
getTaskService().set(buildServices.registerIfAbsent("SpotlessTaskService" + compositeBuildSuffix, SpotlessTaskService.class, spec -> {}));
getBuildEventsListenerRegistry().onTaskCompletion(getTaskService());
unitOutput = new File(getProject().getBuildDir(), "tmp/spotless-register-dependencies");
}

List<FormatterStep> steps = new ArrayList<>();

@Input
public List<FormatterStep> getSteps() {
return steps;
}

File unitOutput;

@OutputFile
public File getUnitOutput() {
return unitOutput;
}

@TaskAction
public void trivialFunction() {
// nothing to do :)
public void trivialFunction() throws IOException {
Files.createParentDirs(unitOutput);
Files.write(Integer.toString(1), unitOutput, StandardCharsets.UTF_8);
}

@Internal
Original file line number Diff line number Diff line change
@@ -27,26 +27,33 @@
import org.gradle.api.Action;
import org.gradle.api.GradleException;
import org.gradle.api.Project;
import org.gradle.api.tasks.TaskContainer;
import org.gradle.api.tasks.TaskProvider;

import com.diffplug.spotless.LineEnding;

public abstract class SpotlessExtension {
final Project project;
private final RegisterDependenciesTask registerDependenciesTask;

protected static final String TASK_GROUP = "Verification";
protected static final String CHECK_DESCRIPTION = "Checks that sourcecode satisfies formatting steps.";
protected static final String APPLY_DESCRIPTION = "Applies code formatting steps to sourcecode in-place.";

static final String EXTENSION = "spotless";
static final String EXTENSION_PREDECLARE = "spotlessPredeclare";
static final String CHECK = "Check";
static final String APPLY = "Apply";
static final String DIAGNOSE = "Diagnose";

protected SpotlessExtension(Project project) {
this.project = requireNonNull(project);
this.registerDependenciesTask = findRegisterDepsTask().get();
}

abstract RegisterDependenciesTask getRegisterDependenciesTask();
RegisterDependenciesTask getRegisterDependenciesTask() {
return registerDependenciesTask;
}

/** Line endings (if any). */
LineEnding lineEndings = LineEnding.GIT_ATTRIBUTES;
@@ -231,4 +238,43 @@ <T extends FormatExtension> T instantiateFormatExtension(Class<T> clazz) {
}

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

TaskProvider<RegisterDependenciesTask> findRegisterDepsTask() {
try {
return findRegisterDepsTask(RegisterDependenciesTask.TASK_NAME);
} catch (Exception e) {
// in a composite build there can be multiple Spotless plugins on the classpath, and they will each try to register
// a task on the root project with the same name. That will generate casting errors, which we can catch and try again
// with an identity-specific identifier.
// https://github.com/diffplug/spotless/pull/1001 for details
return findRegisterDepsTask(RegisterDependenciesTask.TASK_NAME + System.identityHashCode(RegisterDependenciesTask.class));
}
}

private TaskProvider<RegisterDependenciesTask> findRegisterDepsTask(String taskName) {
TaskContainer rootProjectTasks = project.getRootProject().getTasks();
if (!rootProjectTasks.getNames().contains(taskName)) {
return rootProjectTasks.register(taskName, RegisterDependenciesTask.class, RegisterDependenciesTask::setup);
} else {
return rootProjectTasks.named(taskName, RegisterDependenciesTask.class);
}
}

public void predeclareDepsFromBuildscript() {
if (project.getRootProject() != project) {
throw new GradleException("predeclareDepsFromBuildscript can only be called from the root project");
}
predeclare(GradleProvisioner.Policy.ROOT_BUILDSCRIPT);
}

public void predeclareDeps() {
if (project.getRootProject() != project) {
throw new GradleException("predeclareDeps can only be called from the root project");
}
predeclare(GradleProvisioner.Policy.ROOT_PROJECT);
}

protected void predeclare(GradleProvisioner.Policy policy) {
project.getExtensions().create(SpotlessExtension.class, EXTENSION_PREDECLARE, SpotlessExtensionPredeclare.class, project, policy);
}
}
Original file line number Diff line number Diff line change
@@ -23,7 +23,6 @@
import org.gradle.api.tasks.TaskProvider;

public class SpotlessExtensionImpl extends SpotlessExtension {
private final TaskProvider<RegisterDependenciesTask> registerDependenciesTask;
final TaskProvider<?> rootCheckTask, rootApplyTask, rootDiagnoseTask;

public SpotlessExtensionImpl(Project project) {
@@ -39,7 +38,6 @@ public SpotlessExtensionImpl(Project project) {
rootDiagnoseTask = project.getTasks().register(EXTENSION + DIAGNOSE, task -> {
task.setGroup(TASK_GROUP); // no description on purpose
});
registerDependenciesTask = findRegisterDepsTask();

project.afterEvaluate(unused -> {
if (enforceCheck) {
@@ -53,31 +51,6 @@ public SpotlessExtensionImpl(Project project) {
});
}

private TaskProvider<RegisterDependenciesTask> findRegisterDepsTask() {
try {
return findRegisterDepsTask(RegisterDependenciesTask.TASK_NAME);
} catch (Exception e) {
// in a composite build there can be multiple Spotless plugins on the classpath, and they will each try to register
// a task on the root project with the same name. That will generate casting errors, which we can catch and try again
// with an identity-specific identifier.
// https://github.com/diffplug/spotless/pull/1001 for details
return findRegisterDepsTask(RegisterDependenciesTask.TASK_NAME + System.identityHashCode(RegisterDependenciesTask.class));
}
}

private TaskProvider<RegisterDependenciesTask> findRegisterDepsTask(String taskName) {
TaskContainer rootProjectTasks = project.getRootProject().getTasks();
if (!rootProjectTasks.getNames().contains(taskName)) {
return rootProjectTasks.register(taskName, RegisterDependenciesTask.class, RegisterDependenciesTask::setup);
} else {
return rootProjectTasks.named(taskName, RegisterDependenciesTask.class);
}
}

RegisterDependenciesTask getRegisterDependenciesTask() {
return registerDependenciesTask.get();
}

@Override
protected void createFormatTasks(String name, FormatExtension formatExtension) {
boolean isIdeHook = project.hasProperty(IdeHook.PROPERTY);
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* Copyright 2021 DiffPlug
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.diffplug.gradle.spotless;

import java.util.SortedMap;
import java.util.TreeMap;

import org.gradle.api.Action;
import org.gradle.api.Project;

public class SpotlessExtensionPredeclare extends SpotlessExtension {
private final SortedMap<String, FormatExtension> toSetup = new TreeMap<>();

public SpotlessExtensionPredeclare(Project project, GradleProvisioner.Policy policy) {
super(project);
getRegisterDependenciesTask().getTaskService().get().predeclaredProvisioner = policy.dedupingProvisioner(project);
project.afterEvaluate(unused -> {
toSetup.forEach((name, formatExtension) -> {
for (Action<FormatExtension> lazyAction : formatExtension.lazyActions) {
lazyAction.execute(formatExtension);
}
getRegisterDependenciesTask().steps.addAll(formatExtension.steps);
});
});
}

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

@Override
protected void predeclare(GradleProvisioner.Policy policy) {
throw new UnsupportedOperationException("predeclare can't be called from within `" + EXTENSION_PREDECLARE + "`");
}
}
Original file line number Diff line number Diff line change
@@ -20,10 +20,10 @@
import java.util.HashMap;
import java.util.Map;

import javax.annotation.Nullable;
import javax.inject.Inject;

import org.gradle.api.DefaultTask;
import org.gradle.api.Project;
import org.gradle.api.file.DirectoryProperty;
import org.gradle.api.model.ObjectFactory;
import org.gradle.api.provider.Property;
@@ -48,10 +48,19 @@ public abstract class SpotlessTaskService implements BuildService<BuildServicePa
private final Map<String, SpotlessTask> source = Collections.synchronizedMap(new HashMap<>());
private final Map<String, Provisioner> provisioner = Collections.synchronizedMap(new HashMap<>());

Provisioner provisionerFor(Project project) {
return provisioner.computeIfAbsent(project.getPath(), unused -> {
return GradleProvisioner.newDedupingProvisioner(project);
});
@Nullable
GradleProvisioner.DedupingProvisioner predeclaredProvisioner;

Provisioner provisionerFor(SpotlessExtension spotless) {
if (spotless instanceof SpotlessExtensionPredeclare) {
return predeclaredProvisioner;
} else {
if (predeclaredProvisioner != null) {
return predeclaredProvisioner.cachedOnly;
} else {
return provisioner.computeIfAbsent(spotless.project.getPath(), unused -> new GradleProvisioner.DedupingProvisioner(GradleProvisioner.forProject(spotless.project)));
}
}
}

void registerSourceAlreadyRan(SpotlessTask task) {
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
/*
* Copyright 2016-2021 DiffPlug
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.diffplug.gradle.spotless;

import java.io.IOException;

import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;

import com.diffplug.common.base.StringPrinter;

class MultiProjectTest extends GradleIntegrationHarness {
private static int N = 100;

private void createNSubprojects() throws IOException {
for (int i = 0; i < N; ++i) {
createSubproject(Integer.toString(i));
}
String settings = StringPrinter.buildString(printer -> {
for (int i = 0; i < N; ++i) {
printer.println("include '" + i + "'");
}
});
setFile("settings.gradle").toContent(settings);
}

void createSubproject(String name) throws IOException {
setFile(name + "/build.gradle").toLines(
"plugins {",
" id 'com.diffplug.spotless'",
"}",
"repositories { mavenCentral() }",
"",
"spotless {",
" java {",
" target file('test.java')",
" googleJavaFormat('1.2')",
" }",
"}");
setFile(name + "/test.java").toResource("java/googlejavaformat/JavaCodeUnformatted.test");
}

@Test
public void noRootSpotless() throws IOException {
createNSubprojects();
setFile("build.gradle").toLines();
applyIsUpToDate(false);
}

@Test
public void hasRootSpotless() throws IOException {
setFile("build.gradle").toLines(
"plugins {",
" id 'com.diffplug.spotless'",
"}",
"repositories { mavenCentral() }",
"",
"spotless {",
" java {",
" target file('test.java')",
" googleJavaFormat('1.2')",
" }",
"}");
setFile("test.java").toResource("java/googlejavaformat/JavaCodeUnformatted.test");
createNSubprojects();
applyIsUpToDate(false);
}

@Test
public void predeclaredFails() throws IOException {
setFile("build.gradle").toLines(
"plugins {",
" id 'com.diffplug.spotless'",
"}",
"spotless { predeclareDeps() }");
createNSubprojects();
Assertions.assertThat(gradleRunner().withArguments("spotlessApply").buildAndFail().getOutput())
.contains("Add a step with [com.google.googlejavaformat:google-java-format:1.2] into the `spotlessPredeclare` block in the root project.");
}

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

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

@Test
public void predeclaredOrdering() throws IOException {
setFile("build.gradle").toLines(
"plugins {",
" id 'com.diffplug.spotless'",
"}",
"repositories { mavenCentral() }",
"spotlessPredeclare {",
" java { googleJavaFormat('1.2') }",
"}",
"spotless { predeclareDepsFromBuildscript() }");
createNSubprojects();
Assertions.assertThat(gradleRunner().withArguments("spotlessApply").buildAndFail().getOutput())
.contains("Could not find method spotlessPredeclare() for arguments");
}

@Test
public void predeclaredUndeclared() throws IOException {
setFile("build.gradle").toLines(
"plugins {",
" id 'com.diffplug.spotless'",
"}",
"repositories { mavenCentral() }",
"spotlessPredeclare {",
" java { googleJavaFormat('1.2') }",
"}");
createNSubprojects();
Assertions.assertThat(gradleRunner().withArguments("spotlessApply").buildAndFail().getOutput())
.contains("Could not find method spotlessPredeclare() for arguments");
}
}
Original file line number Diff line number Diff line change
@@ -88,8 +88,8 @@ void testPathologicalCase() throws IOException {
// the format task is UP-TO-DATE (same inputs), but the apply tasks will run again
pauseForFilesystem();
BuildResult buildResult = gradleRunner().withArguments("spotlessApply").build();
Assertions.assertThat(buildResult.taskPaths(TaskOutcome.UP_TO_DATE)).containsExactly(":spotlessMisc");
Assertions.assertThat(buildResult.taskPaths(TaskOutcome.SUCCESS)).containsExactly(":spotlessInternalRegisterDependencies", ":spotlessMiscApply", ":spotlessApply");
Assertions.assertThat(buildResult.taskPaths(TaskOutcome.UP_TO_DATE)).containsExactly(":spotlessInternalRegisterDependencies", ":spotlessMisc");
Assertions.assertThat(buildResult.taskPaths(TaskOutcome.SUCCESS)).containsExactly(":spotlessMiscApply", ":spotlessApply");
assertFile("README.md").hasContent("abc");

// and it'll take two more runs to get to fully UP-TO-DATE
3 changes: 3 additions & 0 deletions settings.gradle
Original file line number Diff line number Diff line change
@@ -13,6 +13,8 @@ pluginManagement {
id 'com.diffplug.p2.asmaven' version '3.27.0' // DO NOT UPDATE, see https://github.com/diffplug/spotless/pull/874
// https://github.com/gradle/test-retry-gradle-plugin/releases
id 'org.gradle.test-retry' version '1.3.1'
// https://github.com/radarsh/gradle-test-logger-plugin/blob/develop/CHANGELOG.md
id 'com.adarshr.test-logger' version '3.1.0'
}
}
plugins {
@@ -23,6 +25,7 @@ plugins {
id 'com.diffplug.spotless-changelog' apply false
id 'com.diffplug.p2.asmaven' apply false
id 'org.gradle.test-retry' apply false
id 'com.adarshr.test-logger' apply false
}
if (System.env['CI'] != null) {
// use the remote buildcache on all CI builds