1use std::collections::BTreeSet;
20use std::path::PathBuf;
21use std::sync::{Arc, Mutex, mpsc};
22use std::time::{Duration, Instant};
23
24use notify::{Event, EventKind, RecursiveMode, Watcher};
26
27use super::metadata::{Inputs, Metadata};
29use super::{
30 Build, BuildCancellation, Error,
31 build::{build_resolved, validate_backend},
32};
33use crate::config::{BuildSettings, DevSettings};
34
35pub enum DevEvent {
46 Started { attempt: u64 },
47 Finished { attempt: u64, result: Result<Build, Error> },
48}
49
50pub fn dev(
57 settings: &BuildSettings, policy: DevSettings, cancellation: &BuildCancellation, mut report: impl FnMut(DevEvent) -> Result<(), Error>,
58) -> Result<(), Error> {
59 validate_backend(settings)?;
60 if policy.debounce.is_zero() || Instant::now().checked_add(policy.debounce).is_none() {
61 return Err(Error::Settings("development debounce must be positive and representable".into()));
62 }
63 let inputs = Arc::new(Mutex::new(Inputs::fallback(settings)?));
64 let failure = Arc::new(Mutex::new(None));
65 let monitored = inputs.clone();
66 let fault = failure.clone();
67 let (changes, incoming) = mpsc::sync_channel(1);
68 let mut watcher = notify::recommended_watcher(move |result: notify::Result<Event>| match result {
69 Ok(event)
70 if matches!(
71 event.kind,
72 EventKind::Create(_) | EventKind::Modify(_) | EventKind::Remove(_) | EventKind::Any | EventKind::Other
73 ) =>
74 {
75 if event.need_rescan() || event.paths.iter().any(|path| monitored.lock().expect("watch inputs are healthy").relevant(path)) {
76 let _ = changes.try_send(());
77 }
78 }
79 Err(error) => {
80 fault.lock().expect("watch failure is healthy").get_or_insert(error);
81 let _ = changes.try_send(());
82 }
83 _ => {}
84 })?;
85 let mut watched = BTreeSet::new();
86 configure(&mut watcher, &mut watched, &inputs.lock().expect("watch inputs are healthy"))?;
87 let mut attempt = 0u64;
88 loop {
89 if cancellation.is_cancelled() {
90 return Ok(());
91 }
92 if let Some(error) = failure.lock().expect("watch failure is healthy").take() {
93 return Err(Error::Watch(error));
94 }
95 attempt = attempt.checked_add(1).ok_or_else(|| Error::Settings("development attempt counter exhausted".into()))?;
96 report(DevEvent::Started { attempt })?;
97 let resolved =
98 Metadata::read(settings, cancellation).and_then(|metadata| metadata.inputs(settings).map(|inputs| (metadata, inputs)));
99 let result = match resolved {
100 Ok((metadata, resolved)) => {
101 configure(&mut watcher, &mut watched, &resolved)?;
102 *inputs.lock().expect("watch inputs are healthy") = resolved;
103 build_resolved(settings, cancellation, metadata)
104 }
105 Err(error) => Err(error),
106 };
107 if cancellation.is_cancelled() {
108 return Ok(());
109 }
110 report(DevEvent::Finished { attempt, result })?;
111 let mut changed = false;
112 let mut settled = Instant::now();
113 loop {
114 if cancellation.is_cancelled() {
115 return Ok(());
116 }
117 if let Some(error) = failure.lock().expect("watch failure is healthy").take() {
118 return Err(Error::Watch(error));
119 }
120 if changed && Instant::now() >= settled {
121 break;
122 }
123 let wait = if changed {
124 settled.saturating_duration_since(Instant::now()).min(Duration::from_millis(20))
125 } else {
126 Duration::from_millis(20)
127 };
128 match incoming.recv_timeout(wait) {
129 Ok(()) => {
130 changed = true;
131 settled = Instant::now() + policy.debounce;
132 }
133 Err(mpsc::RecvTimeoutError::Timeout) => {}
134 Err(mpsc::RecvTimeoutError::Disconnected) => {
135 return Err(Error::Settings("source watcher stopped unexpectedly".into()));
136 }
137 }
138 }
139 }
140}
141
142fn configure(watcher: &mut impl Watcher, watched: &mut BTreeSet<PathBuf>, inputs: &Inputs) -> Result<(), Error> {
145 let mut selected = BTreeSet::new();
147 let mut pending: Vec<_> = inputs.roots.iter().cloned().collect();
148 while let Some(directory) = pending.pop() {
149 if !inputs.relevant(&directory) || !selected.insert(directory.clone()) {
150 continue;
151 }
152 for entry in std::fs::read_dir(directory)? {
153 let entry = entry?;
154 if entry.file_type()?.is_dir() {
155 pending.push(entry.path());
156 }
157 }
158 }
159 for root in &inputs.roots {
160 if let Some(parent) = root.parent() {
161 selected.insert(parent.to_owned());
162 }
163 }
164 for file in &inputs.files {
165 let mut parent = file.parent();
166 while let Some(directory) = parent {
167 if directory.is_dir() {
168 selected.insert(directory.to_owned());
169 break;
170 }
171 parent = directory.parent();
172 }
173 }
174 for directory in &selected {
176 watcher.watch(directory, RecursiveMode::NonRecursive)?;
177 }
178 for directory in watched.difference(&selected) {
179 if let Err(error) = watcher.unwatch(directory)
180 && !matches!(error.kind, notify::ErrorKind::WatchNotFound)
181 {
182 return Err(Error::Watch(error));
183 }
184 }
185 *watched = selected;
186 Ok(())
187}
188
189