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
use anyhow::{anyhow, Result};
use std::path::Path;

fn run_command(current_dir: &Path, cmd: &[&str]) -> Result<String> {
    println!("Start: {:?}", cmd);

    let output = std::process::Command::new(cmd[0])
        .args(&cmd[1..])
        .current_dir(current_dir)
        .output()?;

    if output.status.success() {
        Ok(String::from_utf8_lossy(&output.stdout).to_string())
    } else {
        Err(anyhow!(format!("Executing {:?} failed", cmd)))
    }
}

pub struct MRubyBuilder<'a> {
    pub base_dir: &'a Path,
    pub mruby_version: String,
}

impl<'a> MRubyBuilder<'a> {
    pub fn link_mruby(&self) -> Result<()> {
        self.internal_link_mruby(None)
    }

    pub fn link_mruby_with_build_config(&self, build_config_path: &Path) -> Result<()> {
        self.internal_link_mruby(Some(build_config_path))
    }

    fn internal_link_mruby(&self, build_config_path: Option<&Path>) -> Result<()> {
        let mruby_config = self.base_dir.join("mruby").join("bin").join("mruby-config");
        if let Some(path) = build_config_path {
            let c = &[
                "rake",
                "all",
                &format!("MRUBY_CONFIG={}", path.to_string_lossy()),
            ];
            run_command(&self.base_dir.join("mruby"), c)?;
        } else {
            let c = &["rake", "all"];
            run_command(&self.base_dir.join("mruby"), c)?;
        };

        let ldflags_before_libs = run_command(
            self.base_dir,
            &[mruby_config.to_str().unwrap(), "--ldflags-before-libs"],
        )?;
        let ldflags = run_command(
            self.base_dir,
            &[mruby_config.to_str().unwrap(), "--ldflags"],
        )?;
        let libs = run_command(self.base_dir, &[mruby_config.to_str().unwrap(), "--libs"])?;
        println!(
            "cargo:rustc-flags={} {} {}",
            ldflags_before_libs.trim(),
            ldflags.trim(),
            libs.trim()
        );
        Ok(())
    }

    pub fn download_mruby(&self) -> Result<()> {
        if self.base_dir.join("mruby").exists() {
            return Ok(());
        }

        let url = if self.mruby_version == "master" {
            String::from("https://github.com/mruby/mruby/archive/refs/heads/master.tar.gz")
        } else {
            format!(
                "https://github.com/mruby/mruby/archive/refs/tags/{}.tar.gz",
                self.mruby_version
            )
        };

        let resp = reqwest::blocking::get(url)?;
        let tar_gz = resp.bytes()?;
        let tar = {
            use bytes::Buf;
            flate2::read::GzDecoder::new(tar_gz.reader())
        };
        let mut archive = tar::Archive::new(tar);
        archive.unpack(&self.base_dir)?;

        std::fs::rename(
            self.base_dir.join(format!("mruby-{}", self.mruby_version)),
            self.base_dir.join("mruby"),
        )?;

        Ok(())
    }
}