diff options
author | David Sherret <dsherret@users.noreply.github.com> | 2022-07-12 18:58:39 -0400 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-07-12 18:58:39 -0400 |
commit | 0c87dd1e9898d7ac93e274d3611ee491a107d47a (patch) | |
tree | f626332706ccd12e0719f9b84d6b234d5483659b /cli/cache/disk_cache.rs | |
parent | 76107649804e674268becd693b7b2a954eecb3da (diff) |
perf: use emit from swc instead of tsc (#15118)
Diffstat (limited to 'cli/cache/disk_cache.rs')
-rw-r--r-- | cli/cache/disk_cache.rs | 388 |
1 files changed, 388 insertions, 0 deletions
diff --git a/cli/cache/disk_cache.rs b/cli/cache/disk_cache.rs new file mode 100644 index 000000000..01352c398 --- /dev/null +++ b/cli/cache/disk_cache.rs @@ -0,0 +1,388 @@ +// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. + +use crate::fs_util; +use crate::http_cache::url_to_filename; + +use super::CacheType; +use super::Cacher; +use super::EmitMetadata; + +use deno_ast::ModuleSpecifier; +use deno_core::error::AnyError; +use deno_core::serde_json; +use deno_core::url::Host; +use deno_core::url::Url; +use std::ffi::OsStr; +use std::fs; +use std::io; +use std::path::Component; +use std::path::Path; +use std::path::PathBuf; +use std::path::Prefix; +use std::str; + +#[derive(Clone)] +pub struct DiskCache { + pub location: PathBuf, +} + +fn with_io_context<T: AsRef<str>>( + e: &std::io::Error, + context: T, +) -> std::io::Error { + std::io::Error::new(e.kind(), format!("{} (for '{}')", e, context.as_ref())) +} + +impl DiskCache { + /// `location` must be an absolute path. + pub fn new(location: &Path) -> Self { + assert!(location.is_absolute()); + Self { + location: location.to_owned(), + } + } + + /// Ensures the location of the cache. + pub fn ensure_dir_exists(&self, path: &Path) -> io::Result<()> { + if path.is_dir() { + return Ok(()); + } + fs::create_dir_all(&path).map_err(|e| { + io::Error::new(e.kind(), format!( + "Could not create TypeScript compiler cache location: {:?}\nCheck the permission of the directory.", + path + )) + }) + } + + fn get_cache_filename(&self, url: &Url) -> Option<PathBuf> { + let mut out = PathBuf::new(); + + let scheme = url.scheme(); + out.push(scheme); + + match scheme { + "wasm" => { + let host = url.host_str().unwrap(); + let host_port = match url.port() { + // Windows doesn't support ":" in filenames, so we represent port using a + // special string. + Some(port) => format!("{}_PORT{}", host, port), + None => host.to_string(), + }; + out.push(host_port); + + for path_seg in url.path_segments().unwrap() { + out.push(path_seg); + } + } + "http" | "https" | "data" | "blob" => out = url_to_filename(url)?, + "file" => { + let path = match url.to_file_path() { + Ok(path) => path, + Err(_) => return None, + }; + let mut path_components = path.components(); + + if cfg!(target_os = "windows") { + if let Some(Component::Prefix(prefix_component)) = + path_components.next() + { + // Windows doesn't support ":" in filenames, so we need to extract disk prefix + // Example: file:///C:/deno/js/unit_test_runner.ts + // it should produce: file\c\deno\js\unit_test_runner.ts + match prefix_component.kind() { + Prefix::Disk(disk_byte) | Prefix::VerbatimDisk(disk_byte) => { + let disk = (disk_byte as char).to_string(); + out.push(disk); + } + Prefix::UNC(server, share) + | Prefix::VerbatimUNC(server, share) => { + out.push("UNC"); + let host = Host::parse(server.to_str().unwrap()).unwrap(); + let host = host.to_string().replace(':', "_"); + out.push(host); + out.push(share); + } + _ => unreachable!(), + } + } + } + + // Must be relative, so strip forward slash + let mut remaining_components = path_components.as_path(); + if let Ok(stripped) = remaining_components.strip_prefix("/") { + remaining_components = stripped; + }; + + out = out.join(remaining_components); + } + _ => return None, + }; + + Some(out) + } + + pub fn get_cache_filename_with_extension( + &self, + url: &Url, + extension: &str, + ) -> Option<PathBuf> { + let base = self.get_cache_filename(url)?; + + match base.extension() { + None => Some(base.with_extension(extension)), + Some(ext) => { + let original_extension = OsStr::to_str(ext).unwrap(); + let final_extension = format!("{}.{}", original_extension, extension); + Some(base.with_extension(final_extension)) + } + } + } + + pub fn get(&self, filename: &Path) -> std::io::Result<Vec<u8>> { + let path = self.location.join(filename); + fs::read(&path) + } + + pub fn set(&self, filename: &Path, data: &[u8]) -> std::io::Result<()> { + let path = self.location.join(filename); + match path.parent() { + Some(parent) => self.ensure_dir_exists(parent), + None => Ok(()), + }?; + fs_util::atomic_write_file(&path, data, crate::http_cache::CACHE_PERM) + .map_err(|e| with_io_context(&e, format!("{:#?}", &path))) + } + + fn get_emit_metadata( + &self, + specifier: &ModuleSpecifier, + ) -> Option<EmitMetadata> { + let filename = self.get_cache_filename_with_extension(specifier, "meta")?; + let bytes = self.get(&filename).ok()?; + serde_json::from_slice(&bytes).ok() + } + + fn set_emit_metadata( + &self, + specifier: &ModuleSpecifier, + data: EmitMetadata, + ) -> Result<(), AnyError> { + let filename = self + .get_cache_filename_with_extension(specifier, "meta") + .unwrap(); + let bytes = serde_json::to_vec(&data)?; + self.set(&filename, &bytes).map_err(|e| e.into()) + } +} + +// todo(13302): remove and replace with sqlite database +impl Cacher for DiskCache { + fn get( + &self, + cache_type: CacheType, + specifier: &ModuleSpecifier, + ) -> Option<String> { + let extension = match cache_type { + CacheType::Emit => "js", + CacheType::SourceMap => "js.map", + CacheType::Version => { + return self.get_emit_metadata(specifier).map(|d| d.version_hash) + } + }; + let filename = + self.get_cache_filename_with_extension(specifier, extension)?; + self + .get(&filename) + .ok() + .and_then(|b| String::from_utf8(b).ok()) + } + + fn set( + &self, + cache_type: CacheType, + specifier: &ModuleSpecifier, + value: String, + ) -> Result<(), AnyError> { + let extension = match cache_type { + CacheType::Emit => "js", + CacheType::SourceMap => "js.map", + CacheType::Version => { + let data = if let Some(mut data) = self.get_emit_metadata(specifier) { + data.version_hash = value; + data + } else { + EmitMetadata { + version_hash: value, + } + }; + return self.set_emit_metadata(specifier, data); + } + }; + let filename = self + .get_cache_filename_with_extension(specifier, extension) + .unwrap(); + self.set(&filename, value.as_bytes()).map_err(|e| e.into()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use test_util::TempDir; + + #[test] + fn test_create_cache_if_dir_exits() { + let cache_location = TempDir::new(); + let mut cache_path = cache_location.path().to_owned(); + cache_path.push("foo"); + let cache = DiskCache::new(&cache_path); + cache + .ensure_dir_exists(&cache.location) + .expect("Testing expect:"); + assert!(cache_path.is_dir()); + } + + #[test] + fn test_create_cache_if_dir_not_exits() { + let temp_dir = TempDir::new(); + let mut cache_location = temp_dir.path().to_owned(); + assert!(fs::remove_dir(&cache_location).is_ok()); + cache_location.push("foo"); + assert!(!cache_location.is_dir()); + let cache = DiskCache::new(&cache_location); + cache + .ensure_dir_exists(&cache.location) + .expect("Testing expect:"); + assert!(cache_location.is_dir()); + } + + #[test] + fn test_get_cache_filename() { + let cache_location = if cfg!(target_os = "windows") { + PathBuf::from(r"C:\deno_dir\") + } else { + PathBuf::from("/deno_dir/") + }; + + let cache = DiskCache::new(&cache_location); + + let mut test_cases = vec![ + ( + "http://deno.land/std/http/file_server.ts", + "http/deno.land/d8300752800fe3f0beda9505dc1c3b5388beb1ee45afd1f1e2c9fc0866df15cf", + ), + ( + "http://localhost:8000/std/http/file_server.ts", + "http/localhost_PORT8000/d8300752800fe3f0beda9505dc1c3b5388beb1ee45afd1f1e2c9fc0866df15cf", + ), + ( + "https://deno.land/std/http/file_server.ts", + "https/deno.land/d8300752800fe3f0beda9505dc1c3b5388beb1ee45afd1f1e2c9fc0866df15cf", + ), + ("wasm://wasm/d1c677ea", "wasm/wasm/d1c677ea"), + ]; + + if cfg!(target_os = "windows") { + test_cases.push(("file:///D:/a/1/s/format.ts", "file/D/a/1/s/format.ts")); + // IPv4 localhost + test_cases.push(( + "file://127.0.0.1/d$/a/1/s/format.ts", + "file/UNC/127.0.0.1/d$/a/1/s/format.ts", + )); + // IPv6 localhost + test_cases.push(( + "file://[0:0:0:0:0:0:0:1]/d$/a/1/s/format.ts", + "file/UNC/[__1]/d$/a/1/s/format.ts", + )); + // shared folder + test_cases.push(( + "file://comp/t-share/a/1/s/format.ts", + "file/UNC/comp/t-share/a/1/s/format.ts", + )); + } else { + test_cases.push(( + "file:///std/http/file_server.ts", + "file/std/http/file_server.ts", + )); + } + + for test_case in &test_cases { + let cache_filename = + cache.get_cache_filename(&Url::parse(test_case.0).unwrap()); + assert_eq!(cache_filename, Some(PathBuf::from(test_case.1))); + } + } + + #[test] + fn test_get_cache_filename_with_extension() { + let p = if cfg!(target_os = "windows") { + "C:\\foo" + } else { + "/foo" + }; + let cache = DiskCache::new(&PathBuf::from(p)); + + let mut test_cases = vec![ + ( + "http://deno.land/std/http/file_server.ts", + "js", + "http/deno.land/d8300752800fe3f0beda9505dc1c3b5388beb1ee45afd1f1e2c9fc0866df15cf.js", + ), + ( + "http://deno.land/std/http/file_server.ts", + "js.map", + "http/deno.land/d8300752800fe3f0beda9505dc1c3b5388beb1ee45afd1f1e2c9fc0866df15cf.js.map", + ), + ]; + + if cfg!(target_os = "windows") { + test_cases.push(( + "file:///D:/std/http/file_server", + "js", + "file/D/std/http/file_server.js", + )); + } else { + test_cases.push(( + "file:///std/http/file_server", + "js", + "file/std/http/file_server.js", + )); + } + + for test_case in &test_cases { + assert_eq!( + cache.get_cache_filename_with_extension( + &Url::parse(test_case.0).unwrap(), + test_case.1 + ), + Some(PathBuf::from(test_case.2)) + ) + } + } + + #[test] + fn test_get_cache_filename_invalid_urls() { + let cache_location = if cfg!(target_os = "windows") { + PathBuf::from(r"C:\deno_dir\") + } else { + PathBuf::from("/deno_dir/") + }; + + let cache = DiskCache::new(&cache_location); + + let mut test_cases = vec!["unknown://localhost/test.ts"]; + + if cfg!(target_os = "windows") { + test_cases.push("file://"); + test_cases.push("file:///"); + } + + for test_case in &test_cases { + let cache_filename = + cache.get_cache_filename(&Url::parse(test_case).unwrap()); + assert_eq!(cache_filename, None); + } + } +} |