Skip to content

Commit f241062

Browse files
committed
Redesign spirv-builder's watch API
1 parent 29ba02d commit f241062

File tree

3 files changed

+129
-125
lines changed

3 files changed

+129
-125
lines changed

crates/spirv-builder/src/lib.rs

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ pub use rustc_codegen_spirv_types::Capability;
9292
pub use rustc_codegen_spirv_types::{CompileResult, ModuleResult};
9393

9494
#[cfg(feature = "watch")]
95-
pub use self::watch::Watch;
95+
pub use self::watch::{SpirvWatcher, SpirvWatcherError};
9696

9797
#[cfg(feature = "include-target-specs")]
9898
pub use rustc_codegen_spirv_target_specs::TARGET_SPEC_DIR_PATH;
@@ -125,14 +125,15 @@ pub enum SpirvBuilderError {
125125
BuildFailed,
126126
#[error("multi-module build cannot be used with print_metadata = MetadataPrintout::Full")]
127127
MultiModuleWithPrintMetadata,
128-
#[error("watching within build scripts will prevent build completion")]
129-
WatchWithPrintMetadata,
130128
#[error("multi-module metadata file missing")]
131129
MetadataFileMissing(#[from] std::io::Error),
132130
#[error("unable to parse multi-module metadata file")]
133131
MetadataFileMalformed(#[from] serde_json::Error),
134132
#[error("cargo metadata error")]
135133
CargoMetadata(#[from] cargo_metadata::Error),
134+
#[cfg(feature = "watch")]
135+
#[error(transparent)]
136+
WatchFailed(#[from] SpirvWatcherError),
136137
}
137138

138139
const SPIRV_TARGET_PREFIX: &str = "spirv-unknown-";
@@ -518,6 +519,12 @@ impl Default for SpirvBuilder {
518519
}
519520
}
520521

522+
impl AsRef<SpirvBuilder> for SpirvBuilder {
523+
fn as_ref(&self) -> &SpirvBuilder {
524+
self
525+
}
526+
}
527+
521528
impl SpirvBuilder {
522529
pub fn new(path_to_crate: impl AsRef<Path>, target: impl Into<String>) -> Self {
523530
Self {

crates/spirv-builder/src/watch.rs

Lines changed: 107 additions & 111 deletions
Original file line numberDiff line numberDiff line change
@@ -1,153 +1,149 @@
1-
use std::convert::Infallible;
21
use std::path::{Path, PathBuf};
32
use std::sync::mpsc::Receiver;
4-
use std::thread::JoinHandle;
53
use std::{collections::HashSet, sync::mpsc::sync_channel};
64

7-
use notify::{Event, RecommendedWatcher, RecursiveMode, Watcher as _};
5+
use notify::{Event, RecommendedWatcher, RecursiveMode, Watcher};
86
use rustc_codegen_spirv_types::CompileResult;
97

108
use crate::{SpirvBuilder, SpirvBuilderError, leaf_deps};
119

1210
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)
9114
}
9215
}
9316

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,
10820
watcher: RecommendedWatcher,
10921
rx: Receiver<()>,
22+
watch_path: PathBuf,
11023
watched_paths: HashSet<PathBuf>,
24+
first_result: bool,
11125
}
11226

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+
11541
let (tx, rx) = sync_channel(0);
11642
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 {
12045
notify::EventKind::Any
12146
| notify::EventKind::Create(_)
12247
| notify::EventKind::Modify(_)
12348
| notify::EventKind::Remove(_)
12449
| notify::EventKind::Other => {
125-
let _ = tx.try_send(());
50+
if let Err(err) = tx.try_send(()) {
51+
log::error!("send error: {err:?}");
52+
}
12653
}
54+
notify::EventKind::Access(_) => {}
12755
},
128-
Err(e) => println!("notify error: {e:?}"),
56+
Err(err) => log::error!("notify error: {err:?}"),
12957
})
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,
13263
watcher,
13364
rx,
13465
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();
13573
}
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)
13682
}
13783

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> {
139116
leaf_deps(metadata_file, |it| {
140117
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
143121
.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}");
145124
}
146125
})
147-
.expect("Could read dependencies file");
126+
.map_err(SpirvBuilderError::MetadataFileMissing)
148127
}
128+
}
149129

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+
}
152140
}
153141
}
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+
}

examples/runners/wgpu/src/lib.rs

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -177,18 +177,19 @@ fn maybe_watch(
177177
}
178178

179179
if let Some(mut f) = on_watch {
180-
builder
181-
.watch(move |compile_result, accept| {
180+
let mut watcher = builder.watch().unwrap();
181+
let first_compile = watcher.recv().unwrap();
182+
183+
let mut thread_watcher = watcher.forget_lifetime();
184+
std::thread::spawn(move || {
185+
loop {
186+
let compile_result = thread_watcher.recv().unwrap();
182187
let modules = handle_compile_result(compile_result);
183-
if let Some(accept) = accept {
184-
accept.submit(modules);
185-
} else {
186-
f(modules);
187-
}
188-
})
189-
.expect("Configuration is correct for watching")
190-
.first_compile
191-
.unwrap()
188+
f(modules);
189+
}
190+
});
191+
192+
handle_compile_result(first_compile)
192193
} else {
193194
handle_compile_result(builder.build().unwrap())
194195
}

0 commit comments

Comments
 (0)