include_wgsl_oil/
lib.rs

1#![doc = include_str!("../README.md")]
2
3mod error;
4mod exports;
5mod files;
6mod imports;
7mod module;
8mod result;
9mod source;
10
11use std::{fs::File, io::Read, path::PathBuf};
12
13use files::AbsoluteRustFilePathBuf;
14use quote::ToTokens;
15use source::Sourcecode;
16use syn::token::Brace;
17
18// Hacky polyfill for `proc_macro::Span::source_file`
19fn find_me(root: &str, pattern: &str) -> Option<PathBuf> {
20    let mut options = Vec::new();
21
22    for path in glob::glob(&std::path::Path::new(root).join("**/*.rs").to_string_lossy())
23        .unwrap()
24        .flatten()
25    {
26        if let Ok(mut f) = File::open(&path) {
27            let mut contents = String::new();
28            f.read_to_string(&mut contents).ok();
29            if contents.contains(pattern) {
30                options.push(path.to_owned());
31            }
32        }
33    }
34
35    match options.as_slice() {
36        [] => None,
37        [v] => Some(v.clone()),
38        _ => panic!(
39            "found more than one contender for macro invocation location. \
40            This won't be an issue once `proc_macro_span` is stabilized, \
41            but until then each instance of the `include_wgsl_oil` \
42            must be present in the source text, and each must have a unique argument. \
43            found locations: {:?}",
44            options
45                .into_iter()
46                .map(|path| format!("`{}`", path.display()))
47                .collect::<Vec<String>>()
48        ),
49    }
50}
51
52#[proc_macro_attribute]
53pub fn include_wgsl_oil(
54    path: proc_macro::TokenStream,
55    module: proc_macro::TokenStream,
56) -> proc_macro::TokenStream {
57    // Parse module definitions and error if it contains anything
58    let mut module = syn::parse_macro_input!(module as syn::ItemMod);
59    if let Some(content) = &mut module.content {
60        if !content.1.is_empty() {
61            let item = syn::parse_quote_spanned! {content.0.span=>
62                compile_error!(
63                    "`include_wgsl_oil` expects an empty module into which to inject the shader objects, \
64                    but found a module body - try removing everything within the curly braces `{ ... }`.");
65            };
66
67            module.content = Some((Brace::default(), vec![item]));
68        }
69    } else {
70        module.content = Some((Brace::default(), vec![]));
71    }
72    module.semi = None;
73
74    let requested_path = syn::parse_macro_input!(path as syn::LitStr);
75    let requested_path = requested_path.value();
76
77    let root = std::env::var("CARGO_MANIFEST_DIR").expect("proc macros should be run using cargo");
78    let invocation_path = match find_me(&root, &format!("\"{}\"", requested_path)) {
79        Some(invocation_path) => AbsoluteRustFilePathBuf::new(invocation_path),
80        None => {
81            panic!(
82                "could not find invocation point - maybe it was in a macro? This won't be an issue once \
83                `proc_macro_span` is stabilized, but until then each instance of the `include_wgsl_oil` \
84                must be present in the source text, and each must have a unique argument."
85            )
86        }
87    };
88
89    let sourcecode = Sourcecode::new(invocation_path, requested_path);
90
91    let mut result = sourcecode.complete();
92
93    result.validate();
94
95    // Inject items
96    module
97        .content
98        .as_mut()
99        .expect("set to some at start")
100        .1
101        .append(&mut result.items());
102
103    module.to_token_stream().into()
104}