Skip to content

Commit a2c8970

Browse files
authored
Merge pull request #28 from openjavaformat/windows-native-gradle
Run the native formatter from the Gradle plugin on Windows
2 parents 4958189 + e96548e commit a2c8970

11 files changed

Lines changed: 56 additions & 28 deletions

File tree

‎.github/workflows/ci.yml‎

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,6 @@ jobs:
8888
JDK21_HOME: ${{ steps.jdk21.outputs.path }}
8989

9090
# The binary itself on a file that needs formatting, a formatted one and one that does not parse.
91-
# For the Windows binary this is the only check.
9291
- name: Smoke-test the binary
9392
run: |
9493
binary="$PWD/$(ls open-java-format-native/build/native/nativeCompile/open-java-format-* | grep -v '\.txt$')"
@@ -102,9 +101,7 @@ jobs:
102101
set +e; "$binary" B.java; status=$?; set -e
103102
test "$status" -eq 2
104103
105-
# The Gradle plugin does not run a native image on Windows, and its tests have never run there.
106104
- name: Test the plugins against the image
107-
if: runner.os != 'Windows'
108105
run: ./gradlew -PnativeImage=true :open-java-format-jdk-bootstrap:test :gradle-open-java-format:test
109106
env:
110107
JDK21_HOME: ${{ steps.jdk21.outputs.path }}

