use std::fs::read_to_string;
use std::path::PathBuf;
use std::process::Command;
use std::rc::Rc;
use anyhow::{anyhow, Context, Result};
use mlua::{Chunk, IntoLuaMulti, Lua, MultiValue, Table, Value};
use crate::get_project_root;
use crate::submodules::build::Step;
use crate::submodules::sdk::toml_strings::REPOSITORY_NAME;
use crate::submodules::sdk::{get_sdk_path, InstalledPackage};
use crate::submodules::sdkmanager::ToId;
use super::api::fs::load_fs_table;
use super::api::labt::load_labt_table;
use super::api::log::load_log_table;
use super::api::prompt::load_prompt_table;
use super::api::sys::load_sys_table;
use super::api::zip::load_zip_table;
use super::api::MluaAnyhowWrapper;
use super::config::{SdkEntry, CHANNEL, PATH, VERSION};
use super::get_installed_list_hash;
const PREFIX: &str = "sdk:";
pub struct Executable {}
impl Executable {
pub fn main(&self) -> anyhow::Result<()> {
Ok(())
}
}
pub struct ExecutableLua {
build_step: Step,
lua: Lua,
path: PathBuf,
package_paths: String,
sdk: Rc<Vec<SdkEntry>>,
}
impl<'lua, 'a> ExecutableLua {
pub fn new(
path: PathBuf,
package_paths: &[PathBuf],
sdk: Rc<Vec<SdkEntry>>,
unsafe_mode: bool,
) -> Self {
let lua = if unsafe_mode {
unsafe { Lua::unsafe_new() }
} else {
Lua::new()
};
let paths: String = package_paths
.iter()
.filter_map(|p| p.to_str())
.collect::<Vec<&str>>()
.join(";");
ExecutableLua {
lua,
path,
build_step: Step::PRE,
package_paths: paths,
sdk,
}
}
pub fn load(&'lua self) -> Result<Chunk<'lua, 'a>> {
let lua_string =
read_to_string(&self.path).context(format!("Failed to read {:?}", self.path))?;
let chunk = self
.lua
.load(lua_string)
.set_name(self.path.to_str().unwrap_or("[unknown]"));
let globs = &self.lua.globals();
let package: Table = globs
.get("package")
.context("Failed to get package table from lua global context")?;
let package_path: String = package
.get("path")
.context("Failed to get package.path from lua global context")?;
let package_path = package_path.trim_end_matches(';').to_string();
let package_path = [package_path, self.package_paths.clone()].join(";");
package
.set("path", package_path)
.context("Failed to set package.path in lua global context")?;
Ok(chunk)
}
pub fn get_build_step(&self) -> Step {
self.build_step
}
pub fn set_build_step(&mut self, stage: Step) {
self.build_step = stage;
}
fn get_package_directory(package: &InstalledPackage) -> anyhow::Result<PathBuf> {
if let Some(dir) = &package.directory {
return Ok(dir.clone());
}
let mut sdk = get_sdk_path()?;
sdk.push(&package.repository_name);
let sdk = sdk.join(package.path.split(';').collect::<PathBuf>());
Ok(sdk)
}
fn exec_sdk_command(
lua: &'lua Lua,
args: MultiValue,
_package: &InstalledPackage,
dir: PathBuf,
) -> mlua::Result<MultiValue<'lua>> {
let mut cmd = Command::new(dir);
cmd.current_dir(
get_project_root()
.context("Failed to get project root.")
.map_err(MluaAnyhowWrapper::external)?,
);
for arg in args {
cmd.arg(arg.to_string()?);
}
let status = cmd.status()?;
(status.success(), status.code()).into_lua_multi(lua)
}
fn exec_sdk_command_with_output(
lua: &'lua Lua,
args: MultiValue,
_package: &InstalledPackage,
dir: PathBuf,
) -> mlua::Result<MultiValue<'lua>> {
let mut cmd = Command::new(dir);
cmd.current_dir(
get_project_root()
.context("Failed to get project root.")
.map_err(MluaAnyhowWrapper::external)?,
);
for arg in args {
cmd.arg(arg.to_string()?);
}
let out = cmd.output()?;
let stdout = String::from_utf8_lossy(&out.stdout).to_string();
let stderr = String::from_utf8_lossy(&out.stderr).to_string();
(out.status.success(), stdout, stderr).into_lua_multi(lua)
}
fn build_sdk_module(
lua: &'lua Lua,
module: String,
id: String,
sdk: Table<'lua>,
) -> mlua::Result<Table<'lua>> {
let meta = lua.create_table()?;
let installed_list = get_installed_list_hash().map_err(MluaAnyhowWrapper::external)?;
let module = if module.starts_with(PREFIX) {
module.strip_prefix(PREFIX).unwrap()
} else {
module.as_str()
};
let segments: PathBuf = module
.split('/')
.skip(1)
.map(|s| s.trim_matches('.'))
.collect();
let package = if let Some(package) = installed_list.get(&id) {
package
} else {
return Err(mlua::Error::external(format!(
"Trying to index a package \"{}\" that is not installed.",
id
)));
};
let dir = Self::get_package_directory(package)
.context(format!(
"Failed to obtain sdk install directory for package: {}",
package.to_id()
))
.map_err(MluaAnyhowWrapper::external)?
.join(segments);
let fdir = dir.clone();
let file_function = lua.create_function(move |_lua, name: String| {
let mut fdir = fdir.clone(); if name.contains('/') || name.contains('\\') {
return Err(mlua::Error::external(format!(
"Invalid symbols contained in file name {}.",
name
)));
}
let name = name.trim_matches('.');
fdir.push(name);
if !fdir.exists() {
return Err(mlua::Error::external("File not found."));
}
Ok(fdir.to_string_lossy().to_string())
})?;
let exec = lua.create_function(move |lua, (_table, key): (Table, String)| {
if key.contains('/') || key.contains('\\') {
return Err(mlua::Error::external(format!(
"Invalid symbols contained in key {}.",
key
)));
}
let dir = dir.clone();
lua.create_function(move |lua, args: MultiValue| {
let key = key.clone();
let mut dir = dir.clone();
let (key, needs_output) = if key.starts_with("get_") {
(key.strip_prefix("get_").unwrap(), true)
} else {
(key.as_str(), false)
};
dir.push(key);
if !dir.exists() {
return Err(mlua::Error::external("Command not found."));
}
if needs_output {
Self::exec_sdk_command_with_output(lua, args, package, dir)
} else {
Self::exec_sdk_command(lua, args, package, dir)
}
})
})?;
sdk.set("file", file_function)?;
meta.set("__index", exec)?;
sdk.set_metatable(Some(meta));
Ok(sdk)
}
pub fn load_sdk_loader(&mut self) -> Result<()> {
let installed_list = get_installed_list_hash()?;
let sdk = Rc::clone(&self.sdk);
let sdk_resolver = self
.lua
.create_function(move |lua, module: String| -> mlua::Result<Value>{
if module.starts_with(PREFIX) {
let module = module.strip_prefix(PREFIX).unwrap();
let module = if let Some((main, _)) = module.split_once("/") {
main
} else {
module
};
if let Some(sdk) = sdk.iter().find(|s| s.name.eq(module)).cloned() {
if let Some(package) = installed_list.get(&sdk.to_id()) {
Ok(
Value::Function(lua.create_function(move |lua, module:String | {
let table = lua.create_table()?;
table.set("name", sdk.name.clone())?;
table.set(REPOSITORY_NAME, package.repository_name.to_string())?;
table.set(VERSION, package.version.to_string())?;
table.set(PATH, package.path.clone())?;
table.set(CHANNEL, package.channel.to_string())?;
Self::build_sdk_module(lua, module, package.to_id(), table)
})?),
)
} else {
Err(MluaAnyhowWrapper::external(anyhow!(
"Sdk package {} is not installed.",
sdk.to_id()
)))
}
} else {
Err(MluaAnyhowWrapper::external(anyhow!(format!("Trying to require undeclared sdk ({}) in plugin.toml",
module))))
}
} else {
Ok(
Value::String(lua.create_string("\n\tNot a sdk module name. Prefix with 'sdk:' if you intended to load a LABt android sdk module.")?)
)
}
})
.context("Failed to create sdk loader function for lua package.searchers")?;
let package: Table = self
.lua
.globals()
.raw_get("package")
.context("Failed to get lua package table.")?;
let loaders: Table = package
.raw_get("loaders")
.context("Failed to get lua searchers table.")?;
loaders
.push(sdk_resolver)
.context("Failed to push sdk loader to \"package.searchers\".")?;
Ok(())
}
pub fn add_function(&mut self) {}
pub fn load_api_tables(&mut self) -> Result<()> {
load_labt_table(&mut self.lua).context("Failed to add labt table into lua context")?;
load_fs_table(&mut self.lua).context("Failed to add fs table into lua context")?;
load_log_table(&mut self.lua).context("Failed to add log table into lua context")?;
load_zip_table(&mut self.lua).context("Failed to add zip table into lua context")?;
load_sys_table(&mut self.lua).context("Failed to add sys table into lua context")?;
load_prompt_table(&mut self.lua).context("Failed to add prompt table into lua context")?;
Ok(())
}
pub fn get_lua(&self) -> &Lua {
&self.lua
}
}