~repos /rust-embed

#rust#proc-macro#http

git clone https://pyrossh.dev/repos/rust-embed.git

rust macro which loads files into the rust binary at compile time during release and loads the file from the fs during dev.


133ce098 Piotr Osiewicz

1 year ago
perf: Do not build glob matchers repeatedly when include-exclude feature is enabled
Files changed (2) hide show
  1. impl/src/lib.rs +16 -11
  2. utils/src/lib.rs +49 -42
impl/src/lib.rs CHANGED
@@ -6,6 +6,7 @@ extern crate proc_macro;
6
6
 
7
7
  use proc_macro::TokenStream;
8
8
  use proc_macro2::TokenStream as TokenStream2;
9
+ use rust_embed_utils::PathMatcher;
9
10
  use std::{
10
11
  collections::BTreeMap,
11
12
  env,
@@ -25,7 +26,8 @@ fn embedded(
25
26
 
26
27
  let includes: Vec<&str> = includes.iter().map(AsRef::as_ref).collect();
27
28
  let excludes: Vec<&str> = excludes.iter().map(AsRef::as_ref).collect();
29
+ let matcher = PathMatcher::new(&includes, &excludes);
28
- for rust_embed_utils::FileEntry { rel_path, full_canonical_path } in rust_embed_utils::get_files(absolute_folder_path.clone(), &includes, &excludes) {
30
+ for rust_embed_utils::FileEntry { rel_path, full_canonical_path } in rust_embed_utils::get_files(absolute_folder_path.clone(), matcher) {
29
31
  match_values.insert(
30
32
  rel_path.clone(),
31
33
  embed_file(relative_folder_path, ident, &rel_path, &full_canonical_path, metadata_only)?,
@@ -125,8 +127,8 @@ fn dynamic(ident: &syn::Ident, folder_path: String, prefix: Option<&str>, includ
125
127
  const EXCLUDES: &[&str] = &[#(#excludes),*];
126
128
  };
127
129
 
128
- // In metadata_only mode, we still need to read file contents to generate the file hash, but
130
+ // In metadata_only mode, we still need to read file contents to generate the
129
- // then we drop the file data.
131
+ // file hash, but then we drop the file data.
130
132
  let strip_contents = metadata_only.then_some(quote! {
131
133
  .map(|mut file| { file.data = ::std::default::Default::default(); file })
132
134
  });
@@ -137,13 +139,18 @@ fn dynamic(ident: &syn::Ident, folder_path: String, prefix: Option<&str>, includ
137
139
  quote! {
138
140
  #[cfg(debug_assertions)]
139
141
  impl #ident {
142
+
143
+
144
+ fn matcher() -> ::rust_embed::utils::PathMatcher {
145
+ #declare_includes
146
+ #declare_excludes
147
+ static PATH_MATCHER: ::std::sync::OnceLock<::rust_embed::utils::PathMatcher> = ::std::sync::OnceLock::new();
148
+ PATH_MATCHER.get_or_init(|| rust_embed::utils::PathMatcher::new(INCLUDES, EXCLUDES)).clone()
149
+ }
140
150
  /// Get an embedded file and its metadata.
141
151
  pub fn get(file_path: &str) -> ::std::option::Option<rust_embed::EmbeddedFile> {
142
152
  #handle_prefix
143
153
 
144
- #declare_includes
145
- #declare_excludes
146
-
147
154
  let rel_file_path = file_path.replace("\\", "/");
148
155
  let file_path = ::std::path::Path::new(#folder_path).join(&rel_file_path);
149
156
 
@@ -162,8 +169,8 @@ fn dynamic(ident: &syn::Ident, folder_path: String, prefix: Option<&str>, includ
162
169
  return ::std::option::Option::None;
163
170
  }
164
171
  }
165
-
172
+ let path_matcher = Self::matcher();
166
- if rust_embed::utils::is_path_included(&rel_file_path, INCLUDES, EXCLUDES) {
173
+ if path_matcher.is_path_included(&rel_file_path) {
167
174
  rust_embed::utils::read_file_from_fs(&canonical_file_path).ok() #strip_contents
168
175
  } else {
169
176
  ::std::option::Option::None
@@ -174,10 +181,8 @@ fn dynamic(ident: &syn::Ident, folder_path: String, prefix: Option<&str>, includ
174
181
  pub fn iter() -> impl ::std::iter::Iterator<Item = ::std::borrow::Cow<'static, str>> {
175
182
  use ::std::path::Path;
176
183
 
177
- #declare_includes
178
- #declare_excludes
179
184
 
180
- rust_embed::utils::get_files(::std::string::String::from(#folder_path), INCLUDES, EXCLUDES)
185
+ rust_embed::utils::get_files(::std::string::String::from(#folder_path), Self::matcher())
181
186
  .map(|e| #map_iter)
182
187
  }
183
188
  }
utils/src/lib.rs CHANGED
@@ -12,47 +12,8 @@ pub struct FileEntry {
12
12
  pub full_canonical_path: String,
13
13
  }
14
14
 
15
- #[cfg(not(feature = "include-exclude"))]
16
- pub fn is_path_included(_path: &str, _includes: &[&str], _excludes: &[&str]) -> bool {
17
- true
18
- }
19
-
20
- #[cfg(feature = "include-exclude")]
21
- pub fn is_path_included(rel_path: &str, includes: &[&str], excludes: &[&str]) -> bool {
22
- use globset::Glob;
23
-
24
- // ignore path matched by exclusion pattern
25
- for exclude in excludes {
26
- let pattern = Glob::new(exclude)
27
- .unwrap_or_else(|_| panic!("invalid exclude pattern '{}'", exclude))
28
- .compile_matcher();
29
-
30
- if pattern.is_match(rel_path) {
31
- return false;
32
- }
33
- }
34
-
35
- // accept path if no includes provided
36
- if includes.is_empty() {
37
- return true;
38
- }
39
-
40
- // accept path if matched by inclusion pattern
41
- for include in includes {
42
- let pattern = Glob::new(include)
43
- .unwrap_or_else(|_| panic!("invalid include pattern '{}'", include))
44
- .compile_matcher();
45
-
46
- if pattern.is_match(rel_path) {
47
- return true;
48
- }
49
- }
50
-
51
- false
52
- }
53
-
54
15
  #[cfg_attr(all(debug_assertions, not(feature = "debug-embed")), allow(unused))]
55
- pub fn get_files<'patterns>(folder_path: String, includes: &'patterns [&str], excludes: &'patterns [&str]) -> impl Iterator<Item = FileEntry> + 'patterns {
16
+ pub fn get_files(folder_path: String, matcher: PathMatcher) -> impl Iterator<Item = FileEntry> {
56
17
  walkdir::WalkDir::new(&folder_path)
57
18
  .follow_links(true)
58
19
  .sort_by_file_name()
@@ -68,8 +29,7 @@ pub fn get_files<'patterns>(folder_path: String, includes: &'patterns [&str], ex
68
29
  } else {
69
30
  rel_path
70
31
  };
71
-
72
- if is_path_included(&rel_path, includes, excludes) {
32
+ if matcher.is_path_included(&rel_path) {
73
33
  Some(FileEntry { rel_path, full_canonical_path })
74
34
  } else {
75
35
  None
@@ -176,3 +136,50 @@ pub fn read_file_from_fs(file_path: &Path) -> io::Result<EmbeddedFile> {
176
136
  fn path_to_str<P: AsRef<std::path::Path>>(p: P) -> String {
177
137
  p.as_ref().to_str().expect("Path does not have a string representation").to_owned()
178
138
  }
139
+
140
+ #[derive(Clone)]
141
+ pub struct PathMatcher {
142
+ #[cfg(feature = "include-exclude")]
143
+ include_matcher: globset::GlobSet,
144
+ #[cfg(feature = "include-exclude")]
145
+ exclude_matcher: globset::GlobSet,
146
+ }
147
+
148
+ #[cfg(feature = "include-exclude")]
149
+ impl PathMatcher {
150
+ pub fn new(includes: &[&str], excludes: &[&str]) -> Self {
151
+ let mut include_matcher = globset::GlobSetBuilder::new();
152
+ for include in includes {
153
+ include_matcher.add(globset::Glob::new(include).unwrap_or_else(|_| panic!("invalid include pattern '{}'", include)));
154
+ }
155
+ let include_matcher = include_matcher
156
+ .build()
157
+ .unwrap_or_else(|_| panic!("Could not compile included patterns matcher"));
158
+
159
+ let mut exclude_matcher = globset::GlobSetBuilder::new();
160
+ for exclude in excludes {
161
+ exclude_matcher.add(globset::Glob::new(exclude).unwrap_or_else(|_| panic!("invalid exclude pattern '{}'", exclude)));
162
+ }
163
+ let exclude_matcher = exclude_matcher
164
+ .build()
165
+ .unwrap_or_else(|_| panic!("Could not compile excluded patterns matcher"));
166
+
167
+ Self {
168
+ include_matcher,
169
+ exclude_matcher,
170
+ }
171
+ }
172
+ pub fn is_path_included(&self, path: &str) -> bool {
173
+ !self.exclude_matcher.is_match(path) && (self.include_matcher.is_empty() || self.include_matcher.is_match(path))
174
+ }
175
+ }
176
+
177
+ #[cfg(not(feature = "include-exclude"))]
178
+ impl PathMatcher {
179
+ pub fn new(_includes: &[&str], _excludes: &[&str]) -> Self {
180
+ Self {}
181
+ }
182
+ pub fn is_path_included(&self, _path: &str) -> bool {
183
+ true
184
+ }
185
+ }