diff --git a/build.gradle b/build.gradle index 5011b4d..d8da4b9 100644 --- a/build.gradle +++ b/build.gradle @@ -49,6 +49,8 @@ tasks.register('javaProbeJar', Jar) { } dependencies { + compileOnly libs.nulls + implementation libs.jopt implementation libs.gson implementation libs.jtar diff --git a/settings.gradle b/settings.gradle index c663a73..9b77cc0 100644 --- a/settings.gradle +++ b/settings.gradle @@ -4,8 +4,9 @@ plugins { dependencyResolutionManagement { repositories { - maven { url = 'https://maven.minecraftforge.net/' } mavenCentral() + maven { url = 'https://maven.minecraftforge.net/' } + mavenLocal() } versionCatalogs.register('libs') { @@ -13,6 +14,8 @@ dependencyResolutionManagement { plugin 'gradleutils', 'net.minecraftforge.gradleutils' version '2.6.0' plugin 'shadow', 'com.gradleup.shadow' version '9.0.0-beta13' + library 'nulls', 'org.jetbrains:annotations:26.0.2-1' + library 'gson', 'com.google.code.gson:gson:2.10.1' // > 2.9.0 needs Java 7 library 'jopt', 'net.sf.jopt-simple:jopt-simple:6.0-alpha-3' // Java 8 library 'jtar', 'org.kamranzafar:jtar:2.3' @@ -20,7 +23,8 @@ dependencyResolutionManagement { library 'utils-download', 'net.minecraftforge', 'download-utils' version '0.3.0' library 'utils-hash', 'net.minecraftforge', 'hash-utils' version '0.1.9' library 'utils-logging', 'net.minecraftforge', 'log-utils' version '0.3.0' - bundle 'utils', ['utils-download', 'utils-hash', 'utils-logging'] + library 'utils-os', 'net.minecraftforge', 'os-utils' version '0.1.0' + bundle 'utils', ['utils-download', 'utils-hash', 'utils-logging', 'utils-os'] } } diff --git a/src/main/java/net/minecraftforge/java_provisioner/Disco.java b/src/main/java/net/minecraftforge/java_provisioner/Disco.java index 77b03cb..6e420bd 100644 --- a/src/main/java/net/minecraftforge/java_provisioner/Disco.java +++ b/src/main/java/net/minecraftforge/java_provisioner/Disco.java @@ -9,6 +9,7 @@ import java.io.DataInputStream; import java.io.File; import java.io.FileInputStream; +import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; import java.io.InputStream; @@ -18,6 +19,7 @@ import java.nio.file.attribute.PosixFileAttributeView; import java.nio.file.attribute.PosixFilePermission; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.EnumMap; import java.util.HashSet; @@ -25,21 +27,25 @@ import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.Objects; import java.util.Set; import java.util.zip.GZIPInputStream; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; import com.google.gson.GsonBuilder; +import com.google.gson.JsonParseException; import com.google.gson.TypeAdapter; import com.google.gson.internal.bind.TypeAdapters; import com.google.gson.stream.JsonWriter; -import net.minecraftforge.java_provisioner.util.OS; +import net.minecraftforge.util.os.OS; import net.minecraftforge.java_provisioner.util.ProcessUtils; import net.minecraftforge.util.download.DownloadUtils; import net.minecraftforge.util.hash.HashFunction; import net.minecraftforge.util.logging.Log; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.UnknownNullability; import org.kamranzafar.jtar.TarEntry; import org.kamranzafar.jtar.TarInputStream; @@ -62,6 +68,8 @@ * TODO: [DISCO][Threads] Locking files for multiple processes accessing the same cache directory */ public class Disco { + public static final OS CURRENT_OS = OS.current(); + public static final Arch CURRENT_ARCH = Arch.getCurrent(); private static final int CACHE_TIMEOUT = 1000 * 60 * 60 * 12; // 12 hours // A GSO parser that prints good looking output, and treats empty strings as nulls @@ -115,14 +123,18 @@ protected void error(String message) { Log.error(message); } - public List getPackages() { + public List getPackages() throws Exception { + List errors = new ArrayList<>(); + File tmp = new File(cache, "packages.json"); - List ret = readJson(tmp, new TypeToken>(){}); - if (ret != null) - return ret; + try { + return readJson(tmp, new TypeToken>(){}); + } catch (Exception e) { + errors.add(e); + } if (offline) - return null; + throw Throwing.withSuppressed(new Exception("Cannot download package list while offline"), errors); String url = provider + "/packages/?" + "&package_type=jdk" // JDK has everything, could pull just the JRE but who cares. @@ -131,27 +143,39 @@ public List getPackages() { ; debug("Downloading package list"); - String data = DownloadUtils.tryDownloadString(true, url); - if (data == null) - return null; + String data; + try { + data = DownloadUtils.downloadString(url); + } catch (IOException e) { + throw Throwing.withSuppressed(new Exception("Failed to download package list from " + url, e), errors); + } - Response resp = new Response<>(data, Package.class); + Response resp; + try { + resp = Response.of(data, Package.class); + } catch (Exception e) { + throw Throwing.withSuppressed(new Exception("Failed to parse package list from " + url, e), errors); + } if (resp.entries().isEmpty()) { error("Failed to download any packages from " + url); - return null; + throw Throwing.withSuppressed(new Exception("Failed to parse package list from " + url), errors); } - writeJson(tmp, resp.entries(), List.class); + try { + writeJson(tmp, resp.entries(), List.class); + } catch (IOException e) { + throw Throwing.withSuppressed(new Exception("Failed to write package list to " + tmp.getAbsolutePath(), e), errors); + } return resp.entries(); } - public List getPackages(int version) { - return getPackages(version, OS.CURRENT, Distro.TEMURIN, Arch.CURRENT); + public List getPackages(int version) throws Exception { + return getPackages(version, CURRENT_OS, null, CURRENT_ARCH); } - public List getPackages(int version, OS os, Distro distro, Arch arch) { + public List getPackages(int version, OS os, Distro distro, Arch arch) throws Exception { List jdks = getPackages(); if (jdks == null) return null; @@ -193,105 +217,133 @@ public List getPackages(int version, OS os, Distro distro, Arch arch) { return ret; } - public PackageInfo getInfo(Package pkg) { + public PackageInfo getInfo(Package pkg) throws Exception { + List errors = new ArrayList<>(); + File tmp = new File(cache, pkg.filename + ".json"); - DownloadInfo ret = readJson(tmp, TypeToken.get(DownloadInfo.class)); - if (ret != null && ret.info != null) - return ret.info; + try { + return Objects.requireNonNull(readJson(tmp, TypeToken.get(DownloadInfo.class)).info); + } catch (Exception e) { + errors.add(e); + } if (offline) - return null; + throw Throwing.withSuppressed(new Exception("Cannot download package info while offline"), errors); //debug("Downloading package info " + pkg.id); String url = provider + "/ids/" + pkg.id; - String data = DownloadUtils.tryDownloadString(true, url); - if (data == null) - return null; + String data; + try { + data = DownloadUtils.downloadString(url); + } catch (IOException e) { + throw Throwing.withSuppressed(new Exception("Failed to download package info from " + url, e), errors); + } - Response resp = new Response<>(data, PackageInfo.class); + Response resp; + try { + resp = Response.of(data, PackageInfo.class); + } catch (Exception e) { + throw Throwing.withSuppressed(new Exception("Failed to parse package info from " + url, e), errors); + } if (resp.entries().isEmpty()) { - error("Failed to download package info for "+ pkg.id); - return null; + error("Failed to download any packages from " + url); + throw Throwing.withSuppressed(new Exception("Failed to parse package list from " + url), errors); } else if (resp.entries().size() != 1) { // This never happens, but output a warning if it does. debug("Warning: Multiple package infos returned from " + url); } - ret = new DownloadInfo(pkg, resp.entries().get(0)); - writeJson(tmp, ret, DownloadInfo.class); + DownloadInfo ret = new DownloadInfo(pkg, resp.entries().get(0)); + try { + writeJson(tmp, ret, DownloadInfo.class); + } catch (IOException e) { + throw Throwing.withSuppressed(new Exception("Failed to write package info to " + tmp.getAbsolutePath(), e), errors); + } return ret.info; } - public File download(Package pkg) { - PackageInfo info = getInfo(pkg); + public File download(Package pkg) throws Exception { + List errors = new ArrayList<>(); Map checksums = new EnumMap<>(HashFunction.class); String download = pkg.links.pkg_download_redirect; //debug("Downloading " + pkg.filename); - if (info == null) { - debug("Failed to download package info for \"" + pkg.filename + "\" (" + pkg.id + ") , assuming redirect link is valid"); - } else { + try { + PackageInfo info = getInfo(pkg); + if (info.checksum != null && info.checksum_type != null) { HashFunction func = HashFunction.find(info.checksum_type); - if (func != null) + if (func != null) { checksums.put(func, info.checksum); - else + } else { debug("Unknown Checksum " + info.checksum_type + ": " + info.checksum); + errors.add(new Exception("Unknown Checksum " + info.checksum_type + ": " + info.checksum)); + } } else if (info.checksum_uri != null && !offline) { - String raw = DownloadUtils.tryDownloadString(true, info.checksum_uri); - if (raw != null) { + try { + String raw = DownloadUtils.downloadString(info.checksum_uri); + String checksum = raw.split(" ")[0]; HashFunction func = HashFunction.findByHash(checksum); - if (func != null) + if (func != null) { checksums.put(func, checksum); - else + } else { debug("Unknown Checksum " + checksum); + errors.add(new Exception("Unknown Checksum " + checksum)); + } + } catch (IOException e) { + errors.add(new Exception("Failed to download checksum from " + info.checksum_uri, e)); } } if (info.direct_download_uri != null) download = info.direct_download_uri; + } catch (Exception e) { + debug("Failed to download package info for \"" + pkg.filename + "\" (" + pkg.id + ") , assuming redirect link is valid"); + errors.add(new Exception("Failed to download package info for \"" + pkg.filename + "\" (" + pkg.id + ')', e)); } File archive = new File(cache, pkg.filename); if (!archive.exists()) { if (download == null) { - if (offline) - error("Offline mode, can't download " + pkg.filename + " (" + pkg.id + ")"); - else - error("Failed to find download link for " + pkg.filename + " (" + pkg.id + ")"); - return null; + String message = offline + ? "Offline mode, can't download " + pkg.filename + " (" + pkg.id + ")" + : "Failed to find download link for " + pkg.filename + " (" + pkg.id + ")"; + error(message); + throw Throwing.withSuppressed(new Exception(message), errors); } debug("Downloading " + download); - if (!DownloadUtils.tryDownloadFile(true, archive, download)) { - error("Failed to download " + pkg.filename + " from " + download); - return null; + try { + DownloadUtils.tryDownloadFile(true, archive, download); + } catch (Exception e) { + String message = "Failed to download " + pkg.filename + " from " + download; + error(message); + throw Throwing.withSuppressed(new Exception(message, e), errors); } } if (!checksums.isEmpty()) { debug("Verifying checksums"); - boolean success = true; - for (HashFunction func : checksums.keySet()) { + for (Map.Entry entry : checksums.entrySet()) { + HashFunction func = entry.getKey(); try { String actual = func.hash(archive); - String expected = checksums.get(func); + String expected = entry.getValue(); if (expected.equals(actual)) { debug(" " + func.name() + " Validated"); } else { - success = false; debug(" " + func.name() + " Invalid"); debug(" Expected: " + expected); debug(" Actual: " + actual); + throw new Exception("Checksum mismatch for " + pkg.filename + " (" + pkg.id + ") " + func.name() + " expected: " + expected + " actual: " + actual); } - } catch (IOException e) { - error("Failed to calculate " + func.name() + " checksum: " + e.getMessage()); - return null; + } catch (Exception e) { + String message = "Failed to calculate " + func.name() + " checksum"; + error(message + ": " + e.getMessage()); + throw Throwing.withSuppressed(new Exception(message, e), errors); } } - if (!success) - return null; } else { debug(" No checksum found, assuming existing file is valid"); } @@ -316,20 +368,23 @@ private File getExtractedDir(Package pkg) { return new File(cache, filename); } - public File extract(Package pkg) { + public File extract(Package pkg) throws Exception { File archive = new File(cache, pkg.filename); - if (!archive.exists()) - archive = download(pkg); - - if (archive == null) - return null; + if (!archive.exists()) { + try { + archive = download(pkg); + } catch (Exception e) { + error("Failed to download package: " + pkg.id); + throw new Exception("Failed to download package: " + pkg.id, e); + } + } File extracted = getExtractedDir(pkg); return extract(archive, extracted, pkg.os(), pkg.archive()); } private File extract(File archive, File target, OS os, Archive format) { - String exeName = "bin/java" + OS.CURRENT.exe(); + String exeName = "bin/java" + CURRENT_OS.exe(); File exe = new File(target, exeName); if (exe.exists()) return target; @@ -530,28 +585,26 @@ private void extractTar(String exeName, File archive, File target, OS os, boolea } } + private T readJson(File input, TypeToken type) throws Exception { + if (!input.exists()) + throw new FileNotFoundException(input.getAbsolutePath()); - private T readJson(File input, TypeToken type) { - if (!input.exists() || input.lastModified() < System.currentTimeMillis() - CACHE_TIMEOUT) - return null; + if (!offline && input.lastModified() < System.currentTimeMillis() - CACHE_TIMEOUT) + throw new Exception("Cache file is stale, please redownload"); try (FileReader reader = new FileReader(input)) { return GSON.fromJson(new JsonReader(reader), type); - } catch (IOException e) { - debug("Can not read cache file: " + e.getMessage()); } - return null; } - private static void writeJson(File output, T data, Class type) { - output.getParentFile().mkdirs(); + private static void writeJson(File output, T data, Class type) throws IOException { + Files.createDirectories(output.getParentFile().toPath()); try (BufferedWriter out = Files.newBufferedWriter(output.toPath(), StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING)) { GSON.toJson(data, type, GSON.newJsonWriter(out)); - } catch (IOException e) { - e.printStackTrace(); } } + // NOTE: We use our own Arch enum instead of OS Utils since Foojay has a lot of redundancies (x64 and x86_64) public enum Arch { X86("x86", "x86", "x32", "286"), X64, @@ -581,7 +634,6 @@ public enum Arch { UNKNOWN; private static final Arch[] $values = values(); - public static final Arch CURRENT = getCurrent(); private final Arch parent; private final String key; @@ -611,6 +663,10 @@ public boolean is64Bit() { return this == X64 || this == AMD64 || this == ARM64 || this == X86_64 || this == AARCH64 || this == PPC64 || this == PPC64EL || this == RISCV64; } + public boolean isArm() { + return this == ARM || this == ARM32 || this == ARM64 || this == AARCH32 || this == AARCH64 || this == RISCV64; + } + public String key() { return this.key; } @@ -636,43 +692,55 @@ private static Arch getCurrent() { } } + // TODO [Provisioner][Distro] Reconsider the order of this entire list + // Prefer TCK certified JDKs (and also test Microsoft) public enum Distro implements Comparable { - // These are well know/recommended distros + // These are well-known/recommended distros + ORACLE_OPEN_JDK, TEMURIN, - AOJ, ZULU, + MICROSOFT, + OJDK_BUILD, + // Everything else - AOJ_OPENJ9, - BISHENG, - CORRETTO, - DRAGONWELL, - GRAALVM_CE8, - GRAALVM_CE11, - GRAALVM_CE16, - GRAALVM_CE17, - GRAALVM_CE19, - GRAALVM_CE20, + ZULU_PRIME, GRAALVM_COMMUNITY, + GRAALVM_CE17, + GRAALVM_CE11, + GRAALVM_CE8, GRAALVM, JETBRAINS, - KONA, + //BISHENG, // public URL on foojay is a 404. don't bother with this one. + CORRETTO, + DRAGONWELL, + KONA, // aka TENCENT LIBERICA, LIBERICA_NATIVE, MANDREL, - MICROSOFT, - OJDK_BUILD, OPENLOGIC, ORACLE, - ORACLE_OPEN_JDK, REDHAT, + DEBIAN, SAP_MACHINE, SEMERU, SEMERU_CERTIFIED, + + // Known Abandoned JDKs TRAVA, - ZULU_PRIME; + GRAALVM_CE20, + GRAALVM_CE19, + GRAALVM_CE16, + AOJ_OPENJ9, + AOJ; private static final Distro[] $values = values(); + private static final List VALUES = Collections.unmodifiableList(Arrays.asList($values)); + + public static List all() { + return VALUES; + } + private final String key; private Distro() { @@ -762,9 +830,9 @@ public static LibC byKey(String key) { public static final LibC CURRENT = getCurrent(); private static final LibC getCurrent() { - if (OS.CURRENT == OS.MUSL) + if (CURRENT_OS == OS.MUSL) return MUSL; - if (OS.CURRENT != OS.LINUX && OS.CURRENT != OS.ALPINE) + if (CURRENT_OS != OS.LINUX && CURRENT_OS != OS.ALPINE) return GLIBC; ProcessUtils.Result getconf = ProcessUtils.runCommand("getconf", "GNU_LIBC_VERSION"); @@ -792,46 +860,45 @@ private static final LibC getCurrent() { @SuppressWarnings("unused") private static class Response { - public final String raw; + private final @Nullable String message; private final List entries; - public final String message; - Response(String raw, final Class clazz) { - this(raw, e -> GSON.fromJson(e, clazz)); + static Response of(String raw, Class clazz) throws Exception { + return of(raw, e -> GSON.fromJson(e, clazz)); } - Response(String raw, Parser converter) { - this.raw = raw; + static Response of(String raw, Parser converter) throws Exception { + JsonObject root = GSON.fromJson(raw, JsonObject.class); + String message = root.has("message") + ? root.get("message").getAsString() + : null; - String message = null; - List entries = null; + JsonArray result = root.getAsJsonArray("result"); + if (result == null) + throw new Exception("Failed to parse response"); - try { - JsonObject root = GSON.fromJson(raw, JsonObject.class); - if (root.has("message")) - message = root.get("message").getAsString(); - - JsonArray result = root.getAsJsonArray("result"); - if (result != null) { - List tmp = new ArrayList<>(); - for (JsonElement entry : result) { - tmp.add(converter.apply(entry)); - } - entries = Collections.unmodifiableList(tmp); - } - } catch (JsonSyntaxException e) { - e.printStackTrace(); + List entries = new ArrayList<>(); + for (JsonElement entry : result) { + entries.add(converter.apply(entry)); } + return new Response<>(message, entries); + } + + private Response(@Nullable String message, List entries) throws JsonParseException { this.message = message; this.entries = entries; } + public String message() { + return this.message; + } + public List entries() { - return this.entries == null ? Collections.emptyList() : this.entries; + return this.entries; } - public static interface Parser { + public interface Parser { T apply(JsonElement e) throws JsonSyntaxException; } } @@ -976,13 +1043,13 @@ private > int compare(T e1, T e2) { *} */ public static class PackageInfo { - public final String filename = null; - public final String direct_download_uri = null; - public final String download_side_uri = null; - public final String signature_uri = null; - public final String checksum_uri = null; - public final String checksum = null; - public final String checksum_type = null; + public String filename; + public String direct_download_uri; + public String download_side_uri; + public String signature_uri; + public String checksum_uri; + public String checksum; + public String checksum_type; } // Just a helper class that wraps all the information we know about a file, saved in the cache for easy reference diff --git a/src/main/java/net/minecraftforge/java_provisioner/DiscoLocator.java b/src/main/java/net/minecraftforge/java_provisioner/DiscoLocator.java index cb20c03..81c280e 100644 --- a/src/main/java/net/minecraftforge/java_provisioner/DiscoLocator.java +++ b/src/main/java/net/minecraftforge/java_provisioner/DiscoLocator.java @@ -6,13 +6,13 @@ import java.io.File; import java.util.ArrayList; -import java.util.Collections; import java.util.List; +import java.util.Objects; -import net.minecraftforge.java_provisioner.Disco.Arch; import net.minecraftforge.java_provisioner.Disco.Distro; import net.minecraftforge.java_provisioner.api.IJavaInstall; -import net.minecraftforge.java_provisioner.util.OS; +import net.minecraftforge.java_provisioner.api.JavaLocatingFailedException; +import net.minecraftforge.java_provisioner.api.JavaProvisioningFailedException; /** * Locates java installs that have been downloaded from the disco API @@ -44,45 +44,50 @@ public DiscoLocator(File cache, boolean offline) { } @Override - public File find(int version) { - List results = findInternal(version); - return results.isEmpty() ? null : results.get(0).home(); + public File find(int version) throws JavaLocatingFailedException { + return findInternal(version).installs().get(0).home(); } @Override - public List findAll() { + public JavaLocatorResult findAll() throws JavaLocatingFailedException { return findInternal(-1); } - private List findInternal(int version) { + private JavaLocatorResult findInternal(int version) throws JavaLocatingFailedException { if (!cache.exists() || !cache.isDirectory()) - return Collections.emptyList(); + throw new JavaLocatingFailedException("Java Provisioner has not provisioned any Java installations"); List results = new ArrayList<>(); - for (File dir : cache.listFiles()) { + List errors = new ArrayList<>(); + + File[] listFiles; + try { + listFiles = Objects.requireNonNull(cache.listFiles()); + } catch (Exception e) { + throw new JavaLocatingFailedException("An unexpected error occured trying to query the Disco cache: " + cache, e); + } + + for (File dir : listFiles) { if (!dir.isDirectory()) continue; log("Disco Cache: \"" + dir.getAbsolutePath() + "\""); - IJavaInstall ret = fromPath(dir); - if (ret != null) { - if (version == -1) { - results.add(ret); - } else if (ret.majorVersion() != version) { - log(" Wrong version: Was " + ret.majorVersion() + " wanted " + version); - } else { - results.add(ret); - return results; - } + try { + results.add(fromPath(dir, version)); + } catch (Exception e) { + errors.add(e); } } - return results; + if (!results.isEmpty()) + return new JavaLocatorResult(results, errors); + + throw Throwing.provisioningFailed("Failed to find any Java installations from Disco cache", errors); } @Override - public IJavaInstall provision(int version) { + public IJavaInstall provision(int version, Distro distro) throws JavaProvisioningFailedException { log("Locators failed to find any suitable installs, attempting Disco download"); Disco disco = new Disco(cache, offline) { // TODO: [DISCO][Logging] Add a proper logging handler sometime @Override @@ -96,34 +101,31 @@ protected void error(String message) { } }; - List jdks = disco.getPackages(version, OS.CURRENT, Distro.TEMURIN, Arch.CURRENT); - if (jdks == null || jdks.isEmpty()) { - log("Failed to find any distros from Disco for " + version + " " + OS.CURRENT + " " + Arch.CURRENT + " " + Distro.TEMURIN); - - // Try any vendor - jdks = disco.getPackages(version, OS.CURRENT, null, Arch.CURRENT); - if (jdks == null || jdks.isEmpty()) { - log("Failed to find any distros from Disco for " + version + " " + OS.CURRENT + " " + Arch.CURRENT); - - // Try any Architecture and just hope for the best - jdks = disco.getPackages(version, OS.CURRENT, null, null); - if (jdks == null || jdks.isEmpty()) { - log("Failed to find any distros from Disco for " + version + " " + OS.CURRENT); - return null; - } + List jdks; + try { + jdks = disco.getPackages(version, Disco.CURRENT_OS, distro, Disco.CURRENT_ARCH); + } catch (Exception e) { + log(String.format("Failed to find any JDKs from Disco for: %s%d - %s %s", distro != null ? distro + " " : "", version, Disco.CURRENT_OS, Disco.CURRENT_ARCH)); + + // Try any Architecture and just hope for the best + try { + jdks = disco.getPackages(version, Disco.CURRENT_OS, distro, null); + } catch (Exception suppressed) { + log(String.format("Failed to find any JDKs from Disco for: %s%d - %s ANY", distro != null ? distro + " " : "", version, Disco.CURRENT_OS)); + e.addSuppressed(suppressed); + throw new JavaProvisioningFailedException("Failed to provision Disco download", e); } } - log("Found " + jdks.size() + " download canidates"); + log("Found " + jdks.size() + " download candidates"); Disco.Package pkg = jdks.get(0); log("Selected " + pkg.distribution + ": " + pkg.filename); - File java_home = disco.extract(pkg); - - if (java_home == null) - return null; - - IJavaInstall result = fromPath(java_home); - return result; + try { + File java_home = disco.extract(pkg); + return fromPath(java_home); + } catch (Exception e) { + throw new JavaProvisioningFailedException("Failed to provision Disco download: " + pkg.filename, e); + } } } diff --git a/src/main/java/net/minecraftforge/java_provisioner/DiscoMain.java b/src/main/java/net/minecraftforge/java_provisioner/DiscoMain.java index c459c07..1590ee0 100644 --- a/src/main/java/net/minecraftforge/java_provisioner/DiscoMain.java +++ b/src/main/java/net/minecraftforge/java_provisioner/DiscoMain.java @@ -12,7 +12,7 @@ import joptsimple.OptionSet; import joptsimple.OptionSpec; import joptsimple.util.EnumConverter; -import net.minecraftforge.java_provisioner.util.OS; +import net.minecraftforge.util.os.OS; import net.minecraftforge.java_provisioner.util.ProcessUtils; import net.minecraftforge.util.logging.Log; @@ -34,10 +34,10 @@ public static void main(String[] args) throws Exception { .withOptionalArg().ofType(Integer.class); OptionSpec archO = parser.acceptsAll(l("arch", "architecture"), "Architecture for use in Disco api") - .withRequiredArg().withValuesConvertedBy(converter(Disco.Arch.class)).defaultsTo(Disco.Arch.CURRENT); + .withRequiredArg().withValuesConvertedBy(converter(Disco.Arch.class)).defaultsTo(Disco.CURRENT_ARCH); OptionSpec osO = parser.acceptsAll(l("os", "operating-system"), "Operating System for use in Disco api") - .withRequiredArg().withValuesConvertedBy(converter(OS.class)).defaultsTo(OS.CURRENT); + .withRequiredArg().withValuesConvertedBy(converter(OS.class)).defaultsTo(Disco.CURRENT_OS); OptionSpec distroO = parser.acceptsAll(l("distro", "distribution"), "Distribution for use in Disco api") .withRequiredArg().withValuesConvertedBy(converter(Disco.Distro.class)).defaultsTo(Disco.Distro.TEMURIN); @@ -100,12 +100,17 @@ private static boolean downloadJdk( Log.info(" Cache: " + cache.getAbsolutePath()); Disco disco = new Disco(new File(cache, "jdks")); - List jdks = disco.getPackages(javaVersion, os, distro, arch); - Disco.Package pkg = null; - if (jdks == null || jdks.isEmpty()) { + List jdks; + try { + jdks = disco.getPackages(javaVersion, os, distro, arch); + if (jdks.isEmpty()) + throw new Exception("No download found"); + } catch (Exception e) { Log.error("Failed to find any download, try specifying a different java version or distro"); return false; - } else if (jdks.size() == 1 || auto) { + } + Disco.Package pkg = null; + if (jdks.size() == 1 || auto) { pkg = jdks.get(0); } else if (jdks.size() > 1) { for (int x = 0; x < jdks.size(); x++) { @@ -135,10 +140,16 @@ private static boolean downloadJdk( Log.info(); - File java_home = disco.extract(pkg); + File java_home; + try { + java_home = disco.extract(pkg); + } catch (Exception e) { + throw new RuntimeException("Failed to extract downloaded package", e); + } - if (java_home == null) - System.exit(1); + // No longer needed as we throw if no java home, but just in case: + //if (java_home == null) + // System.exit(1); ProcessUtils.Result result = ProcessUtils.testJdk(java_home); if (result.exitCode != 0) { diff --git a/src/main/java/net/minecraftforge/java_provisioner/GradleLocator.java b/src/main/java/net/minecraftforge/java_provisioner/GradleLocator.java index c29eed7..2d41495 100644 --- a/src/main/java/net/minecraftforge/java_provisioner/GradleLocator.java +++ b/src/main/java/net/minecraftforge/java_provisioner/GradleLocator.java @@ -7,12 +7,14 @@ import java.io.File; import java.io.IOException; import java.util.ArrayList; -import java.util.Collection; import java.util.List; +import java.util.Objects; import java.util.regex.Pattern; import net.minecraftforge.java_provisioner.api.IJavaInstall; -import net.minecraftforge.java_provisioner.util.OS; +import net.minecraftforge.java_provisioner.api.IJavaLocator; +import net.minecraftforge.java_provisioner.api.JavaLocatingFailedException; +import net.minecraftforge.util.os.OS; /* * Attempts to find the java install using various tools that Gradle uses @@ -36,99 +38,142 @@ public class GradleLocator extends JavaHomeLocator { private static final Pattern GRADLE_ENV = Pattern.compile("JDK\\d\\d*"); @Override - public File find(int version) { - List results = new ArrayList<>(); - fromGradleEnv(results, version); - if (!results.isEmpty()) - return results.get(0).home(); + public File find(int version) throws JavaLocatingFailedException { + List dummy = new ArrayList<>(); // we aren't actually going to use this, it's just to avoid creating more lists + List errors = new ArrayList<>(); + try { + return fromGradleEnv(dummy, version).home(); + } catch (Exception e) { + errors.add(e); + } - fromPaths(results, version); - if (!results.isEmpty()) - return results.get(0).home(); + try { + return fromPaths(dummy, version).home(); + } catch (Exception e) { + errors.add(e); + } - IJavaInstall result = fromEnv("JDK" + version); - if (result != null) { - if (result.majorVersion() != version) - log(" Wrong version: Was " + result.majorVersion() + " wanted " + version); - else - return result.home(); + try { + return fromEnv("JDK" + version, version).home(); + } catch (Exception e) { + errors.add(e); } - fromGradleHome(results, version); - if (!results.isEmpty()) - return results.get(0).home(); + try { + return fromGradleHome(dummy, version).home(); + } catch (Exception e) { + errors.add(e); + } - return null; + throw Throwing.provisioningFailed("Failed to find any Java installations from Gradle paths and properties", errors); } @Override - public List findAll() { + public IJavaLocator.FindResult findAll() throws JavaLocatingFailedException { List ret = new ArrayList<>(); - fromGradleEnv(ret, -1); - fromPaths(ret, -1); + List errors = new ArrayList<>(); + + try { + fromGradleEnv(ret, -1); + } catch (Exception e) { + errors.add(e); + } + + try { + fromPaths(ret, -1); + } catch (Exception e) { + errors.add(e); + } for (String key : System.getenv().keySet()) { if (GRADLE_ENV.matcher(key).matches()) { - IJavaInstall tmp = fromEnv(key); - if (tmp != null) - ret.add(tmp); + try { + ret.add(fromEnv(key)); + } catch (Exception e) { + errors.add(e); + } } } - fromGradleHome(ret, -1); + try { + fromGradleHome(ret, -1); + } catch (Exception e) { + errors.add(e); + } - return ret; - } + if (!ret.isEmpty()) { + return new JavaLocatorResult(ret, errors); + } - @Override - public List logOutput() { - return this.searched; + throw Throwing.provisioningFailed("Failed to find any Java installations from Gradle paths and properties", errors); } - private void fromGradleEnv(Collection list, int version) { + private IJavaInstall fromGradleEnv(List results, int version) throws Exception { String prop = System.getProperty(GRADLE_FROMENV); log("Property: " + GRADLE_FROMENV + " = " + prop); if (prop == null) - return; + throw new Exception("Could not find " + GRADLE_FROMENV + " in system properties"); String[] envs = prop.split(","); + List found = new ArrayList<>(envs.length); + List errors = new ArrayList<>(envs.length); for (String env : envs) { - IJavaInstall ret = fromEnv(env); - if (ret == null) + IJavaInstall ret; + try { + ret = fromEnv(env); + } catch (Exception e) { + errors.add(e); continue; + } - if (version == -1) { - list.add(ret); - } else if (ret.majorVersion() != version) { + if (version != -1 && ret.majorVersion() != version) { + errors.add(Throwing.incorrectInstallFromEnv(env, ret, version)); log(" Wrong version: Was " + ret.majorVersion() + " wanted " + version); } else { - list.add(ret); - return; + found.add(ret); } } + + results.addAll(found); + + if (!found.isEmpty()) + return found.get(0); + + throw Throwing.withSuppressed(new Exception("Could not find any of " + prop + " in " + GRADLE_FROMENV), errors); } - private void fromPaths(Collection list, int version) { + private IJavaInstall fromPaths(List results, int version) throws Exception { String prop = System.getProperty(GRADLE_PATHS); log("Property: " + GRADLE_PATHS + " = " + prop); if (prop == null) - return; + throw new Exception("Could not find " + GRADLE_PATHS + " in system properties"); String[] envs = prop.split(","); + List found = new ArrayList<>(envs.length); + List errors = new ArrayList<>(envs.length); for (String path : envs) { - IJavaInstall ret = fromPath(path); - if (ret == null) + IJavaInstall ret; + try { + ret = fromPath(path); + } catch (Exception e) { + errors.add(e); continue; + } - if (version == -1) { - list.add(ret); - } else if (ret.majorVersion() != version) { + if (version != -1 && ret.majorVersion() != version) { + errors.add(Throwing.incorrectInstallFromPath(path, ret, version)); log(" Wrong version: Was " + ret.majorVersion() + " wanted " + version); } else { - list.add(ret); - return; + found.add(ret); } } + + results.addAll(found); + + if (!found.isEmpty()) + return found.get(0); + + throw Throwing.withSuppressed(new Exception("Could not find any of " + prop + " in " + GRADLE_PATHS), errors); } private File getGradleHome() { @@ -141,53 +186,86 @@ private File getGradleHome() { File ret = new File(home); try { - ret = ret.getCanonicalFile(); + return ret.getCanonicalFile(); } catch (IOException e) { - return ret; + return ret.getAbsoluteFile(); } - - return ret; } - private void fromGradleHome(Collection list, int version) { + private IJavaInstall fromGradleHome(List results, int version) throws JavaLocatingFailedException, Exception { File gradleHome = getGradleHome(); if (!gradleHome.exists() || !gradleHome.isDirectory()) { log("Gradle home: \"" + gradleHome.getAbsolutePath() + "\" Does not exist"); - return; + throw new JavaLocatingFailedException("Gradle home does not exist: " + gradleHome.getAbsolutePath()); } File jdks = new File(gradleHome, "jdks"); if (!jdks.exists() || !jdks.isDirectory()) { log("Gradle Home JDKs: \"" + jdks.getAbsolutePath() + "\" Does not exist"); - return; + throw new JavaLocatingFailedException("Gradle home does not contain jdks folder: " + jdks.getAbsolutePath()); + } + + File[] listFiles; + try { + listFiles = Objects.requireNonNull(jdks.listFiles()); + } catch (Exception e) { + log("Gradle Home JDKs: \"" + jdks.getAbsolutePath() + "\" Cannot be queried"); + throw new JavaLocatingFailedException("Gradle home jdks folder cannot be queried: " + jdks.getAbsolutePath(), e); } - for (File dir : jdks.listFiles()) { + List found = new ArrayList<>(listFiles.length); + List errors = new ArrayList<>(listFiles.length); + for (File dir : listFiles) { if (!dir.isDirectory()) continue; - List markers = findMarkers(dir); + List markers; + try { + markers = findMarkers(dir); + } catch (IOException e) { + errors.add(e); + continue; + } + for (File marked : markers) { - if (OS.CURRENT == OS.OSX) - marked = findMacHome(dir); + if (Disco.CURRENT_OS == OS.MACOS) { + try { + marked = findMacHome(dir); + } catch (IOException e) { + errors.add(e); + continue; + } + } log("Gradle Home JDK: \"" + marked.getAbsolutePath() + "\""); - IJavaInstall ret = fromPath(marked); + IJavaInstall ret; + try { + ret = fromPath(marked); + } catch (Exception e) { + errors.add(e); + continue; + } + if (ret != null) { - if (version == -1) { - list.add(ret); - } else if (ret.majorVersion() != version) { + if (version != -1 && ret.majorVersion() != version) { log(" Wrong version: Was " + ret.majorVersion() + " wanted " + version); + errors.add(Throwing.incorrectInstallFromPath(marked.getAbsolutePath(), ret, version)); } else { - list.add(ret); - return; + found.add(ret); } } } } + + results.addAll(found); + + if (!found.isEmpty()) + return found.get(0); + + throw Throwing.withSuppressed(new Exception("Could not find any Java installations in " + jdks.getAbsolutePath()), errors); } - private List findMarkers(File root) { + private List findMarkers(File root) throws IOException { // Prior to Gradle 8.8 jdks did not have their root directory trimmed // It also could cause multiple archives to be extracted to the same folder. // So lets just find anything that it marked as 'properly installed' and hope for the best. @@ -196,7 +274,14 @@ private List findMarkers(File root) { new File(root, LEGACY_MARKER_FILE).exists()) ret.add(root); - for (File child : root.listFiles()) { + File[] listFiles; + try { + listFiles = Objects.requireNonNull(root.listFiles()); + } catch (Exception e) { + throw new IOException("An unexpected error occured trying to find markers in: " + root, e); + } + + for (File child : listFiles) { if (!child.isDirectory()) continue; if (new File(child, MARKER_FILE).exists() || @@ -208,12 +293,19 @@ private List findMarkers(File root) { } // Macs are weird and can have their files packaged into a content folder - private File findMacHome(File root) { + private File findMacHome(File root) throws IOException { File tmp = new File(root, MAC_JAVA_HOME_FOLDER); if (tmp.exists()) return tmp; - for (File child : root.listFiles()) { + File[] listFiles; + try { + listFiles = Objects.requireNonNull(root.listFiles()); + } catch (Exception e) { + throw new IOException("An unexpected error occured trying to find the macOS home of: " + root, e); + } + + for (File child : listFiles) { if (!child.isDirectory()) continue; diff --git a/src/main/java/net/minecraftforge/java_provisioner/JavaDirectoryLocator.java b/src/main/java/net/minecraftforge/java_provisioner/JavaDirectoryLocator.java index 19ba7d7..3ae826e 100644 --- a/src/main/java/net/minecraftforge/java_provisioner/JavaDirectoryLocator.java +++ b/src/main/java/net/minecraftforge/java_provisioner/JavaDirectoryLocator.java @@ -8,10 +8,12 @@ import java.util.ArrayList; import java.util.Collection; import java.util.List; +import java.util.stream.Collectors; -import net.minecraftforge.java_provisioner.Disco.Arch; import net.minecraftforge.java_provisioner.api.IJavaInstall; -import net.minecraftforge.java_provisioner.util.OS; +import net.minecraftforge.java_provisioner.api.IJavaLocator; +import net.minecraftforge.java_provisioner.api.JavaLocatingFailedException; +import net.minecraftforge.util.os.OS; /* * Attempts to find the java install from specific folders. @@ -22,14 +24,13 @@ public class JavaDirectoryLocator extends JavaHomeLocator { private static Collection guesses() { Collection ret = new ArrayList<>(); - if (OS.CURRENT == OS.WINDOWS) { // Windows - File[] roots = File.listRoots(); - for(int i = 0; i < roots.length ; i++) { - ret.add(new File(roots[i], "Program Files\\Java")); - if (Arch.CURRENT.is64Bit()) - ret.add(new File(roots[i], "Program Files (x86)\\Java")); + if (Disco.CURRENT_OS == OS.WINDOWS) { // Windows + for (File root : File.listRoots()) { + ret.add(new File(root, "Program Files\\Java")); + if (Disco.CURRENT_ARCH.is64Bit()) + ret.add(new File(root, "Program Files (x86)\\Java")); } - } else if (OS.CURRENT == OS.OSX) { // Mac + } else if (Disco.CURRENT_OS == OS.MACOS) { // Mac ret.add(new File("/Library/Java/JavaVirtualMachines")); } else { // Linux ret.add(new File("/usr/java")); @@ -58,7 +59,7 @@ public JavaDirectoryLocator(Collection paths) { private Collection expand(Collection files) { Collection ret = new ArrayList<>(); - String exe = "bin/java" + OS.CURRENT.exe(); + String exe = "bin/java" + Disco.CURRENT_OS.exe(); for (File file : files) { if (new File(file, exe).exists()) ret.add(file); @@ -76,23 +77,36 @@ private Collection expand(Collection files) { } @Override - public File find(int version) { + public File find(int version) throws JavaLocatingFailedException { + List errors = new ArrayList<>(); + for (File path : paths) { - IJavaInstall result = fromPath(path); - if (result != null && result.majorVersion() == version) - return result.home(); + try { + return fromPath(path, version).home(); + } catch (Exception e) { + errors.add(e); + } } - return null; + + throw Throwing.provisioningFailed(String.format("Failed to find any Java installations from paths: %s", this.paths.stream().map(File::getPath).collect(Collectors.joining(", "))), errors); } @Override - public List findAll() { + public IJavaLocator.FindResult findAll() throws JavaLocatingFailedException { List ret = new ArrayList<>(); + List errors = new ArrayList<>(); + for (File path : paths) { - IJavaInstall result = fromPath(path); - if (result != null) - ret.add(result); + try { + ret.add(fromPath(path)); + } catch (Exception e) { + errors.add(e); + } } - return ret; + + if (!ret.isEmpty()) + return new JavaLocatorResult(ret, errors); + + throw Throwing.provisioningFailed(String.format("Failed to find any Java installations from paths: %s", this.paths.stream().map(File::getPath).collect(Collectors.joining(", "))), errors); } } diff --git a/src/main/java/net/minecraftforge/java_provisioner/JavaHomeLocator.java b/src/main/java/net/minecraftforge/java_provisioner/JavaHomeLocator.java index b6934a1..75fbc26 100644 --- a/src/main/java/net/minecraftforge/java_provisioner/JavaHomeLocator.java +++ b/src/main/java/net/minecraftforge/java_provisioner/JavaHomeLocator.java @@ -10,7 +10,7 @@ import net.minecraftforge.java_provisioner.api.IJavaInstall; import net.minecraftforge.java_provisioner.api.IJavaLocator; -import net.minecraftforge.java_provisioner.util.OS; +import net.minecraftforge.java_provisioner.api.JavaLocatingFailedException; import net.minecraftforge.java_provisioner.util.ProcessUtils; /* @@ -20,8 +20,9 @@ * Github Actions: * The default images ship Java 8, 11, 17, and 21. In the format JAVA_HOME_{version}_{x64|arm64} * https://github.com/actions/runner-images/blob/main/images/ubuntu/Ubuntu2404-Readme.md - * https://github.com/actions/runner-images/blob/main/images/windows/Windows2022-Readme.md - * https://github.com/actions/runner-images/blob/main/images/macos/macos-14-arm64-Readme.md + * https://github.com/actions/runner-images/blob/main/images/windows/Windows2025-Readme.md + * https://github.com/actions/runner-images/blob/main/images/macos/macos-15-Readme.md + * https://github.com/actions/runner-images/blob/main/images/macos/macos-26-arm64-Readme.md * * Lex's Personal Setup: * JAVA_HOME_{version} @@ -32,33 +33,54 @@ public class JavaHomeLocator implements IJavaLocator { protected List searched = new ArrayList<>(); @Override - public File find(int version) { - IJavaInstall result = fromEnv("JAVA_HOME_" + version + "_X64"); - if (result == null) - result = fromEnv("JAVA_HOME_" + version + "_arm64"); - if (result == null) - result = fromEnv("JAVA_HOME_" + version); - if (result == null) { - result = fromEnv("JAVA_HOME"); - if (result != null && result.majorVersion() != version) { - log(" Wrong version: Was " + result.majorVersion() + " wanted " + version); - result = null; - } + public File find(int version) throws JavaLocatingFailedException { + List errors = new ArrayList<>(); + + // For GitHub actions + String suffix = Disco.CURRENT_ARCH.is64Bit() + ? (Disco.CURRENT_ARCH.isArm() ? "_arm64" : "_X64") + : ""; + try { + return fromEnv("JAVA_HOME_" + version + suffix).home(); + } catch (Exception e) { + errors.add(e); + } + + // For Lex's Personal Setup + try { + return fromEnv("JAVA_HOME_" + version).home(); + } catch (Exception e) { + errors.add(e); + } + + // Standard Java Home + try { + return fromEnv("JAVA_HOME", version).home(); + } catch (Exception e) { + errors.add(e); } - return result == null ? null : result.home(); + + throw Throwing.provisioningFailed("Could not find a Java installation using JAVA_HOME(_" + version + suffix + ')', errors); } @Override - public List findAll() { + public IJavaLocator.FindResult findAll() throws JavaLocatingFailedException { List ret = new ArrayList<>(); + List errors = new ArrayList<>(); for (String key : System.getenv().keySet()) { if (key.startsWith("JAVA_HOME")) { - IJavaInstall tmp = fromEnv(key); - if (tmp != null) - ret.add(tmp); + try { + ret.add(fromEnv(key)); + } catch (Exception e) { + errors.add(e); + } } } - return ret; + + if (!ret.isEmpty()) + return new JavaLocatorResult(ret, errors); + + throw Throwing.provisioningFailed("Failed to find any Java installations from JAVA_HOME environment variables", errors); } @Override @@ -70,11 +92,21 @@ protected void log(String line) { searched.add(line); } - protected IJavaInstall fromEnv(String name) { + IJavaInstall fromEnv(String name, int version) throws Exception { + IJavaInstall result = fromEnv(name); + if (result.majorVersion() != version) { + log(" Wrong version: Was " + result.majorVersion() + " wanted " + version); + throw Throwing.incorrectInstallFromEnv("JAVA_HOME", result, version); + } + + return result; + } + + IJavaInstall fromEnv(String name) throws Exception, JavaProbeFailedException { String env = System.getenv(name); if (env == null) { log("Environment: \"" + name + "\" Empty"); - return null; + throw Throwing.envEmpty(name); } log("Environment: \"" + name + "\""); @@ -82,16 +114,16 @@ protected IJavaInstall fromEnv(String name) { return fromPath(env); } - protected IJavaInstall fromPath(String path) { + IJavaInstall fromPath(String path) throws Exception, JavaProbeFailedException { return fromPath(new File(path)); } - protected IJavaInstall fromPath(File path) { - File exe = new File(path, "bin/java" + OS.CURRENT.exe()); + IJavaInstall fromPath(File path) throws Exception, JavaProbeFailedException { + File exe = new File(path, "bin/java" + Disco.CURRENT_OS.exe()); if (!exe.exists()) { log(" Missing Executable"); - return null; + throw new Exception("Could not find java executable: " + exe); } ProcessUtils.ProbeResult result = ProcessUtils.testJdk(path); @@ -99,8 +131,23 @@ protected IJavaInstall fromPath(File path) { log(" Exit code: " + result.exitCode); for (String line : result.lines) searched.add(" " + line); + throw new JavaProbeFailedException(result); } return result.meta; } + + IJavaInstall fromPath(String path, int version) throws Exception { + return fromPath(new File(path), version); + } + + IJavaInstall fromPath(File path, int version) throws Exception { + IJavaInstall result = fromPath(path); + if (result.majorVersion() != version) { + log(" Wrong version: Was " + result.majorVersion() + " wanted " + version); + throw Throwing.incorrectInstallFromPath(path.getAbsolutePath(), result, version); + } + + return result; + } } diff --git a/src/main/java/net/minecraftforge/java_provisioner/JavaLocatorResult.java b/src/main/java/net/minecraftforge/java_provisioner/JavaLocatorResult.java new file mode 100644 index 0000000..a1ef9ab --- /dev/null +++ b/src/main/java/net/minecraftforge/java_provisioner/JavaLocatorResult.java @@ -0,0 +1,24 @@ +package net.minecraftforge.java_provisioner; + +import net.minecraftforge.java_provisioner.api.IJavaInstall; +import net.minecraftforge.java_provisioner.api.IJavaLocator; + +import java.util.List; + +final class JavaLocatorResult implements IJavaLocator.FindResult { + private final List installs; + private final List errors; + + public JavaLocatorResult(List installs, List errors) { + this.installs = installs; + this.errors = errors; + } + + public List installs() { + return installs; + } + + public List errors() { + return errors; + } +} diff --git a/src/main/java/net/minecraftforge/java_provisioner/JavaProbeFailedException.java b/src/main/java/net/minecraftforge/java_provisioner/JavaProbeFailedException.java new file mode 100644 index 0000000..9e8cc78 --- /dev/null +++ b/src/main/java/net/minecraftforge/java_provisioner/JavaProbeFailedException.java @@ -0,0 +1,25 @@ +package net.minecraftforge.java_provisioner; + +import net.minecraftforge.java_provisioner.util.ProcessUtils; + +import java.util.List; + +final class JavaProbeFailedException extends Exception { + private final int exitCode; + private final List lines; + + JavaProbeFailedException(ProcessUtils.ProbeResult result) { + super("Probe failed with exit code " + result.exitCode + "\n" + String.join("\n", result.lines)); + + this.exitCode = result.exitCode; + this.lines = result.lines; + } + + int exitCode() { + return this.exitCode; + } + + List lines() { + return this.lines; + } +} diff --git a/src/main/java/net/minecraftforge/java_provisioner/Main.java b/src/main/java/net/minecraftforge/java_provisioner/Main.java index 1c4ac4d..d449088 100644 --- a/src/main/java/net/minecraftforge/java_provisioner/Main.java +++ b/src/main/java/net/minecraftforge/java_provisioner/Main.java @@ -21,7 +21,7 @@ import net.minecraftforge.java_provisioner.Disco.Distro; import net.minecraftforge.java_provisioner.api.IJavaInstall; import net.minecraftforge.java_provisioner.api.IJavaLocator; -import net.minecraftforge.java_provisioner.util.OS; +import net.minecraftforge.util.os.OS; import net.minecraftforge.util.logging.Log; public class Main { @@ -71,7 +71,7 @@ public static void main(String[] args) throws Exception { Disco tmp = new Disco(cache); int version = options.has(versionO) ? options.valueOf(versionO) : 22; for (Distro dist : new Distro[] { Distro.TEMURIN, Distro.AOJ, Distro.ORACLE, Distro.ZULU, Distro.GRAALVM, Distro.GRAALVM_COMMUNITY}) { - List jdks = tmp.getPackages(version, OS.CURRENT, dist, Arch.CURRENT); + List jdks = tmp.getPackages(version, Disco.CURRENT_OS, dist, Disco.CURRENT_ARCH); int seen = 0; for (Disco.Package pkg : jdks) { if (seen++ < 3) @@ -104,16 +104,20 @@ private static boolean hasArgument(String[] args, String arg) { private static void findSpecificVersion(List locators, DiscoLocator disco, int version) { File result = null; for (IJavaLocator locator : locators) { - result = locator.find(version); - if (result != null) - break; + try { + result = locator.find(version); + } catch (Exception e) { + continue; + } } // Could not find it with a locator, lets try downloading it. if (result == null) { - IJavaInstall probe = disco.provision(version); - if (probe != null) - result = probe.home(); + try { + result = disco.provision(version).home(); + } catch (Exception e) { + e.printStackTrace(); + } } if (result != null && result.exists()) { @@ -137,8 +141,12 @@ private static void listAllJavaInstalls(List locators) { List installs = new ArrayList<>(); for (IJavaLocator locator : locators) { - List found = locator.findAll(); - installs.addAll(found); + try { + List found = locator.findAll().installs(); + installs.addAll(found); + } catch (Exception e) { + continue; + } } // Remove duplicates diff --git a/src/main/java/net/minecraftforge/java_provisioner/Throwing.java b/src/main/java/net/minecraftforge/java_provisioner/Throwing.java new file mode 100644 index 0000000..bf6183d --- /dev/null +++ b/src/main/java/net/minecraftforge/java_provisioner/Throwing.java @@ -0,0 +1,47 @@ +package net.minecraftforge.java_provisioner; + +import net.minecraftforge.java_provisioner.api.IJavaInstall; +import net.minecraftforge.java_provisioner.api.JavaLocatingFailedException; + +import java.util.List; + +final class Throwing { + private Throwing() { } + + public static T withSuppressed(T exception, List suppressed) { + suppressed.forEach(exception::addSuppressed); + return exception; + } + + public static JavaLocatingFailedException provisioningFailed(String message, List errors) { + return withSuppressed(new JavaLocatingFailedException(message), errors); + } + + public static Exception incorrectInstallFromEnv(String env, IJavaInstall requested, int found) { + return incorrectInstallFromEnv(env, requested.majorVersion(), found); + } + + public static Exception incorrectInstallFromEnv(String env, int requested, int found) { + return new Exception(String.format("Java installation or executable from environment variable '%s' does not match the requested version. Was %d wanted %d", env, requested, found)); + } + + public static Exception installNotFoundFromEnv(String env) { + return new Exception(String.format("Java installation or executable from environment variable '%s' not found", env)); + } + + public static Exception envEmpty(String env) { + return new Exception(String.format("Cannot find Java installation or executable in environment variable '%s' as it is empty", env)); + } + + public static Exception incorrectInstallFromPath(String path, IJavaInstall requested, int found) { + return incorrectInstallFromPath(path, requested.majorVersion(), found); + } + + public static Exception incorrectInstallFromPath(String path, int requested, int found) { + return new Exception(String.format("Java installation or executable does not match the requested version. Was %d wanted %d in path: %s", requested, found, path)); + } + + public static Exception installNotFoundFromPath(String path) { + return new Exception(String.format("Java installation or executable not found in path: %s", path)); + } +} diff --git a/src/main/java/net/minecraftforge/java_provisioner/api/IJavaLocator.java b/src/main/java/net/minecraftforge/java_provisioner/api/IJavaLocator.java index 01ba701..4d34619 100644 --- a/src/main/java/net/minecraftforge/java_provisioner/api/IJavaLocator.java +++ b/src/main/java/net/minecraftforge/java_provisioner/api/IJavaLocator.java @@ -8,6 +8,7 @@ import java.util.Arrays; import java.util.List; +import net.minecraftforge.java_provisioner.Disco; import net.minecraftforge.java_provisioner.DiscoLocator; import net.minecraftforge.java_provisioner.GradleLocator; import net.minecraftforge.java_provisioner.JavaDirectoryLocator; @@ -18,14 +19,20 @@ public interface IJavaLocator { * Locates the first available java install for the specified major java version. * @return null is no install found, or a File pointing at the JAVA_HOME directory */ - File find(int version); + File find(int version) throws JavaLocatingFailedException; /** * Locates all possible java installations that this provider knows about. * This can be used as a bulk version of {@link #find(int)} * @return A list containing all java installs, possibly empty but not null. */ - List findAll(); + FindResult findAll() throws JavaLocatingFailedException; + + interface FindResult { + List installs(); + + List errors(); + } /** * Returns all loged messages this provider has output, honestly this is just a hack @@ -40,8 +47,12 @@ public interface IJavaLocator { * * @return Null if this failed to provision a JDK */ - default IJavaInstall provision(int version) { - return null; + default IJavaInstall provision(int version) throws JavaProvisioningFailedException { + return provision(version, null); + } + + default IJavaInstall provision(int version, Disco.Distro distro) throws JavaProvisioningFailedException { + throw new JavaProvisioningFailedException("Provisioning is not implemented by this locator"); } /** diff --git a/src/main/java/net/minecraftforge/java_provisioner/api/JavaLocatingFailedException.java b/src/main/java/net/minecraftforge/java_provisioner/api/JavaLocatingFailedException.java new file mode 100644 index 0000000..80d9941 --- /dev/null +++ b/src/main/java/net/minecraftforge/java_provisioner/api/JavaLocatingFailedException.java @@ -0,0 +1,15 @@ +package net.minecraftforge.java_provisioner.api; + +public class JavaLocatingFailedException extends Exception { + public JavaLocatingFailedException(String message) { + super(message); + } + + public JavaLocatingFailedException(String message, Throwable cause) { + super(message, cause); + } + + public JavaLocatingFailedException(Throwable cause) { + super(cause); + } +} diff --git a/src/main/java/net/minecraftforge/java_provisioner/api/JavaProvisioningFailedException.java b/src/main/java/net/minecraftforge/java_provisioner/api/JavaProvisioningFailedException.java new file mode 100644 index 0000000..c7b5c0f --- /dev/null +++ b/src/main/java/net/minecraftforge/java_provisioner/api/JavaProvisioningFailedException.java @@ -0,0 +1,11 @@ +package net.minecraftforge.java_provisioner.api; + +public class JavaProvisioningFailedException extends Exception { + public JavaProvisioningFailedException(String message) { + super(message); + } + + public JavaProvisioningFailedException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/src/main/java/net/minecraftforge/java_provisioner/util/JavaInstall.java b/src/main/java/net/minecraftforge/java_provisioner/util/JavaInstall.java index 3e00c65..1b414b5 100644 --- a/src/main/java/net/minecraftforge/java_provisioner/util/JavaInstall.java +++ b/src/main/java/net/minecraftforge/java_provisioner/util/JavaInstall.java @@ -6,6 +6,7 @@ import java.io.File; +import net.minecraftforge.java_provisioner.Disco; import net.minecraftforge.java_provisioner.JavaVersion; import net.minecraftforge.java_provisioner.api.IJavaInstall; @@ -22,9 +23,9 @@ class JavaInstall implements IJavaInstall { this.version = version; this.vendor = vendor; this.majorVersion = version == null ? -1 : JavaVersion.parse(version).major(); - File tmp = new File(home, "bin/java" + OS.CURRENT.exe()); + File tmp = new File(home, "bin/java" + Disco.CURRENT_OS.exe()); this.java = tmp.exists() ? tmp : null; - tmp = new File(home, "bin/javac" + OS.CURRENT.exe()); + tmp = new File(home, "bin/javac" + Disco.CURRENT_OS.exe()); this.javac = tmp.exists() ? tmp : null; } diff --git a/src/main/java/net/minecraftforge/java_provisioner/util/OS.java b/src/main/java/net/minecraftforge/java_provisioner/util/OS.java deleted file mode 100644 index 896fbaa..0000000 --- a/src/main/java/net/minecraftforge/java_provisioner/util/OS.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright (c) Forge Development LLC and contributors - * SPDX-License-Identifier: LGPL-2.1-only - */ -package net.minecraftforge.java_provisioner.util; - -import net.minecraftforge.util.logging.Log; - -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Paths; -import java.util.Locale; - -public enum OS { - AIX ("aix", "aix"), - ALPINE ("apline_linux", "alpine"), - LINUX ("linux", "linux", "unix"), - MUSL ("linux_musl", "musl"), - OSX ("macos", "mac", "osx", "darwin"), - QNX ("qnx", "qnx"), - SOLARIS("solaris", "sunos"), - WINDOWS("windows", "win"), - UNKNOWN("unknown"); - - private static final OS[] $values = values(); - public static final OS CURRENT = getCurrent(); - - private final String key; - private final String[] names; - - private OS(String key, String... names) { - this.key = key; - this.names = names; - } - - public String key() { - return this.key; - } - - public static OS byKey(String key) { - for (OS value : $values) { - if (value.key.equals(key)) - return value; - } - return null; - } - - public String exe() { - return this == OS.WINDOWS ? ".exe" : ""; - } - - private static OS getCurrent() { - String prop = System.getProperty("os.name").toLowerCase(Locale.ENGLISH); - for (OS os : $values) { - for (String key : os.names) { - if (prop.contains(key)) { - if (os == LINUX) { - try { - for (String line : Files.readAllLines(Paths.get("/etc/os-release"), StandardCharsets.UTF_8)) { - line = line.toLowerCase(Locale.ENGLISH); - if (line.startsWith("name=") && line.contains("alpine")) { - return ALPINE; - } - } - } catch (IOException e) { - Log.error("Failed to read /etc/os-release: " + e.getMessage()); - } - } - return os; - } - } - } - return UNKNOWN; - } -} \ No newline at end of file diff --git a/src/main/java/net/minecraftforge/java_provisioner/util/ProcessUtils.java b/src/main/java/net/minecraftforge/java_provisioner/util/ProcessUtils.java index a9d641f..fc6d431 100644 --- a/src/main/java/net/minecraftforge/java_provisioner/util/ProcessUtils.java +++ b/src/main/java/net/minecraftforge/java_provisioner/util/ProcessUtils.java @@ -22,6 +22,7 @@ import java.util.Locale; import java.util.Map; +import net.minecraftforge.java_provisioner.Disco; import net.minecraftforge.java_provisioner.api.IJavaInstall; public class ProcessUtils { @@ -149,7 +150,7 @@ protected static Path getPathFromResource(String resource, ClassLoader cl) { public static ProbeResult testJdk(File java_home) { File probe = getPathFromResource(JAVA_PROBE).toFile(); String classpath = probe.getAbsolutePath(); - File exe = new File(java_home, "bin/java" + OS.CURRENT.exe()); + File exe = new File(java_home, "bin/java" + Disco.CURRENT_OS.exe()); if (!exe.exists()) return new ProbeResult(java_home, -1, Collections.singletonList("missing java executable"));