‎gradle-open-java-format/build.gradle‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,8 +109,10 @@ dependencies {
109109

110110
tasks.register("copyNativeImage", Copy.class) {
111111
from(configurations.formatterNativeImage)
112+
// Named like the published artifact, whose extension is its artifact type: bin, and exe on Windows.
113+
// The plugin's ExecutableTransform starts from that type, so a Windows binary must keep .exe.
112114
rename { fileName ->
113-
String.format("%s.bin", fileName)
115+
fileName.endsWith('.exe') ? fileName : String.format("%s.bin", fileName)
114116
}
115117
into("$buildDir/nativeImage")
116118
}

‎gradle-open-java-format/src/main/java/com/palantir/javaformat/gradle/ExecutableTransform.java‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,9 @@ public abstract class ExecutableTransform implements TransformAction<TransformPa
5656
@Override
5757
public void transform(TransformOutputs outputs) {
5858
File inputFile = getInputArtifact().get().getAsFile();
59-
File outputFile = outputs.file(inputFile.getName() + ".executable");
59+
// The Windows binary keeps its name, so that it still ends in .exe like any Windows program.
60+
String name = inputFile.getName().endsWith(".exe") ? inputFile.getName() : inputFile.getName() + ".executable";
61+
File outputFile = outputs.file(name);
6062
try {
6163
Files.copy(inputFile.toPath(), outputFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
6264
makeFileExecutable(outputFile.toPath());
@@ -67,6 +69,10 @@ public void transform(TransformOutputs outputs) {
6769
}
6870

6971
private static void makeFileExecutable(Path pathToExe) {
72+
// Windows file systems have no POSIX permissions, and an .exe needs none to run.
73+
if (!pathToExe.getFileSystem().supportedFileAttributeViews().contains("posix")) {
74+
return;
75+
}
7076
try {
7177
Set<PosixFilePermission> existingPermissions = Files.getPosixFilePermissions(pathToExe);
7278
Files.setPosixFilePermissions(

‎gradle-open-java-format/src/main/java/com/palantir/javaformat/gradle/NativeImageSupport.java‎

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package com.palantir.javaformat.gradle;
1818

19+
import com.palantir.platform.Architecture;
1920
import com.palantir.platform.GradleOperatingSystem;
2021
import com.palantir.platform.OperatingSystem;
2122
import javax.inject.Inject;
@@ -37,14 +38,14 @@ public boolean isNativeImageConfigured() {
3738
/**
3839
* The platforms a native image is published for, and therefore the only ones where it can be
3940
* resolved. macOS is supported on both architectures: the x86-64 image used to be excluded
40-
* because nobody built it, and .github/workflows/ci.yml now does. musl is still absent for the
41-
* same reason — no job produces it. Windows x86-64 is built and published, but not used here yet:
42-
* {@link ExecutableTransform} sets POSIX permissions, which NTFS does not have, and the tests of
43-
* this plugin have never run on Windows.
41+
* because nobody built it, and .github/workflows/ci.yml now does. Windows has an image for
42+
* x86-64 only, and musl none: no job produces them.
4443
*/
4544
private boolean isNativeImageSupported() {
4645
return getOs().getOperatingSystem()
47-
.map(os -> os.equals(OperatingSystem.LINUX_GLIBC) || os.equals(OperatingSystem.MACOS))
46+
.map(os -> os.equals(OperatingSystem.LINUX_GLIBC)
47+
|| os.equals(OperatingSystem.MACOS)
48+
|| (os.equals(OperatingSystem.WINDOWS) && Architecture.get() == Architecture.X86_64))
4849
.get();
4950
}
5051

‎gradle-open-java-format/src/test/java/com/palantir/javaformat/gradle/FormatDiffTest.java‎

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import java.nio.file.Path;
3333
import java.nio.file.Paths;
3434
import java.util.List;
35+
import java.util.Locale;
3536
import java.util.concurrent.TimeUnit;
3637
import java.util.stream.Collectors;
3738
import java.util.stream.Stream;
@@ -50,17 +51,20 @@ class FormatDiffTest {
5051

5152
@Test
5253
void parsing_git_diff_output_works() throws IOException {
54+
// A Windows checkout gives the fixture CRLF line endings, while git prints a diff with LF.
5355
String example1 = Files.readString(
54-
Paths.get("src/test/resources/com/palantir/javaformat/java/FormatDiffCliTest/example1.patch"));
56+
Paths.get("src/test/resources/com/palantir/javaformat/java/FormatDiffCliTest/example1.patch"))
57+
.replace("\r\n", "\n");
5558

5659
List<String> strings = FormatDiff.parseGitDiffOutput(example1)
5760
.map(FormatDiff.SingleFileDiff::toString)
5861
.collect(Collectors.toList());
5962
assertThat(strings)
6063
.containsExactly(
6164
"SingleFileDiff{path=build.gradle, lineRanges=[[24..25), [29..30)]}",
62-
"SingleFileDiff{path=tracing/src/test/java/com/palantir/tracing/TracersTest.java, "
63-
+ "lineRanges=[[659..660), [675..676)]}");
65+
// The path is a Path, which prints with backslashes on Windows.
66+
"SingleFileDiff{path=" + Path.of("tracing/src/test/java/com/palantir/tracing/TracersTest.java")
67+
+ ", lineRanges=[[659..660), [675..676)]}");
6468
}
6569

6670
@ParameterizedTest
@@ -112,7 +116,7 @@ private static Stream<FormatterService> getFormatters() throws IOException {
112116
}
113117

114118
private static List<Path> getClasspath() throws IOException {
115-
return Splitter.on(':')
119+
return Splitter.on(File.pathSeparatorChar)
116120
.trimResults()
117121
.omitEmptyStrings()
118122
.splitToStream(Files.readString(CLASSPATH_FILE.toPath()))
@@ -122,6 +126,10 @@ private static List<Path> getClasspath() throws IOException {
122126

123127
private static Path javaBinPath() {
124128
String javaHome = Preconditions.checkNotNull(System.getProperty("java.home"), "java.home property not set");
125-
return Path.of(javaHome).resolve("bin").resolve("java");
129+
return Path.of(javaHome).resolve("bin").resolve("java" + (isWindows() ? ".exe" : ""));
130+
}
131+
132+
private static boolean isWindows() {
133+
return System.getProperty("os.name").toLowerCase(Locale.ROOT).startsWith("windows");
126134
}
127135
}

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,9 @@
3737

3838
class PalantirJavaFormatIdeaPluginTest {
3939

40-
private static final String NATIVE_IMAGE_FILE = new File("build/nativeImage.path").getAbsolutePath();
40+
// Forward slashes: the path goes into a Groovy string, where a Windows backslash would start an escape.
41+
private static final String NATIVE_IMAGE_FILE =
42+
new File("build/nativeImage.path").getAbsolutePath().replace('\\', '/');
4143

4244
private static final String NATIVE_CONFIG =
4345
"palantirJavaFormatNative files(file(\"" + NATIVE_IMAGE_FILE + "\").text)";

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

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,12 @@
2929
class PalantirJavaFormatPluginTest {
3030

3131
/** ./gradlew writeImplClasspath generates this file. */
32-
private static final String CLASSPATH_FILE = new File("build/impl.classpath").getAbsolutePath();
32+
// Forward slashes: the path goes into a Groovy string, where a Windows backslash would start an escape.
33+
private static final String CLASSPATH_FILE =
34+
new File("build/impl.classpath").getAbsolutePath().replace('\\', '/');
3335

34-
private static final String NATIVE_IMAGE_FILE = new File("build/nativeImage.path").getAbsolutePath();
36+
private static final String NATIVE_IMAGE_FILE =
37+
new File("build/nativeImage.path").getAbsolutePath().replace('\\', '/');
3538

3639
private static final String NATIVE_CONFIG =
3740
"palantirJavaFormatNative files(file(\"" + NATIVE_IMAGE_FILE + "\").text)";
@@ -59,7 +62,7 @@ void formatDiff_updates_only_lines_changed_in_git_diff(String extraGradlePropert
5962
.buildGradle(
6063
"""
6164
dependencies {
62-
palantirJavaFormat files(file("%s").text.split(':'))
65+
palantirJavaFormat files(file("%s").text.split(File.pathSeparator))
6366
%s
6467
}
6568
""",

‎gradle-open-java-format/src/test/java/com/palantir/javaformat/gradle/PalantirJavaFormatSpotlessPluginTest.java‎

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,12 @@
2828
class PalantirJavaFormatSpotlessPluginTest {
2929

3030
/** ./gradlew writeImplClasspath generates this file. */
31-
private static final String CLASSPATH_FILE = new File("build/impl.classpath").getAbsolutePath();
31+
// Forward slashes: the path goes into a Groovy string, where a Windows backslash would start an escape.
32+
private static final String CLASSPATH_FILE =
33+
new File("build/impl.classpath").getAbsolutePath().replace('\\', '/');
3234

33-
private static final String NATIVE_IMAGE_FILE = new File("build/nativeImage.path").getAbsolutePath();
35+
private static final String NATIVE_IMAGE_FILE =
36+
new File("build/nativeImage.path").getAbsolutePath().replace('\\', '/');
3437

3538
private static final String NATIVE_CONFIG =
3639
"palantirJavaFormatNative files(file(\"" + NATIVE_IMAGE_FILE + "\").text)";
@@ -78,7 +81,7 @@ void formats_with_spotless_when_spotless_is_applied(
7881
.buildGradle(
7982
"""
8083
dependencies {
81-
palantirJavaFormat files(file("%s").text.split(':'))
84+
palantirJavaFormat files(file("%s").text.split(File.pathSeparator))
8285
%s
8386
}
8487
""",
@@ -87,7 +90,8 @@ palantirJavaFormat files(file("%s").text.split(':'))
8790

8891
BuildResult result = project.succeeds("spotlessApply", "--info");
8992

90-
assertThat(project.readFile(MAIN_JAVA)).isEqualTo(validJavaFile());
93+
// Spotless writes the platform's line endings, CRLF on Windows.
94+
assertThat(project.readFile(MAIN_JAVA)).isEqualToNormalizingNewlines(validJavaFile());
9195
assertThat(result.getOutput()).contains(expectedOutput);
9296
}
9397

‎gradle-open-java-format/src/test/java/com/palantir/javaformat/gradle/SpotlessExcludesTest.java‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,9 @@
2929

3030
class SpotlessExcludesTest {
3131

32-
private static final String CLASSPATH_FILE = new File("build/impl.classpath").getAbsolutePath();
32+
// Forward slashes: the path goes into a Groovy string, where a Windows backslash would start an escape.
33+
private static final String CLASSPATH_FILE =
34+
new File("build/impl.classpath").getAbsolutePath().replace('\\', '/');
3335

3436
private static final String SOURCE_FILE =
3537
"""
@@ -51,7 +53,7 @@ void setup() {
5153
.buildGradle(
5254
"""
5355
dependencies {
54-
palantirJavaFormat files(file("%s").text.split(':'))
56+
palantirJavaFormat files(file("%s").text.split(File.pathSeparator))
5557
}
5658
""",
5759
CLASSPATH_FILE);

‎gradle-open-java-format/src/test/java/com/palantir/javaformat/gradle/SupportsCurrentSpotlessTest.java‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,9 @@
3333
*/
3434
class SupportsCurrentSpotlessTest {
3535

36-
private static final String CLASSPATH_FILE = new File("build/impl.classpath").getAbsolutePath();
36+
// Forward slashes: the path goes into a Groovy string, where a Windows backslash would start an escape.
37+
private static final String CLASSPATH_FILE =
38+
new File("build/impl.classpath").getAbsolutePath().replace('\\', '/');
3739

3840
@TempDir
3941
private Path projectDir;
@@ -47,7 +49,7 @@ void palantirjavaformatplugin_works_with_current_spotless() {
4749
.buildGradle(
4850
"""
4951
dependencies {
50-
palantirJavaFormat files(file("%s").text.split(':'))
52+
palantirJavaFormat files(file("%s").text.split(File.pathSeparator))
5153
}
5254
5355
// Forces realization of the spotlessJava task, creating the spotless steps. Any

0 commit comments

Comments
 (0)