Skip to main content

yog_cli/script/
jvm.rs

1//! A mod's Yog Script built for the JVM: a jar per game version the
2//! loader SDK runs, made for that version's Java, each in the `.yog` as
3//! `scripts/<mc>/<mod>.jar` — the hosts load their own version's.
4
5use std::collections::BTreeMap;
6use std::path::{Path, PathBuf};
7
8use yog_ids::Format;
9use yog_script_build::{JvmBuild, build_jar};
10
11use crate::diagnostics;
12use crate::project::manifest::{SOURCES_DIR, YogToml};
13
14/// What a mod's compiled Yog Script's package starts with: `yogscript.<mod>`.
15const PACKAGE_ROOT: &str = "yogscript";
16/// Where its builds work, under the build folder.
17const WORK_DIR: &str = "scripts";
18/// The compiler, on `PATH` when no JDK of the version's Java is installed
19/// (a newer one compiles for an older release); in a JDK, with the
20/// platform's executable suffix (`javac.exe` on Windows).
21const JAVAC: &str = "javac";
22/// Where a JDK keeps its tools.
23const JDK_BIN: &str = "bin";
24
25/// The mod's scripts' jars as `.yog` entries; none when it has no `.ys`.
26pub fn jars(
27    root: &Path,
28    meta: &YogToml,
29    build_dir: &Path,
30) -> Result<Vec<(String, Vec<u8>)>, String> {
31    let sources = sources(root)?;
32    if sources.is_empty() {
33        return Ok(Vec::new());
34    }
35    let home = yog_home::Home::locate()?;
36    let resolver = yog_home::targets::Resolver::new(&home)?;
37    let api = resolver.java_api_jar().ok_or(
38        "Yog Script needs the loader SDK's java-api jar: `./build.sh build api` in the SDK",
39    )?;
40    let package = format!("{PACKAGE_ROOT}.{}", yog_ids::mod_lib_name(&meta.id));
41    let mut out = Vec::new();
42    for (mc, java) in versions(&resolver)? {
43        let javac = resolver.jdk(java).map_or_else(
44            || PathBuf::from(JAVAC),
45            |jdk| {
46                jdk.join(JDK_BIN)
47                    .join(format!("{JAVAC}{}", std::env::consts::EXE_SUFFIX))
48            },
49        );
50        let work = build_dir.join(WORK_DIR).join(&mc);
51        let jar = build_dir
52            .join(WORK_DIR)
53            .join(format!("{mc}-{}.jar", meta.id));
54        let b = JvmBuild {
55            javac: &javac,
56            release: java,
57            classpath: std::slice::from_ref(&api),
58            package: &package,
59            work: &work,
60            to_server: meta.to_server,
61        };
62        if let Err(problems) = build_jar(&sources, &b, &jar) {
63            diagnostics::report_all(&problems);
64            problems.iter().for_each(|d| yog_logging::error!("{d}"));
65            return Err(format!("the Yog Script didn't build for {mc}"));
66        }
67        let bytes = std::fs::read(&jar).map_err(|e| format!("{}: {e}", jar.display()))?;
68        out.push((
69            format!("{}/{mc}/{}.jar", yog_ids::archive::SCRIPTS, meta.id),
70            bytes,
71        ));
72        yog_logging::info!("scripts for {mc} (Java {java})");
73    }
74    Ok(out)
75}
76
77/// The mod's `.ys` sources (name, text). A `.yns` (nanites) isn't built for
78/// the JVM this way.
79fn sources(root: &Path) -> Result<Vec<(String, String)>, String> {
80    crate::files::of(&root.join(SOURCES_DIR), Format::Script)
81        .into_iter()
82        .map(|f| {
83            let text = std::fs::read_to_string(&f).map_err(|e| format!("{}: {e}", f.display()))?;
84            Ok((crate::shown_path(root, &f), text))
85        })
86        .collect()
87}
88
89/// The game versions the SDK's loaders run, each with its Java.
90fn versions(resolver: &yog_home::targets::Resolver) -> Result<BTreeMap<String, u32>, String> {
91    let mut out = BTreeMap::new();
92    for t in resolver.targets()? {
93        if let (Some(mc), Some(java)) = (t.variant.clone(), t.java()) {
94            out.insert(mc, java);
95        }
96    }
97    if out.is_empty() {
98        return Err("no game version to build the Yog Script for: no loader SDK".into());
99    }
100    Ok(out)
101}