-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Goethe re-bootstraps itself for jdk16+ support (#53)
- Loading branch information
1 parent
45b5308
commit 04f7cb6
Showing
8 changed files
with
273 additions
and
46 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
type: improvement | ||
improvement: | ||
description: Goethe re-bootstraps itself for jdk16+ support | ||
links: | ||
- https://github.com/palantir/goethe/pull/53 |
91 changes: 91 additions & 0 deletions
91
goethe/src/main/java/com/palantir/goethe/BootstrappingFormatterFacade.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
/* | ||
* (c) Copyright 2021 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.goethe; | ||
|
||
import com.google.common.collect.ImmutableList; | ||
import com.google.common.io.ByteStreams; | ||
import java.io.ByteArrayOutputStream; | ||
import java.io.File; | ||
import java.io.IOException; | ||
import java.io.InputStream; | ||
import java.io.OutputStream; | ||
import java.nio.charset.StandardCharsets; | ||
|
||
/** A {@link FormatterFacade} implementation which spawns new java processes with {@link #EXPORTS} applied. */ | ||
final class BootstrappingFormatterFacade implements FormatterFacade { | ||
|
||
static final ImmutableList<String> EXPORTS = ImmutableList.of( | ||
"--add-exports", | ||
"jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED", | ||
"--add-exports", | ||
"jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED", | ||
"--add-exports", | ||
"jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED", | ||
"--add-exports", | ||
"jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED", | ||
"--add-exports", | ||
"jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED"); | ||
|
||
@Override | ||
public String formatSource(String className, String unformattedSource) throws GoetheException { | ||
try { | ||
Process process = new ProcessBuilder(ImmutableList.<String>builder() | ||
.add(new File(System.getProperty("java.home"), "bin/java").getAbsolutePath()) | ||
.addAll(EXPORTS) | ||
.add( // Classpath | ||
"-cp", | ||
System.getProperty("java.class.path"), | ||
// Main class | ||
GoetheMain.class.getName(), | ||
// Args | ||
className) | ||
.build()) | ||
.start(); | ||
try (OutputStream outputStream = process.getOutputStream()) { | ||
outputStream.write(unformattedSource.getBytes(StandardCharsets.UTF_8)); | ||
} | ||
byte[] data; | ||
try (InputStream inputStream = process.getInputStream()) { | ||
data = ByteStreams.toByteArray(inputStream); | ||
} | ||
int exitStatus = process.waitFor(); | ||
if (exitStatus != 0) { | ||
throw new GoetheException(String.format( | ||
"Formatter exited non-zero (%d) formatting class %s:\n%s", | ||
exitStatus, className, getErrorOutput(process))); | ||
} | ||
return new String(data, StandardCharsets.UTF_8); | ||
} catch (IOException | InterruptedException e) { | ||
throw new GoetheException("Failed to bootstrap jdk", e); | ||
} | ||
} | ||
|
||
private static String getErrorOutput(Process process) { | ||
ByteArrayOutputStream baos = new ByteArrayOutputStream(); | ||
try (InputStream inputStream = process.getErrorStream()) { | ||
ByteStreams.copy(inputStream, baos); | ||
} catch (IOException | RuntimeException e) { | ||
String diagnostic = "<failed to read process stream: " + e + ">"; | ||
try { | ||
baos.write(diagnostic.getBytes(StandardCharsets.UTF_8)); | ||
} catch (IOException ignored) { | ||
// should not happen | ||
} | ||
} | ||
return baos.toString(StandardCharsets.UTF_8); | ||
} | ||
} |
70 changes: 70 additions & 0 deletions
70
goethe/src/main/java/com/palantir/goethe/DirectFormatterFacade.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
/* | ||
* (c) Copyright 2021 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.goethe; | ||
|
||
import com.google.common.base.CharMatcher; | ||
import com.google.common.base.Splitter; | ||
import com.google.common.base.Strings; | ||
import com.palantir.javaformat.java.Formatter; | ||
import com.palantir.javaformat.java.FormatterDiagnostic; | ||
import com.palantir.javaformat.java.FormatterException; | ||
import com.palantir.javaformat.java.JavaFormatterOptions; | ||
import java.util.List; | ||
|
||
final class DirectFormatterFacade implements FormatterFacade { | ||
|
||
private final Formatter formatter = Formatter.createFormatter(JavaFormatterOptions.builder() | ||
.style(JavaFormatterOptions.Style.PALANTIR) | ||
.build()); | ||
|
||
@Override | ||
public String formatSource(String className, String unformattedSource) throws GoetheException { | ||
try { | ||
return formatter.formatSource(unformattedSource); | ||
} catch (FormatterException e) { | ||
throw new GoetheException(generateMessage(className, unformattedSource, e.diagnostics()), e); | ||
} | ||
} | ||
|
||
/** | ||
* Attempt to provide as much actionable information as possible to understand why formatting is failing. This | ||
* is common when generated code is incorrect and cannot compile, so we mustn't make it difficult to understand | ||
* the problem. | ||
*/ | ||
private static String generateMessage( | ||
String className, String unformattedSource, List<FormatterDiagnostic> formatterDiagnostics) { | ||
try { | ||
List<String> lines = Splitter.on('\n').splitToList(unformattedSource); | ||
StringBuilder failureText = new StringBuilder(); | ||
failureText.append("Failed to format '").append(className).append("'\n"); | ||
for (FormatterDiagnostic formatterDiagnostic : formatterDiagnostics) { | ||
failureText | ||
.append(formatterDiagnostic.message()) | ||
.append("\n") | ||
// Diagnostic values are one-indexed, while our list is zero-indexed. | ||
.append(lines.get(formatterDiagnostic.line() - 1)) | ||
.append('\n') | ||
// Offset by two to convert from one-indexed to zero indexed values, and account for the caret. | ||
.append(Strings.repeat(" ", Math.max(0, formatterDiagnostic.column() - 2))) | ||
.append("^\n\n"); | ||
} | ||
return CharMatcher.is('\n').trimFrom(failureText.toString()); | ||
} catch (RuntimeException e) { | ||
return "Failed to format:\n" + unformattedSource; | ||
} | ||
} | ||
} |
22 changes: 22 additions & 0 deletions
22
goethe/src/main/java/com/palantir/goethe/FormatterFacade.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
/* | ||
* (c) Copyright 2021 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.goethe; | ||
|
||
interface FormatterFacade { | ||
|
||
String formatSource(String className, String unformattedSource) throws GoetheException; | ||
} |
36 changes: 36 additions & 0 deletions
36
goethe/src/main/java/com/palantir/goethe/FormatterFacadeFactory.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
/* | ||
* (c) Copyright 2021 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.goethe; | ||
|
||
import java.lang.management.ManagementFactory; | ||
|
||
final class FormatterFacadeFactory { | ||
private FormatterFacadeFactory() {} | ||
|
||
static FormatterFacade create() { | ||
if (Runtime.version().feature() < 16 || currentJvmHasExportArgs()) { | ||
return new DirectFormatterFacade(); | ||
} | ||
return new BootstrappingFormatterFacade(); | ||
} | ||
|
||
private static boolean currentJvmHasExportArgs() { | ||
return ManagementFactory.getRuntimeMXBean() | ||
.getInputArguments() | ||
.containsAll(BootstrappingFormatterFacade.EXPORTS); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
/* | ||
* (c) Copyright 2021 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.goethe; | ||
|
||
import com.google.common.io.ByteStreams; | ||
import java.io.IOException; | ||
import java.nio.charset.StandardCharsets; | ||
|
||
/** Main class used internally to bootstrap the formatter with additional jvm args for compiler class access. */ | ||
@SuppressWarnings({"checkstyle:BanSystemErr", "checkstyle:BanSystemOut"}) | ||
final class GoetheMain { | ||
|
||
private GoetheMain() {} | ||
|
||
public static void main(String[] args) throws IOException { | ||
if (args.length != 1) { | ||
System.err.println("Class name argument is required"); | ||
System.exit(1); | ||
} | ||
String className = args[0]; | ||
String input = new String(ByteStreams.toByteArray(System.in), StandardCharsets.UTF_8); | ||
try { | ||
System.out.print(new DirectFormatterFacade().formatSource(className, input)); | ||
} catch (GoetheException e) { | ||
System.err.println(e.getMessage()); | ||
System.exit(1); | ||
} | ||
} | ||
} |