From 326198c7e211a996dc133bda7d942f2b47189bdc Mon Sep 17 00:00:00 2001 From: Tako Schotanus Date: Wed, 21 Jan 2026 12:21:41 +0100 Subject: [PATCH 1/3] chore: updated to devkitman 0.4.x --- app.yml | 2 +- pom.xml | 29 +++++++++++- src/main/java/org/codejive/jvm/Main.java | 59 +++++++++++++++--------- 3 files changed, 66 insertions(+), 24 deletions(-) diff --git a/app.yml b/app.yml index acfa2d1..d0dda2a 100644 --- a/app.yml +++ b/app.yml @@ -8,7 +8,7 @@ links: documentation: https://github.com/codejive/java-jvm/blob/main/README.md java: 11 dependencies: - - dev.jbang:devkitman:0.3.0 + - dev.jbang:devkitman:0.4.2 - de.vandemeer:asciitable:0.3.2 - info.picocli:picocli:4.7.7 - org.yaml:snakeyaml:2.5 diff --git a/pom.xml b/pom.xml index ff05b50..7805307 100644 --- a/pom.xml +++ b/pom.xml @@ -31,8 +31,9 @@ UTF-8 11 11 + 0.11.2 org.codejive.jvm.Main - 0.3.0 + 0.4.2 0.3.2 4.7.7 2.5 @@ -315,6 +316,32 @@ + + native + + + + org.graalvm.buildtools + native-maven-plugin + ${native.maven.plugin} + true + + + build-native + + compile-no-fork + + package + + + + ${mainClass} + + + + + + release diff --git a/src/main/java/org/codejive/jvm/Main.java b/src/main/java/org/codejive/jvm/Main.java index 7aa0fe5..fb5a82d 100644 --- a/src/main/java/org/codejive/jvm/Main.java +++ b/src/main/java/org/codejive/jvm/Main.java @@ -1,5 +1,5 @@ // spotless:off Dependencies for JBang -//DEPS dev.jbang:devkitman:0.3.0 +//DEPS dev.jbang:devkitman:0.4.2 //DEPS org.yaml:snakeyaml:2.4 //DEPS info.picocli:picocli:4.7.7 //DEPS de.vandermeer:asciitable:0.3.2 @@ -44,36 +44,39 @@ }) public class Main { + static boolean verbose = false; + static boolean quiet = false; + @Option( names = {"-h", "--help"}, description = "Show this help message and exit.", - usageHelp = true) + usageHelp = true, + scope = CommandLine.ScopeType.INHERIT) boolean showHelp; @Option( - names = {"-v", "--version"}, - description = "Show application version.", - versionHelp = true) - boolean showVersion; + names = {"-q", "--quiet"}, + description = "We will be quiet, only print when error occurs.", + scope = CommandLine.ScopeType.INHERIT) + public void setQuiet(boolean quiet) { + Main.quiet = quiet; + } @Option( - names = {"--quiet"}, - description = "We will be quiet, only print when error occurs.") - boolean quiet; + names = {"-v", "--verbose"}, + description = "Enable verbose output for debugging", + scope = CommandLine.ScopeType.INHERIT) + public void setVerbose(boolean verbose) { + Main.verbose = verbose; + } - abstract static class CmdBase implements Callable { - @Option( - names = {"-h", "--help"}, - description = "Show this help message and exit.", - usageHelp = true, - scope = CommandLine.ScopeType.INHERIT) - boolean showHelp; + @Option( + names = {"-V", "--version"}, + description = "Show application version.", + versionHelp = true) + boolean showVersion; - @Option( - names = {"--quiet"}, - description = "We will be quiet, only print when error occurs.") - boolean quiet; - } + abstract static class CmdBase implements Callable {} @Command( name = "list", @@ -230,7 +233,7 @@ public Integer call() { System.err.println("Java version not installed: " + versionOrId); return 1; } - jdkMan.uninstallJdk(jdk); + jdk.uninstall(); if (!quiet) { System.err.println("Successfully uninstalled Java version " + versionOrId); } @@ -427,6 +430,18 @@ private static boolean isId(String s) { return s.matches("[a-zA-Z0-9_\\-.]+"); } + static CommandLine.IExecutionExceptionHandler errorHandler = + (ex, commandLine, parseResult) -> { + System.err.println("Error: " + ex.getMessage()); + if (verbose) { + ex.printStackTrace(); + } else if (!quiet) { + System.err.println( + "(Run with --verbose for more details. If you believe you found a bug in jpm, open an issue at https://github.com/codejive/java-jpm/issues)"); + } + return commandLine.getCommandSpec().exitCodeOnExecutionException(); + }; + /** * Main entry point for the jvm command line tool. * From 767943a109d74748bec0e10442a0b615049e0234 Mon Sep 17 00:00:00 2001 From: Tako Schotanus Date: Fri, 6 Feb 2026 13:59:25 +0100 Subject: [PATCH 2/3] fix: native compiplation now works correctly --- README.md | 25 ++- pom.xml | 8 + .../dev.jbang/devkitman/reflect-config.json | 183 ++++++++++++++++++ .../native-image/native-image.properties | 5 + .../native-image/resource-config.json | 17 ++ .../native-image/serialization-config.json | 27 +++ 6 files changed, 264 insertions(+), 1 deletion(-) create mode 100644 src/main/resources/META-INF/native-image/dev.jbang/devkitman/reflect-config.json create mode 100644 src/main/resources/META-INF/native-image/native-image.properties create mode 100644 src/main/resources/META-INF/native-image/resource-config.json create mode 100644 src/main/resources/META-INF/native-image/serialization-config.json diff --git a/README.md b/README.md index 4f6c3b1..02140ef 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# jvm - Java Version Manager +oes# jvm - Java Version Manager A command line tool that helps you install and manage multiple Java versions on your machine. @@ -27,3 +27,26 @@ To build the project simply run: ```shell ./mvnw spotless:apply clean verify ``` + mavern have +### Building Native Executables + +The project supports building native executables using GraalVM. This creates a standalone binary with faster startup time and lower memory footprint. + +#### Prerequisites + +1. Install GraalVM (recommended: GraalVM 22.3 or later) +2. Set `GRAALVM_HOME` or `JAVA_HOME` to point to your GraalVM installation +3. Install the native-image tool: + ```shell + gu install native-image + ``` + +#### Build Native Executable + +To build a native executable, run: + +```shell +./mvnw clean package -Pnative +``` + +The native executable will be created in `target/jvm` (or `target/jvm.exe` on Windows). diff --git a/pom.xml b/pom.xml index 7805307..5f8a51a 100644 --- a/pom.xml +++ b/pom.xml @@ -336,6 +336,14 @@ ${mainClass} + jvm + + --no-fallback + -H:+ReportExceptionStackTraces + --enable-url-protocols=http,https + --initialize-at-build-time=org.slf4j + --initialize-at-run-time=com.google.gson.internal.UnsafeAllocator + diff --git a/src/main/resources/META-INF/native-image/dev.jbang/devkitman/reflect-config.json b/src/main/resources/META-INF/native-image/dev.jbang/devkitman/reflect-config.json new file mode 100644 index 0000000..10cafd2 --- /dev/null +++ b/src/main/resources/META-INF/native-image/dev.jbang/devkitman/reflect-config.json @@ -0,0 +1,183 @@ +[ + { + "name": "dev.jbang.devkitman.jdkinstallers.FoojayJdkInstaller", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredFields": true, + "allPublicFields": true, + "unsafeAllocated": true + }, + { + "name": "dev.jbang.devkitman.jdkinstallers.FoojayJdkInstaller$VersionsResponse", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredFields": true, + "allPublicFields": true, + "unsafeAllocated": true + }, + { + "name": "dev.jbang.devkitman.jdkinstallers.FoojayJdkInstaller$JdkResult", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredFields": true, + "allPublicFields": true, + "unsafeAllocated": true + }, + { + "name": "dev.jbang.devkitman.jdkinstallers.FoojayJdkInstaller$JdkResultLinks", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredFields": true, + "allPublicFields": true, + "unsafeAllocated": true + }, + { + "name": "dev.jbang.devkitman.jdkinstallers.FoojayJdkInstaller$AvailableFoojayJdk", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredFields": true, + "allPublicFields": true, + "unsafeAllocated": true + }, + { + "name": "dev.jbang.devkitman.jdkinstallers.MetadataJdkInstaller", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredFields": true, + "allPublicFields": true, + "unsafeAllocated": true + }, + { + "name": "dev.jbang.devkitman.jdkinstallers.MetadataJdkInstaller$MetadataResult", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredFields": true, + "allPublicFields": true, + "unsafeAllocated": true + }, + { + "name": "dev.jbang.devkitman.jdkinstallers.MetadataJdkInstaller$AvailableMetadataJdk", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredFields": true, + "allPublicFields": true, + "unsafeAllocated": true + }, + { + "name": "dev.jbang.devkitman.JdkProvider", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "unsafeAllocated": true + }, + { + "name": "dev.jbang.devkitman.JdkProviders", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "unsafeAllocated": true + }, + { + "name": "java.util.ArrayList", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "unsafeAllocated": true + }, + { + "name": "java.util.LinkedHashMap", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "unsafeAllocated": true + }, + { + "name": "java.util.HashMap", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "unsafeAllocated": true + }, + { + "name": "java.lang.String", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "unsafeAllocated": true + }, + { + "name": "dev.jbang.devkitman.jdkproviders.DefaultJdkProvider", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "unsafeAllocated": true + }, + { + "name": "dev.jbang.devkitman.jdkproviders.DefaultJdkProvider$AvailableDefaultJdk", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredFields": true, + "allPublicFields": true, + "unsafeAllocated": true + }, + { + "name": "dev.jbang.devkitman.jdkproviders.JBangJdkProvider", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "unsafeAllocated": true + }, + { + "name": "dev.jbang.devkitman.jdkproviders.SdkmanJdkProvider", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "unsafeAllocated": true + }, + { + "name": "dev.jbang.devkitman.jdkproviders.LinkedJdkProvider", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "unsafeAllocated": true + }, + { + "name": "dev.jbang.devkitman.jdkproviders.LinkedJdkProvider$AvailableLinkedJdk", + "allDeclaredConstructors": true, + "allPublicConstructors": true, + "allDeclaredMethods": true, + "allPublicMethods": true, + "allDeclaredFields": true, + "allPublicFields": true, + "unsafeAllocated": true + } +] + diff --git a/src/main/resources/META-INF/native-image/native-image.properties b/src/main/resources/META-INF/native-image/native-image.properties new file mode 100644 index 0000000..a127131 --- /dev/null +++ b/src/main/resources/META-INF/native-image/native-image.properties @@ -0,0 +1,5 @@ +Args = --no-fallback \ + -H:+ReportExceptionStackTraces \ + --enable-url-protocols=http,https \ + --initialize-at-build-time=org.slf4j \ + --initialize-at-run-time=com.google.gson.internal.UnsafeAllocator diff --git a/src/main/resources/META-INF/native-image/resource-config.json b/src/main/resources/META-INF/native-image/resource-config.json new file mode 100644 index 0000000..da828d8 --- /dev/null +++ b/src/main/resources/META-INF/native-image/resource-config.json @@ -0,0 +1,17 @@ +{ + "resources": { + "includes": [ + { + "pattern": ".*\\.properties$" + }, + { + "pattern": ".*\\.yml$" + }, + { + "pattern": ".*\\.yaml$" + } + ] + }, + "bundles": [] +} + diff --git a/src/main/resources/META-INF/native-image/serialization-config.json b/src/main/resources/META-INF/native-image/serialization-config.json new file mode 100644 index 0000000..736c6e8 --- /dev/null +++ b/src/main/resources/META-INF/native-image/serialization-config.json @@ -0,0 +1,27 @@ +[ + { + "name": "dev.jbang.devkitman.jdkinstallers.FoojayJdkInstaller$VersionsResponse" + }, + { + "name": "dev.jbang.devkitman.jdkinstallers.FoojayJdkInstaller$JdkResult" + }, + { + "name": "dev.jbang.devkitman.jdkinstallers.FoojayJdkInstaller$JdkResultLinks" + }, + { + "name": "dev.jbang.devkitman.jdkinstallers.FoojayJdkInstaller$AvailableFoojayJdk" + }, + { + "name": "dev.jbang.devkitman.jdkinstallers.MetadataJdkInstaller$MetadataResult" + }, + { + "name": "dev.jbang.devkitman.jdkinstallers.MetadataJdkInstaller$AvailableMetadataJdk" + }, + { + "name": "dev.jbang.devkitman.jdkproviders.DefaultJdkProvider$AvailableDefaultJdk" + }, + { + "name": "dev.jbang.devkitman.jdkproviders.LinkedJdkProvider$AvailableLinkedJdk" + } +] + From 002e8015070ab75f7e74b3077ea0a48c3a119011 Mon Sep 17 00:00:00 2001 From: Tako Schotanus Date: Wed, 4 Mar 2026 20:45:04 +0100 Subject: [PATCH 3/3] fix: caching now works --- pom.xml | 15 ++++++++-- src/main/java/org/codejive/jvm/Main.java | 35 ++++++++++++++++++++---- 2 files changed, 42 insertions(+), 8 deletions(-) diff --git a/pom.xml b/pom.xml index 5f8a51a..95deeb6 100644 --- a/pom.xml +++ b/pom.xml @@ -33,7 +33,8 @@ 11 0.11.2 org.codejive.jvm.Main - 0.4.2 + 0.4.3 + 5.6 0.3.2 4.7.7 2.5 @@ -51,6 +52,11 @@ devkitman ${version.devkitman} + + org.apache.httpcomponents.client5 + httpclient5-cache + ${version.httpclient} + de.vandermeer asciitable @@ -76,7 +82,12 @@ slf4j-simple ${version.slf4j} - + + org.slf4j + jcl-over-slf4j + ${version.slf4j} + + org.junit.jupiter diff --git a/src/main/java/org/codejive/jvm/Main.java b/src/main/java/org/codejive/jvm/Main.java index fb5a82d..487348c 100644 --- a/src/main/java/org/codejive/jvm/Main.java +++ b/src/main/java/org/codejive/jvm/Main.java @@ -85,7 +85,7 @@ abstract static class CmdBase implements Callable {} static class ListInstalled extends CmdBase { @Override public Integer call() { - JdkManager manager = JdkManager.create(); + JdkManager manager = createJdkManager(); manager.getOrInstallJdk("11+"); List jdks = manager.listInstalledJdks(); jdks.sort(Comparator.naturalOrder().reversed()); @@ -119,7 +119,7 @@ static class ListAvailable extends CmdBase { @Override public Integer call() { System.err.println("Retrieving available Java versions, this can take a moment..."); - JdkManager manager = JdkManager.create(); + JdkManager manager = createJdkManager(); List jdks = manager.listAvailableJdks(); jdks.sort(Comparator.naturalOrder().reversed()); @@ -183,7 +183,7 @@ static class Install extends CmdBase { @Override public Integer call() { String versionOrId = javaVersionParamMixin.getVersionOrId(quiet); - JdkManager jdkMan = JdkManager.create(); + JdkManager jdkMan = createJdkManager(); Jdk jdk = jdkMan.getInstalledJdk(versionOrId, JdkProvider.Predicates.canUpdate); if (!force && jdk != null) { if (!quiet) { @@ -226,7 +226,7 @@ void setJavaVersion(String versionOrId) { @Override public Integer call() { - JdkManager jdkMan = JdkManager.create(); + JdkManager jdkMan = createJdkManager(); Jdk.InstalledJdk jdk = jdkMan.getInstalledJdk(versionOrId, JdkProvider.Predicates.canUpdate); if (jdk == null) { @@ -283,7 +283,7 @@ static class Env extends CmdBase { @Override public Integer call() { - JdkManager jdkMan = JdkManager.create(); + JdkManager jdkMan = createJdkManager(); Jdk jdk = jdkMan.getOrInstallJdk(javaVersionParamMixin.getVersionOrId(quiet)); return 0; } @@ -305,7 +305,7 @@ public Integer call() throws IOException { if (cmd.isEmpty()) { return 0; } - JdkManager jdkMan = JdkManager.create(); + JdkManager jdkMan = createJdkManager(); Jdk.InstalledJdk jdk = jdkMan.getOrInstallJdk(javaVersionOptionMixin.getVersionOrId(quiet)); if (Paths.get(cmd.get(0)).getNameCount() == 1) { @@ -430,6 +430,29 @@ private static boolean isId(String s) { return s.matches("[a-zA-Z0-9_\\-.]+"); } + private static JdkManager createJdkManager() { + Path installPath = JBangJdkProvider.getJBangJdkDir(); + Path cachePath = cachePath(); + JdkDiscovery.Config cfg = new JdkDiscovery.Config(installPath, cachePath, null); + cfg.properties() + .put("link", JBangJdkProvider.getJBangConfigDir().resolve("currentjdk").toString()); + return JdkManager.builder().providers(JdkProviders.instance().all(cfg)).build(); + } + + private static Path cachePath() { + String cachePath = System.getenv("JVM_CACHE"); + Path cacheDir = null; + if (cachePath == null) { + cachePath = System.getProperty("jvm.cache"); + if (cachePath != null) { + cacheDir = Paths.get(cachePath); + } else { + cacheDir = Paths.get(System.getProperty("user.home"), ".cache", "jvm"); + } + } + return cacheDir; + } + static CommandLine.IExecutionExceptionHandler errorHandler = (ex, commandLine, parseResult) -> { System.err.println("Error: " + ex.getMessage());