1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
use std::{
    fs::File,
    io::{BufWriter, Write},
};

use memofs::Vfs;
use thiserror::Error;
use tokio::runtime::Runtime;

use crate::{cli::BuildCommand, serve_session::ServeSession, snapshot::RojoTree};

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum OutputKind {
    Rbxmx,
    Rbxlx,
    Rbxm,
    Rbxl,
}

fn detect_output_kind(options: &BuildCommand) -> Option<OutputKind> {
    let extension = options.output.extension()?.to_str()?;

    match extension {
        "rbxlx" => Some(OutputKind::Rbxlx),
        "rbxmx" => Some(OutputKind::Rbxmx),
        "rbxl" => Some(OutputKind::Rbxl),
        "rbxm" => Some(OutputKind::Rbxm),
        _ => None,
    }
}

#[derive(Debug, Error)]
enum Error {
    #[error("Could not detect what kind of file to build. Expected output file to end in .rbxl, .rbxlx, .rbxm, or .rbxmx.")]
    UnknownOutputKind,
}

fn xml_encode_config() -> rbx_xml::EncodeOptions {
    rbx_xml::EncodeOptions::new().property_behavior(rbx_xml::EncodePropertyBehavior::WriteUnknown)
}

pub fn build(options: BuildCommand) -> Result<(), anyhow::Error> {
    log::trace!("Constructing in-memory filesystem");

    let vfs = Vfs::new_default();
    vfs.set_watch_enabled(options.watch);

    let session = ServeSession::new(vfs, &options.absolute_project())?;
    let mut cursor = session.message_queue().cursor();

    {
        let tree = session.tree();
        write_model(&tree, &options)?;
    }

    if options.watch {
        let mut rt = Runtime::new().unwrap();

        loop {
            let receiver = session.message_queue().subscribe(cursor);
            let (new_cursor, _patch_set) = rt.block_on(receiver).unwrap();
            cursor = new_cursor;

            let tree = session.tree();
            write_model(&tree, &options)?;
        }
    }

    Ok(())
}

fn write_model(tree: &RojoTree, options: &BuildCommand) -> Result<(), anyhow::Error> {
    let output_kind = detect_output_kind(&options).ok_or(Error::UnknownOutputKind)?;
    log::debug!("Hoping to generate file of type {:?}", output_kind);

    let root_id = tree.get_root_id();

    log::trace!("Opening output file for write");
    let file = File::create(&options.output)?;
    let mut file = BufWriter::new(file);

    match output_kind {
        OutputKind::Rbxmx => {
            // Model files include the root instance of the tree and all its
            // descendants.

            rbx_xml::to_writer(&mut file, tree.inner(), &[root_id], xml_encode_config())?;
        }
        OutputKind::Rbxlx => {
            // Place files don't contain an entry for the DataModel, but our
            // RbxTree representation does.

            let root_instance = tree.get_instance(root_id).unwrap();
            let top_level_ids = root_instance.children();

            rbx_xml::to_writer(&mut file, tree.inner(), top_level_ids, xml_encode_config())?;
        }
        OutputKind::Rbxm => {
            rbx_binary::encode(tree.inner(), &[root_id], &mut file)?;
        }
        OutputKind::Rbxl => {
            log::warn!("Support for building binary places (rbxl) is still experimental.");
            log::warn!("Using the XML place format (rbxlx) is recommended instead.");
            log::warn!("For more info, see https://github.com/rojo-rbx/rojo/issues/180");

            let root_instance = tree.get_instance(root_id).unwrap();
            let top_level_ids = root_instance.children();

            rbx_binary::encode(tree.inner(), top_level_ids, &mut file)?;
        }
    }

    file.flush()?;

    let filename = options
        .output
        .file_name()
        .and_then(|name| name.to_str())
        .unwrap_or("<invalid utf-8>");
    log::info!("Built project to {}", filename);

    Ok(())
}