stak_build/
lib.rs

1//! Build scripts for Stak Scheme.
2//!
3//! See the [`stak`](https://docs.rs/stak) crate's documentation for full examples.
4//!
5//! # Examples
6//!
7//! To build all R7RS Scheme files into bytecodes, add [the following `build.rs` build script](https://doc.rust-lang.org/cargo/reference/build-scripts.html)
8//! in your crate. Then, you can include them into source files in Rust
9//! using the [`stak::include_module`][include_module] macro.
10//!
11//! ```rust no_run
12//! use stak_build::{build_r7rs, BuildError};
13//!
14//! fn main() -> Result<(), BuildError> {
15//!     build_r7rs()
16//! }
17//! ```
18//!
19//! [include_module]: https://docs.rs/stak/latest/stak/macro.include_module.html
20
21extern crate alloc;
22
23mod error;
24
25use alloc::sync::Arc;
26pub use error::BuildError;
27use glob::{Paths, glob};
28use stak_compiler::compile_r7rs;
29use std::{
30    env,
31    ffi::OsStr,
32    path::{Path, PathBuf},
33    process::Stdio,
34};
35use tokio::{
36    fs::{create_dir_all, read, write},
37    io::{AsyncReadExt, AsyncWriteExt},
38    process::Command,
39    runtime::Runtime,
40    spawn,
41};
42use which::which;
43
44/// Builds R7RS Scheme source files into bytecode files.
45///
46/// This function builds all Scheme source files with the `.scm` file extension
47/// under the `src` directory. The resulting bytecode files are stored under the
48/// `target` directory.
49pub fn build_r7rs() -> Result<(), BuildError> {
50    let runtime = Runtime::new()?;
51    let _ = runtime.enter();
52
53    runtime.block_on(build(glob("**/*.scm")?))?;
54
55    Ok(())
56}
57
58async fn build(paths: Paths) -> Result<(), BuildError> {
59    let compiler = which("stak-compile").ok().map(Arc::new);
60
61    if compiler.is_none() {
62        println!("cargo::warning={}",
63            [
64                "Using an internal compiler for Stak Scheme.",
65                "It can be very slow unless you modify `profile.<profile>.build-override` in your `Cargo.toml` file to set `opt-level = 3`.",
66                "For more information, see https://doc.rust-lang.org/cargo/reference/profiles.html#build-dependencies.",
67                "Also, consider installing the external compiler by running `cargo install stak-compile`.",
68            ].join(" ")
69        );
70    }
71
72    let out_directory_variable = env::var("OUT_DIR")?;
73    let out_directory = Path::new(&out_directory_variable);
74
75    let mut handles = vec![];
76
77    for path in paths {
78        let path = path?;
79
80        if path
81            .iter()
82            .any(|component| component == OsStr::new("target"))
83        {
84            continue;
85        }
86
87        println!("cargo::rerun-if-changed={}", path.display());
88
89        let out_path = out_directory.join(&path);
90        handles.push(spawn(compile(path, out_path, compiler.clone())))
91    }
92
93    for handle in handles {
94        handle.await??;
95    }
96
97    Ok(())
98}
99
100async fn compile(
101    src_path: PathBuf,
102    out_path: PathBuf,
103    compiler: Option<Arc<PathBuf>>,
104) -> Result<(), BuildError> {
105    let mut buffer = vec![];
106
107    if let Some(path) = compiler {
108        let mut command = Command::new(&*path)
109            .stdin(Stdio::piped())
110            .stdout(Stdio::piped())
111            .spawn()?;
112        let stdin = command.stdin.as_mut().expect("stdin");
113
114        stdin
115            .write_all(include_str!("prelude.scm").as_bytes())
116            .await?;
117        stdin.write_all(&read(src_path).await?).await?;
118
119        command.wait().await?;
120
121        command
122            .stdout
123            .expect("stdout")
124            .read_to_end(&mut buffer)
125            .await?;
126    } else {
127        compile_r7rs(&*read(&src_path).await?, &mut buffer)?;
128    }
129
130    if let Some(path) = out_path.parent() {
131        create_dir_all(path).await?;
132    }
133
134    write(out_path, &buffer).await?;
135
136    Ok(())
137}