Skip to content

Make palantir-java-format configuration cacheable #1314

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 29 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
a8b996f
make lazy
Jun 3, 2025
a5f872a
Make NativePalantirJavaFormatStep lazy as well
Jun 3, 2025
49faf8f
Format
kelvinou01 Jun 3, 2025
0eaf08e
Add test for configuration cache
kelvinou01 Jun 4, 2025
df1180b
Change test
kelvinou01 Jun 5, 2025
c086eec
Try wrapping operatingSystem into a valueSource
kelvinou01 Jun 5, 2025
4c9bc54
Add generated changelog entries
svc-changelog Jun 5, 2025
9127fc6
Remove configuration cache test
kelvinou01 Jun 5, 2025
462dd24
Merge github.com:palantir/palantir-java-format into finlayw/cc
kelvinou01 Jun 6, 2025
6411dbc
finlay fixes
Jun 6, 2025
ec3c50d
finlay fixes
Jun 6, 2025
6829b18
Merge branch 'finlayw/cc' of github.com:palantir/palantir-java-format…
kelvinou01 Jun 6, 2025
1594037
finlay tidy
Jun 6, 2025
da6a970
Clean things up
kelvinou01 Jun 6, 2025
f714d12
Merge branch 'finlayw/cc' of github.com:palantir/palantir-java-format…
kelvinou01 Jun 6, 2025
e6df7da
Remove dumb comment
kelvinou01 Jun 6, 2025
955e6dd
Merge branch 'finlayw/cc' of github.com:palantir/palantir-java-format…
kelvinou01 Jun 6, 2025
146acdd
Add tests (failing for now)
kelvinou01 Jun 9, 2025
3846df3
Add generated changelog entries
svc-changelog Jun 9, 2025
aa3b4bf
Add generated changelog entries
svc-changelog Jun 9, 2025
f8a4503
Fix release note
kelvinou01 Jun 9, 2025
dc82a57
Add test
kelvinou01 Jun 9, 2025
96b9c1e
Add generated changelog entries
svc-changelog Jun 10, 2025
3a26d4e
Add generated changelog entries
svc-changelog Jun 10, 2025
80aa6fb
Add generated changelog entries
svc-changelog Jun 10, 2025
ac3db90
Merge branch 'finlayw/cc' of github.com:palantir/palantir-java-format…
kelvinou01 Jun 10, 2025
28bb6ec
Add FormatterServiceService
kelvinou01 Jun 10, 2025
996edfe
Test
kelvinou01 Jun 16, 2025
fa200b4
Merge branch 'develop' of github.com:palantir/palantir-java-format in…
kelvinou01 Jun 18, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions changelog/@unreleased/pr-1313.v2.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
type: improvement
improvement:
description: Make palantir-java-format support spotless 6.22.0, which is [transitively
brought in from baseline to support the configuration cache](https://github.com/palantir/gradle-baseline/pull/3119)
links:
- https://github.com/palantir/palantir-java-format/pull/1313
Original file line number Diff line number Diff line change
Expand Up @@ -17,21 +17,24 @@
package com.palantir.javaformat.gradle;

import com.palantir.javaformat.bootstrap.NativeImageFormatterService;
import com.palantir.javaformat.java.FormatterService;
import java.io.File;
import java.io.IOException;
import javax.inject.Inject;
import org.gradle.api.DefaultTask;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.file.FileCollection;
import org.gradle.api.file.ProjectLayout;
import org.gradle.api.file.RegularFileProperty;
import org.gradle.api.logging.Logger;
import org.gradle.api.logging.Logging;
import org.gradle.api.provider.Property;
import org.gradle.api.provider.Provider;
import org.gradle.api.services.ServiceReference;
import org.gradle.api.tasks.InputFile;
import org.gradle.api.tasks.TaskAction;

public final class PalantirJavaFormatPlugin implements Plugin<Project> {
public abstract class PalantirJavaFormatPlugin implements Plugin<Project> {

@Override
public void apply(Project project) {
Expand All @@ -47,9 +50,17 @@ public void apply(Project project) {
// spotless to try out our formatter
project.getTasks().register("formatDiff", FormatDiffTask.class, task -> {
if (NativeImageFormatProviderPlugin.isNativeImageConfigured(project)) {
System.err.println("Configuring native");
task.getNativeImage().fileProvider(getNativeImplConfiguration(project));
}
});

project.getGradle()
.getSharedServices()
.registerIfAbsent("formatterService", FormatterServiceService.class, spec -> {
spec.getParameters().getExtension().set(project.provider(() -> project.getExtensions()
.getByType(JavaFormatExtension.class)));
});
});
}

Expand All @@ -62,12 +73,18 @@ private static Provider<File> getNativeImplConfiguration(Project project) {

public abstract static class FormatDiffTask extends DefaultTask {

@Inject
protected abstract ProjectLayout getProjectLayout();

private static Logger log = Logging.getLogger(FormatDiffTask.class);

@org.gradle.api.tasks.Optional
@InputFile
abstract RegularFileProperty getNativeImage();

@ServiceReference("formatterService")
abstract Property<FormatterServiceService> getFormatterServiceService();

public FormatDiffTask() {
setDescription("Format only chunks of files that appear in git diff");
setGroup("Formatting");
Expand All @@ -78,15 +95,14 @@ public final void formatDiff() throws IOException, InterruptedException {
if (getNativeImage().isPresent()) {
log.info("Using the native-image formatter");
FormatDiff.formatDiff(
getProject().getProjectDir().toPath(),
getProjectLayout().getProjectDirectory().getAsFile().toPath(),
new NativeImageFormatterService(
getNativeImage().get().getAsFile().toPath()));
} else {
log.info("Using the Java-based formatter");
JavaFormatExtension extension =
getProject().getRootProject().getExtensions().getByType(JavaFormatExtension.class);
FormatterService formatterService = extension.serviceLoad();
FormatDiff.formatDiff(getProject().getProjectDir().toPath(), formatterService);
FormatDiff.formatDiff(
getProjectLayout().getProjectDirectory().getAsFile().toPath(),
getFormatterServiceService().get().getFormatterService());
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* (c) Copyright 2025 Palantir Technologies Inc. All rights reserved.
*
* 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.palantir.javaformat.gradle;

import com.palantir.javaformat.gradle.FormatterServiceService.Params;
import com.palantir.javaformat.java.FormatterService;
import org.gradle.api.provider.Property;
import org.gradle.api.provider.Provider;
import org.gradle.api.services.BuildService;
import org.gradle.api.services.BuildServiceParameters;

public abstract class FormatterServiceService implements BuildService<Params> {
public interface Params extends BuildServiceParameters {
Property<JavaFormatExtension> getExtension();
}

Provider<FormatterService> formatter;

public FormatterServiceService() {
this.formatter = getParameters().getExtension().map(ext -> ext.serviceLoad());
}

public FormatterService getFormatterService() {
return this.formatter.get();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,11 @@ public void apply(Project rootProject) {
return;
}
String implementationVersion = JavaFormatExtension.class.getPackage().getImplementationVersion();
OperatingSystem operatingSystem = OperatingSystem.get();
OperatingSystem operatingSystem = rootProject
.getProviders()
.of(OperatingSystemValueSource.class, spec -> {})
.get();

rootProject.getConfigurations().register(NATIVE_CONFIGURATION_NAME, conf -> {
conf.setDescription("Internal configuration for resolving the palantir-java-format native image");
conf.setVisible(false);
Expand All @@ -69,11 +73,13 @@ public void apply(Project rootProject) {
}

public static boolean isNativeImageConfigured(Project project) {
return isNativeImageSupported() && isNativeFlagEnabled(project);
return isNativeImageSupported(project) && isNativeFlagEnabled(project);
}

private static boolean isNativeImageSupported() {
OperatingSystem os = OperatingSystem.get();
private static boolean isNativeImageSupported(Project project) {
OperatingSystem os = project.getProviders()
.of(OperatingSystemValueSource.class, spec -> {})
.get();
return os.equals(OperatingSystem.LINUX_GLIBC)
|| (os.equals(OperatingSystem.MACOS) && Architecture.get().equals(Architecture.AARCH64));
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* (c) Copyright 2025 Palantir Technologies Inc. All rights reserved.
*
* 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.palantir.javaformat.gradle;

import com.palantir.platform.OperatingSystem;
import javax.inject.Inject;
import org.gradle.api.provider.ValueSource;
import org.gradle.api.provider.ValueSourceParameters;

public abstract class OperatingSystemValueSource implements ValueSource<OperatingSystem, ValueSourceParameters.None> {

@Inject
public OperatingSystemValueSource() {}

@Override
public OperatingSystem obtain() {
return OperatingSystem.get();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,41 +25,37 @@
import java.io.Serializable;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.function.Supplier;
import org.gradle.api.artifacts.Configuration;
import org.gradle.api.logging.Logger;
import org.gradle.api.logging.Logging;

public final class NativePalantirJavaFormatStep {
private static Logger logger = Logging.getLogger(NativePalantirJavaFormatStep.class);
private static final Logger logger = Logging.getLogger(NativePalantirJavaFormatStep.class);

private NativePalantirJavaFormatStep() {}

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

/** Creates a step which formats everything - code, import order, and unused imports. */
public static FormatterStep create(Configuration configuration) {
return FormatterStep.createLazy(
NAME,
() -> {
File execFile = configuration.getSingleFile();
logger.info("Using native-image at {}", configuration.getSingleFile());
return new State(FileSignature.signAsSet(execFile));
},
State::createFormat);
return FormatterStep.createLazy(NAME, () -> new State(configuration::getSingleFile), State::createFormat);
}

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

final FileSignature pathToExe;
private final transient Supplier<File> execSupplier;

State(FileSignature pathToExe) {
this.pathToExe = pathToExe;
State(Supplier<File> supplier) {
this.execSupplier = supplier;
}

String format(ProcessRunner runner, String input) throws IOException, InterruptedException {
List<String> argumentsWithPathToExe =
List.of(pathToExe.getOnlyFile().getAbsolutePath(), "--palantir", "-");
File execFile = execSupplier.get();
logger.info("Using native-image at {}", execFile);
File signedFile = FileSignature.signAsSet(execFile).getOnlyFile();
List<String> argumentsWithPathToExe = List.of(signedFile.getAbsolutePath(), "--palantir", "-");
return runner.exec(input.getBytes(StandardCharsets.UTF_8), argumentsWithPathToExe)
.assertExitZero(StandardCharsets.UTF_8);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public static FormatterStep create(Configuration palantirJavaFormat, JavaFormatE
ensureImplementationNotDirectlyLoadable();
Supplier<FormatterService> memoizedService = extension::serviceLoad;
return FormatterStep.createLazy(
NAME, () -> new State(palantirJavaFormat.getFiles(), memoizedService), State::createFormat);
NAME, () -> new State(palantirJavaFormat::getFiles, memoizedService), State::createFormat);
}

static final class State implements Serializable {
Expand All @@ -51,25 +51,36 @@ static final class State implements Serializable {

// Kept for state serialization purposes.
@SuppressWarnings({"unused", "FieldCanBeLocal"})
private final FileSignature jarsSignature;
private FileSignature jarsSignature;

private final transient Supplier<Iterable<File>> jarsSupplier;

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

/**
* Build a cacheable state for spotless from the given jars, that uses the given {@link FormatterService}.
*
* @param jars The jars that contain the palantir-java-format implementation. This is only used for caching and
* @param jarsSupplier Supplies the jars that contain the palantir-java-format implementation. This is only used for caching and
* up-to-dateness purposes.
*/
State(Iterable<File> jars, Supplier<FormatterService> memoizedFormatter) throws IOException {
this.jarsSignature = FileSignature.signAsSet(jars);
State(Supplier<Iterable<File>> jarsSupplier, Supplier<FormatterService> memoizedFormatter) {
this.jarsSupplier = jarsSupplier;
this.memoizedFormatter = memoizedFormatter;
}

@SuppressWarnings("NullableProblems")
FormatterFunc createFormat() {
return memoizedFormatter.get()::formatSourceReflowStringsAndFixImports;
return input -> {
try {
// Only resolve the jars and compute the signature at execution time!
Iterable<File> jars = jarsSupplier.get();
this.jarsSignature = FileSignature.signAsSet(jars);
return memoizedFormatter.get().formatSourceReflowStringsAndFixImports(input);
} catch (IOException e) {
throw new RuntimeException(e);
}
};
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@ class PalantirJavaFormatIdeaPluginTest extends IntegrationTestKitSpec {
private static final NATIVE_IMAGE_FILE = new File("build/nativeImage.path")
private static final NATIVE_CONFIG = String.format("palantirJavaFormatNative files(\"%s\")", NATIVE_IMAGE_FILE.text)

def setup() {
definePluginOutsideOfPluginBlock = true
keepFiles = true
}

def "idea_configuresIpr"() {
file('gradle.properties') << extraGradleProperties

Expand All @@ -39,7 +44,7 @@ class PalantirJavaFormatIdeaPluginTest extends IntegrationTestKitSpec {
""".replace("EXTRA_CONFIGURATION", extraDependencies).stripIndent()

when:
runTasks('idea')
runTasks('idea --configuration-cache')

then:
def iprFile = new File(projectDir, "${moduleName}.ipr")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ class PalantirJavaFormatPluginTest extends IntegrationTestKitSpec {
private static final NATIVE_IMAGE_FILE = new File("build/nativeImage.path").absolutePath
private static final NATIVE_CONFIG = "palantirJavaFormatNative files(file(\"${NATIVE_IMAGE_FILE}\").text)"

def setup() {
definePluginOutsideOfPluginBlock = true
keepFiles = true
}

@Unroll
def 'formatDiff updates only lines changed in git diff'(String extraGradleProperties, String expectedOutput) {
file('gradle.properties') << extraGradleProperties
Expand Down Expand Up @@ -78,7 +83,7 @@ class PalantirJavaFormatPluginTest extends IntegrationTestKitSpec {
'''.stripIndent()

when:
def result = runTasks('formatDiff', '--info')
def result = runTasks('formatDiff', '--info', '--configuration-cache')

then:
result.output.contains(expectedOutput)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,10 @@ class PalantirJavaFormatSpotlessPluginTest extends IntegrationTestKitSpec {
classpath 'com.palantir.baseline:gradle-baseline-java:6.21.0'
classpath 'com.palantir.gradle.jdks:gradle-jdks:0.62.0'
classpath 'com.palantir.gradle.jdkslatest:gradle-jdks-latest:0.17.0'

constraints {
classpath 'com.diffplug.spotless:6.22.0'
}
}
}

Expand Down Expand Up @@ -105,7 +109,7 @@ class PalantirJavaFormatSpotlessPluginTest extends IntegrationTestKitSpec {


when:
def result = runGradlewTasks('spotlessApply', '--info')
def result = runGradlewTasks('spotlessApply', '--info', '--configuration-cache')

then:
result.standardOutput.contains(expectedOutput)
Expand Down