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

21
Cargo.lock generated
View file

@ -94,6 +94,15 @@ dependencies = [
"byteorder 1.2.7 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "bincode"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"byteorder 1.2.7 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.84 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "bitflags"
version = "0.7.0"
@ -1224,6 +1233,7 @@ dependencies = [
name = "rojo"
version = "0.5.0-alpha.0"
dependencies = [
"bincode 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
"clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)",
"csv 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
"env_logger 0.5.13 (registry+https://github.com/rust-lang/crates.io-index)",
@ -1251,6 +1261,16 @@ dependencies = [
name = "rojo-e2e"
version = "0.1.0"
[[package]]
name = "rojo-plugin-moonscript"
version = "0.1.0"
dependencies = [
"bincode 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.84 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_derive 1.0.84 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_json 1.0.34 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "rouille"
version = "2.2.0"
@ -1868,6 +1888,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum backtrace-sys 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)" = "797c830ac25ccc92a7f8a7b9862bde440715531514594a6154e3d4a54dd769b6"
"checksum base64 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "621fc7ecb8008f86d7fb9b95356cd692ce9514b80a86d85b397f32a22da7b9e2"
"checksum base64 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)" = "489d6c0ed21b11d038c31b6ceccca973e65d73ba3bd8ecb9a2babf5546164643"
"checksum bincode 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9f2fb9e29e72fd6bc12071533d5dc7664cb01480c59406f656d7ac25c7bd8ff7"
"checksum bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "aad18937a628ec6abcd26d1489012cc0e18c21798210f491af69ded9b881106d"
"checksum bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "228047a76f468627ca71776ecdebd732a3423081fcf5125585bcd7c49886ce12"
"checksum brotli-sys 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "4445dea95f4c2b41cde57cc9fee236ae4dbae88d8fcbdb4750fc1bb5d86aaecd"

View file

@ -2,4 +2,5 @@
members = [
"server",
"rojo-e2e",
"rojo-plugin-moonscript",
]

View file

@ -0,0 +1,11 @@
[package]
name = "rojo-plugin-moonscript"
version = "0.1.0"
authors = ["Lucien Greathouse <me@lpghatguy.com>"]
edition = "2018"
[dependencies]
serde = "1.0"
serde_derive = "1.0"
serde_json = "1.0"
bincode = "1.0"

View file

@ -0,0 +1,80 @@
use std::{
collections::HashMap,
io::{self, Write},
path::PathBuf,
process::{Command, Stdio},
};
use serde_derive::{Serialize, Deserialize};
#[derive(Debug, Deserialize)]
struct InputFile {
path: PathBuf,
contents: Vec<u8>,
}
#[derive(Debug, Serialize)]
struct Metadata {
ignore_unknown_instances: bool,
}
#[derive(Debug, Serialize)]
#[serde(tag = "Type")]
enum Property {
#[serde(rename_all = "PascalCase")]
String {
value: String,
},
}
#[derive(Debug, Serialize)]
struct Instance {
name: String,
class_name: String,
properties: HashMap<String, Property>,
children: Vec<()>,
metadata: Metadata,
}
fn main() -> io::Result<()> {
let file: InputFile = bincode::deserialize_from(io::stdin())
.expect("Invalid input");
let mut child = Command::new("moonc")
.arg("--")
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn()?;
{
let stdin = child.stdin.as_mut().unwrap();
stdin.write_all(&file.contents)?;
}
let result = child.wait_with_output()?;
let script_name = file.path.file_stem().unwrap().to_string_lossy().into_owned();
let compiled = String::from_utf8_lossy(&result.stdout).into_owned();
let mut properties = HashMap::new();
properties.insert("Source".to_owned(), Property::String {
value: compiled,
});
let output = Instance {
name: script_name,
class_name: "ModuleScript".to_owned(),
properties,
children: Vec::new(),
metadata: Metadata {
ignore_unknown_instances: false,
},
};
let response = serde_json::to_string(&output)
.expect("Couldn't serialize to JSON");
print!("{}", response);
Ok(())
}

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>,

View file

@ -0,0 +1,6 @@
{
"name": "moonscript",
"tree": {
"$path": "src"
}
}

View file

@ -0,0 +1 @@
func_a = -> print "Hello, world!"