summaryrefslogtreecommitdiff
path: root/cli/cache/disk_cache.rs
diff options
context:
space:
mode:
authorDavid Sherret <dsherret@users.noreply.github.com>2022-07-12 18:58:39 -0400
committerGitHub <noreply@github.com>2022-07-12 18:58:39 -0400
commit0c87dd1e9898d7ac93e274d3611ee491a107d47a (patch)
treef626332706ccd12e0719f9b84d6b234d5483659b /cli/cache/disk_cache.rs
parent76107649804e674268becd693b7b2a954eecb3da (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.rs388
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);
+ }
+ }
+}