diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3c18ae62d..5c62b6298 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -88,7 +88,6 @@ jobs: JDK21_HOME: ${{ steps.jdk21.outputs.path }} # The binary itself on a file that needs formatting, a formatted one and one that does not parse. - # For the Windows binary this is the only check. - name: Smoke-test the binary run: | binary="$PWD/$(ls open-java-format-native/build/native/nativeCompile/open-java-format-* | grep -v '\.txt$')" @@ -102,9 +101,7 @@ jobs: set +e; "$binary" B.java; status=$?; set -e test "$status" -eq 2 - # The Gradle plugin does not run a native image on Windows, and its tests have never run there. - name: Test the plugins against the image - if: runner.os != 'Windows' run: ./gradlew -PnativeImage=true :open-java-format-jdk-bootstrap:test :gradle-open-java-format:test env: JDK21_HOME: ${{ steps.jdk21.outputs.path }} diff --git a/gradle-open-java-format/build.gradle b/gradle-open-java-format/build.gradle index adcc4b2b0..fa42af32d 100644 --- a/gradle-open-java-format/build.gradle +++ b/gradle-open-java-format/build.gradle @@ -109,8 +109,10 @@ dependencies { tasks.register("copyNativeImage", Copy.class) { from(configurations.formatterNativeImage) + // Named like the published artifact, whose extension is its artifact type: bin, and exe on Windows. + // The plugin's ExecutableTransform starts from that type, so a Windows binary must keep .exe. rename { fileName -> - String.format("%s.bin", fileName) + fileName.endsWith('.exe') ? fileName : String.format("%s.bin", fileName) } into("$buildDir/nativeImage") } diff --git a/gradle-open-java-format/src/main/java/com/palantir/javaformat/gradle/ExecutableTransform.java b/gradle-open-java-format/src/main/java/com/palantir/javaformat/gradle/ExecutableTransform.java index 440613656..947685bf8 100644 --- a/gradle-open-java-format/src/main/java/com/palantir/javaformat/gradle/ExecutableTransform.java +++ b/gradle-open-java-format/src/main/java/com/palantir/javaformat/gradle/ExecutableTransform.java @@ -56,7 +56,9 @@ public abstract class ExecutableTransform implements TransformAction existingPermissions = Files.getPosixFilePermissions(pathToExe); Files.setPosixFilePermissions( diff --git a/gradle-open-java-format/src/main/java/com/palantir/javaformat/gradle/NativeImageSupport.java b/gradle-open-java-format/src/main/java/com/palantir/javaformat/gradle/NativeImageSupport.java index cbe6a3f1a..b0b3317ef 100644 --- a/gradle-open-java-format/src/main/java/com/palantir/javaformat/gradle/NativeImageSupport.java +++ b/gradle-open-java-format/src/main/java/com/palantir/javaformat/gradle/NativeImageSupport.java @@ -16,6 +16,7 @@ package com.palantir.javaformat.gradle; +import com.palantir.platform.Architecture; import com.palantir.platform.GradleOperatingSystem; import com.palantir.platform.OperatingSystem; import javax.inject.Inject; @@ -37,14 +38,14 @@ public boolean isNativeImageConfigured() { /** * The platforms a native image is published for, and therefore the only ones where it can be * resolved. macOS is supported on both architectures: the x86-64 image used to be excluded - * because nobody built it, and .github/workflows/ci.yml now does. musl is still absent for the - * same reason — no job produces it. Windows x86-64 is built and published, but not used here yet: - * {@link ExecutableTransform} sets POSIX permissions, which NTFS does not have, and the tests of - * this plugin have never run on Windows. + * because nobody built it, and .github/workflows/ci.yml now does. Windows has an image for + * x86-64 only, and musl none: no job produces them. */ private boolean isNativeImageSupported() { return getOs().getOperatingSystem() - .map(os -> os.equals(OperatingSystem.LINUX_GLIBC) || os.equals(OperatingSystem.MACOS)) + .map(os -> os.equals(OperatingSystem.LINUX_GLIBC) + || os.equals(OperatingSystem.MACOS) + || (os.equals(OperatingSystem.WINDOWS) && Architecture.get() == Architecture.X86_64)) .get(); } diff --git a/gradle-open-java-format/src/test/java/com/palantir/javaformat/gradle/FormatDiffTest.java b/gradle-open-java-format/src/test/java/com/palantir/javaformat/gradle/FormatDiffTest.java index 06c85655d..2c800e272 100644 --- a/gradle-open-java-format/src/test/java/com/palantir/javaformat/gradle/FormatDiffTest.java +++ b/gradle-open-java-format/src/test/java/com/palantir/javaformat/gradle/FormatDiffTest.java @@ -32,6 +32,7 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.util.List; +import java.util.Locale; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -50,8 +51,10 @@ class FormatDiffTest { @Test void parsing_git_diff_output_works() throws IOException { + // A Windows checkout gives the fixture CRLF line endings, while git prints a diff with LF. String example1 = Files.readString( - Paths.get("src/test/resources/com/palantir/javaformat/java/FormatDiffCliTest/example1.patch")); + Paths.get("src/test/resources/com/palantir/javaformat/java/FormatDiffCliTest/example1.patch")) + .replace("\r\n", "\n"); List strings = FormatDiff.parseGitDiffOutput(example1) .map(FormatDiff.SingleFileDiff::toString) @@ -59,8 +62,9 @@ void parsing_git_diff_output_works() throws IOException { assertThat(strings) .containsExactly( "SingleFileDiff{path=build.gradle, lineRanges=[[24..25), [29..30)]}", - "SingleFileDiff{path=tracing/src/test/java/com/palantir/tracing/TracersTest.java, " - + "lineRanges=[[659..660), [675..676)]}"); + // The path is a Path, which prints with backslashes on Windows. + "SingleFileDiff{path=" + Path.of("tracing/src/test/java/com/palantir/tracing/TracersTest.java") + + ", lineRanges=[[659..660), [675..676)]}"); } @ParameterizedTest @@ -112,7 +116,7 @@ private static Stream getFormatters() throws IOException { } private static List getClasspath() throws IOException { - return Splitter.on(':') + return Splitter.on(File.pathSeparatorChar) .trimResults() .omitEmptyStrings() .splitToStream(Files.readString(CLASSPATH_FILE.toPath())) @@ -122,6 +126,10 @@ private static List getClasspath() throws IOException { private static Path javaBinPath() { String javaHome = Preconditions.checkNotNull(System.getProperty("java.home"), "java.home property not set"); - return Path.of(javaHome).resolve("bin").resolve("java"); + return Path.of(javaHome).resolve("bin").resolve("java" + (isWindows() ? ".exe" : "")); + } + + private static boolean isWindows() { + return System.getProperty("os.name").toLowerCase(Locale.ROOT).startsWith("windows"); } } diff --git a/gradle-open-java-format/src/test/java/com/palantir/javaformat/gradle/PalantirJavaFormatIdeaPluginTest.java b/gradle-open-java-format/src/test/java/com/palantir/javaformat/gradle/PalantirJavaFormatIdeaPluginTest.java index 2f0420e08..b17dd1464 100644 --- a/gradle-open-java-format/src/test/java/com/palantir/javaformat/gradle/PalantirJavaFormatIdeaPluginTest.java +++ b/gradle-open-java-format/src/test/java/com/palantir/javaformat/gradle/PalantirJavaFormatIdeaPluginTest.java @@ -37,7 +37,9 @@ class PalantirJavaFormatIdeaPluginTest { - private static final String NATIVE_IMAGE_FILE = new File("build/nativeImage.path").getAbsolutePath(); + // Forward slashes: the path goes into a Groovy string, where a Windows backslash would start an escape. + private static final String NATIVE_IMAGE_FILE = + new File("build/nativeImage.path").getAbsolutePath().replace('\\', '/'); private static final String NATIVE_CONFIG = "palantirJavaFormatNative files(file(\"" + NATIVE_IMAGE_FILE + "\").text)"; diff --git a/gradle-open-java-format/src/test/java/com/palantir/javaformat/gradle/PalantirJavaFormatPluginTest.java b/gradle-open-java-format/src/test/java/com/palantir/javaformat/gradle/PalantirJavaFormatPluginTest.java index 6dfaea0c1..71fe18407 100644 --- a/gradle-open-java-format/src/test/java/com/palantir/javaformat/gradle/PalantirJavaFormatPluginTest.java +++ b/gradle-open-java-format/src/test/java/com/palantir/javaformat/gradle/PalantirJavaFormatPluginTest.java @@ -29,9 +29,12 @@ class PalantirJavaFormatPluginTest { /** ./gradlew writeImplClasspath generates this file. */ - private static final String CLASSPATH_FILE = new File("build/impl.classpath").getAbsolutePath(); + // Forward slashes: the path goes into a Groovy string, where a Windows backslash would start an escape. + private static final String CLASSPATH_FILE = + new File("build/impl.classpath").getAbsolutePath().replace('\\', '/'); - private static final String NATIVE_IMAGE_FILE = new File("build/nativeImage.path").getAbsolutePath(); + private static final String NATIVE_IMAGE_FILE = + new File("build/nativeImage.path").getAbsolutePath().replace('\\', '/'); private static final String NATIVE_CONFIG = "palantirJavaFormatNative files(file(\"" + NATIVE_IMAGE_FILE + "\").text)"; @@ -59,7 +62,7 @@ void formatDiff_updates_only_lines_changed_in_git_diff(String extraGradlePropert .buildGradle( """ dependencies { - palantirJavaFormat files(file("%s").text.split(':')) + palantirJavaFormat files(file("%s").text.split(File.pathSeparator)) %s } """, diff --git a/gradle-open-java-format/src/test/java/com/palantir/javaformat/gradle/PalantirJavaFormatSpotlessPluginTest.java b/gradle-open-java-format/src/test/java/com/palantir/javaformat/gradle/PalantirJavaFormatSpotlessPluginTest.java index 6b5ee3fc5..f191c45c3 100644 --- a/gradle-open-java-format/src/test/java/com/palantir/javaformat/gradle/PalantirJavaFormatSpotlessPluginTest.java +++ b/gradle-open-java-format/src/test/java/com/palantir/javaformat/gradle/PalantirJavaFormatSpotlessPluginTest.java @@ -28,9 +28,12 @@ class PalantirJavaFormatSpotlessPluginTest { /** ./gradlew writeImplClasspath generates this file. */ - private static final String CLASSPATH_FILE = new File("build/impl.classpath").getAbsolutePath(); + // Forward slashes: the path goes into a Groovy string, where a Windows backslash would start an escape. + private static final String CLASSPATH_FILE = + new File("build/impl.classpath").getAbsolutePath().replace('\\', '/'); - private static final String NATIVE_IMAGE_FILE = new File("build/nativeImage.path").getAbsolutePath(); + private static final String NATIVE_IMAGE_FILE = + new File("build/nativeImage.path").getAbsolutePath().replace('\\', '/'); private static final String NATIVE_CONFIG = "palantirJavaFormatNative files(file(\"" + NATIVE_IMAGE_FILE + "\").text)"; @@ -78,7 +81,7 @@ void formats_with_spotless_when_spotless_is_applied( .buildGradle( """ dependencies { - palantirJavaFormat files(file("%s").text.split(':')) + palantirJavaFormat files(file("%s").text.split(File.pathSeparator)) %s } """, @@ -87,7 +90,8 @@ palantirJavaFormat files(file("%s").text.split(':')) BuildResult result = project.succeeds("spotlessApply", "--info"); - assertThat(project.readFile(MAIN_JAVA)).isEqualTo(validJavaFile()); + // Spotless writes the platform's line endings, CRLF on Windows. + assertThat(project.readFile(MAIN_JAVA)).isEqualToNormalizingNewlines(validJavaFile()); assertThat(result.getOutput()).contains(expectedOutput); } diff --git a/gradle-open-java-format/src/test/java/com/palantir/javaformat/gradle/SpotlessExcludesTest.java b/gradle-open-java-format/src/test/java/com/palantir/javaformat/gradle/SpotlessExcludesTest.java index 259376175..b710f5131 100644 --- a/gradle-open-java-format/src/test/java/com/palantir/javaformat/gradle/SpotlessExcludesTest.java +++ b/gradle-open-java-format/src/test/java/com/palantir/javaformat/gradle/SpotlessExcludesTest.java @@ -29,7 +29,9 @@ class SpotlessExcludesTest { - private static final String CLASSPATH_FILE = new File("build/impl.classpath").getAbsolutePath(); + // Forward slashes: the path goes into a Groovy string, where a Windows backslash would start an escape. + private static final String CLASSPATH_FILE = + new File("build/impl.classpath").getAbsolutePath().replace('\\', '/'); private static final String SOURCE_FILE = """ @@ -51,7 +53,7 @@ void setup() { .buildGradle( """ dependencies { - palantirJavaFormat files(file("%s").text.split(':')) + palantirJavaFormat files(file("%s").text.split(File.pathSeparator)) } """, CLASSPATH_FILE); diff --git a/gradle-open-java-format/src/test/java/com/palantir/javaformat/gradle/SupportsCurrentSpotlessTest.java b/gradle-open-java-format/src/test/java/com/palantir/javaformat/gradle/SupportsCurrentSpotlessTest.java index 3a58dd3eb..99a80668d 100644 --- a/gradle-open-java-format/src/test/java/com/palantir/javaformat/gradle/SupportsCurrentSpotlessTest.java +++ b/gradle-open-java-format/src/test/java/com/palantir/javaformat/gradle/SupportsCurrentSpotlessTest.java @@ -33,7 +33,9 @@ */ class SupportsCurrentSpotlessTest { - private static final String CLASSPATH_FILE = new File("build/impl.classpath").getAbsolutePath(); + // Forward slashes: the path goes into a Groovy string, where a Windows backslash would start an escape. + private static final String CLASSPATH_FILE = + new File("build/impl.classpath").getAbsolutePath().replace('\\', '/'); @TempDir private Path projectDir; @@ -47,7 +49,7 @@ void palantirjavaformatplugin_works_with_current_spotless() { .buildGradle( """ dependencies { - palantirJavaFormat files(file("%s").text.split(':')) + palantirJavaFormat files(file("%s").text.split(File.pathSeparator)) } // Forces realization of the spotlessJava task, creating the spotless steps. Any diff --git a/open-java-format-jdk-bootstrap/src/test/java/com/palantir/javaformat/bootstrap/FormatterServicesTest.java b/open-java-format-jdk-bootstrap/src/test/java/com/palantir/javaformat/bootstrap/FormatterServicesTest.java index c85ebbf8a..fe7944af3 100644 --- a/open-java-format-jdk-bootstrap/src/test/java/com/palantir/javaformat/bootstrap/FormatterServicesTest.java +++ b/open-java-format-jdk-bootstrap/src/test/java/com/palantir/javaformat/bootstrap/FormatterServicesTest.java @@ -26,6 +26,7 @@ import com.palantir.javaformat.java.FormatterException; import com.palantir.javaformat.java.FormatterService; import com.palantir.javaformat.java.Replacement; +import java.io.File; import java.net.URI; import java.nio.file.Files; import java.nio.file.Path; @@ -116,7 +117,7 @@ private String getTestResourceContent(String resourceName) { private static List getClasspath() { String classpath = System.getProperty("java.class.path"); - return Splitter.on(':') + return Splitter.on(File.pathSeparatorChar) .trimResults() .omitEmptyStrings() .splitToStream(classpath)