|
1 |
| -use std::convert::Infallible; |
2 | 1 | use std::path::{Path, PathBuf};
|
3 | 2 | use std::sync::mpsc::Receiver;
|
4 |
| -use std::thread::JoinHandle; |
5 | 3 | use std::{collections::HashSet, sync::mpsc::sync_channel};
|
6 | 4 |
|
7 |
| -use notify::{Event, RecommendedWatcher, RecursiveMode, Watcher as _}; |
| 5 | +use notify::{Event, RecommendedWatcher, RecursiveMode, Watcher}; |
8 | 6 | use rustc_codegen_spirv_types::CompileResult;
|
9 | 7 |
|
10 | 8 | use crate::{SpirvBuilder, SpirvBuilderError, leaf_deps};
|
11 | 9 |
|
12 | 10 | impl SpirvBuilder {
|
13 |
| - /// Watches the module for changes using [`notify`], rebuilding it upon changes. |
14 |
| - /// |
15 |
| - /// Calls `on_compilation_finishes` after each successful compilation. |
16 |
| - /// The second `Option<AcceptFirstCompile<T>>` param allows you to return some `T` |
17 |
| - /// on the first compile, which is then returned by this function |
18 |
| - /// in pair with [`JoinHandle`] to the watching thread. |
19 |
| - pub fn watch<T>( |
20 |
| - &self, |
21 |
| - mut on_compilation_finishes: impl FnMut(CompileResult, Option<AcceptFirstCompile<'_, T>>) |
22 |
| - + Send |
23 |
| - + 'static, |
24 |
| - ) -> Result<Watch<T>, SpirvBuilderError> { |
25 |
| - let path_to_crate = self |
26 |
| - .path_to_crate |
27 |
| - .as_ref() |
28 |
| - .ok_or(SpirvBuilderError::MissingCratePath)?; |
29 |
| - if !matches!(self.print_metadata, crate::MetadataPrintout::None) { |
30 |
| - return Err(SpirvBuilderError::WatchWithPrintMetadata); |
31 |
| - } |
32 |
| - |
33 |
| - let metadata_result = crate::invoke_rustc(self); |
34 |
| - // Load the dependencies of the thing |
35 |
| - let metadata_file = if let Ok(path) = metadata_result { |
36 |
| - path |
37 |
| - } else { |
38 |
| - // Fall back to watching from the crate root if the initial compilation fails |
39 |
| - // This is likely to notice changes in the `target` dir, however, given that `cargo watch` doesn't seem to handle that, |
40 |
| - let mut watcher = Watcher::new(); |
41 |
| - watcher |
42 |
| - .watcher |
43 |
| - .watch(path_to_crate, RecursiveMode::Recursive) |
44 |
| - .expect("Could watch crate root"); |
45 |
| - loop { |
46 |
| - watcher.recv(); |
47 |
| - let metadata_file = crate::invoke_rustc(self); |
48 |
| - if let Ok(f) = metadata_file { |
49 |
| - break f; |
50 |
| - } |
51 |
| - } |
52 |
| - }; |
53 |
| - let metadata = self.parse_metadata_file(&metadata_file)?; |
54 |
| - let mut first_compile = None; |
55 |
| - on_compilation_finishes(metadata, Some(AcceptFirstCompile(&mut first_compile))); |
56 |
| - |
57 |
| - let builder = self.clone(); |
58 |
| - let watch_thread = std::thread::spawn(move || { |
59 |
| - let mut watcher = Watcher::new(); |
60 |
| - watcher.watch_leaf_deps(&metadata_file); |
61 |
| - |
62 |
| - loop { |
63 |
| - watcher.recv(); |
64 |
| - let metadata_result = crate::invoke_rustc(&builder); |
65 |
| - if let Ok(file) = metadata_result { |
66 |
| - let metadata = builder |
67 |
| - .parse_metadata_file(&file) |
68 |
| - .expect("Metadata file is correct"); |
69 |
| - watcher.watch_leaf_deps(&metadata_file); |
70 |
| - on_compilation_finishes(metadata, None); |
71 |
| - } |
72 |
| - } |
73 |
| - }); |
74 |
| - |
75 |
| - Ok(Watch { |
76 |
| - first_compile, |
77 |
| - watch_thread, |
78 |
| - }) |
79 |
| - } |
80 |
| -} |
81 |
| - |
82 |
| -pub struct AcceptFirstCompile<'a, T>(&'a mut Option<T>); |
83 |
| - |
84 |
| -impl<'a, T> AcceptFirstCompile<'a, T> { |
85 |
| - pub fn new(write: &'a mut Option<T>) -> Self { |
86 |
| - Self(write) |
87 |
| - } |
88 |
| - |
89 |
| - pub fn submit(self, t: T) { |
90 |
| - *self.0 = Some(t); |
| 11 | + /// Watches the module for changes, rebuilding it upon them. |
| 12 | + pub fn watch(&self) -> Result<SpirvWatcher<&Self>, SpirvBuilderError> { |
| 13 | + SpirvWatcher::new(self) |
91 | 14 | }
|
92 | 15 | }
|
93 | 16 |
|
94 |
| -/// Result of [watching](SpirvBuilder::watch) a module for changes. |
95 |
| -#[must_use] |
96 |
| -#[non_exhaustive] |
97 |
| -pub struct Watch<T> { |
98 |
| - /// Result of the first compile, if any. |
99 |
| - pub first_compile: Option<T>, |
100 |
| - /// Join handle to the watching thread. |
101 |
| - /// |
102 |
| - /// You can drop it to detach the watching thread, |
103 |
| - /// or [`join()`](JoinHandle::join) it to block the current thread until shutdown of the program. |
104 |
| - pub watch_thread: JoinHandle<Infallible>, |
105 |
| -} |
106 |
| - |
107 |
| -struct Watcher { |
| 17 | +#[derive(Debug)] |
| 18 | +pub struct SpirvWatcher<B> { |
| 19 | + builder: B, |
108 | 20 | watcher: RecommendedWatcher,
|
109 | 21 | rx: Receiver<()>,
|
| 22 | + watch_path: PathBuf, |
110 | 23 | watched_paths: HashSet<PathBuf>,
|
| 24 | + first_result: bool, |
111 | 25 | }
|
112 | 26 |
|
113 |
| -impl Watcher { |
114 |
| - fn new() -> Self { |
| 27 | +impl<B> SpirvWatcher<B> |
| 28 | +where |
| 29 | + B: AsRef<SpirvBuilder>, |
| 30 | +{ |
| 31 | + fn new(as_builder: B) -> Result<Self, SpirvBuilderError> { |
| 32 | + let builder = as_builder.as_ref(); |
| 33 | + let path_to_crate = builder |
| 34 | + .path_to_crate |
| 35 | + .as_ref() |
| 36 | + .ok_or(SpirvBuilderError::MissingCratePath)?; |
| 37 | + if !matches!(builder.print_metadata, crate::MetadataPrintout::None) { |
| 38 | + return Err(SpirvWatcherError::WatchWithPrintMetadata.into()); |
| 39 | + } |
| 40 | + |
115 | 41 | let (tx, rx) = sync_channel(0);
|
116 | 42 | let watcher =
|
117 |
| - notify::recommended_watcher(move |event: notify::Result<Event>| match event { |
118 |
| - Ok(e) => match e.kind { |
119 |
| - notify::EventKind::Access(_) => (), |
| 43 | + notify::recommended_watcher(move |result: notify::Result<Event>| match result { |
| 44 | + Ok(event) => match event.kind { |
120 | 45 | notify::EventKind::Any
|
121 | 46 | | notify::EventKind::Create(_)
|
122 | 47 | | notify::EventKind::Modify(_)
|
123 | 48 | | notify::EventKind::Remove(_)
|
124 | 49 | | notify::EventKind::Other => {
|
125 |
| - let _ = tx.try_send(()); |
| 50 | + if let Err(err) = tx.try_send(()) { |
| 51 | + log::error!("send error: {err:?}"); |
| 52 | + } |
126 | 53 | }
|
| 54 | + notify::EventKind::Access(_) => {} |
127 | 55 | },
|
128 |
| - Err(e) => println!("notify error: {e:?}"), |
| 56 | + Err(err) => log::error!("notify error: {err:?}"), |
129 | 57 | })
|
130 |
| - .expect("Could create watcher"); |
131 |
| - Self { |
| 58 | + .map_err(SpirvWatcherError::NotifyFailed)?; |
| 59 | + |
| 60 | + Ok(Self { |
| 61 | + watch_path: path_to_crate.clone(), |
| 62 | + builder: as_builder, |
132 | 63 | watcher,
|
133 | 64 | rx,
|
134 | 65 | watched_paths: HashSet::new(),
|
| 66 | + first_result: false, |
| 67 | + }) |
| 68 | + } |
| 69 | + |
| 70 | + pub fn recv(&mut self) -> Result<CompileResult, SpirvBuilderError> { |
| 71 | + if !self.first_result { |
| 72 | + return self.recv_first_result(); |
135 | 73 | }
|
| 74 | + |
| 75 | + self.rx.recv().expect("watcher should be alive"); |
| 76 | + let builder = self.builder.as_ref(); |
| 77 | + let metadata_file = crate::invoke_rustc(builder)?; |
| 78 | + let result = builder.parse_metadata_file(&metadata_file)?; |
| 79 | + |
| 80 | + self.watch_leaf_deps(&self.watch_path.clone())?; |
| 81 | + Ok(result) |
136 | 82 | }
|
137 | 83 |
|
138 |
| - fn watch_leaf_deps(&mut self, metadata_file: &Path) { |
| 84 | + fn recv_first_result(&mut self) -> Result<CompileResult, SpirvBuilderError> { |
| 85 | + let builder = self.builder.as_ref(); |
| 86 | + let metadata_file = match crate::invoke_rustc(builder) { |
| 87 | + Ok(path) => path, |
| 88 | + Err(err) => { |
| 89 | + log::error!("{err}"); |
| 90 | + |
| 91 | + self.watcher |
| 92 | + .watch(&self.watch_path, RecursiveMode::Recursive) |
| 93 | + .map_err(SpirvWatcherError::NotifyFailed)?; |
| 94 | + let path = loop { |
| 95 | + self.rx.recv().expect("watcher should be alive"); |
| 96 | + match crate::invoke_rustc(builder) { |
| 97 | + Ok(path) => break path, |
| 98 | + Err(err) => log::error!("{err}"), |
| 99 | + } |
| 100 | + }; |
| 101 | + self.watcher |
| 102 | + .unwatch(&self.watch_path) |
| 103 | + .map_err(SpirvWatcherError::NotifyFailed)?; |
| 104 | + path |
| 105 | + } |
| 106 | + }; |
| 107 | + let result = builder.parse_metadata_file(&metadata_file)?; |
| 108 | + |
| 109 | + self.watch_leaf_deps(&metadata_file)?; |
| 110 | + self.watch_path = metadata_file; |
| 111 | + self.first_result = true; |
| 112 | + Ok(result) |
| 113 | + } |
| 114 | + |
| 115 | + fn watch_leaf_deps(&mut self, metadata_file: &Path) -> Result<(), SpirvBuilderError> { |
139 | 116 | leaf_deps(metadata_file, |it| {
|
140 | 117 | let path = it.to_path().unwrap();
|
141 |
| - if self.watched_paths.insert(path.to_owned()) { |
142 |
| - self.watcher |
| 118 | + if self.watched_paths.insert(path.to_owned()) |
| 119 | + && let Err(err) = self |
| 120 | + .watcher |
143 | 121 | .watch(it.to_path().unwrap(), RecursiveMode::NonRecursive)
|
144 |
| - .expect("Cargo dependencies are valid files"); |
| 122 | + { |
| 123 | + log::error!("files of cargo dependencies are not valid: {err}"); |
145 | 124 | }
|
146 | 125 | })
|
147 |
| - .expect("Could read dependencies file"); |
| 126 | + .map_err(SpirvBuilderError::MetadataFileMissing) |
148 | 127 | }
|
| 128 | +} |
149 | 129 |
|
150 |
| - fn recv(&self) { |
151 |
| - self.rx.recv().expect("Watcher still alive"); |
| 130 | +impl SpirvWatcher<&SpirvBuilder> { |
| 131 | + pub fn forget_lifetime(self) -> SpirvWatcher<SpirvBuilder> { |
| 132 | + SpirvWatcher { |
| 133 | + builder: self.builder.clone(), |
| 134 | + watcher: self.watcher, |
| 135 | + rx: self.rx, |
| 136 | + watch_path: self.watch_path, |
| 137 | + watched_paths: self.watched_paths, |
| 138 | + first_result: self.first_result, |
| 139 | + } |
152 | 140 | }
|
153 | 141 | }
|
| 142 | + |
| 143 | +#[derive(Debug, thiserror::Error)] |
| 144 | +pub enum SpirvWatcherError { |
| 145 | + #[error("watching within build scripts will prevent build completion")] |
| 146 | + WatchWithPrintMetadata, |
| 147 | + #[error("could not notify for changes: {0}")] |
| 148 | + NotifyFailed(#[from] notify::Error), |
| 149 | +} |
0 commit comments