feat: speed up openjfx dependencies checking

SHA-1 of dependencies are defined in openjfx-dependencies.json
to avoid network requests during startup.

openjfx-dependencies.json can be generated using
tools/generate-openjfx-dependencies.sh
This commit is contained in:
Haowei Wen
2021-06-14 04:28:59 +08:00
committed by Yuhui Huang
parent 9776aafab6
commit 88e23cd0d2
4 changed files with 237 additions and 140 deletions

1
.gitignore vendored
View File

@@ -11,7 +11,6 @@ hs_err_pid*
*.log *.log
.mine* .mine*
NVIDIA NVIDIA
*.json
# gradle build # gradle build
/build/ /build/

View File

@@ -41,59 +41,104 @@
*/ */
package org.jackhuang.hmcl.util; package org.jackhuang.hmcl.util;
import org.jackhuang.hmcl.util.io.ChecksumMismatchException;
import org.jackhuang.hmcl.util.io.NetworkUtils;
import org.jackhuang.hmcl.util.platform.Architecture;
import org.jackhuang.hmcl.util.platform.OperatingSystem;
import javax.swing.*;
import java.awt.*;
import java.io.IOException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.List;
import java.util.*;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.stream.Collectors;
import static java.lang.Class.forName; import static java.lang.Class.forName;
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.stream.Collectors.toSet;
import static org.jackhuang.hmcl.Metadata.HMCL_DIRECTORY; import static org.jackhuang.hmcl.Metadata.HMCL_DIRECTORY;
import static org.jackhuang.hmcl.util.Logging.LOG; import static org.jackhuang.hmcl.util.Logging.LOG;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n; import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
import static org.jackhuang.hmcl.util.platform.JavaVersion.CURRENT_JAVA; import static org.jackhuang.hmcl.util.platform.JavaVersion.CURRENT_JAVA;
/** import java.awt.Dialog;
* Utility for patching self when missing dependencies. import java.awt.GridBagConstraints;
* Copy from https://github.com/Col-E/Recaf/blob/master/src/main/java/me/coley/recaf/util/self/SelfDependencyPatcher.java import java.awt.GridBagLayout;
* import java.awt.Insets;
* @author Matt import java.io.IOException;
*/ import java.io.InputStream;
import java.io.UncheckedIOException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.SwingUtilities;
import javax.swing.WindowConstants;
import org.jackhuang.hmcl.util.io.ChecksumMismatchException;
import org.jackhuang.hmcl.util.io.IOUtils;
import org.jackhuang.hmcl.util.platform.Architecture;
import org.jackhuang.hmcl.util.platform.OperatingSystem;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
// From: https://github.com/Col-E/Recaf/blob/7378b397cee664ae81b7963b0355ef8ff013c3a7/src/main/java/me/coley/recaf/util/self/SelfDependencyPatcher.java
public final class SelfDependencyPatcher { public final class SelfDependencyPatcher {
private SelfDependencyPatcher() { private SelfDependencyPatcher() {
} }
static class DependencyDescriptor {
private static final String REPOSITORY_URL = "https://maven.aliyun.com/repository/central/";
private static final Path DEPENDENCIES_DIR_PATH = HMCL_DIRECTORY.resolve("dependencies"); private static final Path DEPENDENCIES_DIR_PATH = HMCL_DIRECTORY.resolve("dependencies");
private static final String DEFAULT_JFX_VERSION = "16";
private static final Map<String, String> JFX_DEPENDENCIES = new HashMap<>();
static { private static String currentArchClassifier() {
addJfxDependency("base"); switch (OperatingSystem.CURRENT_OS) {
addJfxDependency("controls"); case LINUX:
addJfxDependency("fxml"); return "linux";
addJfxDependency("graphics"); case OSX:
addJfxDependency("media"); return "mac";
// Fix #874: Remove the dependency on javafx.swing default:
// addJfxDependency("swing"); return "win";
addJfxDependency("web"); }
} }
private static void addJfxDependency(String name) { public String module;
JFX_DEPENDENCIES.put("javafx." + name, jfxUrl(name)); public String groupId;
public String artifactId;
public String version;
public Map<String, String> sha1;
public String filename() {
return artifactId + "-" + version + "-" + currentArchClassifier() + ".jar";
} }
public String sha1() {
return sha1.get(currentArchClassifier());
}
public String url() {
return REPOSITORY_URL + groupId.replace('.', '/') + "/" + artifactId + "/" + version + "/" + filename();
}
public Path localPath() {
return DEPENDENCIES_DIR_PATH.resolve(filename());
}
}
private static final String DEPENDENCIES_LIST_FILE = "/assets/openjfx-dependencies.json";
private static List<DependencyDescriptor> readDependencies() {
String content;
try (InputStream in = SelfDependencyPatcher.class.getResourceAsStream(DEPENDENCIES_LIST_FILE)) {
content = IOUtils.readFullyAsString(in, UTF_8);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
return new Gson().fromJson(content, TypeToken.getParameterized(List.class, DependencyDescriptor.class).getType());
}
private static final List<DependencyDescriptor> JFX_DEPENDENCIES = readDependencies();
/** /**
* Patch in any missing dependencies, if any. * Patch in any missing dependencies, if any.
*/ */
@@ -125,17 +170,17 @@ public final class SelfDependencyPatcher {
// Otherwise we're free to download in Java 11+ // Otherwise we're free to download in Java 11+
LOG.info("Missing JavaFX dependencies, attempting to patch in missing classes"); LOG.info("Missing JavaFX dependencies, attempting to patch in missing classes");
// Check if dependencies need to be downloaded
if (!hasCachedDependencies()) { // Download missing dependencies
LOG.info(" - No local cache, downloading dependencies..."); List<DependencyDescriptor> missingDependencies = checkMissingDependencies();
if (!missingDependencies.isEmpty()) {
try { try {
fetchDependencies(); fetchDependencies(missingDependencies);
} catch (Exception ex) { } catch (IOException e) {
throw new PatchException("Failed to download dependencies", ex); throw new PatchException("Failed to download dependencies", e);
} }
} else {
LOG.info(" - Local cache found!");
} }
// Add the dependencies // Add the dependencies
try { try {
loadFromCache(); loadFromCache();
@@ -155,10 +200,16 @@ public final class SelfDependencyPatcher {
*/ */
private static void loadFromCache() throws IOException, ReflectiveOperationException { private static void loadFromCache() throws IOException, ReflectiveOperationException {
LOG.info(" - Loading dependencies..."); LOG.info(" - Loading dependencies...");
List<Path> jarPaths = new ArrayList<>();
List<String> jfxDepFile = JFX_DEPENDENCIES.values().stream().map(SelfDependencyPatcher::getFileName).collect(Collectors.toList()); Set<String> modules = JFX_DEPENDENCIES.stream()
Files.walk(DEPENDENCIES_DIR_PATH).filter(p -> jfxDepFile.contains(p.getFileName().toString())).forEach(jarPaths::add); .map(it -> it.module)
JavaFXPatcher.patch(JFX_DEPENDENCIES.keySet(), jarPaths.toArray(new Path[0])); .collect(toSet());
Path[] jars = JFX_DEPENDENCIES.stream()
.map(it -> it.localPath())
.toArray(Path[]::new);
JavaFXPatcher.patch(modules, jars);
} }
/** /**
@@ -166,106 +217,57 @@ public final class SelfDependencyPatcher {
* *
* @throws IOException When the files cannot be fetched or saved. * @throws IOException When the files cannot be fetched or saved.
*/ */
private static void fetchDependencies() throws Exception { private static void fetchDependencies(List<DependencyDescriptor> dependencies) throws IOException {
// Get dir to store dependencies in
Path dependenciesDir = DEPENDENCIES_DIR_PATH;
if (!Files.isDirectory(dependenciesDir)) {
Files.createDirectories(dependenciesDir);
}
ProgressFrame dialog = new ProgressFrame(i18n("download.javafx")); ProgressFrame dialog = new ProgressFrame(i18n("download.javafx"));
dialog.setVisible(true);
ForkJoinTask<Void> task = ForkJoinPool.commonPool().submit(() -> { int progress = 0;
// Download each dependency for (DependencyDescriptor dependency : dependencies) {
Collection<String> dependencies = JFX_DEPENDENCIES.values(); int currentProgress = ++progress;
int i = 1;
for (String dependencyUrlPath : dependencies) {
URL depURL = new URL(dependencyUrlPath);
Path dependencyFilePath = DEPENDENCIES_DIR_PATH.resolve(getFileName(dependencyUrlPath));
int finalI = i;
SwingUtilities.invokeLater(() -> { SwingUtilities.invokeLater(() -> {
dialog.setStatus(dependencyUrlPath); dialog.setStatus(dependency.url());
dialog.setProgress(finalI, dependencies.size()); dialog.setProgress(currentProgress, dependencies.size());
}); });
Files.copy(depURL.openStream(), dependencyFilePath, StandardCopyOption.REPLACE_EXISTING);
checksum(dependencyFilePath, dependencyUrlPath); LOG.info("Downloading " + dependency.url());
i++; Files.createDirectories(dependency.localPath().getParent());
Files.copy(new URL(dependency.url()).openStream(), dependency.localPath(), StandardCopyOption.REPLACE_EXISTING);
verifyChecksum(dependency);
} }
dialog.dispose(); dialog.dispose();
return null;
});
dialog.setVisible(true);
task.get();
} }
/** private static List<DependencyDescriptor> checkMissingDependencies() {
* @return {@code true} when the dependencies directory has files in it. List<DependencyDescriptor> missing = new ArrayList<>();
*/
private static boolean hasCachedDependencies() {
if (!Files.isDirectory(DEPENDENCIES_DIR_PATH))
return false;
for (String url : JFX_DEPENDENCIES.values()) { for (DependencyDescriptor dependency : JFX_DEPENDENCIES) {
Path dependencyFilePath = DEPENDENCIES_DIR_PATH.resolve(getFileName(url)); if (!Files.exists(dependency.localPath())) {
if (!Files.exists(dependencyFilePath)) missing.add(dependency);
return false; continue;
}
try { try {
checksum(dependencyFilePath, url); verifyChecksum(dependency);
} catch (ChecksumMismatchException e) { } catch (ChecksumMismatchException e) {
return false; LOG.warning("Corrupted dependency " + dependency.filename() + ": " + e.getMessage());
} catch (IOException ignored) { missing.add(dependency);
// Ignore other situations } catch (IOException e) {
throw new UncheckedIOException(e);
} }
} }
return true;
}
private static void checksum(Path dependencyFilePath, String dependencyUrlPath) throws IOException { return missing;
String expectedHash = NetworkUtils.doGet(NetworkUtils.toURL(dependencyUrlPath + ".sha1")); }
String actualHash = Hex.encodeHex(DigestUtils.digest("SHA-1", dependencyFilePath));
private static void verifyChecksum(DependencyDescriptor dependency) throws IOException, ChecksumMismatchException {
String expectedHash = dependency.sha1();
String actualHash = Hex.encodeHex(DigestUtils.digest("SHA-1", dependency.localPath()));
if (!expectedHash.equalsIgnoreCase(actualHash)) { if (!expectedHash.equalsIgnoreCase(actualHash)) {
throw new ChecksumMismatchException("SHA-1", expectedHash, actualHash); throw new ChecksumMismatchException("SHA-1", expectedHash, actualHash);
} }
} }
/**
* @param url Full url path.
* @return Name of file at url.
*/
private static String getFileName(String url) {
return url.substring(url.lastIndexOf('/') + 1);
}
/**
* @param component Name of the component.
* @return Formed URL for the component.
*/
private static String jfxUrl(String component) {
return jfxUrl(component, DEFAULT_JFX_VERSION);
}
private static String jfxUrl(String component, String version) {
// https://repo1.maven.org/maven2/org/openjfx/javafx-%s/%s/javafx-%s-%s-%s.jar
return String.format("https://maven.aliyun.com/repository/central/org/openjfx/javafx-%s/%s/javafx-%s-%s-%s.jar",
component, version, component, version, getMvnName());
// return String.format("https://bmclapi.bangbang93.com/maven/org/openjfx/javafx-%s/%s/javafx-%s-%s-%s.jar",
// component, version, component, version, getMvnName());
}
private static String getMvnName() {
switch (OperatingSystem.CURRENT_OS) {
case LINUX:
return "linux";
case OSX:
return "mac";
default:
return "win";
}
}
public static class PatchException extends Exception { public static class PatchException extends Exception {
PatchException(String message, Throwable cause) { PatchException(String message, Throwable cause) {
super(message, cause); super(message, cause);
@@ -276,8 +278,6 @@ public final class SelfDependencyPatcher {
} }
public static class ProgressFrame extends JDialog { public static class ProgressFrame extends JDialog {
private int totalTasks = 0;
private int finishedTasks = 0;
private final JProgressBar progressBar; private final JProgressBar progressBar;
private final JLabel progressText; private final JLabel progressText;

View File

@@ -0,0 +1,68 @@
[
{
"module": "javafx.base",
"groupId": "org.openjfx",
"artifactId": "javafx-base",
"version": "16",
"sha1": {
"linux": "7d6b85ec89e99ea40c2a63bb1f23d43e05cd2557",
"mac": "ea97d8a5ab95d070df86de1215f29f08378c98c8",
"win": "e0564fea3f27dd3a10fa62e5005612333fc244bc"
}
},
{
"module": "javafx.controls",
"groupId": "org.openjfx",
"artifactId": "javafx-controls",
"version": "16",
"sha1": {
"linux": "116b127e512d23ddb84c62017c256e7f67f5b9eb",
"mac": "8df933b095bdd8203dc937e39e3a9f199654e47e",
"win": "65cdeae29c67d25932dcc66ca4f7d269923631ba"
}
},
{
"module": "javafx.fxml",
"groupId": "org.openjfx",
"artifactId": "javafx-fxml",
"version": "16",
"sha1": {
"linux": "c7147c450773c3d4fd038ac9d1a6fdebbc3c11e0",
"mac": "7e4f07a331991485560673a398d6988cc805b67f",
"win": "369f646fc29d2223cc98cfc3c059f2409b3e8fcb"
}
},
{
"module": "javafx.graphics",
"groupId": "org.openjfx",
"artifactId": "javafx-graphics",
"version": "16",
"sha1": {
"linux": "01bf2fd9f083daa2f492c4c58c4f7d5acb6f4b7d",
"mac": "a45de3d40e217f7ba22cecf54abc281d13a172f6",
"win": "0cd18f8477818b40cfdbcfec7e2fc15f632f0a2f"
}
},
{
"module": "javafx.media",
"groupId": "org.openjfx",
"artifactId": "javafx-media",
"version": "16",
"sha1": {
"linux": "10edb5fd8eb64b3312b31225c396e1850b9d178b",
"mac": "9e0248083267cdc22b2b698f0b0b62b7399ad32d",
"win": "6d776658906fc25051734bc6b03f214cc72c808e"
}
},
{
"module": "javafx.web",
"groupId": "org.openjfx",
"artifactId": "javafx-web",
"version": "16",
"sha1": {
"linux": "522f626e1798a7d589d9b187ca5c74c5c2f8a0ee",
"mac": "32b0e13b649ea828e7d185187f178680cc04e564",
"win": "d2f80089a4ae1629a5ec14637abf4f6d70b96dab"
}
}
]

View File

@@ -0,0 +1,30 @@
#!/bin/bash
set -e
modules=(base controls fxml graphics media web)
arches=(linux mac win)
version=16
echo '['
for module in ${modules[@]}; do
if [[ ! "$module" == "${modules[0]}" ]]; then
echo ','
fi
echo ' {'
echo ' "module": "javafx.'$module'",'
echo ' "groupId": "org.openjfx",'
echo ' "artifactId": "javafx-'$module'",'
echo ' "version": "'$version'",'
echo ' "sha1": {'
for arch in ${arches[@]}; do
if [[ ! "$arch" == "${arches[0]}" ]]; then
echo ','
fi
echo -n ' "'$arch'": "'$(curl -Ss "https://repo1.maven.org/maven2/org/openjfx/javafx-$module/$version/javafx-$module-$version-$arch.jar.sha1")'"'
done
echo
echo ' }'
echo -n ' }'
done
echo
echo ']'