Skip to content

Commit 9973255

Browse files
authored
Make palantir-java-format support spotless 6.22.0 (#1313)
Make palantir-java-format support spotless 6.22.0, which is [transitively brought in from baseline to support the configuration cache](palantir/gradle-baseline#3119)
1 parent 2bc3617 commit 9973255

File tree

8 files changed

+279
-126
lines changed

8 files changed

+279
-126
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
type: improvement
2+
improvement:
3+
description: Make palantir-java-format support spotless 6.22.0, which is [transitively
4+
brought in from baseline to support the configuration cache](https://github.com/palantir/gradle-baseline/pull/3119)
5+
links:
6+
- https://github.com/palantir/palantir-java-format/pull/1313

gradle-palantir-java-format/src/main/java/com/palantir/javaformat/gradle/spotless/NativePalantirJavaFormatStep.java

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -25,41 +25,42 @@
2525
import java.io.Serializable;
2626
import java.nio.charset.StandardCharsets;
2727
import java.util.List;
28+
import java.util.function.Supplier;
2829
import org.gradle.api.artifacts.Configuration;
2930
import org.gradle.api.logging.Logger;
3031
import org.gradle.api.logging.Logging;
3132

3233
public final class NativePalantirJavaFormatStep {
33-
private static Logger logger = Logging.getLogger(NativePalantirJavaFormatStep.class);
34+
private static final Logger logger = Logging.getLogger(NativePalantirJavaFormatStep.class);
3435

3536
private NativePalantirJavaFormatStep() {}
3637

3738
private static final String NAME = "palantir-java-format";
3839

3940
/** Creates a step which formats everything - code, import order, and unused imports. */
4041
public static FormatterStep create(Configuration configuration) {
41-
return FormatterStep.createLazy(
42-
NAME,
43-
() -> {
44-
File execFile = configuration.getSingleFile();
45-
logger.info("Using native-image at {}", configuration.getSingleFile());
46-
return new State(FileSignature.signAsSet(execFile));
47-
},
48-
State::createFormat);
42+
return FormatterStep.createLazy(NAME, () -> new State(configuration::getSingleFile), State::createFormat);
4943
}
5044

5145
static class State implements Serializable {
5246
private static final long serialVersionUID = 1L;
5347

54-
final FileSignature pathToExe;
48+
// Kept for state serialization purposes.
49+
@SuppressWarnings({"unused", "FieldCanBeLocal"})
50+
private FileSignature execSignature;
5551

56-
State(FileSignature pathToExe) {
57-
this.pathToExe = pathToExe;
52+
private final transient Supplier<File> execSupplier;
53+
54+
State(Supplier<File> supplier) {
55+
this.execSupplier = supplier;
5856
}
5957

6058
String format(ProcessRunner runner, String input) throws IOException, InterruptedException {
59+
File execFile = execSupplier.get();
60+
logger.info("Using native-image at {}", execFile);
61+
execSignature = FileSignature.signAsSet(execFile);
6162
List<String> argumentsWithPathToExe =
62-
List.of(pathToExe.getOnlyFile().getAbsolutePath(), "--palantir", "-");
63+
List.of(execSignature.getOnlyFile().getAbsolutePath(), "--palantir", "-");
6364
return runner.exec(input.getBytes(StandardCharsets.UTF_8), argumentsWithPathToExe)
6465
.assertExitZero(StandardCharsets.UTF_8);
6566
}

gradle-palantir-java-format/src/main/java/com/palantir/javaformat/gradle/spotless/PalantirJavaFormatStep.java

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ public static FormatterStep create(Configuration palantirJavaFormat, JavaFormatE
3939
ensureImplementationNotDirectlyLoadable();
4040
Supplier<FormatterService> memoizedService = extension::serviceLoad;
4141
return FormatterStep.createLazy(
42-
NAME, () -> new State(palantirJavaFormat.getFiles(), memoizedService), State::createFormat);
42+
NAME, () -> new State(palantirJavaFormat::getFiles, memoizedService), State::createFormat);
4343
}
4444

4545
static final class State implements Serializable {
@@ -51,25 +51,36 @@ static final class State implements Serializable {
5151

5252
// Kept for state serialization purposes.
5353
@SuppressWarnings({"unused", "FieldCanBeLocal"})
54-
private final FileSignature jarsSignature;
54+
private FileSignature jarsSignature;
55+
56+
private final transient Supplier<Iterable<File>> jarsSupplier;
5557

5658
// Transient as this is not serializable.
5759
private final transient Supplier<FormatterService> memoizedFormatter;
5860

5961
/**
6062
* Build a cacheable state for spotless from the given jars, that uses the given {@link FormatterService}.
6163
*
62-
* @param jars The jars that contain the palantir-java-format implementation. This is only used for caching and
64+
* @param jarsSupplier Supplies the jars that contain the palantir-java-format implementation. This is only used for caching and
6365
* up-to-dateness purposes.
6466
*/
65-
State(Iterable<File> jars, Supplier<FormatterService> memoizedFormatter) throws IOException {
66-
this.jarsSignature = FileSignature.signAsSet(jars);
67+
State(Supplier<Iterable<File>> jarsSupplier, Supplier<FormatterService> memoizedFormatter) {
68+
this.jarsSupplier = jarsSupplier;
6769
this.memoizedFormatter = memoizedFormatter;
6870
}
6971

7072
@SuppressWarnings("NullableProblems")
7173
FormatterFunc createFormat() {
72-
return memoizedFormatter.get()::formatSourceReflowStringsAndFixImports;
74+
return input -> {
75+
try {
76+
// Only resolve the jars and compute the signature at execution time!
77+
Iterable<File> jars = jarsSupplier.get();
78+
this.jarsSignature = FileSignature.signAsSet(jars);
79+
return memoizedFormatter.get().formatSourceReflowStringsAndFixImports(input);
80+
} catch (IOException e) {
81+
throw new RuntimeException(e);
82+
}
83+
};
7384
}
7485
}
7586

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
/*
2+
* (c) Copyright 2025 Palantir Technologies Inc. All rights reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.palantir.javaformat.gradle;
17+
18+
import com.palantir.javaformat.gradle.spotless.PalantirJavaFormatStep;
19+
import com.palantir.javaformat.java.Formatter;
20+
import java.io.BufferedReader;
21+
import java.io.File;
22+
import java.io.IOException;
23+
import java.io.InputStream;
24+
import java.io.InputStreamReader;
25+
import java.nio.charset.StandardCharsets;
26+
import java.util.Arrays;
27+
import java.util.List;
28+
import java.util.Optional;
29+
import java.util.concurrent.TimeUnit;
30+
import java.util.stream.Collectors;
31+
import java.util.stream.Stream;
32+
import nebula.test.IntegrationTestKitSpec;
33+
import nebula.test.functional.internal.classpath.ClasspathAddingInitScriptBuilder;
34+
import org.gradle.testkit.runner.internal.PluginUnderTestMetadataReading;
35+
36+
/**
37+
* {@link IntegrationTestKitSpec} currently loads <a href="https://github.com/nebula-plugins/nebula-test/blob/c5d3af9004898276bde5c68da492c6b0b4c5facc/src/main/groovy/nebula/test/IntegrationTestKitBase.groovy#L136"> more than what it needs into the classpath</a>.
38+
* This means if we run a test with {@link IntegrationTestKitSpec}'s runner, the {@link Formatter} is on the build's classpath by virtue of being in the test's classpath.
39+
* If the test applies the {@link PalantirJavaFormatPlugin}, it complains that the {@link Formatter} is <a href="https://github.com/palantir/palantir-java-format/blob/00b08d2f471d66382d6c4cd2d05f56b6bb546ad3/gradle-palantir-java-format/src/main/java/com/palantir/javaformat/gradle/spotless/PalantirJavaFormatStep.java#L83">erroneously loadable</a>.
40+
* To be clear, this complaint is entirely a result of the {@link IntegrationTestKitSpec} loading too many things onto classpath since it doesn't know what the exact plugin classpath is.
41+
* As a workaround, this runner uses the classpath produced by Gradle Test Kit in {@code plugin-under-test-metadata.properties}.
42+
* This classpath only contains the dependencies required by the plugin, as well as the plugin itself.
43+
* This means that even if we put the formatter on the {@code testClassPath}, it won't leak through to the Gradle build under test and subsequently no error from {@link PalantirJavaFormatStep}.
44+
*/
45+
public class GradlewExecutor {
46+
private File projectDir;
47+
48+
public GradlewExecutor(File projectDir) {
49+
this.projectDir = projectDir;
50+
}
51+
52+
private static List<File> getBuildPluginClasspathInjector() {
53+
return PluginUnderTestMetadataReading.readImplementationClasspath();
54+
}
55+
56+
public GradlewExecutionResult runGradlewTasks(String... tasks) {
57+
try {
58+
ProcessBuilder processBuilder = getProcessBuilder(tasks);
59+
Process process = processBuilder.start();
60+
String output = readAllInput(process.getInputStream());
61+
process.waitFor(1, TimeUnit.MINUTES);
62+
return new GradlewExecutionResult(process.exitValue(), output);
63+
} catch (InterruptedException | IOException e) {
64+
return new GradlewExecutionResult(-1, "", e);
65+
}
66+
}
67+
68+
private static String readAllInput(InputStream inputStream) {
69+
try {
70+
Stream<String> lines =
71+
new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8)).lines();
72+
return lines.collect(Collectors.joining("\n"));
73+
} catch (Exception e) {
74+
throw new RuntimeException("GradlewExecutor failed to readAllInput", e);
75+
}
76+
}
77+
78+
private ProcessBuilder getProcessBuilder(String... tasks) {
79+
File initScript = new File(projectDir, "init.gradle");
80+
ClasspathAddingInitScriptBuilder.build(initScript, getBuildPluginClasspathInjector());
81+
82+
List<String> arguments = Stream.concat(
83+
Stream.of(
84+
"./gradlew",
85+
"--init-script",
86+
initScript.toPath().toString()),
87+
Arrays.stream(tasks))
88+
.toList();
89+
90+
return new ProcessBuilder().command(arguments).directory(projectDir).redirectErrorStream(true);
91+
}
92+
93+
public record GradlewExecutionResult(boolean success, String standardOutput, Optional<Throwable> failure) {
94+
public GradlewExecutionResult(int exitValue, String output, Throwable failure) {
95+
this(exitValue == 0, output, Optional.of(failure));
96+
}
97+
98+
public GradlewExecutionResult(int exitValue, String output) {
99+
this(exitValue == 0, output, Optional.empty());
100+
}
101+
}
102+
}

gradle-palantir-java-format/src/test/groovy/com/palantir/javaformat/gradle/PalantirJavaFormatIdeaPluginTest.groovy

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ class PalantirJavaFormatIdeaPluginTest extends IntegrationTestKitSpec {
3131
id 'com.palantir.java-format-idea'
3232
}
3333
apply plugin: 'idea'
34-
34+
3535
dependencies {
3636
palantirJavaFormat project.files() // no need to store the real thing in here
3737
EXTRA_CONFIGURATION

gradle-palantir-java-format/src/test/groovy/com/palantir/javaformat/gradle/PalantirJavaFormatPluginTest.groovy

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -45,11 +45,11 @@ class PalantirJavaFormatPluginTest extends IntegrationTestKitSpec {
4545

4646
// Add jvm args to allow spotless and formatter gradle plugins to run with Java 16+
4747
file('gradle.properties') << """
48-
org.gradle.jvmargs=--add-exports jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED \
49-
--add-exports jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED \
50-
--add-exports jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED \
51-
--add-exports jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED \
52-
--add-exports jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED
48+
org.gradle.jvmargs=--add-exports jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED \
49+
--add-exports jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED \
50+
--add-exports jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED \
51+
--add-exports jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED \
52+
--add-exports jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED
5353
""".stripIndent()
5454

5555
"git init".execute(Collections.emptyList(), projectDir).waitFor()

0 commit comments

Comments
 (0)