Skip to content

Commit 7849d55

Browse files
authored
Merge pull request #2157 from ehuss/macos-notify-copy
Add workaround for macOS notify problem.
2 parents 57b487e + c903cc8 commit 7849d55

File tree

2 files changed

+58
-2
lines changed

2 files changed

+58
-2
lines changed

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ toml = "0.5.11" # Do not update, see https://github.com/rust-lang/mdBook/issues/
3737
topological-sort = "0.2.2"
3838

3939
# Watch feature
40-
notify = { version = "6.0.1", optional = true, features = ["macos_kqueue"] }
40+
notify = { version = "6.0.1", optional = true }
4141
notify-debouncer-mini = { version = "0.3.0", optional = true }
4242
ignore = { version = "0.4.20", optional = true }
4343

src/utils/fs.rs

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@ pub fn copy_files_except_ext(
166166
.expect("a file should have a file name...")
167167
)
168168
);
169-
fs::copy(
169+
copy(
170170
entry.path(),
171171
&to.join(
172172
entry
@@ -180,6 +180,62 @@ pub fn copy_files_except_ext(
180180
Ok(())
181181
}
182182

183+
/// Copies a file.
184+
fn copy<P: AsRef<Path>, Q: AsRef<Path>>(from: P, to: Q) -> Result<()> {
185+
let from = from.as_ref();
186+
let to = to.as_ref();
187+
return copy_inner(from, to)
188+
.with_context(|| format!("failed to copy `{}` to `{}`", from.display(), to.display()));
189+
190+
// This is a workaround for an issue with the macOS file watcher.
191+
// Rust's `std::fs::copy` function uses `fclonefileat`, which creates
192+
// clones on APFS. Unfortunately fs events seem to trigger on both
193+
// sides of the clone, and there doesn't seem to be a way to differentiate
194+
// which side it is.
195+
// https://github.com/notify-rs/notify/issues/465#issuecomment-1657261035
196+
// contains more information.
197+
//
198+
// This is essentially a copy of the simple copy code path in Rust's
199+
// standard library.
200+
#[cfg(target_os = "macos")]
201+
fn copy_inner(from: &Path, to: &Path) -> Result<()> {
202+
use std::fs::OpenOptions;
203+
use std::os::unix::fs::{OpenOptionsExt, PermissionsExt};
204+
205+
let mut reader = File::open(from)?;
206+
let metadata = reader.metadata()?;
207+
if !metadata.is_file() {
208+
anyhow::bail!(
209+
"expected a file, `{}` appears to be {:?}",
210+
from.display(),
211+
metadata.file_type()
212+
);
213+
}
214+
let perm = metadata.permissions();
215+
let mut writer = OpenOptions::new()
216+
.mode(perm.mode())
217+
.write(true)
218+
.create(true)
219+
.truncate(true)
220+
.open(to)?;
221+
let writer_metadata = writer.metadata()?;
222+
if writer_metadata.is_file() {
223+
// Set the correct file permissions, in case the file already existed.
224+
// Don't set the permissions on already existing non-files like
225+
// pipes/FIFOs or device nodes.
226+
writer.set_permissions(perm)?;
227+
}
228+
std::io::copy(&mut reader, &mut writer)?;
229+
Ok(())
230+
}
231+
232+
#[cfg(not(target_os = "macos"))]
233+
fn copy_inner(from: &Path, to: &Path) -> Result<()> {
234+
fs::copy(from, to)?;
235+
Ok(())
236+
}
237+
}
238+
183239
pub fn get_404_output_file(input_404: &Option<String>) -> String {
184240
input_404
185241
.as_ref()

0 commit comments

Comments
 (0)