minutus_mruby_build_utils/
lib.rs

1use anyhow::{anyhow, Result};
2use std::path::{Path, PathBuf};
3
4/// Helper for building and linking libmruby.
5pub struct MRubyManager {
6    workdir: Option<PathBuf>,
7    mruby_version: Option<String>,
8    do_link: bool,
9    build_config: Option<PathBuf>,
10    do_download: bool,
11}
12
13impl MRubyManager {
14    /// Construct a new instance of a blank set of configuration.
15    /// This builder is finished with the [run][`MRubyManager::run()`] function.
16    pub fn new() -> Self {
17        Self {
18            workdir: None,
19            mruby_version: None,
20            do_link: true,
21            build_config: None,
22            do_download: true,
23        }
24    }
25
26    /// Set workdir. The default is `"OUT_DIR"` environment variable.
27    pub fn workdir(mut self, path: &Path) -> Self {
28        self.workdir = Some(path.to_path_buf());
29        self
30    }
31
32    /// Set mruby version.
33    pub fn mruby_version(mut self, mruby_version: &str) -> Self {
34        self.mruby_version = Some(mruby_version.to_string());
35        self
36    }
37
38    /// Set custom `build_config.rb`. If not set, the builder uses mruby's default config.
39    pub fn build_config(mut self, build_config: &Path) -> Self {
40        self.build_config = Some(build_config.to_path_buf());
41        self
42    }
43
44    /// Whether the builder should build/link `libmruby.a` or not. The default is `true`.
45    ///
46    /// If set to `false`, builder does not build nor link libmruby. So you have to do it by yourself.
47    ///
48    /// If you embed mruby into your Rust project, this should be `true`.
49    pub fn link(mut self, doit: bool) -> Self {
50        self.do_link = doit;
51        self
52    }
53
54    /// Whether the builder should internally download mruby source code or not. The default is `true`.
55    ///
56    /// If set to `false` you have to place `$OUT_DIR/mruby` by yourself.
57    pub fn download(mut self, doit: bool) -> Self {
58        self.do_download = doit;
59        self
60    }
61
62    /// Run the task.
63    pub fn run(self) {
64        let workdir = self.workdir.unwrap_or_else(|| {
65            let out_dir = std::env::var("OUT_DIR")
66                .expect("Could not fetch \"OUT_DIR\" environment variable.");
67            Path::new(&out_dir).to_path_buf()
68        });
69        let mruby_version = self
70            .mruby_version
71            .map(String::from)
72            .expect("mruby_version is not set.");
73        let build_config = self
74            .build_config
75            .unwrap_or(Path::new("default").to_path_buf()); // see: https://github.com/mruby/mruby/blob/3.2.0/doc/guides/compile.md#build
76
77        if self.do_download {
78            download_mruby(&workdir, &mruby_version);
79        }
80        build_mruby(&workdir, &build_config);
81
82        if self.do_link {
83            link_mruby(&workdir);
84        }
85    }
86}
87
88fn build_mruby(workdir: &Path, path: &Path) {
89    let c = &[
90        "rake",
91        "all",
92        &format!("MRUBY_CONFIG={}", path.to_string_lossy()),
93    ];
94    run_command(&workdir.join("mruby"), c).unwrap();
95}
96
97fn link_mruby(workdir: &Path) {
98    let mruby_config = workdir.join("mruby").join("bin").join("mruby-config");
99    let ldflags_before_libs = run_command(
100        workdir,
101        &[mruby_config.to_str().unwrap(), "--ldflags-before-libs"],
102    )
103    .unwrap();
104    let ldflags = run_command(workdir, &[mruby_config.to_str().unwrap(), "--ldflags"]).unwrap();
105    let libs = run_command(workdir, &[mruby_config.to_str().unwrap(), "--libs"]).unwrap();
106    println!(
107        "cargo:rustc-flags={} {} {}",
108        ldflags_before_libs.trim(),
109        ldflags.trim(),
110        libs.trim()
111    );
112
113    // For build on environments where `-Wl,--as-needed` is the default.
114    if cc::Build::new()
115        .is_flag_supported("-Wl,--no-as-needed")
116        .unwrap()
117    {
118        println!("cargo:rustc-link-arg=-Wl,--no-as-needed");
119        println!("cargo:rustc-link-arg=-lmruby");
120    }
121}
122
123/// Downloads mruby source code from github.
124pub fn download_mruby(workdir: &Path, mruby_version: &str) {
125    if workdir.join("mruby").exists() {
126        return;
127    }
128
129    let url = if mruby_version == "master" {
130        String::from("https://github.com/mruby/mruby/archive/refs/heads/master.tar.gz")
131    } else {
132        format!(
133            "https://github.com/mruby/mruby/archive/refs/tags/{}.tar.gz",
134            mruby_version
135        )
136    };
137
138    let resp = reqwest::blocking::get(url).unwrap();
139    let tar_gz = resp.bytes().unwrap();
140    let tar = {
141        use bytes::Buf;
142        flate2::read::GzDecoder::new(tar_gz.reader())
143    };
144    let mut archive = tar::Archive::new(tar);
145    archive.unpack(&workdir).unwrap();
146
147    std::fs::rename(
148        workdir.join(format!("mruby-{}", mruby_version)),
149        workdir.join("mruby"),
150    )
151    .unwrap();
152}
153
154fn run_command(current_dir: &Path, cmd: &[&str]) -> Result<String> {
155    println!("Start: {:?}", cmd);
156
157    let output = std::process::Command::new(cmd[0])
158        .args(&cmd[1..])
159        .current_dir(current_dir)
160        .output()?;
161
162    if output.status.success() {
163        Ok(String::from_utf8_lossy(&output.stdout).to_string())
164    } else {
165        Err(anyhow!(format!(
166            "Executing {:?} failed: {}, {}",
167            cmd,
168            String::from_utf8_lossy(&output.stdout),
169            String::from_utf8_lossy(&output.stderr)
170        )))
171    }
172}