fix(compile): handle when DENO_DIR is readonly (#19257)

Closes #19253
This commit is contained in:
David Sherret 2023-05-25 14:27:45 -04:00 committed by GitHub
parent 76400149a4
commit 2ebd61ee1b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 134 additions and 72 deletions

View file

@ -67,7 +67,6 @@ use std::path::PathBuf;
use std::sync::Arc; use std::sync::Arc;
use thiserror::Error; use thiserror::Error;
use crate::cache::DenoDir;
use crate::file_fetcher::FileFetcher; use crate::file_fetcher::FileFetcher;
use crate::npm::CliNpmRegistryApi; use crate::npm::CliNpmRegistryApi;
use crate::npm::NpmProcessState; use crate::npm::NpmProcessState;
@ -724,10 +723,6 @@ impl CliOptions {
} }
} }
pub fn resolve_deno_dir(&self) -> Result<DenoDir, AnyError> {
Ok(DenoDir::new(self.maybe_custom_root())?)
}
/// Based on an optional command line import map path and an optional /// Based on an optional command line import map path and an optional
/// configuration file, return a resolved module specifier to an import map /// configuration file, return a resolved module specifier to an import map
/// and a boolean indicating if unknown keys should not result in diagnostics. /// and a boolean indicating if unknown keys should not result in diagnostics.
@ -1114,12 +1109,8 @@ impl CliOptions {
&self.flags.location &self.flags.location
} }
pub fn maybe_custom_root(&self) -> Option<PathBuf> { pub fn maybe_custom_root(&self) -> &Option<PathBuf> {
self &self.flags.cache_path
.flags
.cache_path
.clone()
.or_else(|| env::var("DENO_DIR").map(String::into).ok())
} }
pub fn no_clear_screen(&self) -> bool { pub fn no_clear_screen(&self) -> bool {

View file

@ -109,7 +109,6 @@ impl Drop for CacheDB {
} }
impl CacheDB { impl CacheDB {
#[cfg(test)]
pub fn in_memory( pub fn in_memory(
config: &'static CacheDBConfiguration, config: &'static CacheDBConfiguration,
version: &'static str, version: &'static str,

49
cli/cache/caches.rs vendored
View file

@ -1,19 +1,20 @@
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
use std::path::PathBuf; use std::path::PathBuf;
use std::sync::Arc;
use once_cell::sync::OnceCell; use once_cell::sync::OnceCell;
use super::cache_db::CacheDB; use super::cache_db::CacheDB;
use super::cache_db::CacheDBConfiguration; use super::cache_db::CacheDBConfiguration;
use super::check::TYPE_CHECK_CACHE_DB; use super::check::TYPE_CHECK_CACHE_DB;
use super::deno_dir::DenoDirProvider;
use super::incremental::INCREMENTAL_CACHE_DB; use super::incremental::INCREMENTAL_CACHE_DB;
use super::node::NODE_ANALYSIS_CACHE_DB; use super::node::NODE_ANALYSIS_CACHE_DB;
use super::parsed_source::PARSED_SOURCE_CACHE_DB; use super::parsed_source::PARSED_SOURCE_CACHE_DB;
use super::DenoDir;
pub struct Caches { pub struct Caches {
dir: DenoDir, dir_provider: Arc<DenoDirProvider>,
fmt_incremental_cache_db: OnceCell<CacheDB>, fmt_incremental_cache_db: OnceCell<CacheDB>,
lint_incremental_cache_db: OnceCell<CacheDB>, lint_incremental_cache_db: OnceCell<CacheDB>,
dep_analysis_db: OnceCell<CacheDB>, dep_analysis_db: OnceCell<CacheDB>,
@ -22,9 +23,9 @@ pub struct Caches {
} }
impl Caches { impl Caches {
pub fn new(dir: DenoDir) -> Self { pub fn new(dir: Arc<DenoDirProvider>) -> Self {
Self { Self {
dir, dir_provider: dir,
fmt_incremental_cache_db: Default::default(), fmt_incremental_cache_db: Default::default(),
lint_incremental_cache_db: Default::default(), lint_incremental_cache_db: Default::default(),
dep_analysis_db: Default::default(), dep_analysis_db: Default::default(),
@ -36,10 +37,16 @@ impl Caches {
fn make_db( fn make_db(
cell: &OnceCell<CacheDB>, cell: &OnceCell<CacheDB>,
config: &'static CacheDBConfiguration, config: &'static CacheDBConfiguration,
path: PathBuf, path: Option<PathBuf>,
) -> CacheDB { ) -> CacheDB {
cell cell
.get_or_init(|| CacheDB::from_path(config, path, crate::version::deno())) .get_or_init(|| {
if let Some(path) = path {
CacheDB::from_path(config, path, crate::version::deno())
} else {
CacheDB::in_memory(config, crate::version::deno())
}
})
.clone() .clone()
} }
@ -47,7 +54,11 @@ impl Caches {
Self::make_db( Self::make_db(
&self.fmt_incremental_cache_db, &self.fmt_incremental_cache_db,
&INCREMENTAL_CACHE_DB, &INCREMENTAL_CACHE_DB,
self.dir.fmt_incremental_cache_db_file_path(), self
.dir_provider
.get_or_create()
.ok()
.map(|dir| dir.fmt_incremental_cache_db_file_path()),
) )
} }
@ -55,7 +66,11 @@ impl Caches {
Self::make_db( Self::make_db(
&self.lint_incremental_cache_db, &self.lint_incremental_cache_db,
&INCREMENTAL_CACHE_DB, &INCREMENTAL_CACHE_DB,
self.dir.lint_incremental_cache_db_file_path(), self
.dir_provider
.get_or_create()
.ok()
.map(|dir| dir.lint_incremental_cache_db_file_path()),
) )
} }
@ -63,7 +78,11 @@ impl Caches {
Self::make_db( Self::make_db(
&self.dep_analysis_db, &self.dep_analysis_db,
&PARSED_SOURCE_CACHE_DB, &PARSED_SOURCE_CACHE_DB,
self.dir.dep_analysis_db_file_path(), self
.dir_provider
.get_or_create()
.ok()
.map(|dir| dir.dep_analysis_db_file_path()),
) )
} }
@ -71,7 +90,11 @@ impl Caches {
Self::make_db( Self::make_db(
&self.node_analysis_db, &self.node_analysis_db,
&NODE_ANALYSIS_CACHE_DB, &NODE_ANALYSIS_CACHE_DB,
self.dir.node_analysis_db_file_path(), self
.dir_provider
.get_or_create()
.ok()
.map(|dir| dir.node_analysis_db_file_path()),
) )
} }
@ -79,7 +102,11 @@ impl Caches {
Self::make_db( Self::make_db(
&self.type_checking_cache_db, &self.type_checking_cache_db,
&TYPE_CHECK_CACHE_DB, &TYPE_CHECK_CACHE_DB,
self.dir.type_checking_cache_db_file_path(), self
.dir_provider
.get_or_create()
.ok()
.map(|dir| dir.type_checking_cache_db_file_path()),
) )
} }
} }

28
cli/cache/deno_dir.rs vendored
View file

@ -1,10 +1,36 @@
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
use once_cell::sync::OnceCell;
use super::DiskCache; use super::DiskCache;
use std::env; use std::env;
use std::path::PathBuf; use std::path::PathBuf;
/// Lazily creates the deno dir which might be useful in scenarios
/// where functionality wants to continue if the DENO_DIR can't be created.
pub struct DenoDirProvider {
maybe_custom_root: Option<PathBuf>,
deno_dir: OnceCell<std::io::Result<DenoDir>>,
}
impl DenoDirProvider {
pub fn new(maybe_custom_root: Option<PathBuf>) -> Self {
Self {
maybe_custom_root,
deno_dir: Default::default(),
}
}
pub fn get_or_create(&self) -> Result<&DenoDir, std::io::Error> {
self
.deno_dir
.get_or_init(|| DenoDir::new(self.maybe_custom_root.clone()))
.as_ref()
.map_err(|err| std::io::Error::new(err.kind(), err.to_string()))
}
}
/// `DenoDir` serves as coordinator for multiple `DiskCache`s containing them /// `DenoDir` serves as coordinator for multiple `DiskCache`s containing them
/// in single directory that can be controlled with `$DENO_DIR` env variable. /// in single directory that can be controlled with `$DENO_DIR` env variable.
#[derive(Clone)] #[derive(Clone)]
@ -18,6 +44,8 @@ pub struct DenoDir {
impl DenoDir { impl DenoDir {
pub fn new(maybe_custom_root: Option<PathBuf>) -> std::io::Result<Self> { pub fn new(maybe_custom_root: Option<PathBuf>) -> std::io::Result<Self> {
let maybe_custom_root =
maybe_custom_root.or_else(|| env::var("DENO_DIR").map(String::into).ok());
let root: PathBuf = if let Some(root) = maybe_custom_root { let root: PathBuf = if let Some(root) = maybe_custom_root {
root root
} else if let Some(cache_dir) = dirs::cache_dir() { } else if let Some(cache_dir) = dirs::cache_dir() {

1
cli/cache/mod.rs vendored
View file

@ -30,6 +30,7 @@ pub use caches::Caches;
pub use check::TypeCheckCache; pub use check::TypeCheckCache;
pub use common::FastInsecureHasher; pub use common::FastInsecureHasher;
pub use deno_dir::DenoDir; pub use deno_dir::DenoDir;
pub use deno_dir::DenoDirProvider;
pub use disk_cache::DiskCache; pub use disk_cache::DiskCache;
pub use emit::EmitCache; pub use emit::EmitCache;
pub use http_cache::CachedUrlMetadata; pub use http_cache::CachedUrlMetadata;

View file

@ -10,6 +10,7 @@ use crate::args::StorageKeyResolver;
use crate::args::TsConfigType; use crate::args::TsConfigType;
use crate::cache::Caches; use crate::cache::Caches;
use crate::cache::DenoDir; use crate::cache::DenoDir;
use crate::cache::DenoDirProvider;
use crate::cache::EmitCache; use crate::cache::EmitCache;
use crate::cache::HttpCache; use crate::cache::HttpCache;
use crate::cache::NodeAnalysisCache; use crate::cache::NodeAnalysisCache;
@ -130,7 +131,7 @@ impl<T> Deferred<T> {
#[derive(Default)] #[derive(Default)]
struct CliFactoryServices { struct CliFactoryServices {
dir: Deferred<DenoDir>, deno_dir_provider: Deferred<Arc<DenoDirProvider>>,
caches: Deferred<Arc<Caches>>, caches: Deferred<Arc<Caches>>,
file_fetcher: Deferred<Arc<FileFetcher>>, file_fetcher: Deferred<Arc<FileFetcher>>,
http_client: Deferred<Arc<HttpClient>>, http_client: Deferred<Arc<HttpClient>>,
@ -182,16 +183,21 @@ impl CliFactory {
&self.options &self.options
} }
pub fn deno_dir_provider(&self) -> &Arc<DenoDirProvider> {
self.services.deno_dir_provider.get_or_init(|| {
Arc::new(DenoDirProvider::new(
self.options.maybe_custom_root().clone(),
))
})
}
pub fn deno_dir(&self) -> Result<&DenoDir, AnyError> { pub fn deno_dir(&self) -> Result<&DenoDir, AnyError> {
self Ok(self.deno_dir_provider().get_or_create()?)
.services
.dir
.get_or_try_init(|| self.options.resolve_deno_dir())
} }
pub fn caches(&self) -> Result<&Arc<Caches>, AnyError> { pub fn caches(&self) -> Result<&Arc<Caches>, AnyError> {
self.services.caches.get_or_try_init(|| { self.services.caches.get_or_try_init(|| {
let caches = Arc::new(Caches::new(self.deno_dir()?.clone())); let caches = Arc::new(Caches::new(self.deno_dir_provider().clone()));
// Warm up the caches we know we'll likely need based on the CLI mode // Warm up the caches we know we'll likely need based on the CLI mode
match self.options.sub_command() { match self.options.sub_command() {
DenoSubcommand::Run(_) => { DenoSubcommand::Run(_) => {

View file

@ -582,9 +582,7 @@ fn create_npm_resolver_and_resolution(
impl Inner { impl Inner {
fn new(client: Client) -> Self { fn new(client: Client) -> Self {
let maybe_custom_root = env::var("DENO_DIR").map(String::into).ok(); let dir = DenoDir::new(None).expect("could not access DENO_DIR");
let dir =
DenoDir::new(maybe_custom_root).expect("could not access DENO_DIR");
let module_registries_location = dir.registries_folder_path(); let module_registries_location = dir.registries_folder_path();
let http_client = Arc::new(HttpClient::new(None, None)); let http_client = Arc::new(HttpClient::new(None, None));
let module_registries = let module_registries =
@ -904,7 +902,7 @@ impl Inner {
&mut self, &mut self,
new_cache_path: Option<PathBuf>, new_cache_path: Option<PathBuf>,
) -> Result<(), AnyError> { ) -> Result<(), AnyError> {
let dir = self.deno_dir_from_maybe_cache_path(new_cache_path.clone())?; let dir = DenoDir::new(new_cache_path.clone())?;
let workspace_settings = self.config.workspace_settings(); let workspace_settings = self.config.workspace_settings();
let maybe_root_path = self let maybe_root_path = self
.config .config
@ -938,19 +936,8 @@ impl Inner {
Ok(()) Ok(())
} }
fn deno_dir_from_maybe_cache_path(
&self,
cache_path: Option<PathBuf>,
) -> std::io::Result<DenoDir> {
let maybe_custom_root =
cache_path.or_else(|| env::var("DENO_DIR").map(String::into).ok());
DenoDir::new(maybe_custom_root)
}
async fn recreate_npm_services_if_necessary(&mut self) { async fn recreate_npm_services_if_necessary(&mut self) {
let deno_dir = match self let deno_dir = match DenoDir::new(self.maybe_cache_path.clone()) {
.deno_dir_from_maybe_cache_path(self.maybe_cache_path.clone())
{
Ok(deno_dir) => deno_dir, Ok(deno_dir) => deno_dir,
Err(err) => { Err(err) => {
lsp_warn!("Error getting deno dir: {}", err); lsp_warn!("Error getting deno dir: {}", err);

View file

@ -7,7 +7,7 @@ use crate::args::CacheSetting;
use crate::args::PackageJsonDepsProvider; use crate::args::PackageJsonDepsProvider;
use crate::args::StorageKeyResolver; use crate::args::StorageKeyResolver;
use crate::cache::Caches; use crate::cache::Caches;
use crate::cache::DenoDir; use crate::cache::DenoDirProvider;
use crate::cache::NodeAnalysisCache; use crate::cache::NodeAnalysisCache;
use crate::file_fetcher::get_source_from_data_url; use crate::file_fetcher::get_source_from_data_url;
use crate::http_util::HttpClient; use crate::http_util::HttpClient;
@ -282,7 +282,7 @@ pub async fn run(
let current_exe_path = std::env::current_exe().unwrap(); let current_exe_path = std::env::current_exe().unwrap();
let current_exe_name = let current_exe_name =
current_exe_path.file_name().unwrap().to_string_lossy(); current_exe_path.file_name().unwrap().to_string_lossy();
let dir = DenoDir::new(None)?; let deno_dir_provider = Arc::new(DenoDirProvider::new(None));
let root_cert_store_provider = Arc::new(StandaloneRootCertStoreProvider { let root_cert_store_provider = Arc::new(StandaloneRootCertStoreProvider {
ca_stores: metadata.ca_stores, ca_stores: metadata.ca_stores,
ca_data: metadata.ca_data.map(CaData::Bytes), ca_data: metadata.ca_data.map(CaData::Bytes),
@ -362,7 +362,7 @@ pub async fn run(
let node_resolver = let node_resolver =
Arc::new(NodeResolver::new(fs.clone(), npm_resolver.clone())); Arc::new(NodeResolver::new(fs.clone(), npm_resolver.clone()));
let cjs_resolutions = Arc::new(CjsResolutionStore::default()); let cjs_resolutions = Arc::new(CjsResolutionStore::default());
let cache_db = Caches::new(dir.clone()); let cache_db = Caches::new(deno_dir_provider.clone());
let node_analysis_cache = NodeAnalysisCache::new(cache_db.node_analysis_db()); let node_analysis_cache = NodeAnalysisCache::new(cache_db.node_analysis_db());
let cjs_esm_code_analyzer = CliCjsEsmCodeAnalyzer::new(node_analysis_cache); let cjs_esm_code_analyzer = CliCjsEsmCodeAnalyzer::new(node_analysis_cache);
let node_code_translator = Arc::new(NodeCodeTranslator::new( let node_code_translator = Arc::new(NodeCodeTranslator::new(

View file

@ -1,6 +1,7 @@
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
use std::fs::File; use std::fs::File;
use std::path::Path;
use std::process::Command; use std::process::Command;
use test_util as util; use test_util as util;
use test_util::TempDir; use test_util::TempDir;
@ -8,8 +9,9 @@ use util::assert_contains;
use util::TestContextBuilder; use util::TestContextBuilder;
#[test] #[test]
fn compile() { fn compile_basic() {
let dir = TempDir::new(); let context = TestContextBuilder::new().build();
let dir = context.temp_dir();
let exe = if cfg!(windows) { let exe = if cfg!(windows) {
dir.path().join("welcome.exe") dir.path().join("welcome.exe")
} else { } else {
@ -17,26 +19,45 @@ fn compile() {
}; };
// try this twice to ensure it works with the cache // try this twice to ensure it works with the cache
for _ in 0..2 { for _ in 0..2 {
let output = util::deno_cmd_with_deno_dir(&dir) let output = context
.current_dir(util::root_path()) .new_command()
.arg("compile") .args_vec([
.arg("--output") "compile",
.arg(&exe) "--output",
.arg("./test_util/std/examples/welcome.ts") &exe.to_string_lossy(),
.stdout(std::process::Stdio::piped()) "../../../test_util/std/examples/welcome.ts",
.spawn() ])
.unwrap() .run();
.wait_with_output() output.assert_exit_code(0);
.unwrap(); output.skip_output_check();
assert!(output.status.success()); let output = context
let output = Command::new(&exe) .new_command()
.stdout(std::process::Stdio::piped()) .command_name(exe.to_string_lossy())
.spawn() .run();
.unwrap() output.assert_matches_text("Welcome to Deno!\n");
.wait_with_output() }
.unwrap();
assert!(output.status.success()); // now ensure this works when the deno_dir is readonly
assert_eq!(output.stdout, "Welcome to Deno!\n".as_bytes()); let readonly_dir = dir.path().join("readonly");
make_dir_readonly(&readonly_dir);
let readonly_sub_dir = readonly_dir.join("sub");
let output = context
.new_command()
// it should fail creating this, but still work
.env("DENO_DIR", readonly_sub_dir.to_string_lossy())
.command_name(exe.to_string_lossy())
.run();
output.assert_matches_text("Welcome to Deno!\n");
}
fn make_dir_readonly(dir: &Path) {
std::fs::create_dir_all(dir).unwrap();
eprintln!("DIR: {}", dir.display());
if cfg!(windows) {
Command::new("attrib").arg("+r").arg(dir).output().unwrap();
} else if cfg!(unix) {
Command::new("chmod").arg("555").arg(dir).output().unwrap();
} }
} }

View file

@ -107,9 +107,12 @@ pub async fn run(flags: Flags, repl_flags: ReplFlags) -> Result<i32, AnyError> {
)?); )?);
let npm_resolver = factory.npm_resolver().await?.clone(); let npm_resolver = factory.npm_resolver().await?.clone();
let resolver = factory.resolver().await?.clone(); let resolver = factory.resolver().await?.clone();
let dir = factory.deno_dir()?;
let file_fetcher = factory.file_fetcher()?; let file_fetcher = factory.file_fetcher()?;
let worker_factory = factory.create_cli_main_worker_factory().await?; let worker_factory = factory.create_cli_main_worker_factory().await?;
let history_file_path = factory
.deno_dir()
.ok()
.and_then(|dir| dir.repl_history_file_path());
let mut worker = worker_factory let mut worker = worker_factory
.create_main_worker(main_module, permissions) .create_main_worker(main_module, permissions)
@ -126,7 +129,6 @@ pub async fn run(flags: Flags, repl_flags: ReplFlags) -> Result<i32, AnyError> {
sync_sender: rustyline_channel.0, sync_sender: rustyline_channel.0,
}; };
let history_file_path = dir.repl_history_file_path();
let editor = ReplEditor::new(helper, history_file_path)?; let editor = ReplEditor::new(helper, history_file_path)?;
if let Some(eval_files) = repl_flags.eval_files { if let Some(eval_files) = repl_flags.eval_files {