diff --git a/.github/workflows/parparvm-tests.yml b/.github/workflows/parparvm-tests.yml
index 6001b8346f..9d9933d24e 100644
--- a/.github/workflows/parparvm-tests.yml
+++ b/.github/workflows/parparvm-tests.yml
@@ -22,21 +22,69 @@ jobs:
- name: Check out repository
uses: actions/checkout@v4
+ - name: Install native build tools
+ run: |
+ sudo apt-get update
+ sudo apt-get install -y clang
+
+ # Install JDKs and export their paths
- name: Set up JDK 8
uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '8'
- cache: 'maven'
+ - name: Save JDK 8 Path
+ run: echo "JDK_8_HOME=$JAVA_HOME" >> $GITHUB_ENV
- - name: Install native build tools
- run: |
- sudo apt-get update
- sudo apt-get install -y clang
+ - name: Set up JDK 11
+ uses: actions/setup-java@v4
+ with:
+ distribution: 'temurin'
+ java-version: '11'
+ - name: Save JDK 11 Path
+ run: echo "JDK_11_HOME=$JAVA_HOME" >> $GITHUB_ENV
+
+ - name: Set up JDK 17
+ uses: actions/setup-java@v4
+ with:
+ distribution: 'temurin'
+ java-version: '17'
+ - name: Save JDK 17 Path
+ run: echo "JDK_17_HOME=$JAVA_HOME" >> $GITHUB_ENV
+
+ - name: Set up JDK 21
+ uses: actions/setup-java@v4
+ with:
+ distribution: 'temurin'
+ java-version: '21'
+ - name: Save JDK 21 Path
+ run: echo "JDK_21_HOME=$JAVA_HOME" >> $GITHUB_ENV
+
+ - name: Set up JDK 25
+ uses: actions/setup-java@v4
+ with:
+ distribution: 'zulu'
+ java-version: '25'
+ - name: Save JDK 25 Path
+ run: echo "JDK_25_HOME=$JAVA_HOME" >> $GITHUB_ENV
+
+ # Restore JDK 8 as the main runner
+ - name: Restore JDK 8
+ uses: actions/setup-java@v4
+ with:
+ distribution: 'temurin'
+ java-version: '8'
+ cache: 'maven'
- name: Run ParparVM JVM tests
working-directory: vm
- run: mvn -B test
+ run: mvn -B clean test -pl tests -am
+ env:
+ JDK_8_HOME: ${{ env.JDK_8_HOME }}
+ JDK_11_HOME: ${{ env.JDK_11_HOME }}
+ JDK_17_HOME: ${{ env.JDK_17_HOME }}
+ JDK_21_HOME: ${{ env.JDK_21_HOME }}
+ JDK_25_HOME: ${{ env.JDK_25_HOME }}
- name: Publish ByteCodeTranslator quality previews
if: ${{ always() && github.server_url == 'https://github.com' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) }}
diff --git a/vm/pom.xml b/vm/pom.xml
index c1c2e0b132..973451b3b7 100644
--- a/vm/pom.xml
+++ b/vm/pom.xml
@@ -19,7 +19,7 @@
1.8
1.8
UTF-8
- 9.2
+ 9.8
5.10.2
diff --git a/vm/tests/src/test/java/com/codename1/tools/translator/BytecodeInstructionIntegrationTest.java b/vm/tests/src/test/java/com/codename1/tools/translator/BytecodeInstructionIntegrationTest.java
index 7ffd4c2abc..4a55df8d05 100644
--- a/vm/tests/src/test/java/com/codename1/tools/translator/BytecodeInstructionIntegrationTest.java
+++ b/vm/tests/src/test/java/com/codename1/tools/translator/BytecodeInstructionIntegrationTest.java
@@ -41,12 +41,20 @@
class BytecodeInstructionIntegrationTest {
+ static Stream provideCompilerConfigs() {
+ List configs = new ArrayList<>();
+ configs.addAll(CompilerHelper.getAvailableCompilers("1.5"));
+ configs.addAll(CompilerHelper.getAvailableCompilers("1.8"));
+ configs.addAll(CompilerHelper.getAvailableCompilers("11"));
+ configs.addAll(CompilerHelper.getAvailableCompilers("17"));
+ configs.addAll(CompilerHelper.getAvailableCompilers("21"));
+ configs.addAll(CompilerHelper.getAvailableCompilers("25"));
+ return configs.stream();
+ }
+
@ParameterizedTest
- @ValueSource(strings = {"1.5", "1.8"})
- void translatesOptimizedBytecodeToLLVMExecutable(String targetVersion) throws Exception {
- if ("1.5".equals(targetVersion) && !isSourceVersionSupported("1.5")) {
- return; // Skip on newer JDKs that dropped 1.5 support
- }
+ @org.junit.jupiter.params.provider.MethodSource("provideCompilerConfigs")
+ void translatesOptimizedBytecodeToLLVMExecutable(CompilerHelper.CompilerConfig config) throws Exception {
Parser.cleanup();
Path sourceDir = Files.createTempDirectory("bytecode-integration-sources");
@@ -61,28 +69,43 @@ void translatesOptimizedBytecodeToLLVMExecutable(String targetVersion) throws Ex
Path nativeReport = sourceDir.resolve("native_report.c");
Files.write(nativeReport, nativeReportSource().getBytes(StandardCharsets.UTF_8));
- JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
- assertNotNull(compiler, "A JDK is required to compile test sources");
-
- // Compile App using JavaAPI as bootclasspath
+ // Compile App using the specific JDK
List compileArgs = new ArrayList<>();
- compileArgs.add("-source");
- compileArgs.add(targetVersion);
- compileArgs.add("-target");
- compileArgs.add(targetVersion);
- compileArgs.add("-bootclasspath");
- compileArgs.add(javaApiDir.toString());
+
+ double targetVer = 1.8;
+ try { targetVer = Double.parseDouble(config.targetVersion); } catch (NumberFormatException ignored) {}
+
+ double jdkVer = 1.8;
+ try { jdkVer = Double.parseDouble(config.jdkVersion); } catch (NumberFormatException ignored) {}
+
+ if (jdkVer >= 9) {
+ compileArgs.add("-source");
+ compileArgs.add(config.targetVersion);
+ compileArgs.add("-target");
+ compileArgs.add(config.targetVersion);
+ // On JDK 9+, -bootclasspath is removed.
+ // --patch-module is not allowed with -target 8.
+ // We rely on the JDK's own bootstrap classes but include our JavaAPI in classpath
+ // so that any non-replaced classes are found.
+ // This means we compile against JDK 9+ API but emit older bytecode.
+ compileArgs.add("-classpath");
+ compileArgs.add(javaApiDir.toString());
+ } else {
+ compileArgs.add("-source");
+ compileArgs.add(config.targetVersion);
+ compileArgs.add("-target");
+ compileArgs.add(config.targetVersion);
+ compileArgs.add("-bootclasspath");
+ compileArgs.add(javaApiDir.toString());
+ compileArgs.add("-Xlint:-options");
+ }
+
compileArgs.add("-d");
compileArgs.add(classesDir.toString());
compileArgs.add(sourceDir.resolve("BytecodeInstructionApp.java").toString());
- int compileResult = compiler.run(
- null,
- null,
- null,
- compileArgs.toArray(new String[0])
- );
- assertEquals(0, compileResult, "BytecodeInstructionApp should compile");
+ int compileResult = CompilerHelper.compile(config.jdkHome, compileArgs);
+ assertEquals(0, compileResult, "BytecodeInstructionApp should compile with " + config);
Files.copy(nativeReport, classesDir.resolve("native_report.c"));
@@ -190,11 +213,8 @@ private String invokeLdcLocalVarsAppSource() {
}
@ParameterizedTest
- @ValueSource(strings = {"1.5", "1.8"})
- void translatesInvokeAndLdcBytecodeToLLVMExecutable(String targetVersion) throws Exception {
- if ("1.5".equals(targetVersion) && !isSourceVersionSupported("1.5")) {
- return; // Skip on newer JDKs that dropped 1.5 support
- }
+ @org.junit.jupiter.params.provider.MethodSource("provideCompilerConfigs")
+ void translatesInvokeAndLdcBytecodeToLLVMExecutable(CompilerHelper.CompilerConfig config) throws Exception {
Parser.cleanup();
Path sourceDir = Files.createTempDirectory("invoke-ldc-sources");
@@ -209,27 +229,37 @@ void translatesInvokeAndLdcBytecodeToLLVMExecutable(String targetVersion) throws
Path nativeReport = sourceDir.resolve("native_report.c");
Files.write(nativeReport, nativeReportSource().getBytes(StandardCharsets.UTF_8));
- JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
- assertNotNull(compiler, "A JDK is required to compile test sources");
-
List compileArgs = new ArrayList<>();
- compileArgs.add("-source");
- compileArgs.add(targetVersion);
- compileArgs.add("-target");
- compileArgs.add(targetVersion);
- compileArgs.add("-bootclasspath");
- compileArgs.add(javaApiDir.toString());
+
+ double targetVer = 1.8;
+ try { targetVer = Double.parseDouble(config.targetVersion); } catch (NumberFormatException ignored) {}
+
+ double jdkVer = 1.8;
+ try { jdkVer = Double.parseDouble(config.jdkVersion); } catch (NumberFormatException ignored) {}
+
+ if (jdkVer >= 9) {
+ compileArgs.add("-source");
+ compileArgs.add(config.targetVersion);
+ compileArgs.add("-target");
+ compileArgs.add(config.targetVersion);
+ compileArgs.add("-classpath");
+ compileArgs.add(javaApiDir.toString());
+ } else {
+ compileArgs.add("-source");
+ compileArgs.add(config.targetVersion);
+ compileArgs.add("-target");
+ compileArgs.add(config.targetVersion);
+ compileArgs.add("-bootclasspath");
+ compileArgs.add(javaApiDir.toString());
+ compileArgs.add("-Xlint:-options");
+ }
+
compileArgs.add("-d");
compileArgs.add(classesDir.toString());
compileArgs.add(sourceDir.resolve("InvokeLdcLocalVarsApp.java").toString());
- int compileResult = compiler.run(
- null,
- null,
- null,
- compileArgs.toArray(new String[0])
- );
- assertEquals(0, compileResult, "InvokeLdcLocalVarsApp should compile");
+ int compileResult = CompilerHelper.compile(config.jdkHome, compileArgs);
+ assertEquals(0, compileResult, "InvokeLdcLocalVarsApp should compile with " + config);
Files.copy(nativeReport, classesDir.resolve("native_report.c"));
@@ -985,18 +1015,15 @@ private String nativeReportSource() {
}
@ParameterizedTest
- @ValueSource(strings = {"1.5", "1.8"})
- void handleDefaultOutputWritesOutput(String targetVersion) throws Exception {
- if ("1.5".equals(targetVersion) && !isSourceVersionSupported("1.5")) {
- return; // Skip on newer JDKs that dropped 1.5 support
- }
+ @org.junit.jupiter.params.provider.MethodSource("provideCompilerConfigs")
+ void handleDefaultOutputWritesOutput(CompilerHelper.CompilerConfig config) throws Exception {
Parser.cleanup();
resetByteCodeClass();
Path sourceDir = Files.createTempDirectory("default-output-source");
Path outputDir = Files.createTempDirectory("default-output-dest");
Files.write(sourceDir.resolve("resource.txt"), "data".getBytes(StandardCharsets.UTF_8));
- compileDummyMainClass(sourceDir, "com.example", "MyAppDefault", targetVersion);
+ compileDummyMainClass(sourceDir, "com.example", "MyAppDefault", config);
String[] args = new String[] {
"csharp",
@@ -1076,18 +1103,15 @@ void copyDirRecursivelyCopies() throws Exception {
}
@ParameterizedTest
- @ValueSource(strings = {"1.5", "1.8"})
- void handleIosOutputGeneratesProjectStructure(String targetVersion) throws Exception {
- if ("1.5".equals(targetVersion) && !isSourceVersionSupported("1.5")) {
- return; // Skip on newer JDKs that dropped 1.5 support
- }
+ @org.junit.jupiter.params.provider.MethodSource("provideCompilerConfigs")
+ void handleIosOutputGeneratesProjectStructure(CompilerHelper.CompilerConfig config) throws Exception {
Parser.cleanup();
resetByteCodeClass();
Path sourceDir = Files.createTempDirectory("ios-output-source");
Path outputDir = Files.createTempDirectory("ios-output-dest");
Files.write(sourceDir.resolve("resource.txt"), "data".getBytes(StandardCharsets.UTF_8));
- compileDummyMainClass(sourceDir, "com.example", "MyAppIOS", targetVersion);
+ compileDummyMainClass(sourceDir, "com.example", "MyAppIOS", config);
// Add a bundle to test copyDir invocation in execute loop
Path bundleDir = sourceDir.resolve("test.bundle");
@@ -1139,7 +1163,7 @@ private void resetByteCodeClass() throws Exception {
((Set>) writableFieldsField.get(null)).clear();
}
- private void compileDummyMainClass(Path sourceDir, String packageName, String className, String targetVersion) throws Exception {
+ private void compileDummyMainClass(Path sourceDir, String packageName, String className, CompilerHelper.CompilerConfig config) throws Exception {
Path packageDir = sourceDir;
for (String part : packageName.split("\\.")) {
packageDir = packageDir.resolve(part);
@@ -1152,13 +1176,17 @@ private void compileDummyMainClass(Path sourceDir, String packageName, String cl
"}\n";
Files.write(javaFile, content.getBytes(StandardCharsets.UTF_8));
- JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
- int result = compiler.run(null, null, null,
- "-source", targetVersion,
- "-target", targetVersion,
- "-d", sourceDir.toString(),
- javaFile.toString());
- assertEquals(0, result, "Compilation failed");
+ List args = new ArrayList<>();
+ args.add("-source");
+ args.add(config.targetVersion);
+ args.add("-target");
+ args.add(config.targetVersion);
+ args.add("-d");
+ args.add(sourceDir.toString());
+ args.add(javaFile.toString());
+
+ int result = CompilerHelper.compile(config.jdkHome, args);
+ assertEquals(0, result, "Compilation failed with " + config);
}
@Test
@@ -1262,19 +1290,4 @@ void testArithmeticExpressionCoverage() {
// or mock if possible. But here we can check basic behavior.
}
- private boolean isSourceVersionSupported(String version) {
- String javaVersion = System.getProperty("java.specification.version");
- if (javaVersion.startsWith("1.")) {
- return true;
- }
- try {
- int major = Integer.parseInt(javaVersion);
- if ("1.5".equals(version)) {
- if (major >= 9) return false;
- }
- } catch (NumberFormatException e) {
- return true;
- }
- return true;
- }
}
diff --git a/vm/tests/src/test/java/com/codename1/tools/translator/CleanTargetIntegrationTest.java b/vm/tests/src/test/java/com/codename1/tools/translator/CleanTargetIntegrationTest.java
index 026e60dd03..78f148ea92 100644
--- a/vm/tests/src/test/java/com/codename1/tools/translator/CleanTargetIntegrationTest.java
+++ b/vm/tests/src/test/java/com/codename1/tools/translator/CleanTargetIntegrationTest.java
@@ -26,11 +26,8 @@
class CleanTargetIntegrationTest {
@ParameterizedTest
- @ValueSource(strings = {"1.5", "1.8"})
- void generatesRunnableHelloWorldUsingCleanTarget(String targetVersion) throws Exception {
- if ("1.5".equals(targetVersion) && !isSourceVersionSupported("1.5")) {
- return; // Skip on newer JDKs that dropped 1.5 support
- }
+ @org.junit.jupiter.params.provider.MethodSource("com.codename1.tools.translator.BytecodeInstructionIntegrationTest#provideCompilerConfigs")
+ void generatesRunnableHelloWorldUsingCleanTarget(CompilerHelper.CompilerConfig config) throws Exception {
Parser.cleanup();
Path sourceDir = Files.createTempDirectory("clean-target-sources");
@@ -47,25 +44,47 @@ void generatesRunnableHelloWorldUsingCleanTarget(String targetVersion) throws Ex
Files.write(sourceDir.resolve("java/lang/NullPointerException.java"), javaLangNullPointerExceptionSource().getBytes(StandardCharsets.UTF_8));
Files.write(sourceDir.resolve("native_hello.c"), nativeHelloSource().getBytes(StandardCharsets.UTF_8));
- JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
- assertNotNull(compiler, "A JDK is required to compile test sources");
- int compileResult = compiler.run(
- null,
- null,
- null,
- "-source", targetVersion,
- "-target", targetVersion,
- "-d", classesDir.toString(),
- javaFile.toString(),
- sourceDir.resolve("java/lang/Object.java").toString(),
- sourceDir.resolve("java/lang/String.java").toString(),
- sourceDir.resolve("java/lang/Class.java").toString(),
- sourceDir.resolve("java/lang/Throwable.java").toString(),
- sourceDir.resolve("java/lang/Exception.java").toString(),
- sourceDir.resolve("java/lang/RuntimeException.java").toString(),
- sourceDir.resolve("java/lang/NullPointerException.java").toString()
- );
- assertEquals(0, compileResult, "HelloWorld.java should compile");
+ List compileArgs = new java.util.ArrayList<>();
+
+ double jdkVer = 1.8;
+ try { jdkVer = Double.parseDouble(config.jdkVersion); } catch (NumberFormatException ignored) {}
+
+ if (jdkVer >= 9) {
+ // For CleanTarget, we are compiling java.lang classes.
+ // On JDK 9+, this requires patching java.base.
+ // However, --patch-module is incompatible with -target 8.
+ // If we can't use --patch-module with -target 8, we skip this permutation.
+ if (Double.parseDouble(config.targetVersion) < 9) {
+ return; // Skip JDK 9+ compiling for Target < 9 for this specific test
+ }
+ compileArgs.add("-source");
+ compileArgs.add(config.targetVersion);
+ compileArgs.add("-target");
+ compileArgs.add(config.targetVersion);
+ compileArgs.add("--patch-module");
+ compileArgs.add("java.base=" + sourceDir.toString());
+ compileArgs.add("-Xlint:-module");
+ } else {
+ compileArgs.add("-source");
+ compileArgs.add(config.targetVersion);
+ compileArgs.add("-target");
+ compileArgs.add(config.targetVersion);
+ compileArgs.add("-Xlint:-options");
+ }
+
+ compileArgs.add("-d");
+ compileArgs.add(classesDir.toString());
+ compileArgs.add(javaFile.toString());
+ compileArgs.add(sourceDir.resolve("java/lang/Object.java").toString());
+ compileArgs.add(sourceDir.resolve("java/lang/String.java").toString());
+ compileArgs.add(sourceDir.resolve("java/lang/Class.java").toString());
+ compileArgs.add(sourceDir.resolve("java/lang/Throwable.java").toString());
+ compileArgs.add(sourceDir.resolve("java/lang/Exception.java").toString());
+ compileArgs.add(sourceDir.resolve("java/lang/RuntimeException.java").toString());
+ compileArgs.add(sourceDir.resolve("java/lang/NullPointerException.java").toString());
+
+ int compileResult = CompilerHelper.compile(config.jdkHome, compileArgs);
+ assertEquals(0, compileResult, "HelloWorld.java should compile with " + config);
Files.copy(sourceDir.resolve("native_hello.c"), classesDir.resolve("native_hello.c"));
@@ -461,19 +480,4 @@ static String nativeHelloSource() {
"}\n";
}
- private boolean isSourceVersionSupported(String version) {
- String javaVersion = System.getProperty("java.specification.version");
- if (javaVersion.startsWith("1.")) {
- return true;
- }
- try {
- int major = Integer.parseInt(javaVersion);
- if ("1.5".equals(version)) {
- if (major >= 9) return false;
- }
- } catch (NumberFormatException e) {
- return true;
- }
- return true;
- }
}
diff --git a/vm/tests/src/test/java/com/codename1/tools/translator/CompilerHelper.java b/vm/tests/src/test/java/com/codename1/tools/translator/CompilerHelper.java
new file mode 100644
index 0000000000..a4ff6971b3
--- /dev/null
+++ b/vm/tests/src/test/java/com/codename1/tools/translator/CompilerHelper.java
@@ -0,0 +1,138 @@
+package com.codename1.tools.translator;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+
+/**
+ * Helper class to manage external JDK compilers.
+ */
+public class CompilerHelper {
+
+ private static final Map availableJdks = new TreeMap<>();
+
+ static {
+ // Detect JDKs from environment variables set by CI or local setup
+ checkAndAddJdk("8", System.getenv("JDK_8_HOME"));
+ checkAndAddJdk("11", System.getenv("JDK_11_HOME"));
+ checkAndAddJdk("17", System.getenv("JDK_17_HOME"));
+ checkAndAddJdk("21", System.getenv("JDK_21_HOME"));
+ checkAndAddJdk("25", System.getenv("JDK_25_HOME"));
+
+ // Fallback: If no env vars, assume current JVM is JDK 8 (or whatever is running)
+ // This ensures tests pass locally or in environments not fully configured with all JDKs
+ if (availableJdks.isEmpty()) {
+ String currentJavaHome = System.getProperty("java.home");
+ // If it's a JRE, try to find JDK
+ if (currentJavaHome.endsWith("jre")) {
+ currentJavaHome = currentJavaHome.substring(0, currentJavaHome.length() - 4);
+ }
+ availableJdks.put(System.getProperty("java.specification.version"), Paths.get(currentJavaHome));
+ }
+ }
+
+ private static void checkAndAddJdk(String version, String path) {
+ if (path != null && !path.isEmpty() && new File(path).exists()) {
+ availableJdks.put(version, Paths.get(path));
+ }
+ }
+
+ public static List getAvailableCompilers(String targetVersion) {
+ List compilers = new ArrayList<>();
+
+ for (Map.Entry entry : availableJdks.entrySet()) {
+ String jdkVersion = entry.getKey();
+ Path jdkHome = entry.getValue();
+
+ if (canCompile(jdkVersion, targetVersion)) {
+ compilers.add(new CompilerConfig(jdkVersion, jdkHome, targetVersion));
+ }
+ }
+
+ // If we are running in a constrained environment (e.g. local dev without env vars),
+ // we might not have found the specific JDK requested.
+ // If the list is empty, and target is 1.5 or 1.8, and we have *some* JDK, try to use it
+ // if it supports the target.
+ if (compilers.isEmpty() && !availableJdks.isEmpty()) {
+ Map.Entry defaultJdk = availableJdks.entrySet().iterator().next();
+ if (canCompile(defaultJdk.getKey(), targetVersion)) {
+ compilers.add(new CompilerConfig(defaultJdk.getKey(), defaultJdk.getValue(), targetVersion));
+ }
+ }
+
+ return compilers;
+ }
+
+ private static boolean canCompile(String compilerVersion, String targetVersion) {
+ try {
+ double compilerVer = Double.parseDouble(compilerVersion);
+ double targetVer = Double.parseDouble(targetVersion);
+
+ // Java 9+ (version 9, 11, etc) dropped support for 1.5
+ if (targetVer == 1.5) {
+ return compilerVer < 9;
+ }
+ // Java 21? dropped support for 1.6/1.7?
+ // Generally newer JDKs support 1.8+
+ return compilerVer >= targetVer || (compilerVer >= 1.8 && targetVer <= 1.8);
+ } catch (NumberFormatException e) {
+ // Handle "1.8" format
+ if (compilerVersion.startsWith("1.")) {
+ return true; // Old JDKs support old targets
+ }
+ // Fallback for "25-ea"
+ if (compilerVersion.contains("-")) {
+ // Assume it's a new JDK
+ return !"1.5".equals(targetVersion);
+ }
+ return true;
+ }
+ }
+
+ public static int compile(Path jdkHome, List args) throws IOException, InterruptedException {
+ String javac = jdkHome.resolve("bin").resolve("javac").toString();
+ // On Windows it might be javac.exe
+ if (System.getProperty("os.name").toLowerCase().contains("win")) {
+ javac += ".exe";
+ }
+
+ List command = new ArrayList<>();
+ command.add(javac);
+
+ // Filter out flags that might be unsupported on newer JDKs if target is old,
+ // but generally we rely on the caller to provide correct flags.
+ // However, we might need to suppress warnings for obsolete targets.
+ // args.add("-Xlint:-options"); // Added by caller?
+
+ command.addAll(args);
+
+ ProcessBuilder pb = new ProcessBuilder(command);
+ // Inherit IO so we see errors in the log
+ pb.inheritIO();
+ Process p = pb.start();
+ return p.waitFor();
+ }
+
+ public static class CompilerConfig {
+ public final String jdkVersion;
+ public final Path jdkHome;
+ public final String targetVersion;
+
+ public CompilerConfig(String jdkVersion, Path jdkHome, String targetVersion) {
+ this.jdkVersion = jdkVersion;
+ this.jdkHome = jdkHome;
+ this.targetVersion = targetVersion;
+ }
+
+ @Override
+ public String toString() {
+ return "JDK " + jdkVersion + " (Target " + targetVersion + ")";
+ }
+ }
+}