diff --git a/.circleci/config.yml b/.circleci/config.yml index 8890ceeccd..94fda38f08 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -92,6 +92,17 @@ jobs: command: ./gradlew :plugin-maven:check --build-cache - store_test_results: path: plugin-maven/build/test-results/test + test_gradle_modern_8: + << : *env_gradle + steps: + - checkout + - *restore_cache_wrapper + - *restore_cache_deps + - run: + name: gradlew :plugin-gradle:modernTest + command: ./gradlew :plugin-gradle:modernTest --build-cache + - store_test_results: + path: plugin-gradle/build/test-results/modernTest test_npm_8: << : *env_gradle docker: @@ -128,8 +139,8 @@ jobs: # do the test - restore_cache: keys: - - gradle-deps-win-{{ checksum "build.gradle" }}-{{ checksum "gradle.properties" }} - - gradle-deps-win- + - gradle-deps-win2-{{ checksum "build.gradle" }}-{{ checksum "gradle.properties" }} + - gradle-deps-win2- - run: name: gradlew check command: gradlew check --build-cache @@ -142,12 +153,12 @@ jobs: - store_test_results: path: plugin-maven/build/test-results/test - save_cache: + key: gradle-deps-win2-{{ checksum "build.gradle" }}-{{ checksum "gradle.properties" }} paths: - ~/.gradle/caches - ~/.gradle/wrapper - ~/.m2 - ~/project/plugin-maven/build/localMavenRepository - key: gradle-deps-win-{{ checksum "build.gradle" }}-{{ checksum "gradle.properties" }} changelog_print: << : *env_gradle steps: @@ -215,7 +226,10 @@ workflows: - test_npm_8: requires: - assemble_testClasses - deploy: + - test_gradle_modern_8: + requires: + - assemble_testClasses +deploy: jobs: - changelog_print: filters: diff --git a/plugin-gradle/CHANGES.md b/plugin-gradle/CHANGES.md index 80499440d0..384cb4b582 100644 --- a/plugin-gradle/CHANGES.md +++ b/plugin-gradle/CHANGES.md @@ -3,6 +3,8 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format (starting after version `3.27.0`). ## [Unreleased] +### Added +* (spotless devs only) if you specify `-PspotlessModern=true` Spotless will run the in-progress Gradle `5.4+` code. The `modernTest` build task runs our test suite in this way. It will be weeks/months before this is recommended for end-users. ([#598](https://github.com/diffplug/spotless/pull/598)) ## [4.2.1] - 2020-06-04 ### Fixed diff --git a/plugin-gradle/build.gradle b/plugin-gradle/build.gradle index ff4f4d2901..272beb7a21 100644 --- a/plugin-gradle/build.gradle +++ b/plugin-gradle/build.gradle @@ -41,6 +41,13 @@ task npmTest(type: Test) { } } +task modernTest(type: Test) { + systemProperty 'spotlessModern', 'true' + useJUnit { + excludeCategories 'com.diffplug.spotless.category.NpmTest', 'com.diffplug.gradle.spotless.ExcludeFromPluginGradleModern' + } +} + // make it easy for eclipse to run against latest build tasks.eclipse.dependsOn(pluginUnderTestMetadata) diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/CppExtension.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/CppExtension.java index 407bca0679..74f43600ae 100644 --- a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/CppExtension.java +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/CppExtension.java @@ -26,7 +26,7 @@ public class CppExtension extends FormatExtension implements HasBuiltinDelimiterForLicense { static final String NAME = "cpp"; - public CppExtension(SpotlessExtension spotless) { + public CppExtension(SpotlessExtensionBase spotless) { super(spotless); } diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/CssExtension.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/CssExtension.java index df08a5a0a0..9a51a48ca6 100644 --- a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/CssExtension.java +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/CssExtension.java @@ -30,7 +30,7 @@ public class CssExtension extends FormatExtension implements HasBuiltinDelimiterForLicense { static final String NAME = "css"; - public CssExtension(SpotlessExtension spotless) { + public CssExtension(SpotlessExtensionBase spotless) { super(spotless); } diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/FormatExtension.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/FormatExtension.java index c0195ab831..88294b3a79 100644 --- a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/FormatExtension.java +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/FormatExtension.java @@ -55,9 +55,9 @@ /** Adds a `spotless{Name}Check` and `spotless{Name}Apply` task. */ public class FormatExtension { - final SpotlessExtension spotless; + final SpotlessExtensionBase spotless; - public FormatExtension(SpotlessExtension spotless) { + public FormatExtension(SpotlessExtensionBase spotless) { this.spotless = Objects.requireNonNull(spotless); } diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/FreshMarkExtension.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/FreshMarkExtension.java index 8f24ffd06f..e8e130d6e2 100644 --- a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/FreshMarkExtension.java +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/FreshMarkExtension.java @@ -33,7 +33,7 @@ public class FreshMarkExtension extends FormatExtension { public final List>> propertyActions = new ArrayList<>(); - public FreshMarkExtension(SpotlessExtension spotless) { + public FreshMarkExtension(SpotlessExtensionBase spotless) { super(spotless); addStep(FreshMarkStep.create(() -> { Map map = new HashMap<>(); diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/GradleProvisioner.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/GradleProvisioner.java index d69dd9d073..2b0721bcaf 100644 --- a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/GradleProvisioner.java +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/GradleProvisioner.java @@ -32,14 +32,15 @@ import com.diffplug.common.collect.ImmutableList; import com.diffplug.spotless.Provisioner; -/** Gradle integration for Provisioner. */ +/** Should be package-private. */ +@Deprecated public class GradleProvisioner { private GradleProvisioner() {} - // @Deprecated - // public static Provisioner fromProject(Project project) { - // return project.getPlugins().apply(SpotlessPlugin.class).getExtension().registerDependenciesTask.rootProvisioner; - // } + @Deprecated + public static Provisioner fromProject(Project project) { + return project.getPlugins().apply(SpotlessPlugin.class).getExtension().registerDependenciesTask.rootProvisioner; + } /** The provisioner used for the root project. */ static class RootProvisioner implements Provisioner { diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/GroovyExtension.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/GroovyExtension.java index 5040e88e89..0502f0e39f 100644 --- a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/GroovyExtension.java +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/GroovyExtension.java @@ -38,7 +38,7 @@ public class GroovyExtension extends FormatExtension implements HasBuiltinDelimiterForLicense { static final String NAME = "groovy"; - public GroovyExtension(SpotlessExtension spotless) { + public GroovyExtension(SpotlessExtensionBase spotless) { super(spotless); } diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/GroovyGradleExtension.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/GroovyGradleExtension.java index fe98478c92..8e6fea83ee 100644 --- a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/GroovyGradleExtension.java +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/GroovyGradleExtension.java @@ -24,7 +24,7 @@ public class GroovyGradleExtension extends FormatExtension { private static final String GRADLE_FILE_EXTENSION = "*.gradle"; static final String NAME = "groovyGradle"; - public GroovyGradleExtension(SpotlessExtension spotless) { + public GroovyGradleExtension(SpotlessExtensionBase spotless) { super(spotless); } diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/JavaExtension.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/JavaExtension.java index 3c98b225ae..a457e75948 100644 --- a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/JavaExtension.java +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/JavaExtension.java @@ -40,7 +40,7 @@ public class JavaExtension extends FormatExtension implements HasBuiltinDelimiterForLicense { static final String NAME = "java"; - public JavaExtension(SpotlessExtension spotless) { + public JavaExtension(SpotlessExtensionBase spotless) { super(spotless); } diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/KotlinExtension.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/KotlinExtension.java index 55000b6239..be34f2a9a0 100644 --- a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/KotlinExtension.java +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/KotlinExtension.java @@ -33,7 +33,7 @@ public class KotlinExtension extends FormatExtension implements HasBuiltinDelimiterForLicense { static final String NAME = "kotlin"; - public KotlinExtension(SpotlessExtension spotless) { + public KotlinExtension(SpotlessExtensionBase spotless) { super(spotless); } diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/KotlinGradleExtension.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/KotlinGradleExtension.java index 49a6a493cf..0511fcee7b 100644 --- a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/KotlinGradleExtension.java +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/KotlinGradleExtension.java @@ -29,7 +29,7 @@ public class KotlinGradleExtension extends FormatExtension { static final String NAME = "kotlinGradle"; - public KotlinGradleExtension(SpotlessExtension spotless) { + public KotlinGradleExtension(SpotlessExtensionBase spotless) { super(spotless); } diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/ScalaExtension.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/ScalaExtension.java index 40d6782820..e081ac1160 100644 --- a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/ScalaExtension.java +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/ScalaExtension.java @@ -31,7 +31,7 @@ public class ScalaExtension extends FormatExtension { static final String NAME = "scala"; - public ScalaExtension(SpotlessExtension spotless) { + public ScalaExtension(SpotlessExtensionBase spotless) { super(spotless); } diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessApply.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessApply.java index 7261fe788a..e75950b76d 100644 --- a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessApply.java +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessApply.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 DiffPlug + * Copyright 2016-2020 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,10 +30,10 @@ import org.gradle.api.tasks.TaskAction; public class SpotlessApply extends DefaultTask { - private SpotlessTask source; + private SpotlessTaskBase source; /** Bidirectional link between Apply and Spotless allows check to know if Apply ran or not. */ - void linkSource(SpotlessTask source) { + void linkSource(SpotlessTaskBase source) { this.source = source; source.applyTask = this; } diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessCheck.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessCheck.java index c602a465b3..45b944a0de 100644 --- a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessCheck.java +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessCheck.java @@ -35,7 +35,7 @@ import com.diffplug.spotless.extra.integration.DiffMessageFormatter; public class SpotlessCheck extends DefaultTask { - SpotlessTask source; + SpotlessTaskBase source; private File spotlessOutDirectory; @PathSensitive(PathSensitivity.RELATIVE) @@ -81,9 +81,10 @@ public void visitFile(FileVisitDetails fileVisitDetails) { }); if (!problemFiles.isEmpty()) { - Formatter formatter = source.buildFormatter(); - Collections.sort(problemFiles); - throw formatViolationsFor(formatter, problemFiles); + try (Formatter formatter = source.buildFormatter()) { + Collections.sort(problemFiles); + throw formatViolationsFor(formatter, problemFiles); + } } } } diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessExtension.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessExtension.java index 272b66ff9b..fdad423438 100644 --- a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessExtension.java +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessExtension.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 DiffPlug + * Copyright 2016-2020 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,45 +15,17 @@ */ package com.diffplug.gradle.spotless; -import static java.util.Objects.requireNonNull; - -import java.io.File; -import java.lang.reflect.Constructor; -import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; -import java.util.LinkedHashMap; -import java.util.Map; - -import javax.annotation.Nullable; - import org.gradle.api.Action; -import org.gradle.api.GradleException; import org.gradle.api.Project; import org.gradle.api.Task; import org.gradle.api.plugins.BasePlugin; -import com.diffplug.common.base.Errors; -import com.diffplug.spotless.LineEnding; -import com.diffplug.spotless.npm.NodeJsGlobal; - -public class SpotlessExtension { - final Project project; +public class SpotlessExtension extends SpotlessExtensionBase { final Task rootCheckTask, rootApplyTask, rootDiagnoseTask; - final RegisterDependenciesTask registerDependenciesTask; - - static final String EXTENSION = "spotless"; - static final String CHECK = "Check"; - static final String APPLY = "Apply"; - static final String DIAGNOSE = "Diagnose"; - - private static final String TASK_GROUP = "Verification"; - private static final String CHECK_DESCRIPTION = "Checks that sourcecode satisfies formatting steps."; - private static final String APPLY_DESCRIPTION = "Applies code formatting steps to sourcecode in-place."; - private static final String FILES_PROPERTY = "spotlessFiles"; public SpotlessExtension(Project project) { - this.project = requireNonNull(project); + super(project); rootCheckTask = project.task(EXTENSION + CHECK); rootCheckTask.setGroup(TASK_GROUP); rootCheckTask.setDescription(CHECK_DESCRIPTION); @@ -62,114 +34,6 @@ public SpotlessExtension(Project project) { rootApplyTask.setDescription(APPLY_DESCRIPTION); rootDiagnoseTask = project.task(EXTENSION + DIAGNOSE); rootDiagnoseTask.setGroup(TASK_GROUP); // no description on purpose - - RegisterDependenciesTask registerDependenciesTask = (RegisterDependenciesTask) project.getRootProject().getTasks().findByName(RegisterDependenciesTask.TASK_NAME); - if (registerDependenciesTask == null) { - registerDependenciesTask = project.getRootProject().getTasks().create(RegisterDependenciesTask.TASK_NAME, RegisterDependenciesTask.class); - registerDependenciesTask.setup(); - // set where the nodejs runtime will put its temp dlls - NodeJsGlobal.setSharedLibFolder(new File(project.getBuildDir(), "spotless-nodejs-cache")); - } - this.registerDependenciesTask = registerDependenciesTask; - } - - /** Line endings (if any). */ - LineEnding lineEndings = LineEnding.GIT_ATTRIBUTES; - - public LineEnding getLineEndings() { - return lineEndings; - } - - public void setLineEndings(LineEnding lineEndings) { - this.lineEndings = requireNonNull(lineEndings); - } - - Charset encoding = StandardCharsets.UTF_8; - - /** Returns the encoding to use. */ - public Charset getEncoding() { - return encoding; - } - - /** Sets encoding to use (defaults to UTF_8). */ - public void setEncoding(String name) { - requireNonNull(name); - setEncoding(Charset.forName(name)); - } - - /** Sets encoding to use (defaults to UTF_8). */ - public void setEncoding(Charset charset) { - encoding = requireNonNull(charset); - } - - /** Sets encoding to use (defaults to UTF_8). */ - public void encoding(String charset) { - setEncoding(charset); - } - - private @Nullable String ratchetFrom; - - /** - * Limits the target to only the files which have changed since the given git reference, - * which is resolved according to [this](https://javadoc.io/static/org.eclipse.jgit/org.eclipse.jgit/5.6.1.202002131546-r/org/eclipse/jgit/lib/Repository.html#resolve-java.lang.String-) - */ - public void setRatchetFrom(String ratchetFrom) { - this.ratchetFrom = ratchetFrom; - } - - public @Nullable String getRatchetFrom() { - return ratchetFrom; - } - - public void ratchetFrom(String ratchetFrom) { - setRatchetFrom(ratchetFrom); - } - - final Map formats = new LinkedHashMap<>(); - - /** Configures the special java-specific extension. */ - public void java(Action closure) { - requireNonNull(closure); - configure(JavaExtension.NAME, JavaExtension.class, closure); - } - - /** Configures the special scala-specific extension. */ - public void scala(Action closure) { - requireNonNull(closure); - configure(ScalaExtension.NAME, ScalaExtension.class, closure); - } - - /** Configures the special kotlin-specific extension. */ - public void kotlin(Action closure) { - requireNonNull(closure); - configure(KotlinExtension.NAME, KotlinExtension.class, closure); - } - - /** Configures the special Gradle Kotlin DSL specific extension. */ - public void kotlinGradle(Action closure) { - requireNonNull(closure); - configure(KotlinGradleExtension.NAME, KotlinGradleExtension.class, closure); - } - - /** Configures the special freshmark-specific extension. */ - public void freshmark(Action closure) { - requireNonNull(closure); - configure(FreshMarkExtension.NAME, FreshMarkExtension.class, closure); - } - - /** Configures the special groovy-specific extension. */ - public void groovy(Action closure) { - configure(GroovyExtension.NAME, GroovyExtension.class, closure); - } - - /** Configures the special groovy-specific extension for Gradle files. */ - public void groovyGradle(Action closure) { - configure(GroovyGradleExtension.NAME, GroovyGradleExtension.class, closure); - } - - /** Configures the special sql-specific extension for SQL files. */ - public void sql(Action closure) { - configure(SqlExtension.NAME, SqlExtension.class, closure); } /** @@ -194,86 +58,13 @@ public void xml(Action closure) { configure(XmlExtension.NAME, XmlExtension.class, closure); } - /** Configures the special C/C++-specific extension. */ - public void cpp(Action closure) { - configure(CppExtension.NAME, CppExtension.class, closure); - } - - /** Configures the special typescript-specific extension for typescript files. */ - public void typescript(Action closure) { - configure(TypescriptExtension.NAME, TypescriptExtension.class, closure); - } - - /** Configures a custom extension. */ - public void format(String name, Action closure) { - requireNonNull(name, "name"); - requireNonNull(closure, "closure"); - configure(name, FormatExtension.class, closure); - } - - /** Makes it possible to remove a format which was created earlier. */ - public void removeFormat(String name) { - requireNonNull(name); - FormatExtension toRemove = formats.remove(name); - if (toRemove == null) { - project.getLogger().warn("Called removeFormat('" + name + "') but there was no such format."); - } - } - - boolean enforceCheck = true; - - /** Returns `true` if Gradle's `check` task should run `spotlessCheck`; `false` otherwise. */ - public boolean isEnforceCheck() { - return enforceCheck; - } - - /** - * Configures Gradle's `check` task to run `spotlessCheck` if `true`, - * but to not do so if `false`. - * - * `true` by default. - */ - public void setEnforceCheck(boolean enforceCheck) { - this.enforceCheck = enforceCheck; - } - - private void configure(String name, Class clazz, Action configure) { - T value = maybeCreate(name, clazz); - configure.execute(value); - } - - @SuppressWarnings("unchecked") - private T maybeCreate(String name, Class clazz) { - FormatExtension existing = formats.get(name); - if (existing != null) { - if (!existing.getClass().equals(clazz)) { - throw new GradleException("Tried to add format named '" + name + "'" + - " of type " + clazz + " but one has already been created of type " + existing.getClass()); - } else { - return (T) existing; - } - } else { - try { - Constructor constructor = clazz.getConstructor(SpotlessExtension.class); - T formatExtension = constructor.newInstance(this); - formats.put(name, formatExtension); - createFormatTasks(name, formatExtension); - return formatExtension; - } catch (NoSuchMethodException e) { - throw new GradleException("Must have a constructor " + clazz.getSimpleName() + "(SpotlessExtension root)", e); - } catch (Exception e) { - throw Errors.asRuntime(e); - } - } - } - /** * Creates 3 tasks for the supplied format: * - "spotless{FormatName}" is the main `SpotlessTask` that does the work for this format * - "spotless{FormatName}Check" will depend on the main spotless task in `check` mode * - "spotless{FormatName}Apply" will depend on the main spotless task in `apply` mode */ - private void createFormatTasks(String name, FormatExtension formatExtension) { + protected void createFormatTasks(String name, FormatExtension formatExtension) { // create the SpotlessTask String taskName = EXTENSION + SpotlessPlugin.capitalize(name); SpotlessTask spotlessTask = project.getTasks().create(taskName, SpotlessTask.class); diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessExtensionBase.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessExtensionBase.java new file mode 100644 index 0000000000..9ff5b03955 --- /dev/null +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessExtensionBase.java @@ -0,0 +1,236 @@ +/* + * Copyright 2016-2020 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 static java.util.Objects.requireNonNull; + +import java.io.File; +import java.lang.reflect.Constructor; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.util.LinkedHashMap; +import java.util.Map; + +import javax.annotation.Nullable; + +import org.gradle.api.Action; +import org.gradle.api.GradleException; +import org.gradle.api.Project; + +import com.diffplug.common.base.Errors; +import com.diffplug.spotless.LineEnding; +import com.diffplug.spotless.npm.NodeJsGlobal; + +public abstract class SpotlessExtensionBase { + final Project project; + 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 CHECK = "Check"; + static final String APPLY = "Apply"; + static final String DIAGNOSE = "Diagnose"; + + public SpotlessExtensionBase(Project project) { + this.project = requireNonNull(project); + + RegisterDependenciesTask registerDependenciesTask = (RegisterDependenciesTask) project.getRootProject().getTasks().findByName(RegisterDependenciesTask.TASK_NAME); + if (registerDependenciesTask == null) { + registerDependenciesTask = project.getRootProject().getTasks().create(RegisterDependenciesTask.TASK_NAME, RegisterDependenciesTask.class); + registerDependenciesTask.setup(); + // set where the nodejs runtime will put its temp dlls + NodeJsGlobal.setSharedLibFolder(new File(project.getBuildDir(), "spotless-nodejs-cache")); + } + this.registerDependenciesTask = registerDependenciesTask; + } + + /** Line endings (if any). */ + LineEnding lineEndings = LineEnding.GIT_ATTRIBUTES; + + public LineEnding getLineEndings() { + return lineEndings; + } + + public void setLineEndings(LineEnding lineEndings) { + this.lineEndings = requireNonNull(lineEndings); + } + + Charset encoding = StandardCharsets.UTF_8; + + /** Returns the encoding to use. */ + public Charset getEncoding() { + return encoding; + } + + /** Sets encoding to use (defaults to UTF_8). */ + public void setEncoding(String name) { + requireNonNull(name); + setEncoding(Charset.forName(name)); + } + + /** Sets encoding to use (defaults to UTF_8). */ + public void setEncoding(Charset charset) { + encoding = requireNonNull(charset); + } + + /** Sets encoding to use (defaults to UTF_8). */ + public void encoding(String charset) { + setEncoding(charset); + } + + private @Nullable String ratchetFrom; + + /** + * Limits the target to only the files which have changed since the given git reference, + * which is resolved according to [this](https://javadoc.io/static/org.eclipse.jgit/org.eclipse.jgit/5.6.1.202002131546-r/org/eclipse/jgit/lib/Repository.html#resolve-java.lang.String-) + */ + public void setRatchetFrom(String ratchetFrom) { + this.ratchetFrom = ratchetFrom; + } + + public @Nullable String getRatchetFrom() { + return ratchetFrom; + } + + public void ratchetFrom(String ratchetFrom) { + setRatchetFrom(ratchetFrom); + } + + final Map formats = new LinkedHashMap<>(); + + /** Configures the special java-specific extension. */ + public void java(Action closure) { + requireNonNull(closure); + configure(JavaExtension.NAME, JavaExtension.class, closure); + } + + /** Configures the special scala-specific extension. */ + public void scala(Action closure) { + requireNonNull(closure); + configure(ScalaExtension.NAME, ScalaExtension.class, closure); + } + + /** Configures the special kotlin-specific extension. */ + public void kotlin(Action closure) { + requireNonNull(closure); + configure(KotlinExtension.NAME, KotlinExtension.class, closure); + } + + /** Configures the special Gradle Kotlin DSL specific extension. */ + public void kotlinGradle(Action closure) { + requireNonNull(closure); + configure(KotlinGradleExtension.NAME, KotlinGradleExtension.class, closure); + } + + /** Configures the special freshmark-specific extension. */ + public void freshmark(Action closure) { + requireNonNull(closure); + configure(FreshMarkExtension.NAME, FreshMarkExtension.class, closure); + } + + /** Configures the special groovy-specific extension. */ + public void groovy(Action closure) { + configure(GroovyExtension.NAME, GroovyExtension.class, closure); + } + + /** Configures the special groovy-specific extension for Gradle files. */ + public void groovyGradle(Action closure) { + configure(GroovyGradleExtension.NAME, GroovyGradleExtension.class, closure); + } + + /** Configures the special sql-specific extension for SQL files. */ + public void sql(Action closure) { + configure(SqlExtension.NAME, SqlExtension.class, closure); + } + + /** Configures the special C/C++-specific extension. */ + public void cpp(Action closure) { + configure(CppExtension.NAME, CppExtension.class, closure); + } + + /** Configures the special typescript-specific extension for typescript files. */ + public void typescript(Action closure) { + configure(TypescriptExtension.NAME, TypescriptExtension.class, closure); + } + + /** Configures a custom extension. */ + public void format(String name, Action closure) { + requireNonNull(name, "name"); + requireNonNull(closure, "closure"); + configure(name, FormatExtension.class, closure); + } + + /** Makes it possible to remove a format which was created earlier. */ + public void removeFormat(String name) { + requireNonNull(name); + FormatExtension toRemove = formats.remove(name); + if (toRemove == null) { + project.getLogger().warn("Called removeFormat('" + name + "') but there was no such format."); + } + } + + boolean enforceCheck = true; + + /** Returns `true` if Gradle's `check` task should run `spotlessCheck`; `false` otherwise. */ + public boolean isEnforceCheck() { + return enforceCheck; + } + + /** + * Configures Gradle's `check` task to run `spotlessCheck` if `true`, + * but to not do so if `false`. + * + * `true` by default. + */ + public void setEnforceCheck(boolean enforceCheck) { + this.enforceCheck = enforceCheck; + } + + protected void configure(String name, Class clazz, Action configure) { + T value = maybeCreate(name, clazz); + configure.execute(value); + } + + @SuppressWarnings("unchecked") + private T maybeCreate(String name, Class clazz) { + FormatExtension existing = formats.get(name); + if (existing != null) { + if (!existing.getClass().equals(clazz)) { + throw new GradleException("Tried to add format named '" + name + "'" + + " of type " + clazz + " but one has already been created of type " + existing.getClass()); + } else { + return (T) existing; + } + } else { + try { + Constructor constructor = clazz.getConstructor(SpotlessExtensionBase.class); + T formatExtension = constructor.newInstance(this); + formats.put(name, formatExtension); + createFormatTasks(name, formatExtension); + return formatExtension; + } catch (NoSuchMethodException e) { + throw new GradleException("Must have a constructor " + clazz.getSimpleName() + "(SpotlessExtension root)", e); + } catch (Exception e) { + throw Errors.asRuntime(e); + } + } + } + + protected abstract void createFormatTasks(String name, FormatExtension formatExtension); +} diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessExtensionModern.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessExtensionModern.java new file mode 100644 index 0000000000..3398024b2d --- /dev/null +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessExtensionModern.java @@ -0,0 +1,92 @@ +/* + * Copyright 2016-2020 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 org.gradle.api.Project; +import org.gradle.api.Task; +import org.gradle.api.plugins.BasePlugin; +import org.gradle.api.plugins.JavaBasePlugin; + +public class SpotlessExtensionModern extends SpotlessExtensionBase { + public SpotlessExtensionModern(Project project) { + super(project); + rootCheckTask = project.task(EXTENSION + CHECK); + rootCheckTask.setGroup(TASK_GROUP); + rootCheckTask.setDescription(CHECK_DESCRIPTION); + rootApplyTask = project.task(EXTENSION + APPLY); + rootApplyTask.setGroup(TASK_GROUP); + rootApplyTask.setDescription(APPLY_DESCRIPTION); + rootDiagnoseTask = project.task(EXTENSION + DIAGNOSE); + rootDiagnoseTask.setGroup(TASK_GROUP); // no description on purpose + + project.afterEvaluate(unused -> { + if (enforceCheck) { + project.getTasks().named(JavaBasePlugin.CHECK_TASK_NAME) + .configure(task -> task.dependsOn(rootCheckTask)); + } + }); + } + + final Task rootCheckTask, rootApplyTask, rootDiagnoseTask; + + @Override + protected void createFormatTasks(String name, FormatExtension formatExtension) { + // TODO level 1: implement SpotlessExtension::createFormatTasks, but using config avoidance + // TODO level 2: override configure(String name, Class clazz, Action configure) so that it is lazy + + // create the SpotlessTask + String taskName = EXTENSION + SpotlessPlugin.capitalize(name); + SpotlessTaskModern spotlessTask = project.getTasks().create(taskName, SpotlessTaskModern.class); + project.afterEvaluate(unused -> formatExtension.setupTask(spotlessTask)); + + // clean removes the SpotlessCache, so we have to run after clean + Task clean = project.getTasks().getByName(BasePlugin.CLEAN_TASK_NAME); + spotlessTask.mustRunAfter(clean); + + // create the check and apply control tasks + SpotlessCheck checkTask = project.getTasks().create(taskName + CHECK, SpotlessCheck.class); + checkTask.setSpotlessOutDirectory(spotlessTask.getOutputDirectory()); + checkTask.source = spotlessTask; + checkTask.dependsOn(spotlessTask); + + SpotlessApply applyTask = project.getTasks().create(taskName + APPLY, SpotlessApply.class); + applyTask.setSpotlessOutDirectory(spotlessTask.getOutputDirectory()); + applyTask.linkSource(spotlessTask); + applyTask.dependsOn(spotlessTask); + + // if the user runs both, make sure that apply happens first, + checkTask.mustRunAfter(applyTask); + + // the root tasks depend on the control tasks + rootCheckTask.dependsOn(checkTask); + rootApplyTask.dependsOn(applyTask); + + // create the diagnose task + SpotlessDiagnoseTask diagnoseTask = project.getTasks().create(taskName + DIAGNOSE, SpotlessDiagnoseTask.class); + diagnoseTask.source = spotlessTask; + rootDiagnoseTask.dependsOn(diagnoseTask); + diagnoseTask.mustRunAfter(clean); + + if (project.hasProperty(IdeHook.PROPERTY)) { + // disable the normal tasks, to disable their up-to-date checking + spotlessTask.setEnabled(false); + checkTask.setEnabled(false); + applyTask.setEnabled(false); + // the rootApplyTask is no longer just a marker task, now it does a bit of work itself + rootApplyTask.doLast(unused -> IdeHook.performHook(spotlessTask)); + } + } +} diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessPlugin.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessPlugin.java index 8f3bf90908..8644c3b693 100644 --- a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessPlugin.java +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessPlugin.java @@ -28,6 +28,11 @@ public class SpotlessPlugin implements Plugin { @Override public void apply(Project project) { + // if -PspotlessModern=true, then use the modern stuff instead of the legacy stuff + if (project.hasProperty(SpotlessPluginModern.SPOTLESS_MODERN) && project.findProperty(SpotlessPluginModern.SPOTLESS_MODERN).equals("true")) { + new SpotlessPluginModern().apply(project); + return; + } // make sure there's a `clean` task project.getPlugins().apply(BasePlugin.class); diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessPluginModern.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessPluginModern.java new file mode 100644 index 0000000000..7b212d25ff --- /dev/null +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessPluginModern.java @@ -0,0 +1,48 @@ +/* + * Copyright 2016-2020 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 org.gradle.api.Plugin; +import org.gradle.api.Project; +import org.gradle.api.Task; +import org.gradle.api.plugins.BasePlugin; + +import com.diffplug.spotless.SpotlessCache; + +public class SpotlessPluginModern implements Plugin { + static final String SPOTLESS_MODERN = "spotlessModern"; + static final String MINIMUM_GRADLE = "5.4"; + + @Override + public void apply(Project project) { + // make sure there's a `clean` task + project.getPlugins().apply(BasePlugin.class); + + // setup the extension + project.getExtensions().create(SpotlessExtension.EXTENSION, SpotlessExtensionModern.class, project); + + // clear spotless' cache when the user does a clean + Task clean = project.getTasks().getByName(BasePlugin.CLEAN_TASK_NAME); + clean.doLast(unused -> { + // resolution for: https://github.com/diffplug/spotless/issues/243#issuecomment-564323856 + // project.getRootProject() is consistent across every project, so only of one the clears will + // actually happen (as desired) + // + // we use System.identityHashCode() to avoid a memory leak by hanging on to the reference directly + SpotlessCache.clearOnce(System.identityHashCode(project.getRootProject())); + }); + } +} diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessTask.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessTask.java index 7033e11924..deacdb7f5f 100644 --- a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessTask.java +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessTask.java @@ -17,32 +17,19 @@ import java.io.File; import java.io.IOException; -import java.nio.charset.Charset; import java.nio.file.Files; import java.nio.file.Path; -import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; import java.util.Comparator; import java.util.List; -import java.util.Locale; import java.util.Objects; import java.util.regex.Pattern; import java.util.stream.Collectors; -import javax.annotation.Nullable; - -import org.eclipse.jgit.lib.ObjectId; -import org.gradle.api.DefaultTask; import org.gradle.api.GradleException; -import org.gradle.api.file.FileCollection; import org.gradle.api.tasks.CacheableTask; import org.gradle.api.tasks.Input; -import org.gradle.api.tasks.InputFiles; import org.gradle.api.tasks.Internal; -import org.gradle.api.tasks.OutputDirectory; -import org.gradle.api.tasks.PathSensitive; -import org.gradle.api.tasks.PathSensitivity; import org.gradle.api.tasks.TaskAction; import org.gradle.api.tasks.incremental.IncrementalTaskInputs; @@ -50,80 +37,11 @@ import com.diffplug.common.base.Preconditions; import com.diffplug.common.base.StringPrinter; import com.diffplug.common.base.Throwing; -import com.diffplug.spotless.FormatExceptionPolicy; -import com.diffplug.spotless.FormatExceptionPolicyStrict; import com.diffplug.spotless.Formatter; -import com.diffplug.spotless.FormatterStep; -import com.diffplug.spotless.LineEnding; import com.diffplug.spotless.PaddedCell; @CacheableTask -public class SpotlessTask extends DefaultTask { - SpotlessApply applyTask; - - /** @deprecated internal use only, allows coordination between check and apply when they are in the same build */ - @Internal - @Deprecated - public SpotlessApply getApplyTask() { - return applyTask; - } - - // set by SpotlessExtension, but possibly overridden by FormatExtension - protected String encoding = "UTF-8"; - - @Input - public String getEncoding() { - return encoding; - } - - public void setEncoding(String encoding) { - this.encoding = Objects.requireNonNull(encoding); - } - - protected LineEnding.Policy lineEndingsPolicy = LineEnding.UNIX.createPolicy(); - - @Input - public LineEnding.Policy getLineEndingsPolicy() { - return lineEndingsPolicy; - } - - public void setLineEndingsPolicy(LineEnding.Policy lineEndingsPolicy) { - this.lineEndingsPolicy = Objects.requireNonNull(lineEndingsPolicy); - } - - /*** API which performs git up-to-date tasks. */ - @Nullable - GitRatchet ratchet; - /** The sha of the tree at repository root, used for determining if an individual *file* is clean according to git. */ - ObjectId rootTreeSha; - /** - * The sha of the tree at the root of *this project*, used to determine if the git baseline has changed within this folder. - * Using a more fine-grained tree (rather than the project root) allows Gradle to mark more subprojects as up-to-date - * compared to using the project root. - */ - private ObjectId subtreeSha = ObjectId.zeroId(); - - public void setupRatchet(GitRatchet gitRatchet, String ratchetFrom) { - ratchet = gitRatchet; - rootTreeSha = gitRatchet.rootTreeShaOf(getProject(), ratchetFrom); - subtreeSha = gitRatchet.subtreeShaOf(getProject(), rootTreeSha); - } - - @Internal - GitRatchet getRatchet() { - return ratchet; - } - - @Internal - ObjectId getRootTreeSha() { - return rootTreeSha; - } - - @Input - public ObjectId getRatchetSha() { - return subtreeSha; - } - +public class SpotlessTask extends SpotlessTaskBase { @Deprecated @Internal public boolean isPaddedCell() { @@ -146,65 +64,6 @@ public void setFilePatterns(String filePatterns) { this.filePatterns = Objects.requireNonNull(filePatterns); } - protected FormatExceptionPolicy exceptionPolicy = new FormatExceptionPolicyStrict(); - - public void setExceptionPolicy(FormatExceptionPolicy exceptionPolicy) { - this.exceptionPolicy = Objects.requireNonNull(exceptionPolicy); - } - - @Input - public FormatExceptionPolicy getExceptionPolicy() { - return exceptionPolicy; - } - - protected FileCollection target; - - @PathSensitive(PathSensitivity.RELATIVE) - @InputFiles - public FileCollection getTarget() { - return target; - } - - public void setTarget(Iterable target) { - if (target instanceof FileCollection) { - this.target = (FileCollection) target; - } else { - this.target = getProject().files(target); - } - } - - private File outputDirectory = new File(getProject().getBuildDir(), "spotless/" + getName()); - - @OutputDirectory - public File getOutputDirectory() { - return outputDirectory; - } - - protected List steps = new ArrayList<>(); - - @Input - public List getSteps() { - return Collections.unmodifiableList(steps); - } - - public void setSteps(List steps) { - this.steps = PluginGradlePreconditions.requireElementsNonNull(steps); - } - - public boolean addStep(FormatterStep step) { - return this.steps.add(Objects.requireNonNull(step)); - } - - /** Returns the name of this format. */ - String formatName() { - String name = getName(); - if (name.startsWith(SpotlessExtension.EXTENSION)) { - return name.substring(SpotlessExtension.EXTENSION.length()).toLowerCase(Locale.ROOT); - } else { - return name; - } - } - @TaskAction public void performAction(IncrementalTaskInputs inputs) throws Exception { if (target == null) { @@ -260,7 +119,7 @@ public void performAction(IncrementalTaskInputs inputs) throws Exception { }); } - private void processInputFile(Formatter formatter, File input) throws IOException { + protected void processInputFile(Formatter formatter, File input) throws IOException { File output = getOutputFile(input); getLogger().debug("Applying format to " + input + " and writing to " + output); PaddedCell.DirtyState dirtyState; @@ -284,7 +143,7 @@ private void processInputFile(Formatter formatter, File input) throws IOExceptio } } - private void deletePreviousResult(File input) throws IOException { + protected void deletePreviousResult(File input) throws IOException { File output = getOutputFile(input); if (output.isDirectory()) { Files.walk(output.toPath()) @@ -307,14 +166,4 @@ private File getOutputFile(File input) { } return new File(outputDirectory, outputFileName); } - - Formatter buildFormatter() { - return Formatter.builder() - .lineEndingsPolicy(lineEndingsPolicy) - .encoding(Charset.forName(encoding)) - .rootDir(getProject().getRootDir().toPath()) - .steps(steps) - .exceptionPolicy(exceptionPolicy) - .build(); - } } diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessTaskBase.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessTaskBase.java new file mode 100644 index 0000000000..4f768059c3 --- /dev/null +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessTaskBase.java @@ -0,0 +1,178 @@ +/* + * Copyright 2020 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.File; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Locale; +import java.util.Objects; + +import javax.annotation.Nullable; + +import org.eclipse.jgit.lib.ObjectId; +import org.gradle.api.DefaultTask; +import org.gradle.api.file.FileCollection; +import org.gradle.api.tasks.Input; +import org.gradle.api.tasks.InputFiles; +import org.gradle.api.tasks.Internal; +import org.gradle.api.tasks.OutputDirectory; +import org.gradle.api.tasks.PathSensitive; +import org.gradle.api.tasks.PathSensitivity; + +import com.diffplug.spotless.FormatExceptionPolicy; +import com.diffplug.spotless.FormatExceptionPolicyStrict; +import com.diffplug.spotless.Formatter; +import com.diffplug.spotless.FormatterStep; +import com.diffplug.spotless.LineEnding; + +public class SpotlessTaskBase extends DefaultTask { + SpotlessApply applyTask; + + /** @deprecated internal use only, allows coordination between check and apply when they are in the same build */ + @Internal + @Deprecated + public SpotlessApply getApplyTask() { + return applyTask; + } + + // set by SpotlessExtension, but possibly overridden by FormatExtension + protected String encoding = "UTF-8"; + + @Input + public String getEncoding() { + return encoding; + } + + public void setEncoding(String encoding) { + this.encoding = Objects.requireNonNull(encoding); + } + + protected LineEnding.Policy lineEndingsPolicy = LineEnding.UNIX.createPolicy(); + + @Input + public LineEnding.Policy getLineEndingsPolicy() { + return lineEndingsPolicy; + } + + public void setLineEndingsPolicy(LineEnding.Policy lineEndingsPolicy) { + this.lineEndingsPolicy = Objects.requireNonNull(lineEndingsPolicy); + } + + /*** API which performs git up-to-date tasks. */ + @Nullable + GitRatchet ratchet; + /** The sha of the tree at repository root, used for determining if an individual *file* is clean according to git. */ + ObjectId rootTreeSha; + /** + * The sha of the tree at the root of *this project*, used to determine if the git baseline has changed within this folder. + * Using a more fine-grained tree (rather than the project root) allows Gradle to mark more subprojects as up-to-date + * compared to using the project root. + */ + private ObjectId subtreeSha = ObjectId.zeroId(); + + public void setupRatchet(GitRatchet gitRatchet, String ratchetFrom) { + ratchet = gitRatchet; + rootTreeSha = gitRatchet.rootTreeShaOf(getProject(), ratchetFrom); + subtreeSha = gitRatchet.subtreeShaOf(getProject(), rootTreeSha); + } + + @Internal + GitRatchet getRatchet() { + return ratchet; + } + + @Internal + ObjectId getRootTreeSha() { + return rootTreeSha; + } + + @Input + public ObjectId getRatchetSha() { + return subtreeSha; + } + + protected FormatExceptionPolicy exceptionPolicy = new FormatExceptionPolicyStrict(); + + public void setExceptionPolicy(FormatExceptionPolicy exceptionPolicy) { + this.exceptionPolicy = Objects.requireNonNull(exceptionPolicy); + } + + @Input + public FormatExceptionPolicy getExceptionPolicy() { + return exceptionPolicy; + } + + protected FileCollection target; + + @PathSensitive(PathSensitivity.RELATIVE) + @InputFiles + public FileCollection getTarget() { + return target; + } + + public void setTarget(Iterable target) { + if (target instanceof FileCollection) { + this.target = (FileCollection) target; + } else { + this.target = getProject().files(target); + } + } + + protected File outputDirectory = new File(getProject().getBuildDir(), "spotless/" + getName()); + + @OutputDirectory + public File getOutputDirectory() { + return outputDirectory; + } + + protected List steps = new ArrayList<>(); + + @Input + public List getSteps() { + return Collections.unmodifiableList(steps); + } + + public void setSteps(List steps) { + this.steps = PluginGradlePreconditions.requireElementsNonNull(steps); + } + + public boolean addStep(FormatterStep step) { + return this.steps.add(Objects.requireNonNull(step)); + } + + /** Returns the name of this format. */ + String formatName() { + String name = getName(); + if (name.startsWith(SpotlessExtension.EXTENSION)) { + return name.substring(SpotlessExtension.EXTENSION.length()).toLowerCase(Locale.ROOT); + } else { + return name; + } + } + + Formatter buildFormatter() { + return Formatter.builder() + .lineEndingsPolicy(lineEndingsPolicy) + .encoding(Charset.forName(encoding)) + .rootDir(getProject().getRootDir().toPath()) + .steps(steps) + .exceptionPolicy(exceptionPolicy) + .build(); + } +} diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessTaskModern.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessTaskModern.java new file mode 100644 index 0000000000..3c4028bff6 --- /dev/null +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessTaskModern.java @@ -0,0 +1,94 @@ +/* + * Copyright 2016-2020 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.File; +import java.io.IOException; +import java.nio.file.Files; +import java.util.Arrays; +import java.util.List; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +import org.gradle.api.GradleException; +import org.gradle.api.tasks.CacheableTask; +import org.gradle.api.tasks.TaskAction; +import org.gradle.api.tasks.incremental.IncrementalTaskInputs; + +import com.diffplug.common.base.Errors; +import com.diffplug.common.base.Preconditions; +import com.diffplug.common.base.Throwing; +import com.diffplug.spotless.Formatter; + +@CacheableTask +public class SpotlessTaskModern extends SpotlessTask { + @TaskAction + public void performAction(IncrementalTaskInputs inputs) throws Exception { + // TODO: implement using the InputChanges api + + if (target == null) { + throw new GradleException("You must specify 'Iterable target'"); + } + + if (!inputs.isIncremental()) { + getLogger().info("Not incremental: removing prior outputs"); + getProject().delete(outputDirectory); + Files.createDirectories(outputDirectory.toPath()); + } + + Throwing.Specific.Predicate shouldInclude; + if (this.filePatterns.isEmpty()) { + shouldInclude = file -> true; + } else { + Preconditions.checkArgument(ratchet == null, + "Cannot use 'ratchetFrom' and '-PspotlessFiles' at the same time"); + + // a list of files has been passed in via project property + final String[] includePatterns = this.filePatterns.split(","); + final List compiledIncludePatterns = Arrays.stream(includePatterns) + .map(Pattern::compile) + .collect(Collectors.toList()); + shouldInclude = file -> compiledIncludePatterns + .stream() + .anyMatch(filePattern -> filePattern.matcher(file.getAbsolutePath()) + .matches()); + } + + try (Formatter formatter = buildFormatter()) { + inputs.outOfDate(inputDetails -> { + File input = inputDetails.getFile(); + try { + if (shouldInclude.test(input) && input.isFile()) { + processInputFile(formatter, input); + } + } catch (IOException e) { + throw Errors.asRuntime(e); + } + }); + } + + inputs.removed(removedDetails -> { + File input = removedDetails.getFile(); + try { + if (shouldInclude.test(input)) { + deletePreviousResult(input); + } + } catch (IOException e) { + throw Errors.asRuntime(e); + } + }); + } +} diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SqlExtension.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SqlExtension.java index 7072c0cbc0..5f2e0dc4ea 100644 --- a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SqlExtension.java +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SqlExtension.java @@ -25,7 +25,7 @@ public class SqlExtension extends FormatExtension { static final String NAME = "sql"; - public SqlExtension(SpotlessExtension spotless) { + public SqlExtension(SpotlessExtensionBase spotless) { super(spotless); } diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/TypescriptExtension.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/TypescriptExtension.java index 557aa2c082..1378d7b0df 100644 --- a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/TypescriptExtension.java +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/TypescriptExtension.java @@ -36,7 +36,7 @@ public class TypescriptExtension extends FormatExtension { static final String NAME = "typescript"; - public TypescriptExtension(SpotlessExtension spotless) { + public TypescriptExtension(SpotlessExtensionBase spotless) { super(spotless); } diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/XmlExtension.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/XmlExtension.java index 83a4b4e766..35017e3970 100644 --- a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/XmlExtension.java +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/XmlExtension.java @@ -30,7 +30,7 @@ public class XmlExtension extends FormatExtension implements HasBuiltinDelimiterForLicense { static final String NAME = "xml"; - public XmlExtension(SpotlessExtension spotless) { + public XmlExtension(SpotlessExtensionBase spotless) { super(spotless); } diff --git a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/ErrorShouldRethrowJre8Test.java b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/ErrorShouldRethrowJre8Test.java index ed41e007db..59e088bafb 100644 --- a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/ErrorShouldRethrowJre8Test.java +++ b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/ErrorShouldRethrowJre8Test.java @@ -58,7 +58,7 @@ public void passesIfNoException() throws Exception { " } // format", "} // spotless"); setFile("README.md").toContent("This code is fun."); - runWithSuccess(":spotlessMisc"); + runWithSuccess("> Task :spotlessMisc"); } @Test @@ -68,9 +68,9 @@ public void anyExceptionShouldFail() throws Exception { "} // spotless"); setFile("README.md").toContent("This code is fubar."); runWithFailure( - ":spotlessMiscStep 'no swearing' found problem in 'README.md':", - "No swearing!", - "java.lang.RuntimeException: No swearing!"); + "> Task :spotlessMisc FAILED", + "Step 'no swearing' found problem in 'README.md':", + "No swearing!"); } @Test @@ -80,7 +80,7 @@ public void unlessEnforceCheckIsFalse() throws Exception { " enforceCheck false", "} // spotless"); setFile("README.md").toContent("This code is fubar."); - runWithSuccess(":compileJava UP-TO-DATE"); + runWithSuccess("> Task :compileJava NO-SOURCE"); } @Test @@ -90,7 +90,7 @@ public void unlessExemptedByStep() throws Exception { " } // format", "} // spotless"); setFile("README.md").toContent("This code is fubar."); - runWithSuccess(":spotlessMisc", + runWithSuccess("> Task :spotlessMisc", "Unable to apply step 'no swearing' to 'README.md'"); } @@ -101,7 +101,7 @@ public void unlessExemptedByPath() throws Exception { " } // format", "} // spotless"); setFile("README.md").toContent("This code is fubar."); - runWithSuccess(":spotlessMisc", + runWithSuccess("> Task :spotlessMisc", "Unable to apply step 'no swearing' to 'README.md'"); } @@ -114,16 +114,16 @@ public void failsIfNeitherStepNorFileExempted() throws Exception { "} // spotless"); setFile("README.md").toContent("This code is fubar."); runWithFailure( - ":spotlessMiscStep 'no swearing' found problem in 'README.md':", - "No swearing!", - "java.lang.RuntimeException: No swearing!"); + "> Task :spotlessMisc FAILED", + "Step 'no swearing' found problem in 'README.md':", + "No swearing!"); } private void runWithSuccess(String... messages) throws Exception { if (JreVersion.thisVm() != JreVersion._8) { return; } - BuildResult result = gradleRunner().withArguments("check").build(); + BuildResult result = gradleRunner().withGradleVersion(SpotlessPluginModern.MINIMUM_GRADLE).withArguments("check").build(); assertResultAndMessages(result, TaskOutcome.SUCCESS, messages); } @@ -131,17 +131,20 @@ private void runWithFailure(String... messages) throws Exception { if (JreVersion.thisVm() != JreVersion._8) { return; } - BuildResult result = gradleRunner().withArguments("check").buildAndFail(); + BuildResult result = gradleRunner().withGradleVersion(SpotlessPluginModern.MINIMUM_GRADLE).withArguments("check").buildAndFail(); assertResultAndMessages(result, TaskOutcome.FAILED, messages); } private void assertResultAndMessages(BuildResult result, TaskOutcome outcome, String... messages) { String expectedToStartWith = StringPrinter.buildStringFromLines(messages).trim(); int numNewlines = CharMatcher.is('\n').countIn(expectedToStartWith); - List actualLines = Splitter.on('\n').splitToList(LineEnding.toUnix(result.getOutput())); + List actualLines = Splitter.on('\n').splitToList(LineEnding.toUnix(result.getOutput().trim())); String actualStart = String.join("\n", actualLines.subList(0, numNewlines + 1)); Assertions.assertThat(actualStart).isEqualTo(expectedToStartWith); - Assertions.assertThat(result.tasks(outcome).size() + result.tasks(TaskOutcome.UP_TO_DATE).size()) + // result.getTasks() + // .stream() + // .forEach(task -> System.out.println("task " + task.getPath() + " " + task.getOutcome())); + Assertions.assertThat(result.tasks(outcome).size() + result.tasks(TaskOutcome.UP_TO_DATE).size() + result.tasks(TaskOutcome.NO_SOURCE).size()) .isEqualTo(result.getTasks().size()); } } diff --git a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/ExcludeFromPluginGradleModern.java b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/ExcludeFromPluginGradleModern.java new file mode 100644 index 0000000000..48456a975f --- /dev/null +++ b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/ExcludeFromPluginGradleModern.java @@ -0,0 +1,20 @@ +/* + * Copyright 2020 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; + +public interface ExcludeFromPluginGradleModern { + +} diff --git a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/GradleIntegrationHarness.java b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/GradleIntegrationHarness.java index 0c04f21b71..2c11eba67f 100644 --- a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/GradleIntegrationHarness.java +++ b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/GradleIntegrationHarness.java @@ -31,6 +31,7 @@ import com.diffplug.common.base.Errors; import com.diffplug.common.base.StringPrinter; +import com.diffplug.common.collect.ImmutableMap; import com.diffplug.common.tree.TreeDef; import com.diffplug.common.tree.TreeStream; import com.diffplug.spotless.JreVersion; @@ -74,10 +75,15 @@ protected static String requestGradleForJre8and11(String ver) { } protected final GradleRunner gradleRunner() throws IOException { - return GradleRunner.create() + GradleRunner runner = GradleRunner.create() .withGradleVersion(requestGradleForJre8and11("2.14")) .withProjectDir(rootFolder()) .withPluginClasspath(); + if ("true".equals(System.getProperty(SpotlessPluginModern.SPOTLESS_MODERN))) { + runner.withEnvironment(ImmutableMap.of("ORG_GRADLE_PROJECT_" + SpotlessPluginModern.SPOTLESS_MODERN, "true")); + runner.withGradleVersion(SpotlessPluginModern.MINIMUM_GRADLE); + } + return runner; } /** Dumps the complete file contents of the folder to the console. */ diff --git a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/HasBuiltinDelimiterForLicenseTest.java b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/HasBuiltinDelimiterForLicenseTest.java deleted file mode 100644 index ac13298729..0000000000 --- a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/HasBuiltinDelimiterForLicenseTest.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright 2016-2020 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.junit.Test; - -public class HasBuiltinDelimiterForLicenseTest extends GradleIntegrationHarness { - - @Test - public void testWithCommonInterfaceForConfiguringLicences() throws IOException { - // TODO: JLL Convert this to a Kotlin example when supported: https://github.com/gradle/kotlin-dsl/issues/492 - setFile("build.gradle").toLines( - "import com.diffplug.gradle.spotless.HasBuiltinDelimiterForLicense", - "plugins {", - " id(\"org.jetbrains.kotlin.jvm\") version \"1.2.31\"", - " id(\"com.diffplug.gradle.spotless\")", - "}", - "repositories { mavenCentral() }", - "spotless {", - " kotlin {", - " assert (it instanceof HasBuiltinDelimiterForLicense) : \"Was `$it`\"", - " }", - " java {", - " assert (it instanceof HasBuiltinDelimiterForLicense) : \"Was `$it`\"", - " }", - " cpp {", - " assert (it instanceof HasBuiltinDelimiterForLicense) : \"Was `$it`\"", - " }", - " css {", - " assert (it instanceof HasBuiltinDelimiterForLicense) : \"Was `$it`\"", - " }", - " xml {", - " assert (it instanceof HasBuiltinDelimiterForLicense) : \"Was `$it`\"", - " }", - "}"); - gradleRunner() - .withGradleVersion(requestGradleForJre8and11("4.6")) // 4.6 is the min - .withArguments("spotlessApply") - .forwardOutput() - .build(); - } -} diff --git a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/IdeHookTest.java b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/IdeHookTest.java index 0e8fdd7cd6..c8f6bee1b3 100644 --- a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/IdeHookTest.java +++ b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/IdeHookTest.java @@ -21,7 +21,6 @@ import java.nio.charset.StandardCharsets; import org.assertj.core.api.Assertions; -import org.gradle.testkit.runner.GradleRunner; import org.junit.Before; import org.junit.Test; @@ -70,7 +69,8 @@ private void runWith(String... arguments) throws IOException { // gradle 2.14 -> 4.6 confirmed to work // gradle 4.7 -> 5.1 don't work in tooling API because of https://github.com/gradle/gradle/issues/7617 // gradle 5.1 -> current confirmed to work - GradleRunner.create() + gradleRunner() + .withGradleVersion(SpotlessPluginModern.MINIMUM_GRADLE) .withProjectDir(rootFolder()) .withPluginClasspath() .withArguments(arguments) diff --git a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/RegisterDependenciesTaskTest.java b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/RegisterDependenciesTaskTest.java index 87fd4b2bcb..daf63085dc 100644 --- a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/RegisterDependenciesTaskTest.java +++ b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/RegisterDependenciesTaskTest.java @@ -20,8 +20,6 @@ import org.assertj.core.api.Assertions; import org.junit.Test; -import com.diffplug.spotless.JreVersion; - public class RegisterDependenciesTaskTest extends GradleIntegrationHarness { @Test public void registerDependencies() throws IOException { @@ -40,21 +38,8 @@ public void registerDependencies() throws IOException { " }", "}"); - if (JreVersion.thisVm() == JreVersion._8) { - String oldestSupported = gradleRunner() - .withArguments("spotlessCheck").build().getOutput(); - Assertions.assertThat(oldestSupported.replace("\r", "")) - .startsWith(":spotlessCheck UP-TO-DATE\n" + - ":spotlessInternalRegisterDependencies\n") - .contains(":sub:spotlessJava\n" + - ":sub:spotlessJavaCheck\n" + - ":sub:spotlessCheck\n" + - "\n" + - "BUILD SUCCESSFUL"); - } - setFile("gradle.properties").toLines(); - String newestSupported = gradleRunner().withGradleVersion("6.0") + String newestSupported = gradleRunner().withGradleVersion(SpotlessPluginModern.MINIMUM_GRADLE) .withArguments("spotlessCheck").build().getOutput(); Assertions.assertThat(newestSupported.replace("\r", "")) .startsWith("> Task :spotlessCheck UP-TO-DATE\n" + diff --git a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/SpecificFilesTest.java b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/SpecificFilesTest.java index f31f03d172..693dab350e 100644 --- a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/SpecificFilesTest.java +++ b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/SpecificFilesTest.java @@ -21,9 +21,11 @@ import org.gradle.testkit.runner.UnexpectedBuildFailure; import org.junit.Ignore; import org.junit.Test; +import org.junit.experimental.categories.Category; import com.diffplug.spotless.LineEnding; +@Category(ExcludeFromPluginGradleModern.class) public class SpecificFilesTest extends GradleIntegrationHarness { private static String regexWinSafe(String input) {