summaryrefslogtreecommitdiff
path: root/cli/fs_util.rs
diff options
context:
space:
mode:
authorDavid Sherret <dsherret@users.noreply.github.com>2022-11-08 14:17:24 -0500
committerGitHub <noreply@github.com>2022-11-08 14:17:24 -0500
commitcbb3f854332c348bb253e1284f7dcd7287bdf28d (patch)
tree93e2db9439bd745d48118a931bdc8bea61b81af5 /cli/fs_util.rs
parent2c72e8d5f45f12948310c1f0e1e2ed4f1d80fb51 (diff)
feat(unstable/npm): support peer dependencies (#16561)
This adds support for peer dependencies in npm packages. 1. If not found higher in the tree (ancestor and ancestor siblings), peer dependencies are resolved like a dependency similar to npm 7. 2. Optional peer dependencies are only resolved if found higher in the tree. 3. This creates "copy packages" or duplicates of a package when a package has different resolution due to peer dependency resolution—see https://pnpm.io/how-peers-are-resolved. Unlike pnpm though, duplicates of packages will have `_1`, `_2`, etc. added to the end of the package version in the directory in order to minimize the chance of hitting the max file path limit on Windows. This is done for both the local "node_modules" directory and also the global npm cache. The files are hard linked in this case to reduce hard drive space. This is a first pass and the code is definitely more inefficient than it could be. Closes #15823
Diffstat (limited to 'cli/fs_util.rs')
-rw-r--r--cli/fs_util.rs79
1 files changed, 79 insertions, 0 deletions
diff --git a/cli/fs_util.rs b/cli/fs_util.rs
index fa1535469..843f5e0cf 100644
--- a/cli/fs_util.rs
+++ b/cli/fs_util.rs
@@ -15,6 +15,7 @@ use std::io::ErrorKind;
use std::io::Write;
use std::path::Path;
use std::path::PathBuf;
+use std::time::Duration;
use walkdir::WalkDir;
pub fn atomic_write_file<T: AsRef<[u8]>>(
@@ -357,6 +358,84 @@ pub fn copy_dir_recursive(from: &Path, to: &Path) -> Result<(), AnyError> {
Ok(())
}
+/// Hardlinks the files in one directory to another directory.
+///
+/// Note: Does not handle symlinks.
+pub fn hard_link_dir_recursive(from: &Path, to: &Path) -> Result<(), AnyError> {
+ std::fs::create_dir_all(&to)
+ .with_context(|| format!("Creating {}", to.display()))?;
+ let read_dir = std::fs::read_dir(&from)
+ .with_context(|| format!("Reading {}", from.display()))?;
+
+ for entry in read_dir {
+ let entry = entry?;
+ let file_type = entry.file_type()?;
+ let new_from = from.join(entry.file_name());
+ let new_to = to.join(entry.file_name());
+
+ if file_type.is_dir() {
+ hard_link_dir_recursive(&new_from, &new_to).with_context(|| {
+ format!("Dir {} to {}", new_from.display(), new_to.display())
+ })?;
+ } else if file_type.is_file() {
+ // note: chance for race conditions here between attempting to create,
+ // then removing, then attempting to create. There doesn't seem to be
+ // a way to hard link with overwriting in Rust, but maybe there is some
+ // way with platform specific code. The workaround here is to handle
+ // scenarios where something else might create or remove files.
+ if let Err(err) = std::fs::hard_link(&new_from, &new_to) {
+ if err.kind() == ErrorKind::AlreadyExists {
+ if let Err(err) = std::fs::remove_file(&new_to) {
+ if err.kind() == ErrorKind::NotFound {
+ // Assume another process/thread created this hard link to the file we are wanting
+ // to remove then sleep a little bit to let the other process/thread move ahead
+ // faster to reduce contention.
+ std::thread::sleep(Duration::from_millis(10));
+ } else {
+ return Err(err).with_context(|| {
+ format!(
+ "Removing file to hard link {} to {}",
+ new_from.display(),
+ new_to.display()
+ )
+ });
+ }
+ }
+
+ // Always attempt to recreate the hardlink. In contention scenarios, the other process
+ // might have been killed or exited after removing the file, but before creating the hardlink
+ if let Err(err) = std::fs::hard_link(&new_from, &new_to) {
+ // Assume another process/thread created this hard link to the file we are wanting
+ // to now create then sleep a little bit to let the other process/thread move ahead
+ // faster to reduce contention.
+ if err.kind() == ErrorKind::AlreadyExists {
+ std::thread::sleep(Duration::from_millis(10));
+ } else {
+ return Err(err).with_context(|| {
+ format!(
+ "Hard linking {} to {}",
+ new_from.display(),
+ new_to.display()
+ )
+ });
+ }
+ }
+ } else {
+ return Err(err).with_context(|| {
+ format!(
+ "Hard linking {} to {}",
+ new_from.display(),
+ new_to.display()
+ )
+ });
+ }
+ }
+ }
+ }
+
+ Ok(())
+}
+
pub fn symlink_dir(oldpath: &Path, newpath: &Path) -> Result<(), AnyError> {
let err_mapper = |err: Error| {
Error::new(