diff --git a/CHANGES.md b/CHANGES.md
index 23772c0..8cee10f 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -1,8 +1,10 @@
# ImageGrinder
## [Unreleased]
+### Fixed
+- Now supports the Gradle Configuration Cache, but we had to lose support for incremental build ([#9](https://github.com/diffplug/image-grinder/pull/9)).
-## [2.2.0] - 2021-09-04
+## [2.2.0] - 2021-09-04 [YANKED]
### Added
- Now supports the Gradle Configuration Cache ([#8](https://github.com/diffplug/image-grinder/pull/8)).
- This required bumping our minimum required Gradle from `5.6` to `6.0`.
diff --git a/README.md b/README.md
index a63d0f9..25f44cc 100644
--- a/README.md
+++ b/README.md
@@ -49,7 +49,8 @@ imageGrinder {
Every single file in `srcDir` needs to be an image that ImageGrinder can parse. Each image will be parsed, and wrapped into an [`Img`](https://javadoc.io/doc/com.diffplug.gradle/image-grinder/2.2.0/com/diffplug/gradle/imagegrinder/Img.html). Call its methods to grind it into whatever you need in the `dstDir`.
-ImageGrinder uses the gradle [Worker API](https://docs.gradle.org/6.0/userguide/custom_tasks.html#worker_api) introduced in Gradle 5.6 to use all your CPU cores for grinding. It also uses gradle's [incremental task](https://docs.gradle.org/6.0/userguide/custom_tasks.html#incremental_tasks) support to do the minimum amount of grinding required. And if you're using the [configuration cache](https://docs.gradle.org/6.6/userguide/configuration_cache.html) introduced in Gradle 6.6, that'll work too for near-instant startup times.
+ImageGrinder uses the gradle [Worker API](https://docs.gradle.org/6.6/userguide/custom_tasks.html#worker_api) to use all your CPU cores for grinding, the [buildcache](https://docs.gradle.org/6.6/userguide/build_cache.html) to minimize the necessary work, and it also supports the [configuration cache](https://docs.gradle.org/6.6/userguide/configuration_cache.html) for near-instant startup times. It does not currently support [incremental update](https://docs.gradle.org/6.0/userguide/custom_tasks.html#incremental_tasks), but if you go back to `2.1.3` you can get that back in return for losing the configuration cache (see [#9](https://github.com/diffplug/image-grinder/pull/9) for details).
+
## Configuration avoidance
diff --git a/src/main/java/com/diffplug/gradle/imagegrinder/ImageGrinderPlugin.java b/src/main/java/com/diffplug/gradle/imagegrinder/ImageGrinderPlugin.java
index 655c6b1..2a08dd2 100644
--- a/src/main/java/com/diffplug/gradle/imagegrinder/ImageGrinderPlugin.java
+++ b/src/main/java/com/diffplug/gradle/imagegrinder/ImageGrinderPlugin.java
@@ -33,7 +33,6 @@ public void apply(Project project) {
@Override
public ImageGrinderTask create(String name) {
ImageGrinderTask task = project.getTasks().create(name, ImageGrinderTask.class);
- task.getBuildDir().set(project.getBuildDir());
if (name.startsWith("process")) {
Task processResources = project.getTasks().getByName(JavaPlugin.PROCESS_RESOURCES_TASK_NAME);
processResources.dependsOn(task);
diff --git a/src/main/java/com/diffplug/gradle/imagegrinder/ImageGrinderTask.java b/src/main/java/com/diffplug/gradle/imagegrinder/ImageGrinderTask.java
index 4c20e4e..03e4caa 100644
--- a/src/main/java/com/diffplug/gradle/imagegrinder/ImageGrinderTask.java
+++ b/src/main/java/com/diffplug/gradle/imagegrinder/ImageGrinderTask.java
@@ -16,32 +16,24 @@
package com.diffplug.gradle.imagegrinder;
-import com.diffplug.common.collect.HashMultimap;
import java.io.File;
import java.io.Serializable;
import java.util.Objects;
import java.util.Random;
-import java.util.Set;
import javax.inject.Inject;
import org.gradle.api.Action;
import org.gradle.api.DefaultTask;
import org.gradle.api.file.DirectoryProperty;
import org.gradle.api.file.FileSystemOperations;
-import org.gradle.api.file.FileType;
import org.gradle.api.file.RegularFileProperty;
import org.gradle.api.provider.Property;
import org.gradle.api.tasks.CacheableTask;
import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.InputDirectory;
-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.work.ChangeType;
-import org.gradle.work.FileChange;
-import org.gradle.work.Incremental;
-import org.gradle.work.InputChanges;
import org.gradle.workers.WorkAction;
import org.gradle.workers.WorkParameters;
import org.gradle.workers.WorkQueue;
@@ -57,19 +49,6 @@
* Worker requires that all arguments to its worker runnables ({@link ImageGrinderTask}
* in this case) be Serializable. There's no way to serialize our {@link #grinder(Action)}, so we had
* to use {@link SerializableRef} to sneakily pass our task to the worker.
- *
- * ## Tedious thing #2: Removal handling
- *
- * Tedious thing #2: .java to .class has a 1:1 mapping. But that is not true for these images - a pipeline
- * might create two images from one source, and the number of outputs might even change based on the content
- * of the input (e.g. skip hi-res versions of very large images).
- *
- * That means that when the user removes or changes an image, we need to remember exactly which files it
- * created last time, or else we might end up with stale results lying around. So, this task has the
- * {@link #map} field which is a multimap from source file to the dst files it created. When the task starts,
- * it reads this map from disk, and when the task finishes, it writes it to disk. Whenever an {@link Img} is
- * rendered, the filename that was written is saved to this map via the {@link Img#registerDstFile(String)}
- * method.
*/
@CacheableTask
public abstract class ImageGrinderTask extends DefaultTask {
@@ -80,10 +59,6 @@ public ImageGrinderTask(WorkerExecutor workerExecutor) {
this.workerExecutor = workerExecutor;
}
- @Internal
- public abstract DirectoryProperty getBuildDir();
-
- @Incremental
@PathSensitive(PathSensitivity.RELATIVE)
@InputDirectory
public abstract DirectoryProperty getSrcDir();
@@ -114,65 +89,19 @@ public void grinder(Action
> grinder) {
public abstract FileSystemOperations getFs();
@TaskAction
- public void performAction(InputChanges inputChanges) throws Exception {
+ public void performAction() throws Exception {
Objects.requireNonNull(grinder, "grinder");
+ getFs().delete(deleteSpec -> deleteSpec.delete(getDstDir()));
- File cache = new File(getBuildDir().getAsFile().get(), "cache" + getName());
- if (!inputChanges.isIncremental()) {
- getFs().delete(deleteSpec -> deleteSpec.delete(getDstDir().getAsFile().get()));
- map = HashMultimap.create();
- } else {
- readFromCache(cache);
- }
WorkQueue queue = workerExecutor.noIsolation();
- for (FileChange fileChange : inputChanges.getFileChanges(getSrcDir())) {
- if (fileChange.getFileType() == FileType.DIRECTORY) {
- continue;
- }
- boolean modifiedOrRemoved = fileChange.getChangeType() == ChangeType.MODIFIED || fileChange.getChangeType() == ChangeType.REMOVED;
- boolean modifiedOrAdded = fileChange.getChangeType() == ChangeType.MODIFIED || fileChange.getChangeType() == ChangeType.ADDED;
- if (modifiedOrRemoved) {
- logger.info("clean: " + fileChange.getNormalizedPath());
- remove(fileChange.getFile());
- }
- if (modifiedOrAdded) {
- logger.info("render: " + fileChange.getNormalizedPath());
- queue.submit(RenderSvg.class, params -> {
- params.getSourceFile().set(fileChange.getFile());
- params.getTaskRef().set(SerializableRef.create(ImageGrinderTask.this));
- });
- }
- }
- queue.await();
- writeToCache(cache);
- }
-
- private void remove(File srcFile) {
- synchronized (map) {
- Set toDelete = map.removeAll(srcFile);
- getFs().delete(spec -> {
- spec.delete(toDelete.toArray());
+ getSrcDir().get().getAsFileTree().visit(fileVisit -> {
+ logger.info("render: " + fileVisit.getRelativePath());
+ queue.submit(RenderSvg.class, params -> {
+ params.getSourceFile().set(fileVisit.getFile());
+ params.getTaskRef().set(SerializableRef.create(ImageGrinderTask.this));
});
- }
- }
-
- public boolean debug = false;
-
- HashMultimap map;
-
- @SuppressWarnings("unchecked")
- private void readFromCache(File file) {
- if (file.exists()) {
- map = SerializableMisc.fromFile(HashMultimap.class, file);
- } else {
- map = HashMultimap.create();
- }
- }
-
- private void writeToCache(File file) {
- synchronized (map) {
- SerializableMisc.toFile(map, file);
- }
+ });
+ queue.await();
}
public interface RenderSvgParams extends WorkParameters {
diff --git a/src/main/java/com/diffplug/gradle/imagegrinder/Img.java b/src/main/java/com/diffplug/gradle/imagegrinder/Img.java
index 587326f..2af2b42 100644
--- a/src/main/java/com/diffplug/gradle/imagegrinder/Img.java
+++ b/src/main/java/com/diffplug/gradle/imagegrinder/Img.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2020 DiffPlug
+ * Copyright (C) 2020-2021 DiffPlug
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -83,9 +83,10 @@ public void renderFull(String fullPath, Size size) {
File registerDstFile(String fullPath) {
File file = new File(task.getDstDir().getAsFile().get(), fullPath);
FileMisc.mkdirs(file.getParentFile());
- synchronized (task.map) {
- task.map.put(new File(task.getSrcDir().getAsFile().get(), subpath.full()), file);
- }
+ // TODO: useful for incremental build in the future, perhaps
+ // synchronized (task.map) {
+ // task.map.put(new File(task.getSrcDir().getAsFile().get(), subpath.full()), file);
+ // }
return file;
}
diff --git a/src/main/java/com/diffplug/gradle/imagegrinder/Subpath.java b/src/main/java/com/diffplug/gradle/imagegrinder/Subpath.java
index 08763d3..a223768 100644
--- a/src/main/java/com/diffplug/gradle/imagegrinder/Subpath.java
+++ b/src/main/java/com/diffplug/gradle/imagegrinder/Subpath.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2020 DiffPlug
+ * Copyright (C) 2020-2021 DiffPlug
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,12 +18,21 @@
import com.diffplug.common.base.Preconditions;
import java.io.File;
+import org.gradle.api.file.FileSystemLocationProperty;
public class Subpath {
private final String full;
private final String extension;
private final String withoutExtension;
+ public static Subpath from(FileSystemLocationProperty> root, FileSystemLocationProperty> child) {
+ return from(root.getAsFile().get(), child.getAsFile().get());
+ }
+
+ public static Subpath from(FileSystemLocationProperty> root, File child) {
+ return from(root.getAsFile().get(), child);
+ }
+
public static Subpath from(File root, File child) {
String rootPath = root.getAbsolutePath().replace('\\', '/') + '/';
String childPath = child.getAbsolutePath().replace('\\', '/');
@@ -31,6 +40,14 @@ public static Subpath from(File root, File child) {
return new Subpath(childPath.substring(rootPath.length()));
}
+ public File resolve(File root) {
+ return new File(root, full);
+ }
+
+ public File resolve(FileSystemLocationProperty> root) {
+ return resolve(root.getAsFile().get());
+ }
+
public String full() {
return full;
}
@@ -62,4 +79,21 @@ static String extension(String subpath) {
Preconditions.checkArgument(idx < subpath.length() - 1, "'%s' can't end in '.'", subpath);
return subpath.substring(idx + 1);
}
+
+ @Override
+ public int hashCode() {
+ return full.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ } else if (other instanceof Subpath) {
+ Subpath sub = (Subpath) other;
+ return sub.full.equals(full);
+ } else {
+ return false;
+ }
+ }
}
diff --git a/src/test/java/com/diffplug/gradle/imagegrinder/ImageGrinderPluginTest.java b/src/test/java/com/diffplug/gradle/imagegrinder/ImageGrinderPluginTest.java
index 5824415..be354ee 100644
--- a/src/test/java/com/diffplug/gradle/imagegrinder/ImageGrinderPluginTest.java
+++ b/src/test/java/com/diffplug/gradle/imagegrinder/ImageGrinderPluginTest.java
@@ -117,35 +117,4 @@ public void testUpToDate() throws Exception {
runAndAssert(TaskOutcome.SUCCESS);
runAndAssert(TaskOutcome.UP_TO_DATE);
}
-
- @Test
- public void testIncremental() throws Exception {
- writeBuild();
-
- // one file
- write("src/refresh.svg", readTestResource("refresh.svg"));
- runAndAssert(TaskOutcome.SUCCESS).containsExactly("render: refresh.svg");
- assertFolderContent("dst").containsExactly("refresh.png", "refresh@2x.png");
-
- // add a file, and only it changes
- write("src/diffpluglogo.svg", readTestResource("diffpluglogo.svg"));
- runAndAssert(TaskOutcome.SUCCESS).containsExactly("render: diffpluglogo.svg");
- assertFolderContent("dst").containsExactly("diffpluglogo.png", "diffpluglogo@2x.png", "refresh.png", "refresh@2x.png");
-
- // remove a file, and only it is removed
- delete("src/refresh.svg");
- runAndAssert(TaskOutcome.SUCCESS).containsExactly("clean: refresh.svg");
- assertFolderContent("dst").containsExactly("diffpluglogo.png", "diffpluglogo@2x.png");
-
- // remove another file, and we end up with an empty directory
- delete("src/diffpluglogo.svg");
- runAndAssert(TaskOutcome.SUCCESS).containsExactly("clean: diffpluglogo.svg");
- assertFolderContent("dst").isEmpty();
-
- // add them both, and they're both rendered
- write("src/refresh.svg", readTestResource("refresh.svg"));
- write("src/diffpluglogo.svg", readTestResource("diffpluglogo.svg"));
- runAndAssert(TaskOutcome.SUCCESS).containsExactlyInAnyOrder("render: refresh.svg", "render: diffpluglogo.svg");
- assertFolderContent("dst").containsExactly("diffpluglogo.png", "diffpluglogo@2x.png", "refresh.png", "refresh@2x.png");
- }
}