From 08774a1c74e22efc596d51b1ecebd4d936e0dff7 Mon Sep 17 00:00:00 2001 From: Simon Le Bras Date: Fri, 14 Nov 2025 20:27:53 +0100 Subject: [PATCH 1/3] Defer dependency resolution using LazyFiles --- .../spotless/extra/EquoBasedStepBuilder.java | 2 +- .../java/com/diffplug/spotless/JarState.java | 2 +- .../java/com/diffplug/spotless/LazyFiles.java | 32 +++++++++++++ .../com/diffplug/spotless/Provisioner.java | 12 ++--- .../gradle/spotless/GradleProvisioner.java | 48 ++++++++++--------- .../diffplug/spotless/TestProvisioner.java | 35 +++++++------- 6 files changed, 84 insertions(+), 47 deletions(-) create mode 100644 lib/src/main/java/com/diffplug/spotless/LazyFiles.java diff --git a/lib-extra/src/main/java/com/diffplug/spotless/extra/EquoBasedStepBuilder.java b/lib-extra/src/main/java/com/diffplug/spotless/extra/EquoBasedStepBuilder.java index c4c41e41df..676eb0b1e9 100644 --- a/lib-extra/src/main/java/com/diffplug/spotless/extra/EquoBasedStepBuilder.java +++ b/lib-extra/src/main/java/com/diffplug/spotless/extra/EquoBasedStepBuilder.java @@ -139,7 +139,7 @@ public FormatterStep build() { mavenDeps.add("dev.equo.ide:solstice:1.8.1"); mavenDeps.add("com.diffplug.durian:durian-swt.os:4.3.1"); mavenDeps.addAll(query.getJarsOnMavenCentral()); - classpath.addAll(mavenProvisioner.provisionWithTransitives(false, mavenDeps)); + classpath.addAll(mavenProvisioner.provisionWithTransitives(false, mavenDeps).files()); classpath.addAll(query.getJarsNotOnMavenCentral()); for (var nested : NestedJars.inFiles(query.getJarsNotOnMavenCentral()).extractAllNestedJars()) { classpath.add(nested.getValue()); diff --git a/lib/src/main/java/com/diffplug/spotless/JarState.java b/lib/src/main/java/com/diffplug/spotless/JarState.java index bcf55208b2..cba8a91369 100644 --- a/lib/src/main/java/com/diffplug/spotless/JarState.java +++ b/lib/src/main/java/com/diffplug/spotless/JarState.java @@ -129,7 +129,7 @@ public static JarState withoutTransitives(Collection mavenCoordinates, P private static JarState provisionWithTransitives(boolean withTransitives, Collection mavenCoordinates, Provisioner provisioner) throws IOException { Objects.requireNonNull(mavenCoordinates, "mavenCoordinates"); Objects.requireNonNull(provisioner, "provisioner"); - Set jars = provisioner.provisionWithTransitives(withTransitives, mavenCoordinates); + Set jars = provisioner.provisionWithTransitives(withTransitives, mavenCoordinates).files(); if (jars.isEmpty()) { throw new NoSuchElementException("Resolved to an empty result: " + String.join(", ", mavenCoordinates)); } diff --git a/lib/src/main/java/com/diffplug/spotless/LazyFiles.java b/lib/src/main/java/com/diffplug/spotless/LazyFiles.java new file mode 100644 index 0000000000..bed11f6a06 --- /dev/null +++ b/lib/src/main/java/com/diffplug/spotless/LazyFiles.java @@ -0,0 +1,32 @@ +/* + * Copyright 2025 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.spotless; + +import java.io.File; +import java.util.Set; + +/** + * A lazy provider of a set of files, to allow build systems + * to defer resolution of dependencies. + */ +@FunctionalInterface +public interface LazyFiles { + Set files(); + + static LazyFiles of(Set files) { + return () -> files; + } +} diff --git a/lib/src/main/java/com/diffplug/spotless/Provisioner.java b/lib/src/main/java/com/diffplug/spotless/Provisioner.java index 259cac5a7c..1742fab843 100644 --- a/lib/src/main/java/com/diffplug/spotless/Provisioner.java +++ b/lib/src/main/java/com/diffplug/spotless/Provisioner.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2020 DiffPlug + * Copyright 2016-2025 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,10 +15,8 @@ */ package com.diffplug.spotless; -import java.io.File; import java.util.Arrays; import java.util.Collection; -import java.util.Set; /** * Many spotless steps require third-party libraries, but we want to keep @@ -26,16 +24,16 @@ */ public interface Provisioner { /** - * Given a set of Maven coordinates, returns a set of jars which include all + * Given a set of Maven coordinates, returns a lazy set of jars which include all * of the specified coordinates and optionally their transitive dependencies. */ - public default Set provisionWithTransitives(boolean withTransitives, String... mavenCoordinates) { + public default LazyFiles provisionWithTransitives(boolean withTransitives, String... mavenCoordinates) { return provisionWithTransitives(withTransitives, Arrays.asList(mavenCoordinates)); } /** - * Given a set of Maven coordinates, returns a set of jars which include all + * Given a set of Maven coordinates, returns a lazy set of jars which include all * of the specified coordinates and optionally their transitive dependencies. */ - public Set provisionWithTransitives(boolean withTransitives, Collection mavenCoordinates); + public LazyFiles provisionWithTransitives(boolean withTransitives, Collection mavenCoordinates); } 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 b8260e3b9c..da7f56f62f 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 @@ -15,13 +15,12 @@ */ package com.diffplug.gradle.spotless; -import java.io.File; import java.util.Collection; import java.util.HashMap; import java.util.Map; -import java.util.Set; import org.gradle.api.GradleException; +import org.gradle.api.NamedDomainObjectProvider; import org.gradle.api.Project; import org.gradle.api.artifacts.Configuration; import org.gradle.api.artifacts.ConfigurationContainer; @@ -29,12 +28,14 @@ import org.gradle.api.attributes.Bundling; import org.gradle.api.attributes.Category; import org.gradle.api.attributes.java.TargetJvmEnvironment; +import org.gradle.api.file.FileCollection; import org.gradle.api.initialization.dsl.ScriptHandler; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.diffplug.common.base.Unhandled; import com.diffplug.common.collect.ImmutableList; +import com.diffplug.spotless.LazyFiles; import com.diffplug.spotless.Provisioner; /** Should be package-private. */ @@ -59,16 +60,16 @@ public DedupingProvisioner dedupingProvisioner(Project project) { static class DedupingProvisioner implements Provisioner { private final Provisioner provisioner; - private final Map> cache = new HashMap<>(); + private final Map cache = new HashMap<>(); DedupingProvisioner(Provisioner provisioner) { this.provisioner = provisioner; } @Override - public Set provisionWithTransitives(boolean withTransitives, Collection mavenCoordinates) { + public LazyFiles provisionWithTransitives(boolean withTransitives, Collection mavenCoordinates) { Request req = new Request(withTransitives, mavenCoordinates); - Set result; + LazyFiles result; synchronized (cache) { result = cache.get(req); } @@ -89,7 +90,7 @@ public Set provisionWithTransitives(boolean withTransitives, Collection { Request req = new Request(withTransitives, mavenCoordinates); - Set result; + LazyFiles result; synchronized (cache) { result = cache.get(req); } @@ -113,22 +114,25 @@ static Provisioner forRootProjectBuildscript(Project project) { private static Provisioner forConfigurationContainer(Project project, ConfigurationContainer configurations, DependencyHandler dependencies) { return (withTransitives, mavenCoords) -> { try { - Configuration config = configurations.create("spotless" - + new Request(withTransitives, mavenCoords).hashCode()); - mavenCoords.stream() - .map(dependencies::create) - .forEach(config.getDependencies()::add); - config.setDescription(mavenCoords.toString()); - config.setTransitive(withTransitives); - config.setCanBeConsumed(false); - config.setVisible(false); - config.attributes(attr -> { - attr.attribute(Category.CATEGORY_ATTRIBUTE, project.getObjects().named(Category.class, Category.LIBRARY)); - attr.attribute(Bundling.BUNDLING_ATTRIBUTE, project.getObjects().named(Bundling.class, Bundling.EXTERNAL)); - // Add this attribute for resolving Guava dependency, see https://github.com/google/guava/issues/6801. - attr.attribute(TargetJvmEnvironment.TARGET_JVM_ENVIRONMENT_ATTRIBUTE, project.getObjects().named(TargetJvmEnvironment.class, TargetJvmEnvironment.STANDARD_JVM)); - }); - return config.resolve(); + NamedDomainObjectProvider config = configurations.register( + "spotless" + new Request(withTransitives, mavenCoords).hashCode(), + files -> { + mavenCoords.stream() + .map(dependencies::create) + .forEach(files.getDependencies()::add); + files.setDescription(mavenCoords.toString()); + files.setTransitive(withTransitives); + files.setCanBeConsumed(false); + files.setVisible(false); + files.attributes(attr -> { + attr.attribute(Category.CATEGORY_ATTRIBUTE, project.getObjects().named(Category.class, Category.LIBRARY)); + attr.attribute(Bundling.BUNDLING_ATTRIBUTE, project.getObjects().named(Bundling.class, Bundling.EXTERNAL)); + attr.attribute(TargetJvmEnvironment.TARGET_JVM_ENVIRONMENT_ATTRIBUTE, project.getObjects().named(TargetJvmEnvironment.class, TargetJvmEnvironment.STANDARD_JVM)); + }); + }); + + final FileCollection configFiles = project.files(config); + return configFiles::getFiles; } catch (Exception e) { String projName = project.getPath().substring(1).replace(':', '/'); if (!projName.isEmpty()) { diff --git a/testlib/src/main/java/com/diffplug/spotless/TestProvisioner.java b/testlib/src/main/java/com/diffplug/spotless/TestProvisioner.java index 55558f9b37..33b89f72b3 100644 --- a/testlib/src/main/java/com/diffplug/spotless/TestProvisioner.java +++ b/testlib/src/main/java/com/diffplug/spotless/TestProvisioner.java @@ -23,6 +23,7 @@ import java.util.Comparator; import java.util.HashMap; import java.util.Map; +import java.util.Set; import java.util.function.Consumer; import java.util.function.Supplier; @@ -77,22 +78,24 @@ private static Provisioner createWithRepositories(Consumer re // Add this attribute for resolving Guava dependency, see https://github.com/google/guava/issues/6801. attr.attribute(TargetJvmEnvironment.TARGET_JVM_ENVIRONMENT_ATTRIBUTE, project.getObjects().named(TargetJvmEnvironment.class, TargetJvmEnvironment.STANDARD_JVM)); }); - try { - return config.resolve(); - } catch (ResolveException e) { - /* Provide Maven coordinates in exception message instead of static string 'detachedConfiguration' */ - throw new RuntimeException("Error resolving configuration: " + config.getDescription(), e); - } finally { - // delete the temp dir + return (LazyFiles) () -> { try { - java.nio.file.Files.walk(tempDir.toPath()) - .sorted(Comparator.reverseOrder()) - .map(Path::toFile) - .forEach(File::delete); - } catch (IOException e) { - throw Errors.asRuntime(e); + return config.resolve(); + } catch (ResolveException e) { + /* Provide Maven coordinates in exception message instead of static string 'detachedConfiguration' */ + throw new RuntimeException("Error resolving configuration: " + config.getDescription(), e); + } finally { + // delete the temp dir + try { + java.nio.file.Files.walk(tempDir.toPath()) + .sorted(Comparator.reverseOrder()) + .map(Path::toFile) + .forEach(File::delete); + } catch (IOException e) { + throw Errors.asRuntime(e); + } } - } + }; }; } @@ -125,7 +128,7 @@ private static Provisioner caching(String name, Supplier input) { // double-check that depcache pruning hasn't removed them since our cache cached them boolean needsToBeSet = result == null || !result.stream().allMatch(file -> file.exists() && file.isFile() && file.length() > 0); if (needsToBeSet) { - result = ImmutableSet.copyOf(input.get().provisionWithTransitives(withTransitives, mavenCoords)); + result = ImmutableSet.copyOf(input.get().provisionWithTransitives(withTransitives, mavenCoords).files()); cached.put(mavenCoords, result); try (ObjectOutputStream outputStream = new ObjectOutputStream(Files.asByteSink(cacheFile).openBufferedStream())) { outputStream.writeObject(cached); @@ -133,7 +136,7 @@ private static Provisioner caching(String name, Supplier input) { throw Errors.asRuntime(e); } } - return result; + return LazyFiles.of(result); } }; } From 3a6d83a695ddf99eb2e555a09d94b11b45d6c8ea Mon Sep 17 00:00:00 2001 From: Simon Le Bras Date: Fri, 14 Nov 2025 21:00:04 +0100 Subject: [PATCH 2/3] Code formatting --- .../java/com/diffplug/gradle/spotless/GradleProvisioner.java | 1 - testlib/src/main/java/com/diffplug/spotless/TestProvisioner.java | 1 - 2 files changed, 2 deletions(-) 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 da7f56f62f..62b96a4fa5 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 @@ -130,7 +130,6 @@ private static Provisioner forConfigurationContainer(Project project, Configurat attr.attribute(TargetJvmEnvironment.TARGET_JVM_ENVIRONMENT_ATTRIBUTE, project.getObjects().named(TargetJvmEnvironment.class, TargetJvmEnvironment.STANDARD_JVM)); }); }); - final FileCollection configFiles = project.files(config); return configFiles::getFiles; } catch (Exception e) { diff --git a/testlib/src/main/java/com/diffplug/spotless/TestProvisioner.java b/testlib/src/main/java/com/diffplug/spotless/TestProvisioner.java index 33b89f72b3..0e42a4a0bf 100644 --- a/testlib/src/main/java/com/diffplug/spotless/TestProvisioner.java +++ b/testlib/src/main/java/com/diffplug/spotless/TestProvisioner.java @@ -23,7 +23,6 @@ import java.util.Comparator; import java.util.HashMap; import java.util.Map; -import java.util.Set; import java.util.function.Consumer; import java.util.function.Supplier; From a5a7fb611372dad1c262a61ee8c7d80c9a389204 Mon Sep 17 00:00:00 2001 From: Simon Le Bras Date: Fri, 14 Nov 2025 21:16:50 +0100 Subject: [PATCH 3/3] Use LazyFiles on testLib and Maven plugin --- .../java/com/diffplug/spotless/JarStateTest.java | 6 +++++- .../diffplug/spotless/maven/ArtifactResolver.java | 7 +++++-- .../com/diffplug/spotless/ProvisionerTest.java | 14 +++++++++----- 3 files changed, 19 insertions(+), 8 deletions(-) diff --git a/lib/src/test/java/com/diffplug/spotless/JarStateTest.java b/lib/src/test/java/com/diffplug/spotless/JarStateTest.java index 8b3d84fc18..56e9a0fc91 100644 --- a/lib/src/test/java/com/diffplug/spotless/JarStateTest.java +++ b/lib/src/test/java/com/diffplug/spotless/JarStateTest.java @@ -25,6 +25,7 @@ import java.net.URLClassLoader; import java.nio.file.Files; import java.nio.file.Path; +import java.util.Set; import java.util.stream.Collectors; import org.assertj.core.api.SoftAssertions; @@ -42,7 +43,10 @@ class JarStateTest { File b; - Provisioner provisioner = (withTransitives, deps) -> deps.stream().map(name -> name.equals("a") ? a : b).collect(Collectors.toSet()); + Provisioner provisioner = (withTransitives, deps) -> { + Set files = deps.stream().map(name -> name.equals("a") ? a : b).collect(Collectors.toSet()); + return LazyFiles.of(files); + }; @BeforeEach void setUp() throws IOException { diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/ArtifactResolver.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/ArtifactResolver.java index d94238b752..23c969ec53 100644 --- a/plugin-maven/src/main/java/com/diffplug/spotless/maven/ArtifactResolver.java +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/ArtifactResolver.java @@ -39,6 +39,8 @@ import org.eclipse.aether.resolution.DependencyResolutionException; import org.eclipse.aether.resolution.DependencyResult; +import com.diffplug.spotless.LazyFiles; + public class ArtifactResolver { private static final Exclusion EXCLUDE_ALL_TRANSITIVES = new Exclusion("*", "*", "*", "*"); @@ -60,7 +62,7 @@ public ArtifactResolver(RepositorySystem repositorySystem, RepositorySystemSessi * Given a set of maven coordinates, returns a set of jars which include all * of the specified coordinates and optionally their transitive dependencies. */ - public Set resolve(boolean withTransitives, Collection mavenCoordinates) { + public LazyFiles resolve(boolean withTransitives, Collection mavenCoordinates) { Collection excludeTransitive = new ArrayList<>(1); if (!withTransitives) { excludeTransitive.add(EXCLUDE_ALL_TRANSITIVES); @@ -73,12 +75,13 @@ public Set resolve(boolean withTransitives, Collection mavenCoordi DependencyRequest dependencyRequest = new DependencyRequest(collectRequest, null); DependencyResult dependencyResult = resolveDependencies(dependencyRequest); - return dependencyResult.getArtifactResults() + Set jars = dependencyResult.getArtifactResults() .stream() .peek(this::logResolved) .map(ArtifactResult::getArtifact) .map(Artifact::getFile) .collect(toSet()); + return LazyFiles.of(jars); } private DependencyResult resolveDependencies(DependencyRequest dependencyRequest) { diff --git a/testlib/src/test/java/com/diffplug/spotless/ProvisionerTest.java b/testlib/src/test/java/com/diffplug/spotless/ProvisionerTest.java index e3616a5844..c4a8b1c83f 100644 --- a/testlib/src/test/java/com/diffplug/spotless/ProvisionerTest.java +++ b/testlib/src/test/java/com/diffplug/spotless/ProvisionerTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2021 DiffPlug + * Copyright 2016-2025 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,6 +17,7 @@ import java.io.File; import java.util.Arrays; +import java.util.Set; import java.util.stream.Collectors; import org.assertj.core.api.Assertions; @@ -25,12 +26,15 @@ class ProvisionerTest { @Test void testManipulation() { - Provisioner provisioner = (withTransitives, deps) -> deps.stream().map(File::new).collect(Collectors.toSet()); - Assertions.assertThat(provisioner.provisionWithTransitives(true, "a")) + Provisioner provisioner = (withTransitives, deps) -> { + Set files = deps.stream().map(File::new).collect(Collectors.toSet()); + return LazyFiles.of(files); + }; + Assertions.assertThat(provisioner.provisionWithTransitives(true, "a").files()) .containsExactlyInAnyOrder(new File("a")); - Assertions.assertThat(provisioner.provisionWithTransitives(true, "a", "a")) + Assertions.assertThat(provisioner.provisionWithTransitives(true, "a", "a").files()) .containsExactlyInAnyOrder(new File("a")); - Assertions.assertThat(provisioner.provisionWithTransitives(true, Arrays.asList("a", "a"))) + Assertions.assertThat(provisioner.provisionWithTransitives(true, Arrays.asList("a", "a")).files()) .containsExactlyInAnyOrder(new File("a")); } }