Fork of daniellemaywood.uk/gleam — Wasm codegen work
9.8 kB
334 lines
1pub mod memory;
2
3use crate::error::{Error, FileIoAction, FileKind, Result};
4use async_trait::async_trait;
5use debug_ignore::DebugIgnore;
6use flate2::read::GzDecoder;
7use std::{collections::HashMap, fmt::Debug, io, time::SystemTime, vec::IntoIter};
8use tar::{Archive, Entry};
9
10use camino::{Utf8Path, Utf8PathBuf};
11
12/// Takes in a source path and a target path and determines a relative path
13/// from source -> target.
14/// If given a relative target path, no calculation occurs.
15/// # Panics
16/// The provided source path should be absolute, otherwise will panic.
17pub fn make_relative(source_path: &Utf8Path, target_path: &Utf8Path) -> Utf8PathBuf {
18 assert!(source_path.is_absolute());
19 // Input target will always be canonicalised whereas source will not
20 // This causes problems with diffing on windows since canonicalised paths have a special root
21 // As such we are attempting to strip the target path
22 // Based on https://github.com/rust-lang/rust/issues/42869#issuecomment-1712317081
23 #[cfg(target_family = "windows")]
24 let binding = target_path.to_string();
25 #[cfg(target_family = "windows")]
26 let target_path = Utf8Path::new(binding.trim_start_matches(r"\\?\"));
27
28 match target_path.is_absolute() {
29 true => pathdiff::diff_utf8_paths(target_path, source_path)
30 .expect("Should not fail on two absolute paths"),
31
32 false => target_path.into(),
33 }
34}
35
36pub trait Reader: std::io::Read {
37 /// A wrapper around `std::io::Read` that has Gleam's error handling.
38 fn read_bytes(&mut self, buffer: &mut [u8]) -> Result<usize> {
39 self.read(buffer).map_err(|e| self.convert_err(e))
40 }
41
42 fn convert_err<E: std::error::Error>(&self, error: E) -> Error;
43}
44
45pub trait Utf8Writer: std::fmt::Write {
46 /// A wrapper around `fmt::Write` that has Gleam's error handling.
47 fn str_write(&mut self, str: &str) -> Result<()> {
48 self.write_str(str).map_err(|e| self.convert_err(e))
49 }
50
51 fn convert_err<E: std::error::Error>(&self, err: E) -> Error;
52}
53
54impl Utf8Writer for String {
55 fn convert_err<E: std::error::Error>(&self, error: E) -> Error {
56 Error::FileIo {
57 action: FileIoAction::WriteTo,
58 kind: FileKind::File,
59 path: Utf8PathBuf::from("<in memory>"),
60 err: Some(error.to_string()),
61 }
62 }
63}
64
65pub trait Writer: std::io::Write + Utf8Writer {
66 /// A wrapper around `io::Write` that has Gleam's error handling.
67 fn write(&mut self, bytes: &[u8]) -> Result<(), Error> {
68 std::io::Write::write(self, bytes)
69 .map(|_| ())
70 .map_err(|e| self.convert_err(e))
71 }
72}
73
74#[derive(Debug, PartialEq, Eq, Clone)]
75pub enum Content {
76 Binary(Vec<u8>),
77 Text(String),
78}
79
80impl Content {
81 pub fn as_bytes(&self) -> &[u8] {
82 match self {
83 Content::Binary(data) => data,
84 Content::Text(data) => data.as_bytes(),
85 }
86 }
87
88 pub fn text(&self) -> Option<&str> {
89 match self {
90 Content::Binary(_) => None,
91 Content::Text(s) => Some(s),
92 }
93 }
94}
95
96impl From<Vec<u8>> for Content {
97 fn from(bytes: Vec<u8>) -> Self {
98 Content::Binary(bytes)
99 }
100}
101
102impl From<&[u8]> for Content {
103 fn from(bytes: &[u8]) -> Self {
104 Content::Binary(bytes.to_vec())
105 }
106}
107
108impl From<String> for Content {
109 fn from(text: String) -> Self {
110 Content::Text(text)
111 }
112}
113
114impl From<&str> for Content {
115 fn from(text: &str) -> Self {
116 Content::Text(text.to_string())
117 }
118}
119
120#[derive(Debug, PartialEq, Eq, Clone)]
121pub struct OutputFile {
122 pub content: Content,
123 pub path: Utf8PathBuf,
124}
125
126#[derive(Debug)]
127pub struct ReadDir {
128 entries: Vec<io::Result<DirEntry>>,
129}
130
131impl FromIterator<io::Result<DirEntry>> for ReadDir {
132 fn from_iter<I: IntoIterator<Item = io::Result<DirEntry>>>(iter: I) -> Self {
133 ReadDir {
134 entries: iter.into_iter().collect(),
135 }
136 }
137}
138
139impl ReadDir {
140 pub fn extend(mut self, other: ReadDir) -> Self {
141 self.entries.extend(other);
142
143 ReadDir {
144 entries: self.entries,
145 }
146 }
147}
148
149impl IntoIterator for ReadDir {
150 type Item = io::Result<DirEntry>;
151 type IntoIter = IntoIter<Self::Item>;
152
153 fn into_iter(self) -> Self::IntoIter {
154 self.entries.into_iter()
155 }
156}
157
158#[derive(Debug, Clone)]
159pub struct DirEntry {
160 pub pathbuf: Utf8PathBuf,
161}
162
163impl DirEntry {
164 pub fn from_path<P: AsRef<Utf8Path>>(path: P) -> DirEntry {
165 DirEntry {
166 pathbuf: path.as_ref().to_path_buf(),
167 }
168 }
169
170 pub fn from_pathbuf(pathbuf: Utf8PathBuf) -> DirEntry {
171 DirEntry { pathbuf }
172 }
173
174 pub fn as_path(&self) -> &Utf8Path {
175 self.pathbuf.as_path()
176 }
177
178 pub fn into_path(self) -> Utf8PathBuf {
179 self.pathbuf
180 }
181}
182
183/// A trait used to read files.
184/// Typically we use an implementation that reads from the file system,
185/// but in tests and in other places other implementations may be used.
186pub trait FileSystemReader {
187 fn gleam_source_files(&self, dir: &Utf8Path) -> Vec<Utf8PathBuf>;
188 fn gleam_cache_files(&self, dir: &Utf8Path) -> Vec<Utf8PathBuf>;
189 fn read_dir(&self, path: &Utf8Path) -> Result<ReadDir>;
190 fn read(&self, path: &Utf8Path) -> Result<String, Error>;
191 fn read_bytes(&self, path: &Utf8Path) -> Result<Vec<u8>, Error>;
192 fn reader(&self, path: &Utf8Path) -> Result<WrappedReader, Error>;
193 fn is_file(&self, path: &Utf8Path) -> bool;
194 fn is_directory(&self, path: &Utf8Path) -> bool;
195 fn modification_time(&self, path: &Utf8Path) -> Result<SystemTime, Error>;
196 fn canonicalise(&self, path: &Utf8Path) -> Result<Utf8PathBuf, Error>;
197}
198
199/// A trait used to run other programs.
200pub trait CommandExecutor {
201 fn exec(
202 &self,
203 program: &str,
204 args: &[String],
205 env: &[(&str, String)],
206 cwd: Option<&Utf8Path>,
207 stdio: Stdio,
208 ) -> Result<i32, Error>;
209}
210
211#[derive(Clone, Copy, Debug, PartialEq, Eq)]
212pub enum Stdio {
213 Inherit,
214 Null,
215}
216
217impl Stdio {
218 pub fn get_process_stdio(&self) -> std::process::Stdio {
219 match self {
220 Stdio::Inherit => std::process::Stdio::inherit(),
221 Stdio::Null => std::process::Stdio::null(),
222 }
223 }
224}
225
226/// A trait used to write files.
227/// Typically we use an implementation that writes to the file system,
228/// but in tests and in other places other implementations may be used.
229pub trait FileSystemWriter {
230 fn mkdir(&self, path: &Utf8Path) -> Result<(), Error>;
231 fn write(&self, path: &Utf8Path, content: &str) -> Result<(), Error>;
232 fn write_bytes(&self, path: &Utf8Path, content: &[u8]) -> Result<(), Error>;
233 fn delete_directory(&self, path: &Utf8Path) -> Result<(), Error>;
234 fn copy(&self, from: &Utf8Path, to: &Utf8Path) -> Result<(), Error>;
235 fn copy_dir(&self, from: &Utf8Path, to: &Utf8Path) -> Result<(), Error>;
236 fn hardlink(&self, from: &Utf8Path, to: &Utf8Path) -> Result<(), Error>;
237 fn symlink_dir(&self, from: &Utf8Path, to: &Utf8Path) -> Result<(), Error>;
238 fn delete_file(&self, path: &Utf8Path) -> Result<(), Error>;
239}
240
241#[derive(Debug)]
242/// A wrapper around a Read implementing object that has Gleam's error handling.
243pub struct WrappedReader {
244 path: Utf8PathBuf,
245 inner: DebugIgnore<Box<dyn std::io::Read>>,
246}
247
248impl WrappedReader {
249 pub fn new(path: &Utf8Path, inner: Box<dyn std::io::Read>) -> Self {
250 Self {
251 path: path.to_path_buf(),
252 inner: DebugIgnore(inner),
253 }
254 }
255
256 fn read(&mut self, buffer: &mut [u8]) -> std::io::Result<usize> {
257 self.inner.read(buffer)
258 }
259}
260
261impl std::io::Read for WrappedReader {
262 fn read(&mut self, buffer: &mut [u8]) -> std::io::Result<usize> {
263 self.read(buffer)
264 }
265}
266
267impl Reader for WrappedReader {
268 fn convert_err<E: std::error::Error>(&self, err: E) -> Error {
269 Error::FileIo {
270 kind: FileKind::File,
271 action: FileIoAction::Read,
272 path: self.path.clone(),
273 err: Some(err.to_string()),
274 }
275 }
276}
277
278#[async_trait]
279pub trait HttpClient {
280 async fn send(&self, request: http::Request<Vec<u8>>)
281 -> Result<http::Response<Vec<u8>>, Error>;
282}
283
284pub trait TarUnpacker {
285 // FIXME: The reader types are restrictive here. We should be more generic
286 // than this.
287 fn io_result_entries<'a>(
288 &self,
289 archive: &'a mut Archive<WrappedReader>,
290 ) -> io::Result<tar::Entries<'a, WrappedReader>>;
291
292 fn entries<'a>(
293 &self,
294 archive: &'a mut Archive<WrappedReader>,
295 ) -> Result<tar::Entries<'a, WrappedReader>> {
296 tracing::debug!("iterating through tar archive");
297 self.io_result_entries(archive)
298 .map_err(|e| Error::ExpandTar {
299 error: e.to_string(),
300 })
301 }
302
303 fn io_result_unpack(
304 &self,
305 path: &Utf8Path,
306 archive: Archive<GzDecoder<Entry<'_, WrappedReader>>>,
307 ) -> io::Result<()>;
308
309 fn unpack(
310 &self,
311 path: &Utf8Path,
312 archive: Archive<GzDecoder<Entry<'_, WrappedReader>>>,
313 ) -> Result<()> {
314 tracing::debug!(path = ?path, "unpacking tar archive");
315 self.io_result_unpack(path, archive)
316 .map_err(|e| Error::FileIo {
317 action: FileIoAction::WriteTo,
318 kind: FileKind::Directory,
319 path: path.to_path_buf(),
320 err: Some(e.to_string()),
321 })
322 }
323}
324
325pub fn ordered_map<S, K, V>(value: &HashMap<K, V>, serializer: S) -> Result<S::Ok, S::Error>
326where
327 S: serde::Serializer,
328 K: serde::Serialize + Ord,
329 V: serde::Serialize,
330{
331 use serde::Serialize;
332 let ordered: std::collections::BTreeMap<_, _> = value.iter().collect();
333 ordered.serialize(serializer)
334}