Move jars download to client instead of build
This commit is contained in:
parent
5a4562c585
commit
e45e3ec362
553
rust/Cargo.lock
generated
553
rust/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -1,10 +1,9 @@
|
|||||||
use std::{collections::HashSet, fs, path::PathBuf};
|
use std::{fs, path::PathBuf};
|
||||||
|
|
||||||
use j4rs::{JvmBuilder, LocalJarArtifact, MavenArtifact, MavenArtifactRepo, MavenSettings};
|
use j4rs::{JvmBuilder, LocalJarArtifact, MavenArtifactRepo, MavenSettings};
|
||||||
|
|
||||||
pub fn setup_java(base: PathBuf) {
|
pub fn setup_java(base: PathBuf) {
|
||||||
let resources = base.join("resources");
|
let resources = base.join("resources");
|
||||||
let jassets = resources.join("jassets");
|
|
||||||
let deps = resources.join("deps");
|
let deps = resources.join("deps");
|
||||||
|
|
||||||
let mut java_path = base.clone();
|
let mut java_path = base.clone();
|
||||||
@ -17,82 +16,6 @@ pub fn setup_java(base: PathBuf) {
|
|||||||
.join("libs")
|
.join("libs")
|
||||||
.join("patchbukkit.jar");
|
.join("patchbukkit.jar");
|
||||||
|
|
||||||
let dependencies = [
|
|
||||||
("com.google.guava", "guava", "33.3.1-jre"),
|
|
||||||
(
|
|
||||||
"net.md-5",
|
|
||||||
"bungeecord-chat",
|
|
||||||
"1.21-R0.2-deprecated+build.21",
|
|
||||||
),
|
|
||||||
("net.kyori", "adventure-text-logger-slf4j", "4.25.0"),
|
|
||||||
("net.kyori", "adventure-text-minimessage", "4.25.0"),
|
|
||||||
("net.kyori", "adventure-text-serializer-legacy", "4.25.0"),
|
|
||||||
("net.kyori", "adventure-text-serializer-plain", "4.25.0"),
|
|
||||||
("net.kyori", "adventure-text-serializer-json", "4.25.0"),
|
|
||||||
("net.kyori", "adventure-api", "4.25.0"),
|
|
||||||
("net.kyori", "adventure-key", "4.25.0"),
|
|
||||||
("net.kyori", "adventure-text-serializer-commons", "4.25.0"),
|
|
||||||
("net.kyori", "adventure-text-serializer-gson", "4.25.0"),
|
|
||||||
("com.google.code.gson", "gson", "2.11.0"),
|
|
||||||
("org.yaml", "snakeyaml", "2.2"),
|
|
||||||
("org.joml", "joml", "1.10.8"),
|
|
||||||
("it.unimi.dsi", "fastutil", "8.5.15"),
|
|
||||||
("org.apache.logging.log4j", "log4j-api", "2.24.1"),
|
|
||||||
(
|
|
||||||
"org.apache.maven.resolver",
|
|
||||||
"maven-resolver-connector-basic",
|
|
||||||
"1.9.18",
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"org.apache.maven.resolver",
|
|
||||||
"maven-resolver-transport-http",
|
|
||||||
"1.9.18",
|
|
||||||
),
|
|
||||||
("org.apache.maven", "maven-resolver-provider", "3.9.6"),
|
|
||||||
("org.apache.maven.resolver", "maven-resolver-impl", "1.9.18"),
|
|
||||||
("org.slf4j", "jcl-over-slf4j", "1.7.36"),
|
|
||||||
(
|
|
||||||
"org.apache.maven.resolver",
|
|
||||||
"maven-resolver-named-locks",
|
|
||||||
"1.9.18",
|
|
||||||
),
|
|
||||||
("org.slf4j", "slf4j-api", "2.0.16"),
|
|
||||||
("com.mojang", "brigadier", "1.3.10"),
|
|
||||||
("org.jspecify", "jspecify", "1.0.0"),
|
|
||||||
("com.google.guava", "failureaccess", "1.0.2"),
|
|
||||||
(
|
|
||||||
"com.google.guava",
|
|
||||||
"listenablefuture",
|
|
||||||
"9999.0-empty-to-avoid-conflict-with-guava",
|
|
||||||
),
|
|
||||||
("com.google.code.findbugs", "jsr305", "3.0.2"),
|
|
||||||
("org.checkerframework", "checker-qual", "3.43.0"),
|
|
||||||
("com.google.errorprone", "error_prone_annotations", "2.28.0"),
|
|
||||||
("com.google.j2objc", "j2objc-annotations", "3.0.0"),
|
|
||||||
("org.apache.maven", "maven-model-builder", "3.9.6"),
|
|
||||||
("org.apache.maven", "maven-model", "3.9.6"),
|
|
||||||
("org.apache.maven", "maven-repository-metadata", "3.9.6"),
|
|
||||||
("org.apache.maven.resolver", "maven-resolver-spi", "1.9.18"),
|
|
||||||
("org.apache.maven.resolver", "maven-resolver-util", "1.9.18"),
|
|
||||||
("org.apache.maven.resolver", "maven-resolver-api", "1.9.18"),
|
|
||||||
("org.apache.maven", "maven-artifact", "3.9.6"),
|
|
||||||
("org.codehaus.plexus", "plexus-utils", "3.5.1"),
|
|
||||||
("javax.inject", "javax.inject", "1"),
|
|
||||||
("org.apache.httpcomponents", "httpclient", "4.5.14"),
|
|
||||||
("org.apache.httpcomponents", "httpcore", "4.4.16"),
|
|
||||||
("commons-codec", "commons-codec", "1.16.0"),
|
|
||||||
("net.kyori", "examination-string", "1.3.0"),
|
|
||||||
("net.kyori", "examination-api", "1.3.0"),
|
|
||||||
("org.codehaus.plexus", "plexus-interpolation", "1.26"),
|
|
||||||
("org.apache.maven", "maven-builder-support", "3.9.6"),
|
|
||||||
("org.eclipse.sisu", "org.eclipse.sisu.inject", "0.9.0.M2"),
|
|
||||||
("org.apache.commons", "commons-lang3", "3.12.0"),
|
|
||||||
("net.kyori", "option", "1.1.0"),
|
|
||||||
// These are deps of patchbukkit itself
|
|
||||||
("io.papermc.paper", "paper-api", "1.21.11-R0.1-SNAPSHOT"),
|
|
||||||
("com.google.protobuf", "protobuf-java", "4.33.5"),
|
|
||||||
];
|
|
||||||
|
|
||||||
let jvm = JvmBuilder::new()
|
let jvm = JvmBuilder::new()
|
||||||
.with_maven_settings(MavenSettings::new(vec![MavenArtifactRepo::from(
|
.with_maven_settings(MavenSettings::new(vec![MavenArtifactRepo::from(
|
||||||
"papermc::https://repo.papermc.io/repository/maven-public/",
|
"papermc::https://repo.papermc.io/repository/maven-public/",
|
||||||
@ -103,40 +26,6 @@ pub fn setup_java(base: PathBuf) {
|
|||||||
.map_err(|err| format!("jvm failed to init: {err:?}"))
|
.map_err(|err| format!("jvm failed to init: {err:?}"))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let expected: HashSet<String> = dependencies
|
|
||||||
.iter()
|
|
||||||
.map(|d| format!("{}-{}.jar", d.1, d.2))
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
for entry in fs::read_dir(&jassets).unwrap() {
|
|
||||||
let entry = entry.unwrap();
|
|
||||||
let file_name = entry.file_name().to_string_lossy().into_owned();
|
|
||||||
|
|
||||||
if file_name.starts_with("j4rs-") {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if !expected.contains(&file_name) {
|
|
||||||
let remove_path = entry.path();
|
|
||||||
fs::remove_file(remove_path).unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if deps.exists() {
|
|
||||||
fs::remove_dir_all(&deps).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
for dep in dependencies {
|
|
||||||
let jar_path = jassets.join(format!("{}-{}.jar", dep.1, dep.2));
|
|
||||||
if !jar_path.exists() {
|
|
||||||
jvm.deploy_artifact(&MavenArtifact::from(format!(
|
|
||||||
"{}:{}:{}",
|
|
||||||
dep.0, dep.1, dep.2
|
|
||||||
)))
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !&patchbukkit_jar.exists() {
|
if !&patchbukkit_jar.exists() {
|
||||||
panic!(
|
panic!(
|
||||||
"Failed to find patchbukkit.jar, build the java library first by running `gradle build` in the java directory!"
|
"Failed to find patchbukkit.jar, build the java library first by running `gradle build` in the java directory!"
|
||||||
|
|||||||
@ -142,7 +142,7 @@ impl CommandExecutor for JavaCommandExecutor {
|
|||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
Ok(())
|
Ok(1)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,36 +5,170 @@ use std::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
use j4rs::{JvmBuilder, MavenArtifact, MavenArtifactRepo, MavenSettings};
|
||||||
use rust_embed::Embed;
|
use rust_embed::Embed;
|
||||||
|
|
||||||
#[derive(Embed)]
|
#[derive(Embed)]
|
||||||
#[folder = "resources/"]
|
#[folder = "resources/"]
|
||||||
pub struct Resources;
|
pub struct Resources;
|
||||||
|
|
||||||
pub fn cleanup_stale_files(j4rs_folder: &PathBuf) {
|
pub const DEPENDENCIES: &[(&str, &str, &str)] = &[
|
||||||
let allowed: HashSet<PathBuf> = Resources::iter()
|
("com.google.guava", "guava", "33.3.1-jre"),
|
||||||
|
(
|
||||||
|
"net.md-5",
|
||||||
|
"bungeecord-chat",
|
||||||
|
"1.21-R0.2-deprecated+build.21",
|
||||||
|
),
|
||||||
|
("net.kyori", "adventure-text-logger-slf4j", "4.25.0"),
|
||||||
|
("net.kyori", "adventure-text-minimessage", "4.25.0"),
|
||||||
|
("net.kyori", "adventure-text-serializer-legacy", "4.25.0"),
|
||||||
|
("net.kyori", "adventure-text-serializer-plain", "4.25.0"),
|
||||||
|
("net.kyori", "adventure-text-serializer-json", "4.25.0"),
|
||||||
|
("net.kyori", "adventure-api", "4.25.0"),
|
||||||
|
("net.kyori", "adventure-key", "4.25.0"),
|
||||||
|
("net.kyori", "adventure-text-serializer-commons", "4.25.0"),
|
||||||
|
("net.kyori", "adventure-text-serializer-gson", "4.25.0"),
|
||||||
|
("com.google.code.gson", "gson", "2.11.0"),
|
||||||
|
("org.yaml", "snakeyaml", "2.2"),
|
||||||
|
("org.joml", "joml", "1.10.8"),
|
||||||
|
("it.unimi.dsi", "fastutil", "8.5.15"),
|
||||||
|
("org.apache.logging.log4j", "log4j-api", "2.24.1"),
|
||||||
|
(
|
||||||
|
"org.apache.maven.resolver",
|
||||||
|
"maven-resolver-connector-basic",
|
||||||
|
"1.9.18",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"org.apache.maven.resolver",
|
||||||
|
"maven-resolver-transport-http",
|
||||||
|
"1.9.18",
|
||||||
|
),
|
||||||
|
("org.apache.maven", "maven-resolver-provider", "3.9.6"),
|
||||||
|
("org.apache.maven.resolver", "maven-resolver-impl", "1.9.18"),
|
||||||
|
("org.slf4j", "jcl-over-slf4j", "1.7.36"),
|
||||||
|
(
|
||||||
|
"org.apache.maven.resolver",
|
||||||
|
"maven-resolver-named-locks",
|
||||||
|
"1.9.18",
|
||||||
|
),
|
||||||
|
("org.slf4j", "slf4j-api", "2.0.16"),
|
||||||
|
("com.mojang", "brigadier", "1.3.10"),
|
||||||
|
("org.jspecify", "jspecify", "1.0.0"),
|
||||||
|
("com.google.guava", "failureaccess", "1.0.2"),
|
||||||
|
(
|
||||||
|
"com.google.guava",
|
||||||
|
"listenablefuture",
|
||||||
|
"9999.0-empty-to-avoid-conflict-with-guava",
|
||||||
|
),
|
||||||
|
("com.google.code.findbugs", "jsr305", "3.0.2"),
|
||||||
|
("org.checkerframework", "checker-qual", "3.43.0"),
|
||||||
|
("com.google.errorprone", "error_prone_annotations", "2.28.0"),
|
||||||
|
("com.google.j2objc", "j2objc-annotations", "3.0.0"),
|
||||||
|
("org.apache.maven", "maven-model-builder", "3.9.6"),
|
||||||
|
("org.apache.maven", "maven-model", "3.9.6"),
|
||||||
|
("org.apache.maven", "maven-repository-metadata", "3.9.6"),
|
||||||
|
("org.apache.maven.resolver", "maven-resolver-spi", "1.9.18"),
|
||||||
|
("org.apache.maven.resolver", "maven-resolver-util", "1.9.18"),
|
||||||
|
("org.apache.maven.resolver", "maven-resolver-api", "1.9.18"),
|
||||||
|
("org.apache.maven", "maven-artifact", "3.9.6"),
|
||||||
|
("org.codehaus.plexus", "plexus-utils", "3.5.1"),
|
||||||
|
("javax.inject", "javax.inject", "1"),
|
||||||
|
("org.apache.httpcomponents", "httpclient", "4.5.14"),
|
||||||
|
("org.apache.httpcomponents", "httpcore", "4.4.16"),
|
||||||
|
("commons-codec", "commons-codec", "1.16.0"),
|
||||||
|
("net.kyori", "examination-string", "1.3.0"),
|
||||||
|
("net.kyori", "examination-api", "1.3.0"),
|
||||||
|
("org.codehaus.plexus", "plexus-interpolation", "1.26"),
|
||||||
|
("org.apache.maven", "maven-builder-support", "3.9.6"),
|
||||||
|
("org.eclipse.sisu", "org.eclipse.sisu.inject", "0.9.0.M2"),
|
||||||
|
("org.apache.commons", "commons-lang3", "3.12.0"),
|
||||||
|
("net.kyori", "option", "1.1.0"),
|
||||||
|
// These are deps of patchbukkit itself
|
||||||
|
("io.papermc.paper", "paper-api", "1.21.11-R0.1-SNAPSHOT"),
|
||||||
|
("com.google.protobuf", "protobuf-java", "4.33.5"),
|
||||||
|
];
|
||||||
|
|
||||||
|
pub fn setup_j4rs(j4rs_folder: &Path) -> Result<()> {
|
||||||
|
sync_embedded_resources(j4rs_folder)?;
|
||||||
|
cleanup_stale_files(j4rs_folder);
|
||||||
|
resolve_maven_dependencies(j4rs_folder)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resolve_maven_dependencies(j4rs_folder: &Path) -> Result<()> {
|
||||||
|
let jassets = j4rs_folder.join("jassets");
|
||||||
|
|
||||||
|
// Check if all expected jars already exist
|
||||||
|
let all_present = DEPENDENCIES
|
||||||
|
.iter()
|
||||||
|
.all(|&(_, artifact, version)| jassets.join(format!("{artifact}-{version}.jar")).exists());
|
||||||
|
|
||||||
|
if all_present {
|
||||||
|
log::debug!("All Maven dependencies already present, skipping JVM init");
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
log::info!("Some Maven dependencies missing, starting JVM to resolve...");
|
||||||
|
|
||||||
|
let jvm = JvmBuilder::new()
|
||||||
|
.with_maven_settings(MavenSettings::new(vec![MavenArtifactRepo::from(
|
||||||
|
"papermc::https://repo.papermc.io/repository/maven-public/",
|
||||||
|
)]))
|
||||||
|
.skip_setting_native_lib()
|
||||||
|
.with_base_path(j4rs_folder)
|
||||||
|
.build()
|
||||||
|
.map_err(|err| anyhow::anyhow!("JVM failed to init: {err:?}"))?;
|
||||||
|
|
||||||
|
for &(group, artifact, version) in DEPENDENCIES {
|
||||||
|
let jar_path = jassets.join(format!("{artifact}-{version}.jar"));
|
||||||
|
if !jar_path.exists() {
|
||||||
|
log::info!("Downloading Maven dependency: {group}:{artifact}:{version}");
|
||||||
|
jvm.deploy_artifact(&MavenArtifact::from(format!(
|
||||||
|
"{group}:{artifact}:{version}"
|
||||||
|
)))
|
||||||
|
.map_err(|err| {
|
||||||
|
anyhow::anyhow!("Failed to deploy {group}:{artifact}:{version}: {err:?}")
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cleanup_stale_files(j4rs_folder: &Path) {
|
||||||
|
let embedded_paths: HashSet<PathBuf> = Resources::iter()
|
||||||
.map(|p| PathBuf::from(p.to_string()))
|
.map(|p| PathBuf::from(p.to_string()))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
|
let maven_jars: HashSet<String> = DEPENDENCIES
|
||||||
|
.iter()
|
||||||
|
.map(|d| format!("{}-{}.jar", d.1, d.2))
|
||||||
|
.collect();
|
||||||
|
|
||||||
for entry in walkdir::WalkDir::new(j4rs_folder)
|
for entry in walkdir::WalkDir::new(j4rs_folder)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter_map(std::result::Result::ok)
|
.filter_map(|e| e.ok())
|
||||||
.filter(|e| e.file_type().is_file())
|
.filter(|e| e.file_type().is_file())
|
||||||
{
|
{
|
||||||
let path = entry.path();
|
let path = entry.path();
|
||||||
|
if let Ok(rel_path) = path.strip_prefix(j4rs_folder) {
|
||||||
|
// Skip Maven-managed jars — handled by cleanup_stale_maven_jars
|
||||||
|
if let Some(file_name) = path.file_name().and_then(|f| f.to_str())
|
||||||
|
&& maven_jars.contains(file_name)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if let Ok(rel_path) = path.strip_prefix(j4rs_folder)
|
if !embedded_paths.contains(rel_path) {
|
||||||
&& !allowed.contains(rel_path)
|
log::warn!("Removing stale embedded file: {}", rel_path.display());
|
||||||
{
|
let _ = fs::remove_file(path);
|
||||||
log::warn!("Removing stale j4rs file: {}", rel_path.display());
|
|
||||||
if let Err(e) = fs::remove_file(path) {
|
|
||||||
log::error!("Failed to remove stale file {}: {}", path.display(), e);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn sync_embedded_resources(j4rs_folder: &Path) -> Result<(), String> {
|
pub fn sync_embedded_resources(j4rs_folder: &Path) -> Result<()> {
|
||||||
for resource_path_str in Resources::iter() {
|
for resource_path_str in Resources::iter() {
|
||||||
let resource_path = j4rs_folder.join(resource_path_str.to_string());
|
let resource_path = j4rs_folder.join(resource_path_str.to_string());
|
||||||
let resource = Resources::get(&resource_path_str).unwrap();
|
let resource = Resources::get(&resource_path_str).unwrap();
|
||||||
@ -49,25 +183,23 @@ pub fn sync_embedded_resources(j4rs_folder: &Path) -> Result<(), String> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_resource(path: &Path, data: &[u8]) -> Result<(), String> {
|
fn write_resource(path: &Path, data: &[u8]) -> Result<()> {
|
||||||
if let Some(parent) = path.parent() {
|
if let Some(parent) = path.parent() {
|
||||||
fs::create_dir_all(parent)
|
fs::create_dir_all(parent)?;
|
||||||
.map_err(|_| format!("Failed to create directory: {}", parent.display()))?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fs::write(path, data).map_err(|_| format!("Failed to write resource: {}", path.display()))?;
|
fs::write(path, data)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_resource_if_changed(path: &Path, new_data: &[u8]) -> Result<(), String> {
|
fn update_resource_if_changed(path: &Path, new_data: &[u8]) -> Result<()> {
|
||||||
// Quick check: If file sizes differ, it's definitely changed, TODO: use Hash ?
|
// Quick check: If file sizes differ, it's definitely changed, TODO: use Hash ?
|
||||||
let metadata = fs::metadata(path).ok();
|
let metadata = fs::metadata(path).ok();
|
||||||
let size_matches = metadata.is_some_and(|m| m.len() == new_data.len() as u64);
|
let size_matches = metadata.is_some_and(|m| m.len() == new_data.len() as u64);
|
||||||
|
|
||||||
if !size_matches || fs::read(path).map_err(|e| e.to_string())? != new_data {
|
if !size_matches || fs::read(path)? != new_data {
|
||||||
log::debug!("Updating changed resource: {}", path.display());
|
log::debug!("Updating changed resource: {}", path.display());
|
||||||
fs::write(path, new_data)
|
fs::write(path, new_data)?;
|
||||||
.map_err(|_| format!("Failed to update resource: {}", path.display()))?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@ -13,18 +13,18 @@ pub mod java;
|
|||||||
pub mod proto;
|
pub mod proto;
|
||||||
|
|
||||||
use directories::setup_directories;
|
use directories::setup_directories;
|
||||||
use java::{
|
use java::jar::discover_jar_files;
|
||||||
jar::discover_jar_files,
|
|
||||||
resources::{cleanup_stale_files, sync_embedded_resources},
|
|
||||||
};
|
|
||||||
use tokio::sync::{
|
use tokio::sync::{
|
||||||
mpsc::{self, Receiver},
|
mpsc::{self, Receiver},
|
||||||
oneshot,
|
oneshot,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::java::jvm::{
|
use crate::java::{
|
||||||
commands::{JvmCommand, LoadPluginResult},
|
jvm::{
|
||||||
worker::JvmWorker,
|
commands::{JvmCommand, LoadPluginResult},
|
||||||
|
worker::JvmWorker,
|
||||||
|
},
|
||||||
|
resources::setup_j4rs,
|
||||||
};
|
};
|
||||||
|
|
||||||
async fn on_load_inner(plugin: &PatchBukkitPlugin, server: Arc<Context>) -> Result<(), String> {
|
async fn on_load_inner(plugin: &PatchBukkitPlugin, server: Arc<Context>) -> Result<(), String> {
|
||||||
@ -101,8 +101,7 @@ async fn on_load_inner(plugin: &PatchBukkitPlugin, server: Arc<Context>) -> Resu
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Manage embedded resources
|
// Manage embedded resources
|
||||||
cleanup_stale_files(&dirs.j4rs);
|
setup_j4rs(&dirs.j4rs).map_err(|e| format!("Failed to setup J4RS: {e}"))?;
|
||||||
sync_embedded_resources(&dirs.j4rs)?;
|
|
||||||
|
|
||||||
{
|
{
|
||||||
let (tx, rx) = oneshot::channel();
|
let (tx, rx) = oneshot::channel();
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user