Skip to main content

lux_lib/lua_installation/
mod.rs

1use is_executable::IsExecutable;
2use itertools::Itertools;
3use miette::Diagnostic;
4use path_slash::PathBufExt;
5use std::fmt;
6use std::fmt::Display;
7use std::io;
8use std::path::Path;
9use std::path::PathBuf;
10use thiserror::Error;
11use which::which;
12
13use crate::build::external_dependency::to_lib_name;
14use crate::build::external_dependency::ExternalDependencyInfo;
15use crate::build::utils::{c_lib_extension, format_path};
16use crate::config::external_deps::ExternalDependencySearchConfig;
17use crate::fs;
18use crate::lua_rockspec::ExternalDependencySpec;
19use crate::lua_version::LuaVersion;
20use crate::lua_version::LuaVersionUnset;
21use crate::operations;
22use crate::operations::BuildLuaError;
23use crate::tree::InstallTree;
24use crate::variables::GetVariableError;
25use crate::{config::Config, package::PackageVersion, variables::HasVariables};
26use lazy_static::lazy_static;
27use tokio::sync::Mutex;
28
29// Because installing lua is not thread-safe, we have to synchronize with a global Mutex
30lazy_static! {
31    static ref NEW_MUTEX: Mutex<i32> = Mutex::new(0i32);
32    static ref INSTALL_MUTEX: Mutex<i32> = Mutex::new(0i32);
33}
34
35#[derive(Debug)]
36pub struct LuaInstallation {
37    pub version: LuaVersion,
38    dependency_info: ExternalDependencyInfo,
39    /// Binary to the Lua executable, if present
40    pub(crate) bin: Option<PathBuf>,
41}
42
43#[derive(Debug, Error, Diagnostic)]
44pub enum LuaBinaryError {
45    #[error("neither `lua` nor `luajit` found on the PATH")]
46    LuaBinaryNotFound,
47    #[error("neither `luau` nor `lune` found on the PATH.")]
48    #[diagnostic(
49        help("ensure luau is installed or run `lx install-lua --lua-version luau`"),
50        url("https://github.com/luau-lang/luau/releases")
51    )]
52    LuauBinaryNotFound,
53    #[error(transparent)]
54    #[diagnostic(transparent)]
55    DetectLuaVersion(#[from] DetectLuaVersionError),
56    #[error(
57        r#"
58{} -v (= {}) does not match expected Lua version: {}.
59
60Try setting
61
62```toml
63[variables]
64LUA = "/path/to/lua_binary"
65```
66
67in your config, or use `-v LUA=/path/to/lua_binary`.
68    "#,
69        lua_cmd,
70        installed_version,
71        lua_version
72    )]
73    LuaVersionMismatch {
74        lua_cmd: String,
75        installed_version: PackageVersion,
76        lua_version: LuaVersion,
77    },
78    #[error("{0} not found on the PATH")]
79    CustomBinaryNotFound(String),
80}
81
82#[derive(Error, Debug, Diagnostic)]
83#[non_exhaustive]
84pub enum DetectLuaVersionError {
85    #[error("failed to run {0}: {1}")]
86    RunLuaCommand(String, io::Error),
87    #[error("failed to parse Lua version from output: {0}")]
88    ParseLuaVersion(String),
89    #[error(transparent)]
90    #[diagnostic(transparent)]
91    PackageVersionParse(#[from] crate::package::PackageVersionParseError),
92    #[error(transparent)]
93    #[diagnostic(transparent)]
94    LuaVersion(#[from] crate::lua_version::LuaVersionError),
95}
96
97#[derive(Error, Debug, Diagnostic)]
98#[non_exhaustive]
99pub enum LuaInstallationError {
100    #[error("could not find a Lua installation and failed to build Lua from source")]
101    #[diagnostic(forward(0))]
102    Build(#[from] BuildLuaError),
103    #[error(transparent)]
104    #[diagnostic(transparent)]
105    LuaVersionUnset(#[from] LuaVersionUnset),
106    #[error("`luau` binary not found after building Luau from source")]
107    #[diagnostic(help("this is probably a bug"))]
108    LuauBinaryNotFound,
109}
110
111impl LuaInstallation {
112    #[tracing::instrument(level = "trace", skip_all)]
113    pub async fn new_from_config(config: &Config) -> Result<Self, LuaInstallationError> {
114        Self::new(LuaVersion::from(config)?, config).await
115    }
116
117    #[tracing::instrument(level = "trace", skip(config))]
118    pub async fn new(version: &LuaVersion, config: &Config) -> Result<Self, LuaInstallationError> {
119        let _lock = NEW_MUTEX.lock().await;
120        if let Some(lua_intallation) = Self::probe(version, config.external_deps()) {
121            return Ok(lua_intallation);
122        }
123        if version.is_luau() {
124            if let Ok(bin) = PathBuf::try_from(LuaBinary::new(version.clone(), config)) {
125                return Ok(Self::from_bin_path(version.clone(), bin));
126            }
127            return match Self::luau_from_root_dir(&Self::root_dir(version, config)) {
128                Some(lua_installation) => Ok(lua_installation),
129                None => Self::install(version, config).await,
130            };
131        }
132        let output = Self::root_dir(version, config);
133        let include_dir = output.join("include");
134        let lib_dir = output.join("lib");
135        let lua_lib_name = get_lua_lib_name(&lib_dir, version);
136        if include_dir.is_dir() && lua_lib_name.is_some() {
137            let bin_dir = Some(output.join("bin")).filter(|bin_path| bin_path.is_dir());
138            let bin = bin_dir
139                .as_ref()
140                .and_then(|bin_path| find_lua_executable(bin_path, version));
141            let lib_dir = output.join("lib");
142            let lua_lib_name = get_lua_lib_name(&lib_dir, version);
143            let include_dir = Some(output.join("include"));
144            Ok(LuaInstallation {
145                version: version.clone(),
146                dependency_info: ExternalDependencyInfo {
147                    include_dir,
148                    lib_dir: Some(lib_dir),
149                    bin_dir,
150                    lib_info: None,
151                    lib_name: lua_lib_name,
152                },
153                bin,
154            })
155        } else {
156            Self::install(version, config).await
157        }
158    }
159
160    #[tracing::instrument(level = "trace")]
161    pub(crate) fn probe(
162        version: &LuaVersion,
163        search_config: &ExternalDependencySearchConfig,
164    ) -> Option<Self> {
165        let pkg_name_probes = match version {
166            LuaVersion::Lua51 => vec!["lua5.1", "lua-5.1"],
167            LuaVersion::Lua52 => vec!["lua5.2", "lua-5.2"],
168            LuaVersion::Lua53 => vec!["lua5.3", "lua-5.3"],
169            LuaVersion::Lua54 => vec!["lua5.4", "lua-5.4"],
170            LuaVersion::Lua55 => vec!["lua5.5", "lua-5.5"],
171            LuaVersion::LuaJIT | LuaVersion::LuaJIT52 => vec!["luajit"],
172            // NOTE: lune is a standalone binary with no pkg-config metadata or C API
173            LuaVersion::Luau => vec!["luau"],
174        };
175
176        let mut dependency_info = pkg_name_probes
177            .iter()
178            .map(|pkg_name| {
179                ExternalDependencyInfo::probe(
180                    pkg_name,
181                    &ExternalDependencySpec::default(),
182                    search_config,
183                )
184            })
185            .find_map(Result::ok);
186
187        if let Some(info) = &mut dependency_info {
188            if info.include_dir.is_none() || info.lib_dir.is_none() {
189                return None;
190            }
191            let bin = info.lib_dir.as_ref().and_then(|lib_dir| {
192                lib_dir
193                    .parent()
194                    .map(|parent| parent.join("bin"))
195                    .filter(|dir| dir.is_dir())
196                    .and_then(|bin_path| find_lua_executable(&bin_path, version))
197            });
198            let lua_lib_name = info
199                .lib_dir
200                .as_ref()
201                .and_then(|lib_dir| get_lua_lib_name(lib_dir, version));
202            info.lib_name = lua_lib_name;
203            dependency_info.map(|dependency_info| Self {
204                version: version.clone(),
205                dependency_info,
206                bin,
207            })
208        } else {
209            None
210        }
211    }
212
213    #[tracing::instrument(level = "trace", skip(config))]
214    pub async fn install(
215        version: &LuaVersion,
216        config: &Config,
217    ) -> Result<Self, LuaInstallationError> {
218        let _lock = INSTALL_MUTEX.lock().await;
219
220        let target = Self::root_dir(version, config);
221
222        operations::BuildLua::new()
223            .lua_version(version)
224            .install_dir(&target)
225            .config(config)
226            .build()
227            .await?;
228
229        if version.is_luau() {
230            return Self::luau_from_root_dir(&target)
231                .ok_or(LuaInstallationError::LuauBinaryNotFound);
232        }
233
234        let include_dir = target.join("include");
235        let lib_dir = target.join("lib");
236        let bin_dir = Some(target.join("bin")).filter(|bin_path| bin_path.is_dir());
237        let bin = bin_dir
238            .as_ref()
239            .and_then(|bin_path| find_lua_executable(bin_path, version));
240        let lua_lib_name = get_lua_lib_name(&lib_dir, version);
241        Ok(LuaInstallation {
242            version: version.clone(),
243            dependency_info: ExternalDependencyInfo {
244                include_dir: Some(include_dir),
245                lib_dir: Some(lib_dir),
246                bin_dir,
247                lib_info: None,
248                lib_name: lua_lib_name,
249            },
250            bin,
251        })
252    }
253
254    pub fn includes(&self) -> Vec<&PathBuf> {
255        self.dependency_info.include_dir.iter().collect_vec()
256    }
257
258    pub fn bin(&self) -> &Option<PathBuf> {
259        &self.bin
260    }
261
262    fn luau_from_root_dir(root: &Path) -> Option<Self> {
263        let bin_dir = root.join("bin");
264        let bin = find_lua_executable(&bin_dir, &LuaVersion::Luau)?;
265        Some(Self {
266            version: LuaVersion::Luau,
267            dependency_info: ExternalDependencyInfo {
268                include_dir: None,
269                lib_dir: None,
270                bin_dir: bin.parent().map(Path::to_path_buf),
271                lib_info: None,
272                lib_name: None,
273            },
274            bin: Some(bin),
275        })
276    }
277
278    /// A Lua installation referencing a binary found on the PATH,
279    /// without probing for headers/libraries or building from source.
280    /// Used for installations that don't link against the Lua C API.
281    fn from_bin_path(version: LuaVersion, bin: PathBuf) -> Self {
282        Self {
283            version,
284            dependency_info: ExternalDependencyInfo {
285                include_dir: None,
286                lib_dir: None,
287                bin_dir: bin.parent().map(Path::to_path_buf),
288                lib_info: None,
289                lib_name: None,
290            },
291            bin: Some(bin),
292        }
293    }
294
295    fn root_dir(version: &LuaVersion, config: &Config) -> PathBuf {
296        if let Some(lua_dir) = config.lua_dir() {
297            return lua_dir.clone();
298        } else if let Ok(tree) = config.user_tree(version.clone()) {
299            return tree.root().join(".lua");
300        }
301        config.data_dir().join(".lua").join(version.to_string())
302    }
303
304    #[cfg(not(target_env = "msvc"))]
305    fn lua_lib(&self) -> Option<String> {
306        self.dependency_info
307            .lib_name
308            .as_ref()
309            .map(|name| format!("{}.{}", name, c_lib_extension()))
310    }
311
312    #[cfg(target_env = "msvc")]
313    fn lua_lib(&self) -> Option<String> {
314        self.dependency_info.lib_name.clone()
315    }
316
317    pub(crate) fn define_flags(&self) -> Vec<String> {
318        let mut defines = self.dependency_info.define_flags();
319        if self.version.is_luajit() {
320            defines.push("-DLUAJIT_VERSION".into());
321        }
322        defines
323    }
324
325    pub(crate) fn lib_link_args(&self, compiler: &cc::Tool) -> Vec<String> {
326        self.dependency_info.lib_link_args(compiler)
327    }
328
329    /// Get the Lua binary (if present), prioritising
330    /// a potentially overridden value in the config.
331    pub(crate) fn lua_binary_or_config_override(&self, config: &Config) -> Option<String> {
332        let variable = if self.version.is_luau() {
333            "LUAU"
334        } else {
335            "LUA"
336        };
337        config.variables().get(variable).cloned().or(self
338            .bin
339            .clone()
340            .or(LuaBinary::new(self.version.clone(), config).try_into().ok())
341            .map(|bin| bin.to_slash_lossy().to_string()))
342    }
343}
344
345impl HasVariables for LuaInstallation {
346    #[tracing::instrument(level = "trace")]
347    fn get_variable(&self, input: &str) -> Result<Option<String>, GetVariableError> {
348        Ok(match input {
349            "LUA_INCDIR" => self
350                .dependency_info
351                .include_dir
352                .as_ref()
353                .map(|dir| format_path(dir)),
354            "LUA_LIBDIR" => self
355                .dependency_info
356                .lib_dir
357                .as_ref()
358                .map(|dir| format_path(dir)),
359            "LUA_BINDIR" => self
360                .bin
361                .as_ref()
362                .and_then(|bin| bin.parent().map(format_path)),
363            "LUA" => self
364                .bin
365                .clone()
366                .or(LuaBinary::Lua {
367                    lua_version: self.version.clone(),
368                }
369                .try_into()
370                .ok())
371                .map(|lua| format_path(&lua)),
372            "LUAU" if self.version.is_luau() => self.bin.as_ref().map(|bin| format_path(bin)),
373            "LUALIB" => self.lua_lib().or(Some("".into())),
374            _ => None,
375        })
376    }
377}
378
379#[derive(Clone)]
380pub enum LuaBinary {
381    /// The regular Lua interpreter.
382    Lua { lua_version: LuaVersion },
383    /// Custom Lua interpreter.
384    Custom(String),
385}
386
387impl Display for LuaBinary {
388    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
389        match self {
390            LuaBinary::Lua { lua_version } => write!(f, "lua {lua_version}"),
391            LuaBinary::Custom(cmd) => write!(f, "{cmd}"),
392        }
393    }
394}
395
396impl LuaBinary {
397    /// Construct a new `LuaBinary` for the given `LuaVersion`,
398    /// potentially prioritising an overridden value in the config.
399    pub fn new(lua_version: LuaVersion, config: &Config) -> Self {
400        let variable = if lua_version.is_luau() { "LUAU" } else { "LUA" };
401        match config.variables().get(variable).cloned() {
402            Some(bin) => Self::Custom(bin),
403            None => Self::Lua { lua_version },
404        }
405    }
406}
407
408impl From<PathBuf> for LuaBinary {
409    fn from(value: PathBuf) -> Self {
410        Self::Custom(value.to_string_lossy().to_string())
411    }
412}
413
414impl TryFrom<LuaBinary> for PathBuf {
415    type Error = LuaBinaryError;
416
417    fn try_from(value: LuaBinary) -> Result<Self, Self::Error> {
418        match value {
419            LuaBinary::Lua { lua_version } => {
420                if lua_version.is_luau() {
421                    return which("luau")
422                        .or_else(|_| which("lune"))
423                        .map_err(|_| LuaBinaryError::LuauBinaryNotFound);
424                }
425                if let Some(lua_binary) =
426                    LuaInstallation::probe(&lua_version, &ExternalDependencySearchConfig::default())
427                        .and_then(|lua_installation| lua_installation.bin)
428                {
429                    return Ok(lua_binary);
430                }
431                if lua_version.is_luajit() {
432                    if let Ok(path) = which("luajit") {
433                        return Ok(path);
434                    }
435                }
436                match which(format!("lua{}", lua_version)) {
437                    Ok(path) => Ok(path),
438                    Err(_) => detect_default_lua_bin(lua_version),
439                }
440            }
441            LuaBinary::Custom(bin) => match which(&bin) {
442                Ok(path) => Ok(path),
443                Err(_) => Err(LuaBinaryError::CustomBinaryNotFound(bin)),
444            },
445        }
446    }
447}
448
449fn detect_default_lua_bin(lua_version: LuaVersion) -> Result<PathBuf, LuaBinaryError> {
450    match which("lua") {
451        Ok(path) => {
452            let installed_version = detect_installed_lua_version_from_path(&path)?;
453            if lua_version
454                .clone()
455                .as_version_req()
456                .matches(&installed_version)
457            {
458                Ok(path)
459            } else {
460                Err(LuaBinaryError::LuaVersionMismatch {
461                    lua_cmd: path.to_slash_lossy().to_string(),
462                    installed_version,
463                    lua_version,
464                })?
465            }
466        }
467        Err(_) => Err(LuaBinaryError::LuaBinaryNotFound),
468    }
469}
470
471pub fn detect_installed_lua_version() -> Option<LuaVersion> {
472    which("lua")
473        .ok()
474        .or(which("luajit").ok())
475        .and_then(|lua_cmd| {
476            detect_installed_lua_version_from_path(&lua_cmd)
477                .ok()
478                .and_then(|version| LuaVersion::from_version(version).ok())
479        })
480}
481
482/// Determine the Lua version to use with the `nvim` preset.
483/// Prefers LuaJIT if installed, falling back to Lua 5.1.
484pub fn nvim_lua_version() -> Option<LuaVersion> {
485    if LuaInstallation::probe(&LuaVersion::LuaJIT, &Default::default()).is_some() {
486        Some(LuaVersion::LuaJIT)
487    } else if LuaInstallation::probe(&LuaVersion::Lua51, &Default::default()).is_some() {
488        Some(LuaVersion::Lua51)
489    } else {
490        None
491    }
492}
493
494fn find_lua_executable(bin_path: &Path, version: &LuaVersion) -> Option<PathBuf> {
495    fs::sync::read_dir(bin_path).ok().and_then(|entries| {
496        let bin_files = entries
497            .filter_map(Result::ok)
498            .map(|entry| entry.path().to_path_buf())
499            .collect_vec();
500
501        #[cfg(windows)]
502        let ext = ".exe";
503
504        #[cfg(not(windows))]
505        let ext = "";
506
507        if version.is_luau() {
508            let luau_bin = format!("luau{ext}");
509            return bin_files
510                .into_iter()
511                .filter(|file| {
512                    file.is_executable()
513                        && file
514                            .file_name()
515                            .is_some_and(|name| name.to_string_lossy() == luau_bin)
516                })
517                .collect_vec()
518                .first()
519                .cloned();
520        }
521
522        // Prioritise Lua binaries with version suffix
523        // (see https://github.com/lumen-oss/lux/issues/1215)
524        let lua_version_bin = format!("lua{}{}", version, ext);
525        if let Some(lua_bin) = bin_files
526            .iter()
527            .filter(|file| {
528                file.is_executable()
529                    && file
530                        .file_name()
531                        .is_some_and(|name| name.to_string_lossy() == lua_version_bin)
532            })
533            .collect_vec()
534            .first()
535            .cloned()
536        {
537            Some(lua_bin.clone())
538        } else {
539            // Fall back to Lua binary without version suffix
540            bin_files
541                .into_iter()
542                .filter(|file| {
543                    file.is_executable()
544                        && file.file_name().is_some_and(|name| {
545                            matches!(
546                                name.to_string_lossy().to_string().as_str(),
547                                "lua" | "luajit" | "lua.exe" | "luajit.exe"
548                            )
549                        })
550                })
551                .collect_vec()
552                .first()
553                .cloned()
554        }
555    })
556}
557
558fn is_lua_lib_name(name: &str, lua_version: &LuaVersion) -> bool {
559    let prefixes = match lua_version {
560        LuaVersion::LuaJIT | LuaVersion::LuaJIT52 => vec!["luajit", "lua"],
561        _ => vec!["lua"],
562    };
563    let version_str = lua_version.version_compatibility_str();
564    let version_suffix = version_str.replace(".", "");
565    #[cfg(target_family = "unix")]
566    let name = name.trim_start_matches("lib");
567    prefixes
568        .iter()
569        .any(|prefix| name == format!("{}.{}", *prefix, c_lib_extension()))
570        || prefixes.iter().any(|prefix| name.starts_with(*prefix))
571            && (name.contains(&version_str) || name.contains(&version_suffix))
572}
573
574fn get_lua_lib_name(lib_dir: &Path, lua_version: &LuaVersion) -> Option<String> {
575    fs::sync::read_dir(lib_dir)
576        .ok()
577        .and_then(|entries| {
578            entries
579                .filter_map(Result::ok)
580                .map(|entry| entry.path().to_path_buf())
581                .filter(|file| file.extension().is_some_and(|ext| ext == c_lib_extension()))
582                .filter(|file| {
583                    file.file_name()
584                        .is_some_and(|name| is_lua_lib_name(&name.to_string_lossy(), lua_version))
585                })
586                .collect_vec()
587                .first()
588                .cloned()
589        })
590        .map(|file| to_lib_name(&file))
591}
592
593fn detect_installed_lua_version_from_path(
594    lua_cmd: &Path,
595) -> Result<PackageVersion, DetectLuaVersionError> {
596    let output = match std::process::Command::new(lua_cmd).arg("-v").output() {
597        Ok(output) => Ok(output),
598        Err(err) => Err(DetectLuaVersionError::RunLuaCommand(
599            lua_cmd.to_string_lossy().to_string(),
600            err,
601        )),
602    }?;
603    let output_vec = if output.stderr.is_empty() {
604        output.stdout
605    } else {
606        // Yes, Lua 5.1 prints to stderr (-‸ლ)
607        output.stderr
608    };
609    let lua_output = String::from_utf8_lossy(&output_vec).to_string();
610    parse_lua_version_from_output(&lua_output)
611}
612
613fn parse_lua_version_from_output(
614    lua_output: &str,
615) -> Result<PackageVersion, DetectLuaVersionError> {
616    let lua_version_str = lua_output
617        .trim_start_matches("Lua")
618        .trim_start_matches("JIT")
619        .split_whitespace()
620        .next()
621        .map(|s| s.to_string())
622        .ok_or(DetectLuaVersionError::ParseLuaVersion(
623            lua_output.to_string(),
624        ))?;
625    Ok(PackageVersion::parse(&lua_version_str)?)
626}
627
628#[cfg(test)]
629mod tests {
630    use super::*;
631    #[cfg(feature = "impure_tests")]
632    use flaky_test::flaky_test;
633
634    #[tokio::test]
635    async fn parse_luajit_version() {
636        let luajit_output =
637            "LuaJIT 2.1.1713773202 -- Copyright (C) 2005-2023 Mike Pall. https://luajit.org/";
638        parse_lua_version_from_output(luajit_output).unwrap();
639    }
640
641    #[tokio::test]
642    async fn parse_lua_51_version() {
643        let lua_output = "Lua 5.1.5  Copyright (C) 1994-2012 Lua.org, PUC-Rio";
644        parse_lua_version_from_output(lua_output).unwrap();
645    }
646
647    #[cfg(feature = "impure_tests")]
648    #[flaky_test(tokio, times = 5)]
649    async fn lua_installation_bin() {
650        let config = crate::config::ConfigBuilder::new()
651            .unwrap()
652            .build()
653            .unwrap();
654        let lua_version = config.lua_version().unwrap();
655        let lua_installation = LuaInstallation::new(lua_version, &config).await.unwrap();
656        // FIXME: This fails when run in the nix checkPhase
657        assert!(lua_installation.bin.is_some());
658        let lua_binary: LuaBinary = lua_installation.bin.unwrap().into();
659        let lua_bin_path: PathBuf = lua_binary.try_into().unwrap();
660        let pkg_version = detect_installed_lua_version_from_path(&lua_bin_path).unwrap();
661        assert_eq!(&LuaVersion::from_version(pkg_version).unwrap(), lua_version);
662    }
663
664    #[cfg(not(target_env = "msvc"))]
665    #[tokio::test]
666    async fn test_is_lua_lib_name() {
667        assert!(is_lua_lib_name("lua.a", &LuaVersion::Lua51));
668        assert!(is_lua_lib_name("lua-5.1.a", &LuaVersion::Lua51));
669        assert!(is_lua_lib_name("lua5.1.a", &LuaVersion::Lua51));
670        assert!(is_lua_lib_name("lua51.a", &LuaVersion::Lua51));
671        assert!(!is_lua_lib_name("lua-5.2.a", &LuaVersion::Lua51));
672        assert!(is_lua_lib_name("luajit-5.2.a", &LuaVersion::LuaJIT52));
673        assert!(is_lua_lib_name("lua-5.2.a", &LuaVersion::LuaJIT52));
674        assert!(is_lua_lib_name("liblua.a", &LuaVersion::Lua51));
675        assert!(is_lua_lib_name("liblua-5.1.a", &LuaVersion::Lua51));
676        assert!(is_lua_lib_name("liblua53.a", &LuaVersion::Lua53));
677        assert!(is_lua_lib_name("liblua-54.a", &LuaVersion::Lua54));
678        assert!(is_lua_lib_name("liblua-55.a", &LuaVersion::Lua55));
679    }
680
681    #[cfg(target_env = "msvc")]
682    #[tokio::test]
683    async fn test_is_lua_lib_name() {
684        assert!(is_lua_lib_name("lua.lib", &LuaVersion::Lua51));
685        assert!(is_lua_lib_name("lua-5.1.lib", &LuaVersion::Lua51));
686        assert!(!is_lua_lib_name("lua-5.2.lib", &LuaVersion::Lua51));
687        assert!(!is_lua_lib_name("lua53.lib", &LuaVersion::Lua53));
688        assert!(!is_lua_lib_name("lua53.lib", &LuaVersion::Lua53));
689        assert!(is_lua_lib_name("luajit-5.2.lib", &LuaVersion::LuaJIT52));
690        assert!(is_lua_lib_name("lua-5.2.lib", &LuaVersion::LuaJIT52));
691    }
692}