transarch_macro/
lib.rs

1use std::{
2    env::current_dir,
3    fs::{copy, create_dir, create_dir_all, read_dir, write},
4    path::PathBuf,
5    process::{Command, Stdio},
6    sync::Mutex,
7};
8
9use proc_macro::TokenStream;
10
11extern crate proc_macro;
12
13const CARGO_TOML_CONTENTS: &str = r#"[package]
14name = "transarch-tmp-pkg"
15version = "0.1.0"
16edition = "2021"
17
18[lib]
19crate-type = ["cdylib", "rlib"]
20
21[workspace]
22members = []
23"#;
24
25static mut COMP_BLOB_ID: Mutex<usize> = Mutex::new(0);
26
27fn target_dir() -> PathBuf {
28    current_dir()
29        .unwrap()
30        .ancestors()
31        .find(|x| x.join("Cargo.toml").is_file() && x.join("target").is_dir())
32        .map(|x| x.join("target"))
33        .expect("no target dir found. what did you do?")
34}
35
36fn build(tokens: TokenStream, target: Option<String>) -> (PathBuf, String) {
37    let dir = target_dir().join("transarch/transarch-tmp-crate");
38
39    if !dir.exists() {
40        create_dir_all(dir.clone()).expect("setup failed: failed to create <TRANSARCH-ROOT>");
41        create_dir(dir.join("src")).expect("setup failed: failed to create <TRANSARCH-ROOT>/src");
42        write(dir.join("Cargo.toml"), CARGO_TOML_CONTENTS)
43            .expect("setup failed: failed to create <TRANSARCH-ROOT>/Cargo.toml");
44    }
45
46    let mut iter = tokens.into_iter();
47
48    let target = target.unwrap_or_else(|| {
49        iter.next()
50            .and_then(|x| match x {
51                proc_macro::TokenTree::Literal(x) => {
52                    let x = x.to_string();
53                    if !x.starts_with('"') || !x.is_ascii() {
54                        panic!("target must be a string");
55                    }
56
57                    Some(x[1..x.len() - 1].to_string())
58                }
59                _ => None,
60            })
61            .expect("no target provided")
62    });
63
64    write(
65        dir.join("src/lib.rs"),
66        TokenStream::from_iter(iter).to_string(),
67    )
68    .expect("setup failed: failed to create <TRANSARCH-ROOT>/src/lib.rs");
69
70    let mut cmd = Command::new("cargo")
71        .arg("build")
72        .arg(format!("--target={target}"))
73        .arg("--color=always")
74        .stdout(Stdio::inherit())
75        .stderr(Stdio::inherit())
76        .current_dir(&dir)
77        .spawn()
78        .expect("failed to run cargo");
79
80    if !cmd.wait().expect("how").success() {
81        panic!("failed to execute cargo");
82    }
83
84    (dir, target)
85}
86
87#[proc_macro]
88/// Cross-compile into a target and obtain contents of build directory
89/// ```rust
90/// let dir = cross! {
91///     "wasm32-unknown-unknown"
92///     pub fn hi() -> String {
93///         "hi".to_string()
94///     }
95/// };
96/// ```
97pub fn cross(tokens: TokenStream) -> TokenStream {
98    let mut id = unsafe { COMP_BLOB_ID.lock().unwrap() };
99
100    let (dir, target) = build(tokens, None);
101
102    let out_dir = dir.join(format!("target/{target}/debug"));
103
104    let mut buf = String::new();
105
106    fn make(buf: &mut String, path: PathBuf, out_dir: PathBuf, blob_id: &mut usize) {
107        if path.is_dir() {
108            buf.push_str("Into::<::transarch::Dir>::into({");
109            buf.push_str("let mut map=::std::collections::HashMap::new();");
110            for x in read_dir(path).unwrap() {
111                let x = x.unwrap();
112                if x.path().is_dir() {
113                    buf.push_str(&format!(
114                        r#"map.insert("{}",::transarch::Entry::Dir("#,
115                        x.file_name().into_string().unwrap()
116                    ));
117                } else {
118                    buf.push_str(&format!(
119                        r#"map.insert("{}",::transarch::Entry::File("#,
120                        x.file_name().into_string().unwrap()
121                    ));
122                }
123                make(buf, x.path(), out_dir.clone(), blob_id);
124                buf.push_str("));");
125            }
126            buf.push_str("map})");
127            return;
128        }
129
130        let file = out_dir.join(format!("../blob{}", *blob_id));
131        copy(path, &file).expect("your drive is full. do `cargo clean`");
132        *blob_id += 1;
133        buf.push_str(&format!("include_bytes!({:?})", file.display()));
134    }
135
136    make(&mut buf, out_dir.clone(), out_dir, &mut id);
137
138    buf.parse().unwrap()
139}
140
141#[proc_macro]
142/// Build a wasm module. Requires `wasm32-unknown-unknown` target
143/// ```rust
144/// let blob = cross! {
145///     "wasm32-unknown-unknown"
146///     pub fn hi() -> String {
147///         "hi".to_string()
148///     }
149/// };
150/// ```
151pub fn wasm(tokens: TokenStream) -> TokenStream {
152    let mut id = unsafe { COMP_BLOB_ID.lock().unwrap() };
153
154    let (dir, target) = build(tokens, Some("wasm32-unknown-unknown".to_string()));
155
156    let out_dir = dir.join(format!("target/{target}/debug"));
157
158    let file = out_dir.join(format!("../blob{}", *id));
159    copy(out_dir.join("transarch_tmp_pkg.wasm"), &file).expect("copy failed");
160
161    *id += 1;
162
163    format!("include_bytes!({:?})", file.display())
164        .parse()
165        .unwrap()
166}