include_fs_macros/
lib.rs

1use quote::quote;
2
3/// Include a bundled directory in the binary.
4/// To use this macro, include a matching call to `include_fs::bundle` in your build script.
5///
6/// # Example
7///
8/// ```rust
9/// // In build.rs
10/// include_fs::bundle("assets", "assets").unwrap();
11/// include_fs::bundle("./static/public", "public").unwrap();
12///
13/// // In main.rs
14/// static ASSETS: IncludeFs = include_fs!("assets");
15/// static PUBLIC: IncludeFs = include_fs!("public");
16/// ```
17#[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}