|
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