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>`.
15use yog_ids::script_jvm::PACKAGE_ROOT;
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, their content declarations
26/// bound to `content` (each file's (kind, id)); none when it has no `.ys`.
27pub fn jars(
28    root: &Path,
29    meta: &YogToml,
30    build_dir: &Path,
31    content: &[(String, String)],
32) -> Result<Vec<(String, Vec<u8>)>, String> {
33    let sources = sources(root)?;
34    if sources.is_empty() {
35        return Ok(Vec::new());
36    }
37    let home = yog_home::Home::locate()?;
38    let resolver = yog_home::targets::Resolver::new(&home)?;
39    let api = resolver.java_api_jar().ok_or(
40        "Yog Script needs the loader SDK's java-api jar: `./build.sh build api` in the SDK",
41    )?;
42    let files = yog_script_build::FilesBeside {
43        base: root.to_path_buf(),
44    };
45    let package = format!("{PACKAGE_ROOT}.{}", yog_ids::mod_lib_name(&meta.id));
46    let mut out = Vec::new();
47    for (mc, java) in versions(&resolver)? {
48        let javac = resolver.jdk(java).map_or_else(
49            || PathBuf::from(JAVAC),
50            |jdk| {
51                jdk.join(JDK_BIN)
52                    .join(format!("{JAVAC}{}", std::env::consts::EXE_SUFFIX))
53            },
54        );
55        let work = build_dir.join(WORK_DIR).join(&mc);
56        let jar = build_dir
57            .join(WORK_DIR)
58            .join(format!("{mc}-{}.jar", meta.id));
59        let b = JvmBuild {
60            javac: &javac,
61            release: java,
62            classpath: std::slice::from_ref(&api),
63            package: &package,
64            work: &work,
65            to_server: meta.to_server,
66            files: Some(&files),
67            mod_id: &meta.id,
68            content,
69        };
70        if let Err(problems) = build_jar(&sources, &b, &jar) {
71            diagnostics::report_all(&problems);
72            problems.iter().for_each(|d| yog_logging::error!("{d}"));
73            return Err(format!("the Yog Script didn't build for {mc}"));
74        }
75        let bytes = std::fs::read(&jar).map_err(|e| format!("{}: {e}", jar.display()))?;
76        out.push((
77            format!("{}/{mc}/{}.jar", yog_ids::archive::SCRIPTS, meta.id),
78            bytes,
79        ));
80        yog_logging::info!("scripts for {mc} (Java {java})");
81    }
82    Ok(out)
83}
84
85/// The mod's `.ys` sources (name, text), those the build generates
86/// (`obj/generated/**`: the content's ids, the resources) among them. A
87/// `.yns` (nanites) isn't built for the JVM this way.
88pub(crate) fn sources(root: &Path) -> Result<Vec<(String, String)>, String> {
89    let generated = crate::files::of(&root.join(yog_ids::project::GENERATED), Format::Script);
90    crate::files::of(&root.join(SOURCES_DIR), Format::Script)
91        .into_iter()
92        .chain(generated)
93        .map(|f| {
94            let text = std::fs::read_to_string(&f).map_err(|e| format!("{}: {e}", f.display()))?;
95            Ok((crate::shown_path(root, &f), text))
96        })
97        .collect()
98}
99
100/// The game versions the SDK's loaders run, each with its Java.
101fn versions(resolver: &yog_home::targets::Resolver) -> Result<BTreeMap<String, u32>, String> {
102    let mut out = BTreeMap::new();
103    for t in resolver.targets()? {
104        if let (Some(mc), Some(java)) = (t.variant.clone(), t.java()) {
105            out.insert(mc, java);
106        }
107    }
108    if out.is_empty() {
109        return Err("no game version to build the Yog Script for: no loader SDK".into());
110    }
111    Ok(out)
112}