diff --git a/.gitignore b/.gitignore index db5b15d..05fc9cf 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,7 @@ # # LambdAurora's ignore file # -# v0.23 +# v0.24 # JetBrains .idea/ @@ -50,6 +50,8 @@ logs/ # Languages ## Java classes/ +## Kotlin +.kotlin/ ## Python __pycache__/ venv/ diff --git a/CHANGELOG.md b/CHANGELOG.md index f3c2676..07c0475 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,4 +35,8 @@ - Switched from `PatternFilterable` to `Set` for file matching in `HeaderCommentManager` as only extensions are matched. - Adapted `LicenseYearSelectionMode` to not consume `Project` arguments anymore. +## 2.1.0 + +- Made check license tasks incremental. + [#3]: https://github.com/YumiProject/yumi-gradle-licenser/pull/3 diff --git a/README.md b/README.md index 1a5688a..e0c6b20 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ For a project you need to apply the plugin to your project: ```groovy plugins { - id "dev.yumi.gradle.licenser" version "2.0.+" + id "dev.yumi.gradle.licenser" version "2.1.+" } ``` diff --git a/build.gradle.kts b/build.gradle.kts index dec2b4d..16ffcb8 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -9,7 +9,7 @@ plugins { } group = "dev.yumi" -version = "2.0.0" +version = "2.1.0" val javaVersion = 17 repositories { @@ -79,8 +79,10 @@ tasks.withType().configureEach { } tasks.jar { + val archivesName = base.archivesName.get() + from("LICENSE") { - rename { "${it}_${base.archivesName.get()}" } + rename { "${it}_${archivesName}" } } } diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..5ad6974 --- /dev/null +++ b/gradle.properties @@ -0,0 +1 @@ +org.gradle.configuration-cache=true diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 51049cb..c0b8522 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,11 +1,11 @@ [versions] -jetbrains-annotations = "24.0.1" +jetbrains-annotations = "26.0.1" jgit = "6.7.0.202309050840-r" # Gradle-specific gradle-kotlin = "1.9.22" # Testing -junit-jupiter = "5.10.0" -junit-launcher = "1.10.0" +junit-jupiter = "5.11.3" +junit-launcher = "1.11.3" [libraries] jetbrains-annotations = { module = "org.jetbrains:annotations", version.ref = "jetbrains-annotations" } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 79eb9d0..e2847c8 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/src/functionalTest/java/dev/yumi/gradle/licenser/test/BaseJavaFunctionalTest.java b/src/functionalTest/java/dev/yumi/gradle/licenser/test/BaseJavaFunctionalTest.java index cf6b17a..72f1603 100644 --- a/src/functionalTest/java/dev/yumi/gradle/licenser/test/BaseJavaFunctionalTest.java +++ b/src/functionalTest/java/dev/yumi/gradle/licenser/test/BaseJavaFunctionalTest.java @@ -28,7 +28,7 @@ public class BaseJavaFunctionalTest { @Test public void canRunTask() throws IOException { - var runner = new ScenarioRunner("base_java", projectDir.toPath()); + var runner = new ScenarioRunner("base_java", projectDir.toPath(), false); runner.setup(); Path testClassPath = runner.path("src/main/java/test/TestClass.java"); diff --git a/src/functionalTest/java/dev/yumi/gradle/licenser/test/ConfigurationCacheFunctionalTest.java b/src/functionalTest/java/dev/yumi/gradle/licenser/test/ConfigurationCacheFunctionalTest.java index 0ee884b..c9f8922 100644 --- a/src/functionalTest/java/dev/yumi/gradle/licenser/test/ConfigurationCacheFunctionalTest.java +++ b/src/functionalTest/java/dev/yumi/gradle/licenser/test/ConfigurationCacheFunctionalTest.java @@ -15,6 +15,7 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.StandardCopyOption; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -30,7 +31,7 @@ public class ConfigurationCacheFunctionalTest { @Test public void canRunTask() throws IOException { - var runner = new ScenarioRunner("base_java", projectDir.toPath()); + var runner = new ScenarioRunner("base_java", projectDir.toPath(), true); runner.setup(); Path testClassPath = runner.path("src/main/java/test/TestClass.java"); @@ -39,7 +40,7 @@ public void canRunTask() throws IOException { Path testClassWithOptionalPath = runner.path("src/main/java/test/TestClassWithOptional.java"); Path testPackageInfoPath = runner.path("src/main/java/test/package-info.java"); - var result = runner.run("--configuration-cache", "applyLicenses", "--stacktrace"); + var result = runner.run(); // Verify the result assertTrue( @@ -81,5 +82,22 @@ public void canRunTask() throws IOException { runner.runCheck(); // Test the caching. runner.runCheck(); + + Files.copy( + ScenarioRunner.TEST_JAR_PATH.resolve("scenarios/cache/TestClassModified.java"), + testClassPath, + StandardCopyOption.REPLACE_EXISTING + ); + result = runner.run(); + // Verify the result + assertTrue( + result.getOutput().contains("- Updated file " + testClassPath), + "Missing updated file string in output log." + ); + result = runner.runCheck(); + assertTrue( + result.getOutput().contains("All license header checks passed (1 files)."), + "Missing accurate checks passed string in output log." + ); } } diff --git a/src/functionalTest/java/dev/yumi/gradle/licenser/test/CustomSourceSetFunctionalTest.java b/src/functionalTest/java/dev/yumi/gradle/licenser/test/CustomSourceSetFunctionalTest.java index 9381014..b0fb02a 100644 --- a/src/functionalTest/java/dev/yumi/gradle/licenser/test/CustomSourceSetFunctionalTest.java +++ b/src/functionalTest/java/dev/yumi/gradle/licenser/test/CustomSourceSetFunctionalTest.java @@ -26,7 +26,7 @@ public class CustomSourceSetFunctionalTest { @Test public void canRunTask() throws IOException { - var runner = new ScenarioRunner("custom_sourceset", projectDir.toPath()); + var runner = new ScenarioRunner("custom_sourceset", projectDir.toPath(), false); runner.setup(); var result = runner.run(); diff --git a/src/functionalTest/java/dev/yumi/gradle/licenser/test/IgnoreBuildDirectoryFunctionalTest.java b/src/functionalTest/java/dev/yumi/gradle/licenser/test/IgnoreBuildDirectoryFunctionalTest.java index ce9f434..864a513 100644 --- a/src/functionalTest/java/dev/yumi/gradle/licenser/test/IgnoreBuildDirectoryFunctionalTest.java +++ b/src/functionalTest/java/dev/yumi/gradle/licenser/test/IgnoreBuildDirectoryFunctionalTest.java @@ -28,7 +28,7 @@ public class IgnoreBuildDirectoryFunctionalTest { @Test public void canRunTask() throws IOException { - var runner = new ScenarioRunner("ignore_build_directory", projectDir.toPath()); + var runner = new ScenarioRunner("ignore_build_directory", projectDir.toPath(), false); runner.setup(); runner.runCheck(); diff --git a/src/functionalTest/java/dev/yumi/gradle/licenser/test/KotlinFunctionalTest.java b/src/functionalTest/java/dev/yumi/gradle/licenser/test/KotlinFunctionalTest.java index 3759c8e..49531c9 100644 --- a/src/functionalTest/java/dev/yumi/gradle/licenser/test/KotlinFunctionalTest.java +++ b/src/functionalTest/java/dev/yumi/gradle/licenser/test/KotlinFunctionalTest.java @@ -38,7 +38,7 @@ public void canRunMultiplatform() throws IOException { } private void run(String name, String path) throws IOException { - var runner = new ScenarioRunner(name, projectDir.toPath()); + var runner = new ScenarioRunner(name, projectDir.toPath(), false); runner.setup(); Path testKotlinPath = runner.path(path); diff --git a/src/functionalTest/java/dev/yumi/gradle/licenser/test/ScenarioRunner.java b/src/functionalTest/java/dev/yumi/gradle/licenser/test/ScenarioRunner.java index 63fe4db..9761830 100644 --- a/src/functionalTest/java/dev/yumi/gradle/licenser/test/ScenarioRunner.java +++ b/src/functionalTest/java/dev/yumi/gradle/licenser/test/ScenarioRunner.java @@ -10,22 +10,27 @@ import org.gradle.testkit.runner.BuildResult; import org.gradle.testkit.runner.GradleRunner; +import org.jetbrains.annotations.NotNull; import java.io.IOException; import java.net.JarURLConnection; import java.net.URL; import java.nio.file.*; import java.nio.file.attribute.BasicFileAttributes; +import java.util.ArrayList; +import java.util.List; import java.util.Objects; public class ScenarioRunner { - private static final PathResolver TEST_JAR_PATH; + static final PathResolver TEST_JAR_PATH; private final String name; private final Path projectDir; + private final boolean forceConfigurationCache; - public ScenarioRunner(String name, Path projectDir) { + public ScenarioRunner(String name, Path projectDir, boolean forceConfigurationCache) { this.name = name; this.projectDir = projectDir; + this.forceConfigurationCache = forceConfigurationCache; } private Path getScenarioPath(String path) { @@ -36,12 +41,13 @@ public Path path(String path) { return this.projectDir.resolve(path).normalize(); } - private Path copy(String pathStr) throws IOException { + public Path copy(String pathStr, @NotNull CopyOption... options) throws IOException { Path destinationPath = this.path(pathStr); Files.createDirectories(destinationPath.getParent()); Files.copy( this.getScenarioPath('/' + pathStr), - destinationPath + destinationPath, + options ); return destinationPath.toAbsolutePath(); } @@ -95,11 +101,17 @@ public void setup() throws IOException { } public BuildResult run(String... args) { + var argsList = new ArrayList<>(List.of(args)); + + if (this.forceConfigurationCache) { + argsList.add(0, "--configuration-cache"); + } + // Run the build var runner = GradleRunner.create(); runner.forwardOutput(); runner.withPluginClasspath(); - runner.withArguments(args); + runner.withArguments(argsList.toArray(String[]::new)); runner.withProjectDir(this.projectDir.toFile()); return runner.build(); } @@ -108,8 +120,8 @@ public BuildResult run() { return this.run("applyLicenses", "--stacktrace"); } - public void runCheck() { - this.run("checkLicenses", "--stacktrace"); + public BuildResult runCheck() { + return this.run("checkLicenses", "--stacktrace"); } interface PathResolver { diff --git a/src/functionalTest/resources/scenarios/cache/TestClassModified.java b/src/functionalTest/resources/scenarios/cache/TestClassModified.java new file mode 100644 index 0000000..5628d53 --- /dev/null +++ b/src/functionalTest/resources/scenarios/cache/TestClassModified.java @@ -0,0 +1,7 @@ +package test; + +public class TestClass { + public static void main(String[] args) { + System.out.println("Hello world again."); + } +} diff --git a/src/main/java/dev/yumi/gradle/licenser/task/ApplyLicenseTask.java b/src/main/java/dev/yumi/gradle/licenser/task/ApplyLicenseTask.java index 424a9bc..0013ce9 100644 --- a/src/main/java/dev/yumi/gradle/licenser/task/ApplyLicenseTask.java +++ b/src/main/java/dev/yumi/gradle/licenser/task/ApplyLicenseTask.java @@ -16,16 +16,14 @@ import org.gradle.api.Action; import org.gradle.api.GradleException; import org.gradle.api.Project; -import org.gradle.api.file.ConfigurableFileCollection; import org.gradle.api.file.SourceDirectorySet; import org.gradle.api.logging.Logger; -import org.gradle.api.tasks.IgnoreEmptyDirectories; -import org.gradle.api.tasks.InputFiles; -import org.gradle.api.tasks.SkipWhenEmpty; import org.gradle.api.tasks.TaskAction; +import org.gradle.api.tasks.UntrackedTask; import org.jetbrains.annotations.ApiStatus; import javax.inject.Inject; +import java.io.File; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; @@ -33,15 +31,17 @@ import java.nio.file.StandardCopyOption; import java.util.ArrayList; import java.util.List; +import java.util.stream.StreamSupport; /** * Represents the task that applies license headers to project files. * * @author LambdAurora - * @version 2.0.0 + * @version 2.1.0 * @since 1.0.0 */ @ApiStatus.Internal +@UntrackedTask(because = "Task may rewrite the input files.") public abstract class ApplyLicenseTask extends SourceDirectoryBasedTask { @Inject public ApplyLicenseTask(YumiLicenserGradleExtension extension) { @@ -56,7 +56,11 @@ public ApplyLicenseTask(YumiLicenserGradleExtension extension) { @TaskAction public void execute() { - this.execute(this.getHeaderCommentManager().get(), new Consumer(this.getLicenseHeader().get())); + this.execute( + this.getHeaderCommentManager().get(), + StreamSupport.stream(this.getSourceFiles().spliterator(), false).map(File::toPath), + new Consumer(this.getLicenseHeader().get()) + ); } /** @@ -123,7 +127,9 @@ public void consume( String read = Files.readString(path, StandardCharsets.UTF_8); var readComment = headerComment.readHeaderComment(read); - List lines = this.licenseHeader.format(rootDir, projectCreationYear, logger, path, readComment.existing()); + List lines = this.licenseHeader.format( + rootDir, projectCreationYear, logger, path, readComment.existing() + ); if (lines != null) { this.updatedFiles.add(path); @@ -142,7 +148,9 @@ public void consume( end = readComment.separator() + readComment.separator() + end; } - String content = start + headerComment.writeHeaderComment(lines, readComment.separator()) + end; + String content = start + + headerComment.writeHeaderComment(lines, readComment.separator()) + + end; try { var backupPath = Utils.getBackupPath(buildPath, projectPath, path); diff --git a/src/main/java/dev/yumi/gradle/licenser/task/CheckLicenseTask.java b/src/main/java/dev/yumi/gradle/licenser/task/CheckLicenseTask.java index da6c04a..b35f5b1 100644 --- a/src/main/java/dev/yumi/gradle/licenser/task/CheckLicenseTask.java +++ b/src/main/java/dev/yumi/gradle/licenser/task/CheckLicenseTask.java @@ -15,27 +15,28 @@ import org.gradle.api.Action; import org.gradle.api.GradleException; import org.gradle.api.Project; -import org.gradle.api.file.ConfigurableFileCollection; import org.gradle.api.file.SourceDirectorySet; import org.gradle.api.logging.Logger; -import org.gradle.api.tasks.IgnoreEmptyDirectories; -import org.gradle.api.tasks.InputFiles; -import org.gradle.api.tasks.SkipWhenEmpty; import org.gradle.api.tasks.TaskAction; +import org.gradle.work.ChangeType; +import org.gradle.work.FileChange; +import org.gradle.work.InputChanges; import org.jetbrains.annotations.ApiStatus; import javax.inject.Inject; +import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; import java.util.List; +import java.util.stream.StreamSupport; /** * Represents a task that checks the validity of license headers in project files. * * @author LambdAurora - * @version 2.0.0 + * @version 2.1.0 * @since 1.0.0 */ @ApiStatus.Internal @@ -52,8 +53,17 @@ public CheckLicenseTask(YumiLicenserGradleExtension extension) { } @TaskAction - public void execute() { - this.execute(this.getHeaderCommentManager().get(), new Consumer(this.getLicenseHeader().get())); + public void execute(InputChanges inputChanges) { + this.execute( + this.getHeaderCommentManager().get(), + StreamSupport.stream( + inputChanges.getFileChanges(this.getSourceFiles()).spliterator(), + false + ).filter(action -> action.getChangeType() != ChangeType.REMOVED) + .map(FileChange::getFile) + .map(File::toPath), + new Consumer(this.getLicenseHeader().get()) + ); } /** diff --git a/src/main/java/dev/yumi/gradle/licenser/task/SourceDirectoryBasedTask.java b/src/main/java/dev/yumi/gradle/licenser/task/SourceDirectoryBasedTask.java index 1f1c96b..987dcd9 100644 --- a/src/main/java/dev/yumi/gradle/licenser/task/SourceDirectoryBasedTask.java +++ b/src/main/java/dev/yumi/gradle/licenser/task/SourceDirectoryBasedTask.java @@ -25,12 +25,13 @@ import java.io.IOException; import java.nio.file.Path; import java.util.Set; +import java.util.stream.Stream; /** * Represents a task that acts on a given source directory set. * * @author LambdAurora - * @version 2.0.0 + * @version 2.1.0 * @since 1.0.0 */ @ApiStatus.Internal @@ -95,15 +96,15 @@ protected SourceDirectoryBasedTask(YumiLicenserGradleExtension extension) { * Executes the given action to all matched files. * * @param headerCommentManager the header comment manager to find out the header comments of files + * @param sourceFiles the source files to treat in this task * @param consumer the action to execute on a given file */ - void execute(HeaderCommentManager headerCommentManager, SourceConsumer consumer) { + void execute(HeaderCommentManager headerCommentManager, Stream sourceFiles, SourceConsumer consumer) { Path rootDir = Path.of(this.getRootDirectory().get()); Path projectDir = Path.of(this.getProjectDirectory().get()); Path buildDir = Path.of(this.getBuildDirectory().get()); - for (var file : this.getSourceFiles()) { - Path sourcePath = file.toPath(); + sourceFiles.forEach(sourcePath -> { HeaderComment headerComment = headerCommentManager.findHeaderComment(sourcePath); if (headerComment != null) { @@ -121,7 +122,7 @@ void execute(HeaderCommentManager headerCommentManager, SourceConsumer consumer) throw new GradleException("Failed to load file " + sourcePath, e); } } - } + }); consumer.end(this.getLogger()); }