Skip to main content

lux_lib/lua_version/
mod.rs

1use std::{
2    fmt::Display,
3    path::{Path, PathBuf},
4    str::FromStr,
5};
6
7use serde::{Deserialize, Serialize};
8use thiserror::Error;
9
10use crate::{
11    config::Config,
12    package::{PackageVersion, PackageVersionReq},
13};
14
15#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
16pub enum LuaVersion {
17    #[serde(rename = "5.1")]
18    Lua51,
19    #[serde(rename = "5.2")]
20    Lua52,
21    #[serde(rename = "5.3")]
22    Lua53,
23    #[serde(rename = "5.4")]
24    Lua54,
25    #[serde(rename = "5.5")]
26    Lua55,
27    #[serde(rename = "jit")]
28    LuaJIT,
29    #[serde(rename = "jit5.2")]
30    LuaJIT52,
31    // TODO(vhyrro): Support luau?
32    // LuaU,
33}
34
35#[derive(Debug, Error)]
36pub enum LuaVersionError {
37    #[error("unsupported Lua version: {0}")]
38    UnsupportedLuaVersion(PackageVersion),
39}
40
41impl LuaVersion {
42    pub fn as_version(&self) -> PackageVersion {
43        unsafe {
44            match self {
45                LuaVersion::Lua51 => "5.1.0".parse().unwrap_unchecked(),
46                LuaVersion::Lua52 => "5.2.0".parse().unwrap_unchecked(),
47                LuaVersion::Lua53 => "5.3.0".parse().unwrap_unchecked(),
48                LuaVersion::Lua54 => "5.4.0".parse().unwrap_unchecked(),
49                LuaVersion::Lua55 => "5.5.0".parse().unwrap_unchecked(),
50                LuaVersion::LuaJIT => "5.1.0".parse().unwrap_unchecked(),
51                LuaVersion::LuaJIT52 => "5.2.0".parse().unwrap_unchecked(),
52            }
53        }
54    }
55    pub fn version_compatibility_str(&self) -> String {
56        match self {
57            LuaVersion::Lua51 | LuaVersion::LuaJIT => "5.1".into(),
58            LuaVersion::Lua52 | LuaVersion::LuaJIT52 => "5.2".into(),
59            LuaVersion::Lua53 => "5.3".into(),
60            LuaVersion::Lua54 => "5.4".into(),
61            LuaVersion::Lua55 => "5.5".into(),
62        }
63    }
64    pub fn as_version_req(&self) -> PackageVersionReq {
65        unsafe {
66            format!("~> {}", self.version_compatibility_str())
67                .parse()
68                .unwrap_unchecked()
69        }
70    }
71
72    /// Get the LuaVersion from a version that has been parsed from the `lua -v` output
73    pub fn from_version(version: PackageVersion) -> Result<LuaVersion, LuaVersionError> {
74        // NOTE: Special case. luajit -v outputs 2.x.y as a version
75        let luajit_version_req: PackageVersionReq = unsafe { "~> 2".parse().unwrap_unchecked() };
76        if luajit_version_req.matches(&version) {
77            Ok(LuaVersion::LuaJIT)
78        } else if LuaVersion::Lua51.as_version_req().matches(&version) {
79            Ok(LuaVersion::Lua51)
80        } else if LuaVersion::Lua52.as_version_req().matches(&version) {
81            Ok(LuaVersion::Lua52)
82        } else if LuaVersion::Lua53.as_version_req().matches(&version) {
83            Ok(LuaVersion::Lua53)
84        } else if LuaVersion::Lua54.as_version_req().matches(&version) {
85            Ok(LuaVersion::Lua54)
86        } else if LuaVersion::Lua55.as_version_req().matches(&version) {
87            Ok(LuaVersion::Lua55)
88        } else {
89            Err(LuaVersionError::UnsupportedLuaVersion(version))
90        }
91    }
92
93    pub(crate) fn is_luajit(&self) -> bool {
94        matches!(self, Self::LuaJIT | Self::LuaJIT52)
95    }
96
97    /// Searches for the path to the lux-lua library for this version
98    pub fn lux_lib_dir(&self) -> Option<PathBuf> {
99        option_env!("LUX_LIB_DIR")
100            .map(PathBuf::from)
101            .map(|path| path.join(self.to_string()))
102            .or_else(|| {
103                let lib_name = format!("lux-lua{self}");
104                pkg_config::Config::new()
105                    .print_system_libs(false)
106                    .cargo_metadata(false)
107                    .env_metadata(false)
108                    .probe(&lib_name)
109                    .ok()
110                    .and_then(|library| library.link_paths.first().cloned())
111            })
112            .or_else(|| lux_lib_resource_dir().map(|path| path.join(self.to_string())))
113    }
114}
115
116#[derive(Error, Debug)]
117#[error(
118    r#"lua version not set.
119Please provide a version through `lx --lua-version <ver> <cmd>`
120Valid versions are: '5.1', '5.2', '5.3', '5.4', '5.5', 'jit' and 'jit52'.
121"#
122)]
123pub struct LuaVersionUnset;
124
125impl LuaVersion {
126    pub fn from(config: &Config) -> Result<&Self, LuaVersionUnset> {
127        config.lua_version().ok_or(LuaVersionUnset)
128    }
129}
130
131impl FromStr for LuaVersion {
132    type Err = String;
133
134    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
135        match s {
136            "5.1" | "51" => Ok(LuaVersion::Lua51),
137            "5.2" | "52" => Ok(LuaVersion::Lua52),
138            "5.3" | "53" => Ok(LuaVersion::Lua53),
139            "5.4" | "54" => Ok(LuaVersion::Lua54),
140            "5.5" | "55" => Ok(LuaVersion::Lua55),
141            "jit" | "luajit" => Ok(LuaVersion::LuaJIT),
142            "jit52" | "luajit52" => Ok(LuaVersion::LuaJIT52),
143            _ => Err(r#"unrecognized Lua version.
144                 Supported versions: '5.1', '5.2', '5.3', '5.4', '5.5', 'jit', 'jit52'.
145                 "#
146            .into()),
147        }
148    }
149}
150
151impl Display for LuaVersion {
152    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
153        f.write_str(match self {
154            LuaVersion::Lua51 => "5.1",
155            LuaVersion::Lua52 => "5.2",
156            LuaVersion::Lua53 => "5.3",
157            LuaVersion::Lua54 => "5.4",
158            LuaVersion::Lua55 => "5.5",
159            LuaVersion::LuaJIT => "jit",
160            LuaVersion::LuaJIT52 => "jit52",
161        })
162    }
163}
164
165/// Searches for the lux-lib directory in a binary distribution's resources
166fn lux_lib_resource_dir() -> Option<PathBuf> {
167    if cfg!(target_env = "msvc") {
168        // The msvc .exe and .msi binary installers install lux-lua to the executable's directory.
169        std::env::current_exe()
170            .ok()
171            .and_then(|exe_path| exe_path.parent().map(Path::to_path_buf))
172            .and_then(|exe_dir| {
173                let lib_dir = exe_dir.join("lux-lua");
174                if lib_dir.is_dir() {
175                    Some(lib_dir)
176                } else {
177                    None
178                }
179            })
180    } else if cfg!(target_os = "macos") {
181        // Currently, we only bundle resources with an .app ApplicationBundle
182        std::env::current_exe()
183            .ok()
184            .and_then(|exe_path| exe_path.parent().map(Path::to_path_buf))
185            .and_then(|macos_dir| macos_dir.parent().map(Path::to_path_buf))
186            .and_then(|contents_dir| {
187                let lib_dir = contents_dir.join("Resources").join("lux-lua");
188                if lib_dir.is_dir() {
189                    Some(lib_dir)
190                } else {
191                    None
192                }
193            })
194    } else {
195        // .deb and AppImage packages
196        let lib_dir = PathBuf::from("/usr/share/lux-lua");
197        if lib_dir.is_dir() {
198            Some(lib_dir)
199        } else {
200            None
201        }
202    }
203}