1use quote::quote;
2
3#[proc_macro]
18pub fn include_fs(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
19 let input = syn::parse_macro_input!(input as syn::LitStr);
20
21 let Ok(out_dir) = std::env::var("OUT_DIR") else {
22 return quote! { compile_error!("OUT_DIR not set") }.into();
23 };
24
25 let mut input_value = input.value();
26
27 let not_found_err = format!(
28 "Bundle does not exist, did you add `include_fs::bundle(..., \"{}\")` to your build script?",
29 input.value(),
30 );
31
32 input_value.push_str(".embed_fs");
33
34 let bundle_path = std::path::Path::new(&input_value);
35 if bundle_path.is_absolute() {
36 return syn::Error::new_spanned(input, "Bundle path must be relative")
37 .into_compile_error()
38 .into();
39 }
40
41 let Ok(bundle_path) = std::path::Path::new(&out_dir)
42 .join(input_value)
43 .canonicalize()
44 else {
45 return syn::Error::new_spanned(input, not_found_err)
46 .into_compile_error()
47 .into();
48 };
49
50 if !bundle_path.starts_with(&out_dir) {
51 return syn::Error::new_spanned(input, "Bundle path can not escape OUT_DIR")
52 .into_compile_error()
53 .into();
54 }
55
56 if !bundle_path.exists() {
57 return syn::Error::new_spanned(input, not_found_err)
58 .into_compile_error()
59 .into();
60 }
61
62 let include_path = bundle_path
63 .to_str()
64 .expect("bundle path is not valid unicode");
65
66 quote! {
67 std::sync::LazyLock::new(|| {
68 let archived_bytes: &[u8] = include_bytes!(#include_path);
69 include_fs::IncludeFsInner::new(archived_bytes)
70 .expect("Failed to initialize IncludeFs")
71 })
72 }
73 .into()
74}