Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
/*
* (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.spotless.PalantirJavaFormatStep;
import com.palantir.javaformat.java.Formatter;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import nebula.test.IntegrationTestKitSpec;
import nebula.test.functional.internal.classpath.ClasspathAddingInitScriptBuilder;
import org.gradle.testkit.runner.internal.PluginUnderTestMetadataReading;

/**
* {@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>.
* 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.
* 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>.
* 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.
* As a workaround, this runner uses the classpath produced by Gradle Test Kit in {@code plugin-under-test-metadata.properties}.
* This classpath only contains the dependencies required by the plugin, as well as the plugin itself.
* 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}.
*/
public class GradlewExecutor {
private File projectDir;

public GradlewExecutor(File projectDir) {
this.projectDir = projectDir;
}

private static List<File> getBuildPluginClasspathInjector() {
return PluginUnderTestMetadataReading.readImplementationClasspath();
}

public GradlewExecutionResult runGradlewTasks(String... tasks) {
try {
ProcessBuilder processBuilder = getProcessBuilder(tasks);
Process process = processBuilder.start();
String output = readAllInput(process.getInputStream());
process.waitFor(1, TimeUnit.MINUTES);
return new GradlewExecutionResult(process.exitValue(), output);
} catch (InterruptedException | IOException e) {
return new GradlewExecutionResult(-1, "", e);
}
}

private static String readAllInput(InputStream inputStream) {
try {
Stream<String> lines =
new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8)).lines();
return lines.collect(Collectors.joining("\n"));
} catch (Exception e) {
throw new RuntimeException("GradlewExecutor failed to readAllInput", e);
}
}

private ProcessBuilder getProcessBuilder(String... tasks) {
File initScript = new File(projectDir, "init.gradle");
ClasspathAddingInitScriptBuilder.build(initScript, getBuildPluginClasspathInjector());

List<String> arguments = Stream.concat(
Stream.of(
"./gradlew",
"--init-script",
initScript.toPath().toString()),
Arrays.stream(tasks))
.toList();

return new ProcessBuilder().command(arguments).directory(projectDir).redirectErrorStream(true);
}

public record GradlewExecutionResult(boolean success, String standardOutput, Optional<Throwable> failure) {
public GradlewExecutionResult(int exitValue, String output, Throwable failure) {
this(exitValue == 0, output, Optional.of(failure));
}

public GradlewExecutionResult(int exitValue, String output) {
this(exitValue == 0, output, Optional.empty());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,16 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.palantir.javaformat.gradle
package com.palantir.javaformat.gradle;

import nebula.test.IntegrationTestKitSpec
import static org.assertj.core.api.Assertions.assertThat;

import com.palantir.gradle.testing.junit.DisabledConfigurationCache;
import com.palantir.gradle.testing.junit.GradlePluginTests;
import com.palantir.gradle.testing.project.RootProject;
import java.io.File;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

/**
* When we were getting gradle-baseline to support the configuration cache, spotless had some poorly written tasks
Expand All @@ -28,20 +35,22 @@
*
* This test forces creation of the spotless steps, which will reveal any eager resolution of configurations.
*/
class SupportsSpotless622 extends IntegrationTestKitSpec {
private static final CLASSPATH_FILE = new File("build/impl.classpath").absolutePath
@GradlePluginTests
@DisabledConfigurationCache
class SupportsSpotless622 {
private static final String CLASSPATH_FILE = new File("build/impl.classpath").getAbsolutePath();

private GradlewExecutor executor
private GradlewExecutor executor;

def setup() {
definePluginOutsideOfPluginBlock = true
keepFiles = true
executor = new GradlewExecutor(projectDir)
@BeforeEach
void setup(RootProject project) {
executor = new GradlewExecutor(project.path().toFile());
}

def "PalantirJavaFormatPlugin works with spotless 6.22.0"() {
// language=Gradle
buildFile << '''
@Test
@SuppressWarnings("GradleTestPluginsBlock") // Uses buildscript with apply plugin syntax intentionally
void palantir_java_format_plugin_works_with_spotless_6_22_0(RootProject project) throws Exception {
project.buildGradle().overwrite("""
buildscript {
repositories {
mavenCentral() { metadataSources { mavenPom(); ignoreGradleMetadataRedirection() } }
Expand All @@ -52,36 +61,35 @@ def setup() {
classpath 'com.diffplug.spotless:spotless-plugin-gradle:6.22.0'
}
}

apply plugin: 'java'
apply plugin: 'com.palantir.java-format'
apply plugin: 'com.palantir.consistent-versions'
apply plugin: 'com.diffplug.spotless'

version = '0.1.0'
'''.stripIndent(true)

version = '0.1.0'
""");

file("versions.props")
file("versions.lock")
project.file("versions.props").createEmpty();
project.file("versions.lock").createEmpty();

runTasks('wrapper')
// Run wrapper task to generate gradlew
GradlewExecutor.GradlewExecutionResult wrapperResult = executor.runGradlewTasks("wrapper");
assertThat(wrapperResult.success()).isTrue();

buildFile << """
project.buildGradle().append("""
dependencies {
palantirJavaFormat files(file("${CLASSPATH_FILE}").text.split(':'))
palantirJavaFormat files(file("%s").text.split(':'))
}

// This forces the realization of the spotlessJava task, creating the spotless steps.
// If any configurations are eagerly resolved in the spotless steps,
// consistent-versions should catch it and throw here.
// This forces the realization of the spotlessJava task, creating the spotless steps.
// If any configurations are eagerly resolved in the spotless steps,
// consistent-versions should catch it and throw here.
project.getTasks().getByName("spotlessJava")
""".stripIndent(true)
""", CLASSPATH_FILE);

when:
def result = executor.runGradlewTasks('classes', '--info')
GradlewExecutor.GradlewExecutionResult result = executor.runGradlewTasks("classes", "--info");

then:
assert result.success
assertThat(result.success()).isTrue();
}
}
Loading