Skip to main content

minecraft_java_rs_core/utils/
platform.rs

1use serde::{Deserialize, Serialize};
2
3// ── Rule types (used here and re-exported via models::minecraft in Step 3) ──
4
5#[derive(Debug, Clone, Deserialize, Serialize)]
6pub struct LibraryRule {
7    pub action: RuleAction,
8    pub os: Option<OsRule>,
9    /// Feature flags (e.g. `has_custom_resolution`). We don't evaluate them;
10    /// rules that carry features are skipped conservatively.
11    pub features: Option<serde_json::Value>,
12}
13
14#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
15#[serde(rename_all = "lowercase")]
16pub enum RuleAction {
17    Allow,
18    Disallow,
19}
20
21#[derive(Debug, Clone, Deserialize, Serialize)]
22pub struct OsRule {
23    pub name: Option<String>,
24    pub version: Option<String>,
25    pub arch: Option<String>,
26}
27
28// ── Platform detection ───────────────────────────────────────────────────────
29
30/// OS name in Mojang's format: `"windows"`, `"osx"`, `"linux"`.
31/// macOS is `"macos"` in Rust but `"osx"` in all Mojang JSON.
32pub fn mojang_os() -> &'static str {
33    match std::env::consts::OS {
34        "macos" => "osx",
35        other => other, // "windows" and "linux" pass through unchanged
36    }
37}
38
39/// CPU architecture in the format used by Mojang native classifiers.
40///
41/// Rust's `std::env::consts::ARCH` already produces `"x86"`, `"x86_64"`,
42/// `"aarch64"`, `"arm"` — these align with Mojang's native strings.
43/// Anything unrecognised defaults to `"x86_64"` (the overwhelmingly common case).
44pub fn mojang_arch() -> &'static str {
45    match std::env::consts::ARCH {
46        "x86" => "x86",
47        "aarch64" => "aarch64",
48        "arm" => "arm",
49        _ => "x86_64",
50    }
51}
52
53// ── Library rule evaluation ──────────────────────────────────────────────────
54
55/// Returns `true` if the library should be **skipped** on the current platform.
56///
57/// Implements the Mojang rule semantics from `skipLibrary` in the original JS:
58/// - No rules → never skip.
59/// - Rules are evaluated in order; the last matching rule wins.
60/// - `action = allow`    → include the library.
61/// - `action = disallow` → exclude the library.
62/// - Rules with `features` are ignored (not yet modelled).
63/// - A rule with no `os` clause matches every platform.
64pub fn skip_library(rules: &[LibraryRule]) -> bool {
65    if rules.is_empty() {
66        return false;
67    }
68
69    let current_os = mojang_os();
70    let mut should_skip = true;
71
72    for rule in rules {
73        // Skip feature-gated rules — we don't model feature flags yet.
74        if rule.features.is_some() {
75            continue;
76        }
77
78        let os_matches = match &rule.os {
79            None => true,
80            Some(os_rule) => os_rule.name.as_deref() == Some(current_os),
81        };
82
83        if os_matches {
84            should_skip = matches!(rule.action, RuleAction::Disallow);
85        }
86    }
87
88    should_skip
89}
90
91#[cfg(test)]
92mod tests {
93    use super::*;
94
95    fn allow(os: Option<&str>) -> LibraryRule {
96        LibraryRule {
97            action: RuleAction::Allow,
98            os: os.map(|n| OsRule {
99                name: Some(n.to_string()),
100                version: None,
101                arch: None,
102            }),
103            features: None,
104        }
105    }
106
107    fn disallow(os: Option<&str>) -> LibraryRule {
108        LibraryRule {
109            action: RuleAction::Disallow,
110            os: os.map(|n| OsRule {
111                name: Some(n.to_string()),
112                version: None,
113                arch: None,
114            }),
115            features: None,
116        }
117    }
118
119    #[test]
120    fn no_rules_means_include() {
121        assert!(!skip_library(&[]));
122    }
123
124    #[test]
125    fn allow_all_includes_on_every_os() {
126        // allow with no os clause → matches all platforms
127        assert!(!skip_library(&[allow(None)]));
128    }
129
130    #[test]
131    fn last_rule_wins() {
132        // allow then disallow(all) → skip
133        assert!(skip_library(&[allow(None), disallow(None)]));
134        // disallow then allow(all) → include
135        assert!(!skip_library(&[disallow(None), allow(None)]));
136    }
137
138    #[test]
139    fn os_specific_rule_only_matches_that_os() {
140        // A disallow rule for a different OS should not affect the current platform.
141        let other_os = if mojang_os() == "linux" { "windows" } else { "linux" };
142        let rules = vec![allow(None), disallow(Some(other_os))];
143        // allow(all) set should_skip=false, then disallow(other) doesn't match → still false
144        assert!(!skip_library(&rules));
145    }
146
147    #[test]
148    fn mojang_os_is_not_macos() {
149        // Ensure macOS is mapped to "osx" in Mojang format
150        if std::env::consts::OS == "macos" {
151            assert_eq!(mojang_os(), "osx");
152        }
153    }
154}