1use std::io;
2use std::path::{Path, PathBuf};
3
4use thiserror::Error;
5
6#[derive(Debug, Error)]
7pub enum BinaryError {
8 #[error("build output not found at `{0}`; check `[build].output` in your recipe")]
9 OutputMissing(PathBuf),
10
11 #[error(transparent)]
12 Io(#[from] io::Error),
13}
14
15pub fn place(src: &Path, dest: &Path) -> Result<(), BinaryError> {
16 if !src.exists() {
17 return Err(BinaryError::OutputMissing(src.to_path_buf()));
18 }
19
20 if let Some(parent) = dest.parent() {
21 std::fs::create_dir_all(parent)?;
22 }
23
24 std::fs::copy(src, dest)?;
25 Ok(())
26}
27
28pub fn make_executable(path: &Path) -> Result<(), BinaryError> {
29 #[cfg(unix)]
30 {
31 use std::os::unix::fs::PermissionsExt;
32 let mut perms = std::fs::metadata(path)?.permissions();
33 perms.set_mode(0o755);
34 std::fs::set_permissions(path, perms)?;
35 }
36 Ok(())
37}
38
39#[cfg(test)]
40mod tests {
41 use super::*;
42
43 #[test]
44 fn place_copies_file_contents() {
45 let dir = tempfile::tempdir().unwrap();
46 let src = dir.path().join("src.bin");
47 let dest = dir.path().join("subdir").join("dest.bin");
48
49 std::fs::write(&src, b"hello world").unwrap();
50 place(&src, &dest).unwrap();
51
52 let contents = std::fs::read(&dest).unwrap();
53 assert_eq!(contents, b"hello world");
54 }
55
56 #[test]
57 fn place_creates_missing_parent_dir() {
58 let dir = tempfile::tempdir().unwrap();
59 let src = dir.path().join("src.bin");
60 let dest = dir.path().join("nested").join("dirs").join("dest.bin");
61
62 std::fs::write(&src, b"x").unwrap();
63 place(&src, &dest).unwrap();
64
65 assert!(dest.exists());
66 }
67
68 #[test]
69 fn place_overwrites_existing_dest() {
70 let dir = tempfile::tempdir().unwrap();
71 let src = dir.path().join("src.bin");
72 let dest = dir.path().join("dest.bin");
73
74 std::fs::write(&src, b"new").unwrap();
75 std::fs::write(&dest, b"old").unwrap();
76 place(&src, &dest).unwrap();
77
78 assert_eq!(std::fs::read(&dest).unwrap(), b"new");
79 }
80
81 #[test]
82 fn place_returns_output_missing_when_src_absent() {
83 let dir = tempfile::tempdir().unwrap();
84 let src = dir.path().join("missing.bin");
85 let dest = dir.path().join("dest.bin");
86
87 let err = place(&src, &dest).unwrap_err();
88 assert!(matches!(err, BinaryError::OutputMissing(p) if p == src));
89 }
90
91 #[cfg(unix)]
92 #[test]
93 fn make_executable_sets_mode_755() {
94 use std::os::unix::fs::PermissionsExt;
95
96 let dir = tempfile::tempdir().unwrap();
97 let path = dir.path().join("bin");
98 std::fs::write(&path, b"x").unwrap();
99
100 let mut perms = std::fs::metadata(&path).unwrap().permissions();
101 perms.set_mode(0o644);
102 std::fs::set_permissions(&path, perms).unwrap();
103
104 make_executable(&path).unwrap();
105
106 let mode = std::fs::metadata(&path).unwrap().permissions().mode() & 0o777;
107 assert_eq!(mode, 0o755);
108 }
109}