Prototype server plugins, add example MoonScript plugin

This commit is contained in:
Lucien Greathouse 2019-01-18 22:18:52 -08:00
parent 6fcb895d70
commit 4b9fc137f8
9 changed files with 189 additions and 2 deletions

View file

@ -20,6 +20,7 @@ default = []
bundle-plugin = []
[dependencies]
bincode = "1.0"
clap = "2.27"
csv = "1.0"
env_logger = "0.5"

View file

@ -3,6 +3,7 @@ use std::{
collections::HashMap,
fmt,
path::{Path, PathBuf},
process::{Command, Stdio},
str,
};
@ -33,6 +34,13 @@ use crate::{
rbx_session::{MetadataPerPath, MetadataPerInstance},
};
// In a real plugin implementation, this would be part of the project file.
// Should the executable name be limited to try to prevent malicious projects
// from executing arbitrary code?
static PLUGINS: [(&str, &str); 1] = [
("moon", "target/debug/rojo-plugin-moonscript"),
];
const INIT_MODULE_NAME: &str = "init.lua";
const INIT_SERVER_NAME: &str = "init.server.lua";
const INIT_CLIENT_NAME: &str = "init.client.lua";
@ -246,7 +254,65 @@ fn snapshot_imfs_file<'source>(
Some("txt") => snapshot_txt_file(file)?,
Some("rbxmx") => snapshot_xml_model_file(file)?,
Some("rbxm") => snapshot_binary_model_file(file)?,
Some(_) | None => return Ok(None),
// In a real plugin implementation, plugins would probably want to
// execute before any of the builtin instance routines.
//
// Alternatively, maybe we want to run the builtin routines and send the
// snapshot generated by that routine as well as the original file? That
// would open the door to make plugins that transform instances to do
// things like add source headers or optimize code without having to
// create another kind of plugin.
Some(unknown_ext) => {
let mut result = None;
for (plugin_ext, plugin_path) in &PLUGINS {
if plugin_ext == &unknown_ext {
let mut child = Command::new(plugin_path)
// This argument is standard for telling processes to
// read from stdin, we can pass it as a courtesy even
// though I bet all plugin processes will be
// purpose-built to correctly deal with structured input
// from Rojo
.arg("--")
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn()
.expect("Could not spawn plugin process");
{
let stdin = child.stdin.as_mut().unwrap();
// We're using bincode here since it makes Vec<u8>
// serialize more cleanly than JSON, but we could use
// whatever.
bincode::serialize_into(stdin, file)
.expect("Could not serialize file into plugin stdin");
}
let output = child.wait_with_output()
.expect("Could not wait for plugin process output");
assert!(output.status.success());
// I'm accepting JSON for the output because bincode doesn't
// implement all of the features needed to actually
// deserialize a RbxSnapshotInstance I guess.
//
// Whatever format is used for plugins, it should probably
// be consistent between input/output.
//
// This also doesn't account for representing failure or a
// plugin returning no instance at all.
let snapshot: RbxSnapshotInstance<'static> = serde_json::from_slice(&output.stdout)
.expect("Could not deserialize plugin output");
result = Some(snapshot);
break;
}
}
result
},
None => return Ok(None),
};
if let Some(snapshot) = maybe_snapshot.as_mut() {

View file

@ -55,7 +55,7 @@ impl InstanceChanges {
}
}
#[derive(Debug)]
#[derive(Debug, Deserialize)]
pub struct RbxSnapshotInstance<'a> {
pub name: Cow<'a, str>,
pub class_name: Cow<'a, str>,