use std::{collections::HashSet, fs, path::PathBuf}; use glob::glob; use j4rs::{JvmBuilder, LocalJarArtifact, MavenArtifact, MavenArtifactRepo, MavenSettings}; use prost_build::{Config, Service, ServiceGenerator}; struct FfiServiceGenerator; impl ServiceGenerator for FfiServiceGenerator { fn generate(&mut self, service: Service, buf: &mut String) { for method in &service.methods { let fn_name = format!("ffi_{}_{}", to_snake_case(&service.name), &method.name); let input_type = &method.input_type; let output_type = &method.output_type; buf.push_str(&format!( r#" #[no_mangle] pub unsafe extern "C" fn {fn_name}( input_ptr: *const u8, input_len: usize, output_len: *mut usize, ) -> *mut u8 {{ use prost::Message; let input_slice = std::slice::from_raw_parts(input_ptr, input_len); let request = match {input_type}::decode(input_slice) {{ Ok(req) => req, Err(_) => {{ *output_len = 0; return std::ptr::null_mut(); }} }}; let response = {fn_name}_impl(request); let encoded = response.encode_to_vec(); *output_len = encoded.len(); let ptr = encoded.as_ptr() as *mut u8; std::mem::forget(encoded); ptr }} "# )); } } } fn to_snake_case(s: &str) -> String { let mut result = String::new(); for (i, c) in s.chars().enumerate() { if c.is_uppercase() { if i > 0 { result.push('_'); } result.push(c.to_ascii_lowercase()); } else { result.push(c); } } result } #[expect(clippy::too_many_lines)] fn main() -> Result<(), Box> { println!("cargo::rerun-if-changed=rust/src/build.rs"); println!("cargo::rerun-if-changed=java/build/libs/"); env_logger::init(); let base = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap()); let resources = base.join("resources"); let jassets = resources.join("jassets"); let deps = resources.join("deps"); let mut java_path = base.clone(); java_path.pop(); let java_path = java_path.join("java"); let patchbukkit_jar = java_path .join("patchbukkit") .join("build") .join("libs") .join("patchbukkit.jar"); let mut proto_path = base.clone(); proto_path.pop(); let proto_path = proto_path.join("proto"); let paths = glob(&format!("{}/**/*.proto", proto_path.display())) .expect("Failed to read glob pattern") .filter_map(|p| p.ok()) .collect::>(); let mut config = Config::new(); config.service_generator(Box::new(FfiServiceGenerator)); config.compile_protos(paths.as_slice(), &[proto_path])?; 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"), ("io.papermc.paper", "paper-api", "1.21.11-R0.1-SNAPSHOT"), ]; 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(resources) .build() .map_err(|err| format!("jvm failed to init: {err:?}")) .unwrap(); let expected: HashSet = 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() { panic!( "Failed to find patchbukkit.jar, build the java library first by running `gradle build` in the java directory!" ); } jvm.deploy_artifact(&LocalJarArtifact::new(&patchbukkit_jar.to_string_lossy())) .unwrap(); let cdylib = std::env::var("CARGO_CDYLIB_FILE_J4RS").unwrap(); let cdylib = PathBuf::from(cdylib); let mut cdylib_to = deps; fs::create_dir_all(&cdylib_to).unwrap(); let original_name = cdylib.file_name().unwrap().to_string_lossy(); let stem = original_name.split('-').next().unwrap(); // before the first '-' let ext = cdylib.extension().unwrap().to_string_lossy(); cdylib_to.push(format!("{stem}.{ext}")); fs::copy(&cdylib, &cdylib_to) .map_err(|err| format!("Failed to copy j4rs native lib: {err:?}")) .unwrap(); }