makepad/libs/relax/native_macros/build.rs

105 lines
6 KiB
Rust

use relax_lex::tokens::{Delimiter, Span, TokenStream, TokenTree};
use std::path::PathBuf;
#[path = "../build_sources.rs"]
mod build_sources;
#[path = "src/native_cfg.rs"]
mod native_cfg;
// Package, Makepad path, and (for proc-macro wrappers) the generated module.
const PACKAGES: &[(&str, &str, Option<&str>)] = &[
("makepad_micro_proc_macro", "libs/micro_proc_macro", None),
("makepad_live_id", "libs/live_id", None),
("makepad_live_id_macros", "libs/live_id/id_macros", Some("id")),
("makepad_live_id_macros_impl", "libs/live_id/id_macros_impl", None),
("makepad_micro_serde_derive", "libs/micro_serde/derive", Some("serde")),
("makepad_micro_serde_derive_impl", "libs/micro_serde/derive_impl", None),
("makepad_script_derive", "platform/script/derive", Some("script")),
("makepad_script_derive_impl", "platform/script/derive_impl", None),
("makepad_derive_widget", "widgets/derive_widget", Some("widget")),
("makepad_derive_widget_impl", "widgets/derive_widget_impl", None),
("makepad_relax_api", "libs/relax_api", None),
("makepad_relax_api_macro", "libs/relax_api/macro", Some("relax_api")),
("makepad_relax_api_macro_impl", "libs/relax_api/macro_impl", None),
];
fn ident(token: Option<&TokenTree>, name: &str) -> bool {
matches!(token, Some(TokenTree::Ident(i)) if i.to_string() == name)
}
/// Adapt the actual wrapper source at every build. Only the proc-macro annotations and
/// token import change; wrapper bodies cannot drift from the embedded source identity.
fn bindings(source: &[u8], krate: &str, module: &str, entries: &mut String) -> String {
let mut flat = Vec::new();
relax_lex::lexer::lex(source, 2021, &mut flat).unwrap_or_else(|_| panic!("cannot lex proc-macro wrapper in {krate}"));
let stream = relax_lex::tokens::from_toks(source, &flat, &|_, _| Span::DUMMY).expect("parse proc-macro wrapper");
let tokens: Vec<_> = stream.into_iter().collect();
let mut adapted = Vec::new();
let mut i = 0;
while i < tokens.len() {
if matches!(&tokens[i], TokenTree::Punct(p) if p.as_char() == '#') {
if let Some(TokenTree::Group(attr)) = tokens.get(i + 1) {
if attr.delimiter() == Delimiter::Bracket {
let first = attr.stream().into_iter().next();
if ["proc_macro", "proc_macro_derive", "proc_macro_attribute"].iter().any(|name| ident(first.as_ref(), name)) {
assert!(ident(tokens.get(i + 2), "pub") && ident(tokens.get(i + 3), "fn"), "unsupported proc-macro wrapper in {krate}");
let Some(TokenTree::Ident(fname)) = tokens.get(i + 4) else { panic!("missing proc-macro name in {krate}") };
let kind = if ident(first.as_ref(), "proc_macro_attribute") { "Attribute" } else { "Unary" };
let declaration_name = format!("declaration_{fname}");
let declaration = if tokens.windows(2).any(|pair| ident(pair.first(), "fn") && ident(pair.get(1), &declaration_name)) {
format!("Some(Function::{kind}({module}::{declaration_name}))")
} else { "None".into() };
entries.push_str(&format!("Entry {{ krate: {krate:?}, fname: {:?}, function: Function::{kind}({module}::{fname}), declaration: {declaration} }},\n", fname.to_string()));
i += 2;
continue;
}
}
}
}
if ident(tokens.get(i), "extern") && ident(tokens.get(i + 1), "crate") && ident(tokens.get(i + 2), "proc_macro") {
assert!(matches!(tokens.get(i + 3), Some(TokenTree::Punct(p)) if p.as_char() == ';'), "unsupported proc_macro import in {krate}");
i += 4;
continue;
}
if ident(tokens.get(i), "fn") && matches!(tokens.get(i + 1), Some(TokenTree::Ident(name)) if name.to_string().starts_with("declaration_")) {
adapted.push(TokenTree::Ident(relax_lex::tokens::Ident::new("pub", Span::DUMMY)));
}
adapted.push(tokens[i].clone());
i += 1;
}
let adapted: TokenStream = adapted.into_iter().collect();
format!("mod {module} {{ use makepad_micro_proc_macro::proc_macro; {adapted} }}\n")
}
fn main() {
println!("cargo:rerun-if-changed=build.rs");
println!("cargo:rerun-if-changed=../build_sources.rs");
println!("cargo:rerun-if-changed=src/native_cfg.rs");
let makepad = PathBuf::from(std::env::var_os("CARGO_MANIFEST_DIR").unwrap()).join("../../..").canonicalize().expect("Makepad checkout");
let out = PathBuf::from(std::env::var_os("OUT_DIR").unwrap());
let mut packages = String::from("pub static PACKAGES: &[(&str, &[(&str, &[u8])])] = &[\n");
let mut wrappers = String::from("use super::{Entry, Function};\n");
let mut entries = String::new();
let mut divergent = Vec::new();
for (name, relative, module) in PACKAGES {
let root = makepad.join(relative);
packages.push_str(&format!("({name:?}, &[\n"));
let files = build_sources::package(&root);
divergent.extend(native_cfg::check_package(name, &files));
for (path, data) in files {
packages.push_str(&format!("({path:?}, include_bytes!({:?})),\n", root.join(&path)));
if path == "src/lib.rs" {
if let Some(module) = module {
wrappers.push_str(&bindings(&data, name, module, &mut entries));
}
}
}
packages.push_str("]),\n");
}
packages.push_str("];\n");
// source identity certifies behaviour only when no cfg differs between this copy and rustc's
assert!(divergent.is_empty(), "native proc-macro sources would expand differently under relax than under cargo:\n{}", divergent.join("\n"));
wrappers.push_str(&format!("pub static ENTRIES: &[Entry] = &[\n{entries}];\n"));
std::fs::write(out.join("sources.rs"), packages).expect("write native source identity");
std::fs::write(out.join("bindings.rs"), wrappers).expect("write native wrappers");
}