diff options
Diffstat (limited to 'xtask/src/not_bash.rs')
-rw-r--r-- | xtask/src/not_bash.rs | 165 |
1 files changed, 165 insertions, 0 deletions
diff --git a/xtask/src/not_bash.rs b/xtask/src/not_bash.rs new file mode 100644 index 000000000..3e30e7279 --- /dev/null +++ b/xtask/src/not_bash.rs | |||
@@ -0,0 +1,165 @@ | |||
1 | //! A bad shell -- small cross platform module for writing glue code | ||
2 | use std::{ | ||
3 | cell::RefCell, | ||
4 | env, | ||
5 | ffi::OsStr, | ||
6 | fs, | ||
7 | path::{Path, PathBuf}, | ||
8 | process::{Command, Stdio}, | ||
9 | }; | ||
10 | |||
11 | use anyhow::{bail, Context, Result}; | ||
12 | |||
13 | pub mod fs2 { | ||
14 | use std::{fs, path::Path}; | ||
15 | |||
16 | use anyhow::{Context, Result}; | ||
17 | |||
18 | pub fn read_dir<P: AsRef<Path>>(path: P) -> Result<fs::ReadDir> { | ||
19 | let path = path.as_ref(); | ||
20 | fs::read_dir(path).with_context(|| format!("Failed to read {}", path.display())) | ||
21 | } | ||
22 | |||
23 | pub fn write<P: AsRef<Path>, C: AsRef<[u8]>>(path: P, contents: C) -> Result<()> { | ||
24 | let path = path.as_ref(); | ||
25 | fs::write(path, contents).with_context(|| format!("Failed to write {}", path.display())) | ||
26 | } | ||
27 | |||
28 | pub fn copy<P: AsRef<Path>, Q: AsRef<Path>>(from: P, to: Q) -> Result<u64> { | ||
29 | let from = from.as_ref(); | ||
30 | let to = to.as_ref(); | ||
31 | fs::copy(from, to) | ||
32 | .with_context(|| format!("Failed to copy {} to {}", from.display(), to.display())) | ||
33 | } | ||
34 | |||
35 | pub fn remove_file<P: AsRef<Path>>(path: P) -> Result<()> { | ||
36 | let path = path.as_ref(); | ||
37 | fs::remove_file(path).with_context(|| format!("Failed to remove file {}", path.display())) | ||
38 | } | ||
39 | |||
40 | pub fn remove_dir_all<P: AsRef<Path>>(path: P) -> Result<()> { | ||
41 | let path = path.as_ref(); | ||
42 | fs::remove_dir_all(path).with_context(|| format!("Failed to remove dir {}", path.display())) | ||
43 | } | ||
44 | } | ||
45 | |||
46 | macro_rules! _run { | ||
47 | ($($expr:expr),*) => { | ||
48 | run!($($expr),*; echo = true) | ||
49 | }; | ||
50 | ($($expr:expr),* ; echo = $echo:expr) => { | ||
51 | $crate::not_bash::run_process(format!($($expr),*), $echo) | ||
52 | }; | ||
53 | } | ||
54 | pub(crate) use _run as run; | ||
55 | |||
56 | pub struct Pushd { | ||
57 | _p: (), | ||
58 | } | ||
59 | |||
60 | pub fn pushd(path: impl Into<PathBuf>) -> Pushd { | ||
61 | Env::with(|env| env.pushd(path.into())); | ||
62 | Pushd { _p: () } | ||
63 | } | ||
64 | |||
65 | impl Drop for Pushd { | ||
66 | fn drop(&mut self) { | ||
67 | Env::with(|env| env.popd()) | ||
68 | } | ||
69 | } | ||
70 | |||
71 | pub fn rm(glob: &str) -> Result<()> { | ||
72 | let cwd = Env::with(|env| env.cwd()); | ||
73 | ls(glob)?.into_iter().try_for_each(|it| fs::remove_file(cwd.join(it)))?; | ||
74 | Ok(()) | ||
75 | } | ||
76 | |||
77 | pub fn rm_rf(path: impl AsRef<Path>) -> Result<()> { | ||
78 | let path = path.as_ref(); | ||
79 | if path.is_file() { | ||
80 | fs2::remove_file(path) | ||
81 | } else { | ||
82 | fs2::remove_dir_all(path) | ||
83 | } | ||
84 | } | ||
85 | |||
86 | pub fn ls(glob: &str) -> Result<Vec<PathBuf>> { | ||
87 | let cwd = Env::with(|env| env.cwd()); | ||
88 | let mut res = Vec::new(); | ||
89 | for entry in fs::read_dir(&cwd)? { | ||
90 | let entry = entry?; | ||
91 | if matches(&entry.file_name(), glob) { | ||
92 | let path = entry.path(); | ||
93 | let path = path.strip_prefix(&cwd).unwrap(); | ||
94 | res.push(path.to_path_buf()) | ||
95 | } | ||
96 | } | ||
97 | return Ok(res); | ||
98 | |||
99 | fn matches(file_name: &OsStr, glob: &str) -> bool { | ||
100 | assert!(glob.starts_with('*')); | ||
101 | file_name.to_string_lossy().ends_with(&glob[1..]) | ||
102 | } | ||
103 | } | ||
104 | |||
105 | #[doc(hidden)] | ||
106 | pub fn run_process(cmd: String, echo: bool) -> Result<String> { | ||
107 | run_process_inner(&cmd, echo).with_context(|| format!("process `{}` failed", cmd)) | ||
108 | } | ||
109 | |||
110 | fn run_process_inner(cmd: &str, echo: bool) -> Result<String> { | ||
111 | let cwd = Env::with(|env| env.cwd()); | ||
112 | let mut args = shelx(cmd); | ||
113 | let binary = args.remove(0); | ||
114 | |||
115 | if echo { | ||
116 | println!("> {}", cmd) | ||
117 | } | ||
118 | |||
119 | let output = Command::new(binary) | ||
120 | .args(args) | ||
121 | .current_dir(cwd) | ||
122 | .stdin(Stdio::null()) | ||
123 | .stderr(Stdio::inherit()) | ||
124 | .output()?; | ||
125 | let stdout = String::from_utf8(output.stdout)?; | ||
126 | |||
127 | if echo { | ||
128 | print!("{}", stdout) | ||
129 | } | ||
130 | |||
131 | if !output.status.success() { | ||
132 | bail!("{}", output.status) | ||
133 | } | ||
134 | |||
135 | Ok(stdout.trim().to_string()) | ||
136 | } | ||
137 | |||
138 | // FIXME: some real shell lexing here | ||
139 | fn shelx(cmd: &str) -> Vec<String> { | ||
140 | cmd.split_whitespace().map(|it| it.to_string()).collect() | ||
141 | } | ||
142 | |||
143 | #[derive(Default)] | ||
144 | struct Env { | ||
145 | pushd_stack: Vec<PathBuf>, | ||
146 | } | ||
147 | |||
148 | impl Env { | ||
149 | fn with<F: FnOnce(&mut Env) -> T, T>(f: F) -> T { | ||
150 | thread_local! { | ||
151 | static ENV: RefCell<Env> = Default::default(); | ||
152 | } | ||
153 | ENV.with(|it| f(&mut *it.borrow_mut())) | ||
154 | } | ||
155 | |||
156 | fn pushd(&mut self, dir: PathBuf) { | ||
157 | self.pushd_stack.push(dir) | ||
158 | } | ||
159 | fn popd(&mut self) { | ||
160 | self.pushd_stack.pop().unwrap(); | ||
161 | } | ||
162 | fn cwd(&self) -> PathBuf { | ||
163 | self.pushd_stack.last().cloned().unwrap_or_else(|| env::current_dir().unwrap()) | ||
164 | } | ||
165 | } | ||