From 1d7ddddbe8db8f7b07e255d1286cd1207d7a5bee Mon Sep 17 00:00:00 2001 From: --global Date: Mon, 3 Apr 2023 14:53:50 -0500 Subject: [PATCH 01/19] Starting a new turbopath library from scratch --- .github/CODEOWNERS | 1 + Cargo.lock | 14 +++ Cargo.toml | 1 + crates/turbopath/Cargo.toml | 11 +++ crates/turbopath/src/absolute_system_path.rs | 94 +++++++++++++++++++ .../turbopath/src/absolute_system_path_buf.rs | 84 +++++++++++++++++ crates/turbopath/src/lib.rs | 18 ++++ crates/turbopath/src/relative_system_path.rs | 68 ++++++++++++++ .../turbopath/src/relative_system_path_buf.rs | 76 +++++++++++++++ .../turbopath/src/relative_unix_path_buf.rs | 60 ++++++++++++ 10 files changed, 427 insertions(+) create mode 100644 crates/turbopath/Cargo.toml create mode 100644 crates/turbopath/src/absolute_system_path.rs create mode 100644 crates/turbopath/src/absolute_system_path_buf.rs create mode 100644 crates/turbopath/src/lib.rs create mode 100644 crates/turbopath/src/relative_system_path.rs create mode 100644 crates/turbopath/src/relative_system_path_buf.rs create mode 100644 crates/turbopath/src/relative_unix_path_buf.rs diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index e09f48bd1f77a..25fafdf548a33 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -33,6 +33,7 @@ pnpm-lock.yaml # crates in alphabetical order /crates @vercel/web-tooling # overrides for crates that are owned by turbo-oss +/crates/turbopath @vercel/turbo-oss /crates/turborepo @vercel/turbo-oss /crates/turborepo-api-client @vercel/turbo-oss /crates/turborepo-ffi @vercel/turbo-oss diff --git a/Cargo.lock b/Cargo.lock index 71c8ea6ad048d..6615e799abf10 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3951,6 +3951,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ecba01bf2678719532c5e3059e0b5f0811273d94b397088b82e3bd0a78c78fdd" +[[package]] +name = "path-slash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e91099d4268b0e11973f036e885d652fb0b21fedcf69738c627f94db6a44f42" + [[package]] name = "pathdiff" version = "0.2.1" @@ -7948,6 +7954,14 @@ dependencies = [ "tokio", ] +[[package]] +name = "turbopath" +version = "0.1.0" +dependencies = [ + "path-slash", + "thiserror", +] + [[package]] name = "turborepo-ffi" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 14d82025ab4b3..840300dc9516f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,6 +35,7 @@ members = [ "crates/turbopack-swc-utils", "crates/turbopack-test-utils", "crates/turbopack-tests", + "crates/turbopath", "crates/turborepo", "crates/turborepo-api-client", "crates/turborepo-ffi", diff --git a/crates/turbopath/Cargo.toml b/crates/turbopath/Cargo.toml new file mode 100644 index 0000000000000..1cfd6ee68e4fe --- /dev/null +++ b/crates/turbopath/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "turbopath" +version = "0.1.0" +license = "MPL-2.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +path-slash = "0.2.1" +thiserror = { workspace = true } diff --git a/crates/turbopath/src/absolute_system_path.rs b/crates/turbopath/src/absolute_system_path.rs new file mode 100644 index 0000000000000..ef94b8f2a5777 --- /dev/null +++ b/crates/turbopath/src/absolute_system_path.rs @@ -0,0 +1,94 @@ +use std::{ + fmt, + path::{Components, Path}, +}; + +use crate::{ + absolute_system_path_buf::AbsoluteSystemPathBuf, relative_system_path::RelativeSystemPath, +}; + +pub struct AbsoluteSystemPath<'a>(&'a Path); + +impl<'a> AbsoluteSystemPath<'a> { + /// Creates a `AbsoluteSystemPath` from a `Path` with *no* validation + /// Note that there is no safe way to create an `AbsoluteSystemPath` + /// because if the path separators need to be replaced, that would + /// require allocating a new `PathBuf`, which we cannot do. + /// + /// # Arguments + /// + /// * `path`: + /// + /// returns: AbsoluteSystemPath + /// + /// # Examples + /// + /// ``` + /// use std::path::Path; + /// let path = AbsoluteSystemPath::new_unchecked(Path::new("/foo/bar")); + /// assert_eq!(path.to_str(), Some("/foo/bar")); + /// assert_eq!(path.file_name(), Some("bar")); + /// // Unsafe! + /// let path = AbsoluteSystemPath::new_unchecked(Path::new("./foo/")); + /// assert_eq!(path.to_str(), Some("./foo/")); + /// ``` + pub fn new_unchecked(path: &'a Path) -> Self { + AbsoluteSystemPath(path) + } + + pub fn as_path(&self) -> &Path { + &self.0 + } + + pub fn components(&self) -> Components<'a> { + self.0.components() + } + + pub fn parent(&self) -> Option { + self.0.parent().map(AbsoluteSystemPath::new_unchecked) + } + + pub fn starts_with>(&self, base: P) -> bool { + self.0.starts_with(base.as_ref()) + } + + pub fn ends_with>(&self, child: P) -> bool { + self.0.ends_with(child.as_ref()) + } + + pub fn join(&self, path: &RelativeSystemPath) -> AbsoluteSystemPathBuf { + let mut new_path = self.0.to_path_buf(); + new_path.push(path.as_path()); + AbsoluteSystemPathBuf::new_unchecked(new_path) + } + + pub fn to_str(&self) -> Option<&str> { + self.0.to_str() + } + + pub fn file_name(&self) -> Option<&str> { + self.0.file_name().and_then(|s| s.to_str()) + } + + pub fn extension(&self) -> Option<&str> { + self.0.extension().and_then(|s| s.to_str()) + } +} + +impl<'a> fmt::Display for AbsoluteSystemPath<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.0.display().fmt(f) + } +} + +impl<'a> fmt::Debug for AbsoluteSystemPath<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.0.fmt(f) + } +} + +impl<'a> PartialEq for AbsoluteSystemPath<'a> { + fn eq(&self, other: &Self) -> bool { + self.0 == other.0 + } +} diff --git a/crates/turbopath/src/absolute_system_path_buf.rs b/crates/turbopath/src/absolute_system_path_buf.rs new file mode 100644 index 0000000000000..ba4ab26606dc3 --- /dev/null +++ b/crates/turbopath/src/absolute_system_path_buf.rs @@ -0,0 +1,84 @@ +use std::{ + fmt, + path::{Components, Path, PathBuf}, +}; + +use path_slash::PathBufExt; + +use crate::{absolute_system_path::AbsoluteSystemPath, PathValidationError}; + +pub struct AbsoluteSystemPathBuf(PathBuf); + +impl AbsoluteSystemPathBuf { + pub fn new(unchecked_path: PathBuf) -> Result { + let path_str = unchecked_path + .to_str() + .ok_or(PathValidationError::NonUtf8)?; + let path = PathBuf::from_slash(path_str); + if path.is_absolute() { + Ok(AbsoluteSystemPathBuf(path)) + } else { + Err(PathValidationError::NotAbsolute) + } + } + + pub fn new_unchecked(path: PathBuf) -> Self { + AbsoluteSystemPathBuf(path) + } + + pub fn as_absolute_path(&self) -> AbsoluteSystemPath { + AbsoluteSystemPath::new_unchecked(self.0.as_path()) + } + + pub fn components(&self) -> Components<'_> { + self.0.components() + } + + pub fn parent(&self) -> Option { + self.0 + .parent() + .map(|p| AbsoluteSystemPathBuf(p.to_path_buf())) + } + + pub fn starts_with>(&self, base: P) -> bool { + self.0.starts_with(base.as_ref()) + } + + pub fn ends_with>(&self, child: P) -> bool { + self.0.ends_with(child.as_ref()) + } + + pub fn join>(&self, path: P) -> AbsoluteSystemPathBuf { + AbsoluteSystemPathBuf(self.0.join(path)) + } + + pub fn to_str(&self) -> Option<&str> { + self.0.to_str() + } + + pub fn file_name(&self) -> Option<&str> { + self.0.file_name().and_then(|s| s.to_str()) + } + + pub fn extension(&self) -> Option<&str> { + self.0.extension().and_then(|s| s.to_str()) + } +} + +impl fmt::Display for AbsoluteSystemPathBuf { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.0.display().fmt(f) + } +} + +impl fmt::Debug for AbsoluteSystemPathBuf { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.0.fmt(f) + } +} + +impl PartialEq for AbsoluteSystemPathBuf { + fn eq(&self, other: &Self) -> bool { + self.0 == other.0 + } +} diff --git a/crates/turbopath/src/lib.rs b/crates/turbopath/src/lib.rs new file mode 100644 index 0000000000000..8e447a75d9d6b --- /dev/null +++ b/crates/turbopath/src/lib.rs @@ -0,0 +1,18 @@ +mod absolute_system_path; +mod absolute_system_path_buf; +mod relative_system_path; +mod relative_system_path_buf; +mod relative_unix_path_buf; + +use thiserror::Error; + +// Custom error type for path validation errors +#[derive(Debug, Error)] +pub enum PathValidationError { + #[error("Path is non-UTF-8")] + NonUtf8, + #[error("Path is not absolute")] + NotAbsolute, + #[error("Path is not relative")] + NotRelative, +} diff --git a/crates/turbopath/src/relative_system_path.rs b/crates/turbopath/src/relative_system_path.rs new file mode 100644 index 0000000000000..6c3be5d093f66 --- /dev/null +++ b/crates/turbopath/src/relative_system_path.rs @@ -0,0 +1,68 @@ +use std::{ + fmt, + path::{Components, Path}, +}; + +use crate::{relative_system_path_buf::RelativeSystemPathBuf, PathValidationError}; + +pub struct RelativeSystemPath<'a>(&'a Path); + +impl<'a> RelativeSystemPath<'a> { + fn new_unchecked(path: &'a Path) -> Self { + RelativeSystemPath(path) + } + + pub fn as_path(&self) -> &Path { + &self.0 + } + + pub fn components(&self) -> Components<'_> { + self.0.components() + } + + pub fn parent(&self) -> Option { + self.0.parent().map(RelativeSystemPath::new_unchecked) + } + + pub fn starts_with>(&self, base: P) -> bool { + self.0.starts_with(base.as_ref()) + } + + pub fn ends_with>(&self, child: P) -> bool { + self.0.ends_with(child.as_ref()) + } + + pub fn join>>(&self, path: P) -> RelativeSystemPathBuf { + RelativeSystemPathBuf::new_unchecked(self.0.join(path.as_ref().as_path())) + } + + pub fn to_str(&self) -> Option<&str> { + self.0.to_str() + } + + pub fn file_name(&self) -> Option<&str> { + self.0.file_name().and_then(|s| s.to_str()) + } + + pub fn extension(&self) -> Option<&str> { + self.0.extension().and_then(|s| s.to_str()) + } +} + +impl<'a> fmt::Display for RelativeSystemPath<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.0.display().fmt(f) + } +} + +impl<'a> fmt::Debug for RelativeSystemPath<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.0.fmt(f) + } +} + +impl<'a> PartialEq for RelativeSystemPath<'a> { + fn eq(&self, other: &Self) -> bool { + self.0 == other.0 + } +} diff --git a/crates/turbopath/src/relative_system_path_buf.rs b/crates/turbopath/src/relative_system_path_buf.rs new file mode 100644 index 0000000000000..5f6e095b0ed9a --- /dev/null +++ b/crates/turbopath/src/relative_system_path_buf.rs @@ -0,0 +1,76 @@ +use std::{ + fmt, + path::{Components, Path, PathBuf}, +}; + +pub struct RelativeSystemPathBuf(PathBuf); + +impl RelativeSystemPathBuf { + pub fn new(path: PathBuf) -> Option { + if !path.is_absolute() { + Some(RelativeSystemPathBuf(path)) + } else { + None + } + } + + pub fn new_unchecked(path: PathBuf) -> Self { + RelativeSystemPathBuf(path) + } + + pub fn as_path(&self) -> &Path { + &self.0 + } + + pub fn components(&self) -> Components<'_> { + self.0.components() + } + + pub fn parent(&self) -> Option { + self.0 + .parent() + .map(|p| RelativeSystemPathBuf(p.to_path_buf())) + } + + pub fn starts_with>(&self, base: P) -> bool { + self.0.starts_with(base.as_ref()) + } + + pub fn ends_with>(&self, child: P) -> bool { + self.0.ends_with(child.as_ref()) + } + + pub fn join>(&self, path: P) -> RelativeSystemPathBuf { + RelativeSystemPathBuf(self.0.join(path)) + } + + pub fn to_str(&self) -> Option<&str> { + self.0.to_str() + } + + pub fn file_name(&self) -> Option<&str> { + self.0.file_name().and_then(|s| s.to_str()) + } + + pub fn extension(&self) -> Option<&str> { + self.0.extension().and_then(|s| s.to_str()) + } +} + +impl fmt::Display for RelativeSystemPathBuf { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.0.display().fmt(f) + } +} + +impl fmt::Debug for RelativeSystemPathBuf { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.0.fmt(f) + } +} + +impl PartialEq for RelativeSystemPathBuf { + fn eq(&self, other: &Self) -> bool { + self.0 == other.0 + } +} diff --git a/crates/turbopath/src/relative_unix_path_buf.rs b/crates/turbopath/src/relative_unix_path_buf.rs new file mode 100644 index 0000000000000..3c3375abfcb62 --- /dev/null +++ b/crates/turbopath/src/relative_unix_path_buf.rs @@ -0,0 +1,60 @@ +use std::path::{Components, Path, PathBuf}; + +use path_slash::PathBufExt; + +use crate::PathValidationError; + +pub struct RelativeUnixPathBuf(PathBuf); + +impl RelativeUnixPathBuf { + pub fn new(path: PathBuf) -> Result { + if path.is_absolute() { + return Err(PathValidationError::NotRelative); + } + + let path = path.to_slash().ok_or(PathValidationError::NonUtf8)?; + Ok(RelativeUnixPathBuf(PathBuf::from(path.to_string()))) + } + + pub fn new_unchecked(path: PathBuf) -> Self { + RelativeUnixPathBuf(path) + } + + pub fn as_path(&self) -> &Path { + &self.0 + } + + pub fn components(&self) -> Components<'_> { + self.0.components() + } + + pub fn parent(&self) -> Option { + self.0 + .parent() + .map(|p| RelativeUnixPathBuf(p.to_path_buf())) + } + + pub fn starts_with>(&self, base: P) -> bool { + self.0.starts_with(base.as_ref()) + } + + pub fn ends_with>(&self, child: P) -> bool { + self.0.ends_with(child.as_ref()) + } + + pub fn join>(&self, path: P) -> RelativeUnixPathBuf { + RelativeUnixPathBuf(self.0.join(path)) + } + + pub fn to_str(&self) -> Option<&str> { + self.0.to_str() + } + + pub fn file_name(&self) -> Option<&str> { + self.0.file_name().and_then(|s| s.to_str()) + } + + pub fn extension(&self) -> Option<&str> { + self.0.extension().and_then(|s| s.to_str()) + } +} From 53f54a15c084007859ffb20556558414c866c864 Mon Sep 17 00:00:00 2001 From: --global Date: Mon, 3 Apr 2023 15:43:08 -0500 Subject: [PATCH 02/19] Added traits to convert to System and Unix style paths --- .../turbopath/src/absolute_system_path_buf.rs | 18 ++++-------- crates/turbopath/src/lib.rs | 29 +++++++++++++++++++ crates/turbopath/src/relative_system_path.rs | 4 +-- .../turbopath/src/relative_system_path_buf.rs | 17 +++++++---- .../turbopath/src/relative_unix_path_buf.rs | 7 ++--- 5 files changed, 51 insertions(+), 24 deletions(-) diff --git a/crates/turbopath/src/absolute_system_path_buf.rs b/crates/turbopath/src/absolute_system_path_buf.rs index ba4ab26606dc3..750e9d2877f79 100644 --- a/crates/turbopath/src/absolute_system_path_buf.rs +++ b/crates/turbopath/src/absolute_system_path_buf.rs @@ -3,23 +3,17 @@ use std::{ path::{Components, Path, PathBuf}, }; -use path_slash::PathBufExt; - -use crate::{absolute_system_path::AbsoluteSystemPath, PathValidationError}; - +use crate::{absolute_system_path::AbsoluteSystemPath, IntoSystem, PathValidationError}; pub struct AbsoluteSystemPathBuf(PathBuf); impl AbsoluteSystemPathBuf { pub fn new(unchecked_path: PathBuf) -> Result { - let path_str = unchecked_path - .to_str() - .ok_or(PathValidationError::NonUtf8)?; - let path = PathBuf::from_slash(path_str); - if path.is_absolute() { - Ok(AbsoluteSystemPathBuf(path)) - } else { - Err(PathValidationError::NotAbsolute) + if !unchecked_path.is_absolute() { + return Err(PathValidationError::NotAbsolute); } + + let system_path = unchecked_path.into_system()?; + Ok(AbsoluteSystemPathBuf(system_path)) } pub fn new_unchecked(path: PathBuf) -> Self { diff --git a/crates/turbopath/src/lib.rs b/crates/turbopath/src/lib.rs index 8e447a75d9d6b..9dd5ba7a94487 100644 --- a/crates/turbopath/src/lib.rs +++ b/crates/turbopath/src/lib.rs @@ -4,6 +4,9 @@ mod relative_system_path; mod relative_system_path_buf; mod relative_unix_path_buf; +use std::path::{Path, PathBuf}; + +use path_slash::{PathBufExt, PathExt}; use thiserror::Error; // Custom error type for path validation errors @@ -16,3 +19,29 @@ pub enum PathValidationError { #[error("Path is not relative")] NotRelative, } + +trait IntoSystem { + fn into_system(&self) -> Result; +} + +trait IntoUnix { + fn into_unix(&self) -> Result; +} + +impl IntoSystem for Path { + fn into_system(&self) -> Result { + let path_str = self.to_str().ok_or(PathValidationError::NonUtf8)?; + + Ok(PathBuf::from_slash(path_str)) + } +} + +impl IntoUnix for Path { + fn into_unix(&self) -> Result { + Ok(PathBuf::from( + self.to_slash() + .ok_or(PathValidationError::NonUtf8)? + .as_ref(), + )) + } +} diff --git a/crates/turbopath/src/relative_system_path.rs b/crates/turbopath/src/relative_system_path.rs index 6c3be5d093f66..d2186db5d150e 100644 --- a/crates/turbopath/src/relative_system_path.rs +++ b/crates/turbopath/src/relative_system_path.rs @@ -3,12 +3,12 @@ use std::{ path::{Components, Path}, }; -use crate::{relative_system_path_buf::RelativeSystemPathBuf, PathValidationError}; +use crate::relative_system_path_buf::RelativeSystemPathBuf; pub struct RelativeSystemPath<'a>(&'a Path); impl<'a> RelativeSystemPath<'a> { - fn new_unchecked(path: &'a Path) -> Self { + pub fn new_unchecked(path: &'a Path) -> Self { RelativeSystemPath(path) } diff --git a/crates/turbopath/src/relative_system_path_buf.rs b/crates/turbopath/src/relative_system_path_buf.rs index 5f6e095b0ed9a..036e11c12e17c 100644 --- a/crates/turbopath/src/relative_system_path_buf.rs +++ b/crates/turbopath/src/relative_system_path_buf.rs @@ -3,21 +3,28 @@ use std::{ path::{Components, Path, PathBuf}, }; +use crate::{relative_system_path::RelativeSystemPath, IntoSystem, PathValidationError}; + pub struct RelativeSystemPathBuf(PathBuf); impl RelativeSystemPathBuf { - pub fn new(path: PathBuf) -> Option { - if !path.is_absolute() { - Some(RelativeSystemPathBuf(path)) - } else { - None + pub fn new(unchecked_path: PathBuf) -> Result { + if unchecked_path.is_absolute() { + return Err(PathValidationError::NotRelative); } + + let system_path = unchecked_path.into_system()?; + Ok(RelativeSystemPathBuf(system_path)) } pub fn new_unchecked(path: PathBuf) -> Self { RelativeSystemPathBuf(path) } + pub fn as_relative_path(&self) -> RelativeSystemPath { + RelativeSystemPath::new_unchecked(&self.0) + } + pub fn as_path(&self) -> &Path { &self.0 } diff --git a/crates/turbopath/src/relative_unix_path_buf.rs b/crates/turbopath/src/relative_unix_path_buf.rs index 3c3375abfcb62..b18a8e2d9451b 100644 --- a/crates/turbopath/src/relative_unix_path_buf.rs +++ b/crates/turbopath/src/relative_unix_path_buf.rs @@ -1,8 +1,6 @@ use std::path::{Components, Path, PathBuf}; -use path_slash::PathBufExt; - -use crate::PathValidationError; +use crate::{IntoUnix, PathValidationError}; pub struct RelativeUnixPathBuf(PathBuf); @@ -12,8 +10,7 @@ impl RelativeUnixPathBuf { return Err(PathValidationError::NotRelative); } - let path = path.to_slash().ok_or(PathValidationError::NonUtf8)?; - Ok(RelativeUnixPathBuf(PathBuf::from(path.to_string()))) + Ok(RelativeUnixPathBuf(path.into_unix()?)) } pub fn new_unchecked(path: PathBuf) -> Self { From c4aef7d6ae04ce2d0f70353279f778145aded12a Mon Sep 17 00:00:00 2001 From: --global Date: Mon, 3 Apr 2023 17:53:22 -0500 Subject: [PATCH 03/19] Converted scm to use turbopath --- Cargo.lock | 2 +- Cargo.toml | 1 + crates/turbopath/src/absolute_system_path.rs | 32 +++-- .../turbopath/src/absolute_system_path_buf.rs | 19 ++- .../turbopath/src/anchored_system_path_buf.rs | 45 ++++++ crates/turbopath/src/lib.rs | 20 ++- crates/turbopath/src/relative_system_path.rs | 4 + .../turbopath/src/relative_system_path_buf.rs | 15 +- .../turbopath/src/relative_unix_path_buf.rs | 64 ++++++++- crates/turborepo-scm/Cargo.toml | 2 +- crates/turborepo-scm/src/git.rs | 130 ++++++------------ crates/turborepo-scm/src/lib.rs | 9 +- 12 files changed, 209 insertions(+), 134 deletions(-) create mode 100644 crates/turbopath/src/anchored_system_path_buf.rs diff --git a/Cargo.lock b/Cargo.lock index 6615e799abf10..4df14fc6ac992 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8081,7 +8081,7 @@ dependencies = [ "git2", "tempfile", "thiserror", - "turborepo-paths", + "turbopath", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 840300dc9516f..6260bbbc7f035 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -134,6 +134,7 @@ turbopack-static = { path = "crates/turbopack-static" } turbopack-swc-utils = { path = "crates/turbopack-swc-utils" } turbopack-test-utils = { path = "crates/turbopack-test-utils" } turbopack-tests = { path = "crates/turbopack-tests" } +turbopath = { path = "crates/turbopath" } turborepo = { path = "crates/turborepo" } turborepo-api-client = { path = "crates/turborepo-api-client" } turborepo-ffi = { path = "crates/turborepo-ffi" } diff --git a/crates/turbopath/src/absolute_system_path.rs b/crates/turbopath/src/absolute_system_path.rs index ef94b8f2a5777..2cd036c119762 100644 --- a/crates/turbopath/src/absolute_system_path.rs +++ b/crates/turbopath/src/absolute_system_path.rs @@ -5,8 +5,10 @@ use std::{ use crate::{ absolute_system_path_buf::AbsoluteSystemPathBuf, relative_system_path::RelativeSystemPath, + AnchoredSystemPathBuf, PathValidationError, }; +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct AbsoluteSystemPath<'a>(&'a Path); impl<'a> AbsoluteSystemPath<'a> { @@ -25,6 +27,7 @@ impl<'a> AbsoluteSystemPath<'a> { /// /// ``` /// use std::path::Path; + /// use turbopath::AbsoluteSystemPath; /// let path = AbsoluteSystemPath::new_unchecked(Path::new("/foo/bar")); /// assert_eq!(path.to_str(), Some("/foo/bar")); /// assert_eq!(path.file_name(), Some("bar")); @@ -36,6 +39,19 @@ impl<'a> AbsoluteSystemPath<'a> { AbsoluteSystemPath(path) } + pub fn anchor_at( + &self, + root: &AbsoluteSystemPath, + ) -> Result { + AnchoredSystemPathBuf::strip_root(root, self) + } + + pub fn resolve(&self, anchored_path: &AnchoredSystemPathBuf) -> AbsoluteSystemPathBuf { + let new_path = self.0.join(anchored_path.as_path()); + + AbsoluteSystemPathBuf::new_unchecked(new_path) + } + pub fn as_path(&self) -> &Path { &self.0 } @@ -62,8 +78,8 @@ impl<'a> AbsoluteSystemPath<'a> { AbsoluteSystemPathBuf::new_unchecked(new_path) } - pub fn to_str(&self) -> Option<&str> { - self.0.to_str() + pub fn to_str(&self) -> Result<&str, PathValidationError> { + self.0.to_str().ok_or(PathValidationError::NonUtf8) } pub fn file_name(&self) -> Option<&str> { @@ -80,15 +96,3 @@ impl<'a> fmt::Display for AbsoluteSystemPath<'a> { self.0.display().fmt(f) } } - -impl<'a> fmt::Debug for AbsoluteSystemPath<'a> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.0.fmt(f) - } -} - -impl<'a> PartialEq for AbsoluteSystemPath<'a> { - fn eq(&self, other: &Self) -> bool { - self.0 == other.0 - } -} diff --git a/crates/turbopath/src/absolute_system_path_buf.rs b/crates/turbopath/src/absolute_system_path_buf.rs index 750e9d2877f79..904624922926d 100644 --- a/crates/turbopath/src/absolute_system_path_buf.rs +++ b/crates/turbopath/src/absolute_system_path_buf.rs @@ -3,19 +3,30 @@ use std::{ path::{Components, Path, PathBuf}, }; -use crate::{absolute_system_path::AbsoluteSystemPath, IntoSystem, PathValidationError}; +use crate::{ + absolute_system_path::AbsoluteSystemPath, AnchoredSystemPathBuf, IntoSystem, + PathValidationError, +}; + pub struct AbsoluteSystemPathBuf(PathBuf); impl AbsoluteSystemPathBuf { pub fn new(unchecked_path: PathBuf) -> Result { if !unchecked_path.is_absolute() { - return Err(PathValidationError::NotAbsolute); + return Err(PathValidationError::NotAbsolute(unchecked_path)); } let system_path = unchecked_path.into_system()?; Ok(AbsoluteSystemPathBuf(system_path)) } + pub fn anchor_at( + &self, + root: &AbsoluteSystemPath, + ) -> Result { + AnchoredSystemPathBuf::strip_root(root, &self.as_absolute_path()) + } + pub fn new_unchecked(path: PathBuf) -> Self { AbsoluteSystemPathBuf(path) } @@ -46,8 +57,8 @@ impl AbsoluteSystemPathBuf { AbsoluteSystemPathBuf(self.0.join(path)) } - pub fn to_str(&self) -> Option<&str> { - self.0.to_str() + pub fn to_str(&self) -> Result<&str, PathValidationError> { + self.0.to_str().ok_or(PathValidationError::NonUtf8) } pub fn file_name(&self) -> Option<&str> { diff --git a/crates/turbopath/src/anchored_system_path_buf.rs b/crates/turbopath/src/anchored_system_path_buf.rs new file mode 100644 index 0000000000000..e18e338d067bc --- /dev/null +++ b/crates/turbopath/src/anchored_system_path_buf.rs @@ -0,0 +1,45 @@ +use std::path::{Path, PathBuf}; + +use crate::{absolute_system_path::AbsoluteSystemPath, IntoSystem, PathValidationError}; + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] +pub struct AnchoredSystemPathBuf(PathBuf); + +impl TryFrom<&Path> for AnchoredSystemPathBuf { + type Error = PathValidationError; + + fn try_from(path: &Path) -> Result { + if path.is_absolute() { + return Err(PathValidationError::NotRelative(path.to_path_buf())); + } + + Ok(AnchoredSystemPathBuf(path.into_system()?)) + } +} + +impl AnchoredSystemPathBuf { + pub fn strip_root( + root: &AbsoluteSystemPath, + path: &AbsoluteSystemPath, + ) -> Result { + let stripped_path = path + .as_path() + .strip_prefix(root.as_path()) + .map_err(|_| PathValidationError::NotParent(root.to_string(), path.to_string()))? + .to_path_buf(); + + Ok(AnchoredSystemPathBuf(stripped_path)) + } + + pub fn new_unchecked(path: PathBuf) -> Self { + AnchoredSystemPathBuf(path) + } + + pub fn as_path(&self) -> &Path { + self.0.as_path() + } + + pub fn to_str(&self) -> Result<&str, PathValidationError> { + self.0.to_str().ok_or_else(|| PathValidationError::NonUtf8) + } +} diff --git a/crates/turbopath/src/lib.rs b/crates/turbopath/src/lib.rs index 9dd5ba7a94487..e902505544afc 100644 --- a/crates/turbopath/src/lib.rs +++ b/crates/turbopath/src/lib.rs @@ -1,12 +1,19 @@ mod absolute_system_path; mod absolute_system_path_buf; +mod anchored_system_path_buf; mod relative_system_path; mod relative_system_path_buf; mod relative_unix_path_buf; use std::path::{Path, PathBuf}; +pub use absolute_system_path::AbsoluteSystemPath; +pub use absolute_system_path_buf::AbsoluteSystemPathBuf; +pub use anchored_system_path_buf::AnchoredSystemPathBuf; use path_slash::{PathBufExt, PathExt}; +pub use relative_system_path::RelativeSystemPath; +pub use relative_system_path_buf::RelativeSystemPathBuf; +pub use relative_unix_path_buf::RelativeUnixPathBuf; use thiserror::Error; // Custom error type for path validation errors @@ -14,10 +21,12 @@ use thiserror::Error; pub enum PathValidationError { #[error("Path is non-UTF-8")] NonUtf8, - #[error("Path is not absolute")] - NotAbsolute, - #[error("Path is not relative")] - NotRelative, + #[error("Path is not absolute: {0}")] + NotAbsolute(PathBuf), + #[error("Path is not relative: {0}")] + NotRelative(PathBuf), + #[error("Path {0} is not parent of {1}")] + NotParent(String, String), } trait IntoSystem { @@ -37,6 +46,9 @@ impl IntoSystem for Path { } impl IntoUnix for Path { + /// NOTE: `into_unix` *only* converts Windows paths to Unix paths *on* a + /// Windows system. Do not pass a Windows path on a Unix system and + /// assume it'll be converted. fn into_unix(&self) -> Result { Ok(PathBuf::from( self.to_slash() diff --git a/crates/turbopath/src/relative_system_path.rs b/crates/turbopath/src/relative_system_path.rs index d2186db5d150e..a2ec813256827 100644 --- a/crates/turbopath/src/relative_system_path.rs +++ b/crates/turbopath/src/relative_system_path.rs @@ -8,6 +8,10 @@ use crate::relative_system_path_buf::RelativeSystemPathBuf; pub struct RelativeSystemPath<'a>(&'a Path); impl<'a> RelativeSystemPath<'a> { + /// Creates a `RelativeSystemPath` from a `Path` with *no* validation + /// Note that there is no safe way to create an `RelativeSystemPath` + /// because if the path separators need to be replaced, that would + /// require allocating a new `PathBuf`, which we cannot do. pub fn new_unchecked(path: &'a Path) -> Self { RelativeSystemPath(path) } diff --git a/crates/turbopath/src/relative_system_path_buf.rs b/crates/turbopath/src/relative_system_path_buf.rs index 036e11c12e17c..f42896739b25a 100644 --- a/crates/turbopath/src/relative_system_path_buf.rs +++ b/crates/turbopath/src/relative_system_path_buf.rs @@ -5,12 +5,13 @@ use std::{ use crate::{relative_system_path::RelativeSystemPath, IntoSystem, PathValidationError}; +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] pub struct RelativeSystemPathBuf(PathBuf); impl RelativeSystemPathBuf { pub fn new(unchecked_path: PathBuf) -> Result { if unchecked_path.is_absolute() { - return Err(PathValidationError::NotRelative); + return Err(PathValidationError::NotRelative(unchecked_path)); } let system_path = unchecked_path.into_system()?; @@ -69,15 +70,3 @@ impl fmt::Display for RelativeSystemPathBuf { self.0.display().fmt(f) } } - -impl fmt::Debug for RelativeSystemPathBuf { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.0.fmt(f) - } -} - -impl PartialEq for RelativeSystemPathBuf { - fn eq(&self, other: &Self) -> bool { - self.0 == other.0 - } -} diff --git a/crates/turbopath/src/relative_unix_path_buf.rs b/crates/turbopath/src/relative_unix_path_buf.rs index b18a8e2d9451b..476d7a065921d 100644 --- a/crates/turbopath/src/relative_unix_path_buf.rs +++ b/crates/turbopath/src/relative_unix_path_buf.rs @@ -2,12 +2,29 @@ use std::path::{Components, Path, PathBuf}; use crate::{IntoUnix, PathValidationError}; +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] pub struct RelativeUnixPathBuf(PathBuf); impl RelativeUnixPathBuf { + /// Create a new RelativeUnixPathBuf from a PathBuf by calling `into_unix()` + /// + /// NOTE: `into_unix` *only* converts Windows paths to Unix paths *on* a + /// Windows system. Do not pass a Windows path on a Unix system and + /// assume it'll be converted. + /// + /// # Arguments + /// + /// * `path`: + /// + /// returns: Result + /// + /// # Examples + /// + /// ``` + /// ``` pub fn new(path: PathBuf) -> Result { if path.is_absolute() { - return Err(PathValidationError::NotRelative); + return Err(PathValidationError::NotRelative(path)); } Ok(RelativeUnixPathBuf(path.into_unix()?)) @@ -55,3 +72,48 @@ impl RelativeUnixPathBuf { self.0.extension().and_then(|s| s.to_str()) } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_relative_unix_path_buf() { + let path = RelativeUnixPathBuf::new(PathBuf::from("foo/bar")).unwrap(); + assert_eq!(path.as_path(), Path::new("foo/bar")); + assert_eq!(path.components().count(), 2); + assert_eq!(path.parent().unwrap().as_path(), Path::new("foo")); + assert!(path.starts_with("foo")); + assert!(path.ends_with("bar")); + assert_eq!(path.join("baz").as_path(), Path::new("foo/bar/baz")); + assert_eq!(path.to_str(), Some("foo/bar")); + assert_eq!(path.file_name(), Some("bar")); + assert_eq!(path.extension(), None); + } + + #[test] + fn test_relative_unix_path_buf_with_extension() { + let path = RelativeUnixPathBuf::new(PathBuf::from("foo/bar.txt")).unwrap(); + assert_eq!(path.as_path(), Path::new("foo/bar.txt")); + assert_eq!(path.components().count(), 2); + assert_eq!(path.parent().unwrap().as_path(), Path::new("foo")); + assert!(path.starts_with("foo")); + assert!(path.ends_with("bar.txt")); + assert_eq!(path.join("baz").as_path(), Path::new("foo/bar.txt/baz")); + assert_eq!(path.to_str(), Some("foo/bar.txt")); + assert_eq!(path.file_name(), Some("bar.txt")); + assert_eq!(path.extension(), Some("txt")); + } + + #[test] + fn test_relative_unix_path_buf_errors() { + assert!(RelativeUnixPathBuf::new(PathBuf::from("/foo/bar")).is_err()); + } + + #[cfg(windows)] + #[test] + fn test_convert_from_windows_path() { + let path = RelativeUnixPathBuf::new(PathBuf::from("foo\\bar")).unwrap(); + assert_eq!(path.as_path(), Path::new("foo/bar")); + } +} diff --git a/crates/turborepo-scm/Cargo.toml b/crates/turborepo-scm/Cargo.toml index 844a30ae988a4..9c40f507ce844 100644 --- a/crates/turborepo-scm/Cargo.toml +++ b/crates/turborepo-scm/Cargo.toml @@ -11,7 +11,7 @@ anyhow = { workspace = true } dunce = { workspace = true } git2 = { version = "0.16.1", default-features = false } thiserror = { workspace = true } -turborepo-paths = { path = "../turborepo-paths" } +turbopath = { workspace = true } [dev-dependencies] tempfile = { workspace = true } diff --git a/crates/turborepo-scm/src/git.rs b/crates/turborepo-scm/src/git.rs index 3e122cb128e3d..cb3a6369d9a23 100644 --- a/crates/turborepo-scm/src/git.rs +++ b/crates/turborepo-scm/src/git.rs @@ -1,11 +1,7 @@ -use std::{ - collections::HashSet, - path::{Path, PathBuf}, -}; +use std::{collections::HashSet, path::PathBuf}; -use anyhow::anyhow; use git2::{DiffFormat, DiffOptions, Repository}; -use turborepo_paths::{fs_util, project::ProjectRoot, project_relative_path::ProjectRelativePath}; +use turbopath::{AbsoluteSystemPath, AbsoluteSystemPathBuf, AnchoredSystemPathBuf}; use crate::Error; @@ -23,32 +19,28 @@ use crate::Error; /// /// returns: Result, Error> pub fn changed_files( - repo_root: PathBuf, - monorepo_root: PathBuf, + git_root: PathBuf, + turbo_root: PathBuf, commit_range: Option<(&str, &str)>, ) -> Result, Error> { // Initialize repository at repo root - let repo = Repository::open(&repo_root)?; - let repo_root = ProjectRoot::new(fs_util::canonicalize(repo_root)?)?; - - if monorepo_root.is_relative() { - return Err(Error::PathError(anyhow!( - "monorepo_root must be an absolute path: {:?}", - monorepo_root - ))); - } - - let monorepo_root = fs_util::canonicalize(monorepo_root)?; - let monorepo_root = repo_root.relativize(&monorepo_root)?; + let repo = Repository::open(&git_root)?; + let git_root = AbsoluteSystemPathBuf::new(git_root)?; + let turbo_root = AbsoluteSystemPathBuf::new(turbo_root)?; let mut files = HashSet::new(); - add_changed_files_from_unstaged_changes(&repo_root, &repo, monorepo_root.as_ref(), &mut files)?; + add_changed_files_from_unstaged_changes( + &git_root.as_absolute_path(), + &repo, + &turbo_root.as_absolute_path(), + &mut files, + )?; if let Some((from_commit, to_commit)) = commit_range { add_changed_files_from_commits( - &repo_root, + &git_root.as_absolute_path(), &repo, - monorepo_root.as_ref(), + &turbo_root.as_absolute_path(), &mut files, from_commit, to_commit, @@ -58,63 +50,28 @@ pub fn changed_files( Ok(files) } -// Gets the system version of `monorepo_root` and `file_path` by calling -// `fs_util::canonicalize`, then strips the `monorepo_root` from the -// `file_path`. -fn get_stripped_system_file_path( - repo_root: &ProjectRoot, - file_path: &Path, - monorepo_root: &ProjectRelativePath, -) -> Result { - // We know the path is relative to the repo root so we can convert it to a - // ProjectRelativePath - let project_relative_file_path = ProjectRelativePath::new(file_path)?; - // Which we then resolve to an absolute path - let absolute_file_path = repo_root.resolve(project_relative_file_path); - // Then we call canonicalize to get a system path instead of a Unix style path - let path = fs_util::canonicalize(absolute_file_path)?; - - // We do the same with the monorepo root - let absolute_monorepo_root = repo_root.resolve(monorepo_root); - let monorepo_root_normalized = fs_util::canonicalize(absolute_monorepo_root)?; - - // NOTE: In the original Go code, `Rel` works even if the base path is not a - // prefix of the original path. In Rust, `strip_prefix` returns an - // error if the base path is not a prefix of the original path. - // However since we're passing a pathspec to `git2` we know that the - // base path is a prefix of the original path. - Ok(path - .as_path() - .strip_prefix(monorepo_root_normalized)? - .to_path_buf()) -} - fn add_changed_files_from_unstaged_changes( - repo_root: &ProjectRoot, + repo_root: &AbsoluteSystemPath, repo: &Repository, - monorepo_root: &ProjectRelativePath, + turbo_root: &AbsoluteSystemPath, files: &mut HashSet, ) -> Result<(), Error> { let mut options = DiffOptions::new(); options.include_untracked(true); options.recurse_untracked_dirs(true); - options.pathspec(monorepo_root.to_string()); + let anchored_turbo_root = turbo_root.anchor_at(repo_root)?; + options.pathspec(anchored_turbo_root.to_str()?.to_string()); let diff = repo.diff_index_to_workdir(None, Some(&mut options))?; for delta in diff.deltas() { let file = delta.old_file(); if let Some(file_path) = file.path() { - let stripped_file_path = - get_stripped_system_file_path(repo_root, file_path, monorepo_root)?; - - files.insert( - stripped_file_path - .to_str() - .ok_or_else(|| Error::NonUtf8Path(stripped_file_path.to_path_buf()))? - .to_string(), - ); + let anchored_to_repo_root_file_path: AnchoredSystemPathBuf = file_path.try_into()?; + let absolute_file_path = repo_root.resolve(&anchored_to_repo_root_file_path); + let anchored_to_turbo_root_file_path = absolute_file_path.anchor_at(turbo_root)?; + files.insert(anchored_to_turbo_root_file_path.to_str()?.to_string()); } } @@ -122,9 +79,9 @@ fn add_changed_files_from_unstaged_changes( } fn add_changed_files_from_commits( - repo_root: &ProjectRoot, + repo_root: &AbsoluteSystemPath, repo: &Repository, - monorepo_root: &ProjectRelativePath, + turbo_root: &AbsoluteSystemPath, files: &mut HashSet, from_commit: &str, to_commit: &str, @@ -137,7 +94,8 @@ fn add_changed_files_from_commits( let to_tree = to_commit.tree()?; let mut options = DiffOptions::new(); - options.pathspec(monorepo_root.to_string()); + let anchored_turbo_root = turbo_root.anchor_at(repo_root)?; + options.pathspec(anchored_turbo_root.to_str()?); let diff = repo.diff_tree_to_tree(Some(&from_tree), Some(&to_tree), Some(&mut options))?; diff.print(DiffFormat::NameOnly, |_, _, _| true)?; @@ -145,14 +103,10 @@ fn add_changed_files_from_commits( for delta in diff.deltas() { let file = delta.old_file(); if let Some(file_path) = file.path() { - let stripped_path = get_stripped_system_file_path(repo_root, file_path, monorepo_root)?; - - files.insert( - stripped_path - .to_str() - .ok_or_else(|| Error::NonUtf8Path(stripped_path.to_path_buf()))? - .to_string(), - ); + let anchored_to_repo_root_file_path: AnchoredSystemPathBuf = file_path.try_into()?; + let absolute_file_path = repo_root.resolve(&anchored_to_repo_root_file_path); + let anchored_to_turbo_root_file_path = absolute_file_path.anchor_at(turbo_root)?; + files.insert(anchored_to_turbo_root_file_path.to_str()?.to_string()); } } @@ -175,17 +129,15 @@ pub fn previous_content( file_path: PathBuf, ) -> Result, Error> { let repo = Repository::open(&repo_root)?; - let repo_root = ProjectRoot::new(fs_util::canonicalize(repo_root)?)?; + let repo_root = AbsoluteSystemPathBuf::new(dunce::canonicalize(repo_root)?)?; + let file_path = AbsoluteSystemPathBuf::new(dunce::canonicalize(file_path)?)?; let from_commit_ref = repo.revparse_single(from_commit)?; let from_commit = from_commit_ref.peel_to_commit()?; let from_tree = from_commit.tree()?; - // Canonicalize so strip_prefix works properly - let file_path = fs_util::canonicalize(file_path)?; - - let relative_path = repo_root.relativize(&file_path)?; + let relative_path = file_path.anchor_at(&repo_root.as_absolute_path())?; - let file = from_tree.get_path(Path::new(relative_path.as_str()))?; + let file = from_tree.get_path(relative_path.as_path())?; let blob = repo.find_blob(file.id())?; let content = blob.content(); @@ -238,7 +190,7 @@ mod tests { fn test_changed_files() -> Result<(), Error> { let repo_root = tempfile::tempdir()?; let repo = Repository::init(repo_root.path())?; - let monorepo_root = repo_root.path(); + let turbo_root = repo_root.path(); let mut config = repo.config()?; config.set_str("user.name", "test")?; config.set_str("user.email", "test@example.com")?; @@ -255,7 +207,7 @@ mod tests { // Test that uncommitted file is marked as changed let files = super::changed_files( repo_root.path().to_path_buf(), - monorepo_root.to_path_buf(), + turbo_root.to_path_buf(), None, )?; assert_eq!(files, HashSet::from(["bar.js".to_string()])); @@ -266,7 +218,7 @@ mod tests { // Test that only second file is marked as changed when we check commit range let files = super::changed_files( repo_root.path().to_path_buf(), - monorepo_root.to_path_buf(), + turbo_root.to_path_buf(), Some(( first_commit_oid.to_string().as_str(), second_commit_oid.to_string().as_str(), @@ -279,7 +231,7 @@ mod tests { let new_file = repo_root.path().join("subdir").join("baz.js"); fs::write(new_file, "let x = 2;")?; - // Test that `monorepo_root` filters out files not in the specified directory + // Test that `turbo_root` filters out files not in the specified directory let files = super::changed_files( repo_root.path().to_path_buf(), repo_root.path().join("subdir"), @@ -322,10 +274,10 @@ mod tests { Ok(()) } - // Tests that we can use a subdir as the monorepo_root path + // Tests that we can use a subdir as the turbo_root path // (occurs when the monorepo is nested inside a subdirectory of git repository) #[test] - fn test_changed_files_with_subdir_as_monorepo_root() -> Result<(), Error> { + fn test_changed_files_with_subdir_as_turbo_root() -> Result<(), Error> { let repo_root = tempfile::tempdir()?; let repo = Repository::init(repo_root.path())?; let mut config = repo.config()?; diff --git a/crates/turborepo-scm/src/lib.rs b/crates/turborepo-scm/src/lib.rs index 7cea8bdd06c7d..60456ffe4542d 100644 --- a/crates/turborepo-scm/src/lib.rs +++ b/crates/turborepo-scm/src/lib.rs @@ -1,13 +1,10 @@ -use std::path::PathBuf; - use thiserror::Error; +use turbopath::PathValidationError; pub mod git; #[derive(Debug, Error)] pub enum Error { - #[error("non utf-8 path encountered: {0}")] - NonUtf8Path(PathBuf), #[error("git error: {0}")] Git(#[from] git2::Error), #[error("repository not found")] @@ -15,7 +12,5 @@ pub enum Error { #[error("io error: {0}")] Io(#[from] std::io::Error), #[error("path error: {0}")] - PathError(#[from] anyhow::Error), - #[error("strip prefix error: {0}")] - StripPrefixError(#[from] std::path::StripPrefixError), + Path(#[from] PathValidationError), } From 443bd73fa02c528c7d4574a401c64730b564ecf8 Mon Sep 17 00:00:00 2001 From: --global Date: Mon, 3 Apr 2023 18:05:52 -0500 Subject: [PATCH 04/19] Standardizing methods --- crates/turbopath/src/absolute_system_path.rs | 2 +- crates/turbopath/src/absolute_system_path_buf.rs | 2 +- crates/turbopath/src/anchored_system_path_buf.rs | 4 +++- crates/turbopath/src/lib.rs | 6 +++--- crates/turbopath/src/relative_system_path.rs | 6 +++--- crates/turbopath/src/relative_system_path_buf.rs | 4 ++-- crates/turbopath/src/relative_unix_path_buf.rs | 4 ++-- 7 files changed, 15 insertions(+), 13 deletions(-) diff --git a/crates/turbopath/src/absolute_system_path.rs b/crates/turbopath/src/absolute_system_path.rs index 2cd036c119762..bcfdf53f22c52 100644 --- a/crates/turbopath/src/absolute_system_path.rs +++ b/crates/turbopath/src/absolute_system_path.rs @@ -79,7 +79,7 @@ impl<'a> AbsoluteSystemPath<'a> { } pub fn to_str(&self) -> Result<&str, PathValidationError> { - self.0.to_str().ok_or(PathValidationError::NonUtf8) + self.0.to_str().ok_or(PathValidationError::InvalidUnicode) } pub fn file_name(&self) -> Option<&str> { diff --git a/crates/turbopath/src/absolute_system_path_buf.rs b/crates/turbopath/src/absolute_system_path_buf.rs index 904624922926d..b962b2e8dc079 100644 --- a/crates/turbopath/src/absolute_system_path_buf.rs +++ b/crates/turbopath/src/absolute_system_path_buf.rs @@ -58,7 +58,7 @@ impl AbsoluteSystemPathBuf { } pub fn to_str(&self) -> Result<&str, PathValidationError> { - self.0.to_str().ok_or(PathValidationError::NonUtf8) + self.0.to_str().ok_or(PathValidationError::InvalidUnicode) } pub fn file_name(&self) -> Option<&str> { diff --git a/crates/turbopath/src/anchored_system_path_buf.rs b/crates/turbopath/src/anchored_system_path_buf.rs index e18e338d067bc..07de424e5c9bd 100644 --- a/crates/turbopath/src/anchored_system_path_buf.rs +++ b/crates/turbopath/src/anchored_system_path_buf.rs @@ -40,6 +40,8 @@ impl AnchoredSystemPathBuf { } pub fn to_str(&self) -> Result<&str, PathValidationError> { - self.0.to_str().ok_or_else(|| PathValidationError::NonUtf8) + self.0 + .to_str() + .ok_or_else(|| PathValidationError::InvalidUnicode) } } diff --git a/crates/turbopath/src/lib.rs b/crates/turbopath/src/lib.rs index e902505544afc..de352e6555fd2 100644 --- a/crates/turbopath/src/lib.rs +++ b/crates/turbopath/src/lib.rs @@ -20,7 +20,7 @@ use thiserror::Error; #[derive(Debug, Error)] pub enum PathValidationError { #[error("Path is non-UTF-8")] - NonUtf8, + InvalidUnicode, #[error("Path is not absolute: {0}")] NotAbsolute(PathBuf), #[error("Path is not relative: {0}")] @@ -39,7 +39,7 @@ trait IntoUnix { impl IntoSystem for Path { fn into_system(&self) -> Result { - let path_str = self.to_str().ok_or(PathValidationError::NonUtf8)?; + let path_str = self.to_str().ok_or(PathValidationError::InvalidUnicode)?; Ok(PathBuf::from_slash(path_str)) } @@ -52,7 +52,7 @@ impl IntoUnix for Path { fn into_unix(&self) -> Result { Ok(PathBuf::from( self.to_slash() - .ok_or(PathValidationError::NonUtf8)? + .ok_or(PathValidationError::InvalidUnicode)? .as_ref(), )) } diff --git a/crates/turbopath/src/relative_system_path.rs b/crates/turbopath/src/relative_system_path.rs index a2ec813256827..283477611bb61 100644 --- a/crates/turbopath/src/relative_system_path.rs +++ b/crates/turbopath/src/relative_system_path.rs @@ -3,7 +3,7 @@ use std::{ path::{Components, Path}, }; -use crate::relative_system_path_buf::RelativeSystemPathBuf; +use crate::{relative_system_path_buf::RelativeSystemPathBuf, PathValidationError}; pub struct RelativeSystemPath<'a>(&'a Path); @@ -40,8 +40,8 @@ impl<'a> RelativeSystemPath<'a> { RelativeSystemPathBuf::new_unchecked(self.0.join(path.as_ref().as_path())) } - pub fn to_str(&self) -> Option<&str> { - self.0.to_str() + pub fn to_str(&self) -> Result<&str, PathValidationError> { + self.0.to_str().ok_or(PathValidationError::InvalidUnicode) } pub fn file_name(&self) -> Option<&str> { diff --git a/crates/turbopath/src/relative_system_path_buf.rs b/crates/turbopath/src/relative_system_path_buf.rs index f42896739b25a..d5779c292cb08 100644 --- a/crates/turbopath/src/relative_system_path_buf.rs +++ b/crates/turbopath/src/relative_system_path_buf.rs @@ -52,8 +52,8 @@ impl RelativeSystemPathBuf { RelativeSystemPathBuf(self.0.join(path)) } - pub fn to_str(&self) -> Option<&str> { - self.0.to_str() + pub fn to_str(&self) -> Result<&str, PathValidationError> { + self.0.to_str().ok_or(PathValidationError::InvalidUnicode) } pub fn file_name(&self) -> Option<&str> { diff --git a/crates/turbopath/src/relative_unix_path_buf.rs b/crates/turbopath/src/relative_unix_path_buf.rs index 476d7a065921d..e341674104a58 100644 --- a/crates/turbopath/src/relative_unix_path_buf.rs +++ b/crates/turbopath/src/relative_unix_path_buf.rs @@ -60,8 +60,8 @@ impl RelativeUnixPathBuf { RelativeUnixPathBuf(self.0.join(path)) } - pub fn to_str(&self) -> Option<&str> { - self.0.to_str() + pub fn to_str(&self) -> Result<&str, PathValidationError> { + self.0.to_str().ok_or(PathValidationError::InvalidUnicode) } pub fn file_name(&self) -> Option<&str> { From 4c6c6a9ff56389159aac8bbad841cfb5a211cd4d Mon Sep 17 00:00:00 2001 From: --global Date: Mon, 3 Apr 2023 19:31:16 -0500 Subject: [PATCH 05/19] Deleted the Path types because they were unnecessary --- crates/turbopath/src/absolute_system_path.rs | 98 ------------------- .../turbopath/src/absolute_system_path_buf.rs | 55 ++++++++--- .../turbopath/src/anchored_system_path_buf.rs | 10 +- crates/turbopath/src/lib.rs | 4 - crates/turbopath/src/relative_system_path.rs | 72 -------------- .../turbopath/src/relative_system_path_buf.rs | 13 +-- .../turbopath/src/relative_unix_path_buf.rs | 10 +- crates/turborepo-scm/src/git.rs | 31 +++--- 8 files changed, 70 insertions(+), 223 deletions(-) delete mode 100644 crates/turbopath/src/absolute_system_path.rs delete mode 100644 crates/turbopath/src/relative_system_path.rs diff --git a/crates/turbopath/src/absolute_system_path.rs b/crates/turbopath/src/absolute_system_path.rs deleted file mode 100644 index bcfdf53f22c52..0000000000000 --- a/crates/turbopath/src/absolute_system_path.rs +++ /dev/null @@ -1,98 +0,0 @@ -use std::{ - fmt, - path::{Components, Path}, -}; - -use crate::{ - absolute_system_path_buf::AbsoluteSystemPathBuf, relative_system_path::RelativeSystemPath, - AnchoredSystemPathBuf, PathValidationError, -}; - -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct AbsoluteSystemPath<'a>(&'a Path); - -impl<'a> AbsoluteSystemPath<'a> { - /// Creates a `AbsoluteSystemPath` from a `Path` with *no* validation - /// Note that there is no safe way to create an `AbsoluteSystemPath` - /// because if the path separators need to be replaced, that would - /// require allocating a new `PathBuf`, which we cannot do. - /// - /// # Arguments - /// - /// * `path`: - /// - /// returns: AbsoluteSystemPath - /// - /// # Examples - /// - /// ``` - /// use std::path::Path; - /// use turbopath::AbsoluteSystemPath; - /// let path = AbsoluteSystemPath::new_unchecked(Path::new("/foo/bar")); - /// assert_eq!(path.to_str(), Some("/foo/bar")); - /// assert_eq!(path.file_name(), Some("bar")); - /// // Unsafe! - /// let path = AbsoluteSystemPath::new_unchecked(Path::new("./foo/")); - /// assert_eq!(path.to_str(), Some("./foo/")); - /// ``` - pub fn new_unchecked(path: &'a Path) -> Self { - AbsoluteSystemPath(path) - } - - pub fn anchor_at( - &self, - root: &AbsoluteSystemPath, - ) -> Result { - AnchoredSystemPathBuf::strip_root(root, self) - } - - pub fn resolve(&self, anchored_path: &AnchoredSystemPathBuf) -> AbsoluteSystemPathBuf { - let new_path = self.0.join(anchored_path.as_path()); - - AbsoluteSystemPathBuf::new_unchecked(new_path) - } - - pub fn as_path(&self) -> &Path { - &self.0 - } - - pub fn components(&self) -> Components<'a> { - self.0.components() - } - - pub fn parent(&self) -> Option { - self.0.parent().map(AbsoluteSystemPath::new_unchecked) - } - - pub fn starts_with>(&self, base: P) -> bool { - self.0.starts_with(base.as_ref()) - } - - pub fn ends_with>(&self, child: P) -> bool { - self.0.ends_with(child.as_ref()) - } - - pub fn join(&self, path: &RelativeSystemPath) -> AbsoluteSystemPathBuf { - let mut new_path = self.0.to_path_buf(); - new_path.push(path.as_path()); - AbsoluteSystemPathBuf::new_unchecked(new_path) - } - - pub fn to_str(&self) -> Result<&str, PathValidationError> { - self.0.to_str().ok_or(PathValidationError::InvalidUnicode) - } - - pub fn file_name(&self) -> Option<&str> { - self.0.file_name().and_then(|s| s.to_str()) - } - - pub fn extension(&self) -> Option<&str> { - self.0.extension().and_then(|s| s.to_str()) - } -} - -impl<'a> fmt::Display for AbsoluteSystemPath<'a> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.0.display().fmt(f) - } -} diff --git a/crates/turbopath/src/absolute_system_path_buf.rs b/crates/turbopath/src/absolute_system_path_buf.rs index b962b2e8dc079..3bca94f9f813b 100644 --- a/crates/turbopath/src/absolute_system_path_buf.rs +++ b/crates/turbopath/src/absolute_system_path_buf.rs @@ -3,15 +3,13 @@ use std::{ path::{Components, Path, PathBuf}, }; -use crate::{ - absolute_system_path::AbsoluteSystemPath, AnchoredSystemPathBuf, IntoSystem, - PathValidationError, -}; +use crate::{AnchoredSystemPathBuf, IntoSystem, PathValidationError}; pub struct AbsoluteSystemPathBuf(PathBuf); impl AbsoluteSystemPathBuf { - pub fn new(unchecked_path: PathBuf) -> Result { + pub fn new(unchecked_path: impl Into) -> Result { + let unchecked_path = unchecked_path.into(); if !unchecked_path.is_absolute() { return Err(PathValidationError::NotAbsolute(unchecked_path)); } @@ -20,19 +18,48 @@ impl AbsoluteSystemPathBuf { Ok(AbsoluteSystemPathBuf(system_path)) } - pub fn anchor_at( - &self, - root: &AbsoluteSystemPath, - ) -> Result { - AnchoredSystemPathBuf::strip_root(root, &self.as_absolute_path()) + pub fn new_unchecked(path: impl Into) -> Self { + let path = path.into(); + AbsoluteSystemPathBuf(path) } - pub fn new_unchecked(path: PathBuf) -> Self { - AbsoluteSystemPathBuf(path) + /// Resolves `path` with `self` as anchor. + /// + /// # Arguments + /// + /// * `path`: The path to be anchored at `self` + /// + /// returns: AbsoluteSystemPathBuf + /// + /// # Examples + /// + /// ``` + /// ``` + pub fn resolve(&self, path: &AnchoredSystemPathBuf) -> AbsoluteSystemPathBuf { + AbsoluteSystemPathBuf(self.0.join(path.as_path())) + } + + /// Anchors `path` at `self`. + /// + /// # Arguments + /// + /// * `root`: + /// + /// returns: Result + /// + /// # Examples + /// + /// ``` + /// ``` + pub fn anchor( + &self, + path: &AbsoluteSystemPathBuf, + ) -> Result { + AnchoredSystemPathBuf::strip_root(&self, path) } - pub fn as_absolute_path(&self) -> AbsoluteSystemPath { - AbsoluteSystemPath::new_unchecked(self.0.as_path()) + pub fn as_path(&self) -> &Path { + self.0.as_path() } pub fn components(&self) -> Components<'_> { diff --git a/crates/turbopath/src/anchored_system_path_buf.rs b/crates/turbopath/src/anchored_system_path_buf.rs index 07de424e5c9bd..d3e4945041627 100644 --- a/crates/turbopath/src/anchored_system_path_buf.rs +++ b/crates/turbopath/src/anchored_system_path_buf.rs @@ -1,6 +1,6 @@ use std::path::{Path, PathBuf}; -use crate::{absolute_system_path::AbsoluteSystemPath, IntoSystem, PathValidationError}; +use crate::{AbsoluteSystemPathBuf, IntoSystem, PathValidationError}; #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] pub struct AnchoredSystemPathBuf(PathBuf); @@ -19,8 +19,8 @@ impl TryFrom<&Path> for AnchoredSystemPathBuf { impl AnchoredSystemPathBuf { pub fn strip_root( - root: &AbsoluteSystemPath, - path: &AbsoluteSystemPath, + root: &AbsoluteSystemPathBuf, + path: &AbsoluteSystemPathBuf, ) -> Result { let stripped_path = path .as_path() @@ -31,8 +31,8 @@ impl AnchoredSystemPathBuf { Ok(AnchoredSystemPathBuf(stripped_path)) } - pub fn new_unchecked(path: PathBuf) -> Self { - AnchoredSystemPathBuf(path) + pub fn new_unchecked(path: impl Into) -> Self { + AnchoredSystemPathBuf(path.into()) } pub fn as_path(&self) -> &Path { diff --git a/crates/turbopath/src/lib.rs b/crates/turbopath/src/lib.rs index de352e6555fd2..a77099c012b4d 100644 --- a/crates/turbopath/src/lib.rs +++ b/crates/turbopath/src/lib.rs @@ -1,17 +1,13 @@ -mod absolute_system_path; mod absolute_system_path_buf; mod anchored_system_path_buf; -mod relative_system_path; mod relative_system_path_buf; mod relative_unix_path_buf; use std::path::{Path, PathBuf}; -pub use absolute_system_path::AbsoluteSystemPath; pub use absolute_system_path_buf::AbsoluteSystemPathBuf; pub use anchored_system_path_buf::AnchoredSystemPathBuf; use path_slash::{PathBufExt, PathExt}; -pub use relative_system_path::RelativeSystemPath; pub use relative_system_path_buf::RelativeSystemPathBuf; pub use relative_unix_path_buf::RelativeUnixPathBuf; use thiserror::Error; diff --git a/crates/turbopath/src/relative_system_path.rs b/crates/turbopath/src/relative_system_path.rs deleted file mode 100644 index 283477611bb61..0000000000000 --- a/crates/turbopath/src/relative_system_path.rs +++ /dev/null @@ -1,72 +0,0 @@ -use std::{ - fmt, - path::{Components, Path}, -}; - -use crate::{relative_system_path_buf::RelativeSystemPathBuf, PathValidationError}; - -pub struct RelativeSystemPath<'a>(&'a Path); - -impl<'a> RelativeSystemPath<'a> { - /// Creates a `RelativeSystemPath` from a `Path` with *no* validation - /// Note that there is no safe way to create an `RelativeSystemPath` - /// because if the path separators need to be replaced, that would - /// require allocating a new `PathBuf`, which we cannot do. - pub fn new_unchecked(path: &'a Path) -> Self { - RelativeSystemPath(path) - } - - pub fn as_path(&self) -> &Path { - &self.0 - } - - pub fn components(&self) -> Components<'_> { - self.0.components() - } - - pub fn parent(&self) -> Option { - self.0.parent().map(RelativeSystemPath::new_unchecked) - } - - pub fn starts_with>(&self, base: P) -> bool { - self.0.starts_with(base.as_ref()) - } - - pub fn ends_with>(&self, child: P) -> bool { - self.0.ends_with(child.as_ref()) - } - - pub fn join>>(&self, path: P) -> RelativeSystemPathBuf { - RelativeSystemPathBuf::new_unchecked(self.0.join(path.as_ref().as_path())) - } - - pub fn to_str(&self) -> Result<&str, PathValidationError> { - self.0.to_str().ok_or(PathValidationError::InvalidUnicode) - } - - pub fn file_name(&self) -> Option<&str> { - self.0.file_name().and_then(|s| s.to_str()) - } - - pub fn extension(&self) -> Option<&str> { - self.0.extension().and_then(|s| s.to_str()) - } -} - -impl<'a> fmt::Display for RelativeSystemPath<'a> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.0.display().fmt(f) - } -} - -impl<'a> fmt::Debug for RelativeSystemPath<'a> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.0.fmt(f) - } -} - -impl<'a> PartialEq for RelativeSystemPath<'a> { - fn eq(&self, other: &Self) -> bool { - self.0 == other.0 - } -} diff --git a/crates/turbopath/src/relative_system_path_buf.rs b/crates/turbopath/src/relative_system_path_buf.rs index d5779c292cb08..fee3042ce8d34 100644 --- a/crates/turbopath/src/relative_system_path_buf.rs +++ b/crates/turbopath/src/relative_system_path_buf.rs @@ -3,13 +3,14 @@ use std::{ path::{Components, Path, PathBuf}, }; -use crate::{relative_system_path::RelativeSystemPath, IntoSystem, PathValidationError}; +use crate::{IntoSystem, PathValidationError}; #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] pub struct RelativeSystemPathBuf(PathBuf); impl RelativeSystemPathBuf { - pub fn new(unchecked_path: PathBuf) -> Result { + pub fn new(unchecked_path: impl Into) -> Result { + let unchecked_path = unchecked_path.into(); if unchecked_path.is_absolute() { return Err(PathValidationError::NotRelative(unchecked_path)); } @@ -18,12 +19,8 @@ impl RelativeSystemPathBuf { Ok(RelativeSystemPathBuf(system_path)) } - pub fn new_unchecked(path: PathBuf) -> Self { - RelativeSystemPathBuf(path) - } - - pub fn as_relative_path(&self) -> RelativeSystemPath { - RelativeSystemPath::new_unchecked(&self.0) + pub fn new_unchecked(path: impl Into) -> Self { + RelativeSystemPathBuf(path.into()) } pub fn as_path(&self) -> &Path { diff --git a/crates/turbopath/src/relative_unix_path_buf.rs b/crates/turbopath/src/relative_unix_path_buf.rs index e341674104a58..d186e7989df7a 100644 --- a/crates/turbopath/src/relative_unix_path_buf.rs +++ b/crates/turbopath/src/relative_unix_path_buf.rs @@ -22,7 +22,8 @@ impl RelativeUnixPathBuf { /// /// ``` /// ``` - pub fn new(path: PathBuf) -> Result { + pub fn new(path: impl Into) -> Result { + let path = path.into(); if path.is_absolute() { return Err(PathValidationError::NotRelative(path)); } @@ -30,7 +31,8 @@ impl RelativeUnixPathBuf { Ok(RelativeUnixPathBuf(path.into_unix()?)) } - pub fn new_unchecked(path: PathBuf) -> Self { + pub fn new_unchecked(path: impl Into) -> Self { + let path = path.into(); RelativeUnixPathBuf(path) } @@ -86,7 +88,7 @@ mod tests { assert!(path.starts_with("foo")); assert!(path.ends_with("bar")); assert_eq!(path.join("baz").as_path(), Path::new("foo/bar/baz")); - assert_eq!(path.to_str(), Some("foo/bar")); + assert_eq!(path.to_str().unwrap(), "foo/bar"); assert_eq!(path.file_name(), Some("bar")); assert_eq!(path.extension(), None); } @@ -100,7 +102,7 @@ mod tests { assert!(path.starts_with("foo")); assert!(path.ends_with("bar.txt")); assert_eq!(path.join("baz").as_path(), Path::new("foo/bar.txt/baz")); - assert_eq!(path.to_str(), Some("foo/bar.txt")); + assert_eq!(path.to_str().unwrap(), "foo/bar.txt"); assert_eq!(path.file_name(), Some("bar.txt")); assert_eq!(path.extension(), Some("txt")); } diff --git a/crates/turborepo-scm/src/git.rs b/crates/turborepo-scm/src/git.rs index cb3a6369d9a23..f59a6e674d4b7 100644 --- a/crates/turborepo-scm/src/git.rs +++ b/crates/turborepo-scm/src/git.rs @@ -1,7 +1,7 @@ use std::{collections::HashSet, path::PathBuf}; use git2::{DiffFormat, DiffOptions, Repository}; -use turbopath::{AbsoluteSystemPath, AbsoluteSystemPathBuf, AnchoredSystemPathBuf}; +use turbopath::{AbsoluteSystemPathBuf, AnchoredSystemPathBuf}; use crate::Error; @@ -29,18 +29,13 @@ pub fn changed_files( let turbo_root = AbsoluteSystemPathBuf::new(turbo_root)?; let mut files = HashSet::new(); - add_changed_files_from_unstaged_changes( - &git_root.as_absolute_path(), - &repo, - &turbo_root.as_absolute_path(), - &mut files, - )?; + add_changed_files_from_unstaged_changes(&git_root, &repo, &turbo_root, &mut files)?; if let Some((from_commit, to_commit)) = commit_range { add_changed_files_from_commits( - &git_root.as_absolute_path(), + &git_root, &repo, - &turbo_root.as_absolute_path(), + &turbo_root, &mut files, from_commit, to_commit, @@ -51,16 +46,16 @@ pub fn changed_files( } fn add_changed_files_from_unstaged_changes( - repo_root: &AbsoluteSystemPath, + repo_root: &AbsoluteSystemPathBuf, repo: &Repository, - turbo_root: &AbsoluteSystemPath, + turbo_root: &AbsoluteSystemPathBuf, files: &mut HashSet, ) -> Result<(), Error> { let mut options = DiffOptions::new(); options.include_untracked(true); options.recurse_untracked_dirs(true); - let anchored_turbo_root = turbo_root.anchor_at(repo_root)?; + let anchored_turbo_root = repo_root.anchor(turbo_root)?; options.pathspec(anchored_turbo_root.to_str()?.to_string()); let diff = repo.diff_index_to_workdir(None, Some(&mut options))?; @@ -70,7 +65,7 @@ fn add_changed_files_from_unstaged_changes( if let Some(file_path) = file.path() { let anchored_to_repo_root_file_path: AnchoredSystemPathBuf = file_path.try_into()?; let absolute_file_path = repo_root.resolve(&anchored_to_repo_root_file_path); - let anchored_to_turbo_root_file_path = absolute_file_path.anchor_at(turbo_root)?; + let anchored_to_turbo_root_file_path = turbo_root.anchor(&absolute_file_path)?; files.insert(anchored_to_turbo_root_file_path.to_str()?.to_string()); } } @@ -79,9 +74,9 @@ fn add_changed_files_from_unstaged_changes( } fn add_changed_files_from_commits( - repo_root: &AbsoluteSystemPath, + repo_root: &AbsoluteSystemPathBuf, repo: &Repository, - turbo_root: &AbsoluteSystemPath, + turbo_root: &AbsoluteSystemPathBuf, files: &mut HashSet, from_commit: &str, to_commit: &str, @@ -94,7 +89,7 @@ fn add_changed_files_from_commits( let to_tree = to_commit.tree()?; let mut options = DiffOptions::new(); - let anchored_turbo_root = turbo_root.anchor_at(repo_root)?; + let anchored_turbo_root = repo_root.anchor(turbo_root)?; options.pathspec(anchored_turbo_root.to_str()?); let diff = repo.diff_tree_to_tree(Some(&from_tree), Some(&to_tree), Some(&mut options))?; @@ -105,7 +100,7 @@ fn add_changed_files_from_commits( if let Some(file_path) = file.path() { let anchored_to_repo_root_file_path: AnchoredSystemPathBuf = file_path.try_into()?; let absolute_file_path = repo_root.resolve(&anchored_to_repo_root_file_path); - let anchored_to_turbo_root_file_path = absolute_file_path.anchor_at(turbo_root)?; + let anchored_to_turbo_root_file_path = turbo_root.anchor(&absolute_file_path)?; files.insert(anchored_to_turbo_root_file_path.to_str()?.to_string()); } } @@ -135,7 +130,7 @@ pub fn previous_content( let from_commit = from_commit_ref.peel_to_commit()?; let from_tree = from_commit.tree()?; - let relative_path = file_path.anchor_at(&repo_root.as_absolute_path())?; + let relative_path = repo_root.anchor(&file_path)?; let file = from_tree.get_path(relative_path.as_path())?; let blob = repo.find_blob(file.id())?; From ca813938da09875652bb3ffc6afacbff0436958d Mon Sep 17 00:00:00 2001 From: --global Date: Mon, 3 Apr 2023 19:44:08 -0500 Subject: [PATCH 06/19] Deleted turborepo-paths and cleaned up/documented parts of the API --- .github/workflows/test.yml | 8 +- Cargo.lock | 149 +- Cargo.toml | 1 - .../turbopath/src/absolute_system_path_buf.rs | 81 +- .../turbopath/src/relative_system_path_buf.rs | 21 + crates/turborepo-lib/Cargo.toml | 1 - crates/turborepo-paths/Cargo.toml | 26 - crates/turborepo-paths/LICENSE | 21 - .../src/absolute_normalized_path.rs | 982 ------------- crates/turborepo-paths/src/absolute_path.rs | 194 --- crates/turborepo-paths/src/cmp_impls.rs | 161 -- crates/turborepo-paths/src/file_name.rs | 329 ----- crates/turborepo-paths/src/fmt.rs | 23 - .../src/forward_relative_path.rs | 1306 ----------------- crates/turborepo-paths/src/fs_util.rs | 883 ----------- .../src/into_filename_buf_iterator.rs | 63 - crates/turborepo-paths/src/io_counters.rs | 112 -- crates/turborepo-paths/src/lib.rs | 136 -- crates/turborepo-paths/src/project.rs | 1051 ------------- .../src/project_relative_path.rs | 669 --------- 20 files changed, 83 insertions(+), 6134 deletions(-) delete mode 100644 crates/turborepo-paths/Cargo.toml delete mode 100644 crates/turborepo-paths/LICENSE delete mode 100644 crates/turborepo-paths/src/absolute_normalized_path.rs delete mode 100644 crates/turborepo-paths/src/absolute_path.rs delete mode 100644 crates/turborepo-paths/src/cmp_impls.rs delete mode 100644 crates/turborepo-paths/src/file_name.rs delete mode 100644 crates/turborepo-paths/src/fmt.rs delete mode 100644 crates/turborepo-paths/src/forward_relative_path.rs delete mode 100644 crates/turborepo-paths/src/fs_util.rs delete mode 100644 crates/turborepo-paths/src/into_filename_buf_iterator.rs delete mode 100644 crates/turborepo-paths/src/io_counters.rs delete mode 100644 crates/turborepo-paths/src/lib.rs delete mode 100644 crates/turborepo-paths/src/project.rs delete mode 100644 crates/turborepo-paths/src/project_relative_path.rs diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b016e7ef44375..a5115565902d8 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -641,12 +641,12 @@ jobs: - name: Build nextest timeout-minutes: 120 run: | - cargo nextest run --no-run --workspace --release --exclude turbo --exclude turborepo-ffi --exclude turborepo-lib --exclude turborepo-scm --exclude turborepo-paths --exclude turborepo-lockfiles --exclude turborepo-api-client + cargo nextest run --no-run --workspace --release --exclude turbo --exclude turborepo-ffi --exclude turborepo-lib --exclude turborepo-scm --exclude turbopath --exclude turborepo-lockfiles --exclude turborepo-api-client - name: Run nextest timeout-minutes: 120 run: | - cargo nextest run --workspace --release --no-fail-fast --exclude turbo --exclude turborepo-ffi --exclude turborepo-lib --exclude turborepo-scm --exclude turborepo-paths --exclude turborepo-lockfiles --exclude turborepo-api-client + cargo nextest run --workspace --release --no-fail-fast --exclude turbo --exclude turborepo-ffi --exclude turborepo-lib --exclude turborepo-scm --exclude turbopath --exclude turborepo-lockfiles --exclude turborepo-api-client turbopack_rust_test2: needs: [determine_jobs, rust_prepare] @@ -705,13 +705,13 @@ jobs: timeout-minutes: 120 # We exclude turbo as it requires linking Go and all logic resides in turborepo-lib run: | - cargo nextest run --no-run --workspace --release --exclude turbo --exclude turborepo-ffi --exclude turborepo-lib --exclude turborepo-scm --exclude turborepo-paths --exclude turborepo-lockfiles --exclude turborepo-api-client + cargo nextest run --no-run --workspace --release --exclude turbo --exclude turborepo-ffi --exclude turborepo-lib --exclude turborepo-scm --exclude turbopath --exclude turborepo-lockfiles --exclude turborepo-api-client - name: Run nextest timeout-minutes: 120 # We exclude turbo as it requires linking Go and all logic resides in turborepo-lib run: | - cargo nextest run --workspace --release --no-fail-fast --exclude turbo --exclude turborepo-ffi --exclude turborepo-lib --exclude turborepo-scm --exclude turborepo-paths --exclude turborepo-lockfiles --exclude turborepo-api-client + cargo nextest run --workspace --release --no-fail-fast --exclude turbo --exclude turborepo-ffi --exclude turborepo-lib --exclude turborepo-scm --exclude turbopath --exclude turborepo-lockfiles --exclude turborepo-api-client turbopack_rust_test_bench1: needs: [determine_jobs, rust_prepare] diff --git a/Cargo.lock b/Cargo.lock index 4df14fc6ac992..cb4122e9620a4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -48,26 +48,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "allocative" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e99be155533c99b299ea3b05243b1e7c1747d250d61f92e68452cf8d21f3cab" -dependencies = [ - "allocative_derive", -] - -[[package]] -name = "allocative_derive" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e80c214d3f3f8a93eb62ce1406047e249ef2467e7bc290b88c08fad7e06f34a" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "android_system_properties" version = "0.1.5" @@ -162,12 +142,6 @@ dependencies = [ "wait-timeout", ] -[[package]] -name = "assert_matches" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b34d609dfbaf33d6889b2b7106d3ca345eacad44200913df5ba02bfd31d2ba9" - [[package]] name = "ast_node" version = "0.8.6" @@ -710,15 +684,6 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2698f953def977c68f935bb0dfa959375ad4638570e969e2f1e9f433cbf1af6" -[[package]] -name = "castaway" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a17ed5635fc8536268e5d4de1e22e81ac34419e5f052d4d51f4e01dcc263fcc" -dependencies = [ - "rustversion", -] - [[package]] name = "cbindgen" version = "0.24.3" @@ -925,25 +890,6 @@ dependencies = [ "winapi 0.3.9", ] -[[package]] -name = "common-path" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2382f75942f4b3be3690fe4f86365e9c853c1587d6ee58212cebf6e2a9ccd101" - -[[package]] -name = "compact_str" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bff0805f79ecb1b35163f3957a6934ea8d04fcd36ef98b52e7316f63e72e73d1" -dependencies = [ - "castaway 0.2.2", - "cfg-if 1.0.0", - "itoa", - "ryu", - "static_assertions", -] - [[package]] name = "concurrent-queue" version = "2.1.0" @@ -1069,12 +1015,6 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13418e745008f7349ec7e449155f419a61b92b58a99cc3616942b926825ec76b" -[[package]] -name = "convert_case" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" - [[package]] name = "convert_case" version = "0.5.0" @@ -1569,30 +1509,6 @@ version = "2.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23d8666cb01533c39dde32bcbab8e227b4ed6679b2c925eba05feabea39508fb" -[[package]] -name = "derivative" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "derive_more" -version = "0.99.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" -dependencies = [ - "convert_case 0.4.0", - "proc-macro2", - "quote", - "rustc_version 0.4.0", - "syn 1.0.109", -] - [[package]] name = "dialoguer" version = "0.10.3" @@ -2225,26 +2141,6 @@ dependencies = [ "byteorder", ] -[[package]] -name = "gazebo" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4cfd241cd4d34c5f96bacd2b6ead66171c835713b848162dfedb52d38bf1416" -dependencies = [ - "gazebo_derive", -] - -[[package]] -name = "gazebo_derive" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4afe97206e1db9c227c539f9757fb8e8197cb44e380231de1eb5b75fdea7267" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "generational-arena" version = "0.2.8" @@ -2842,7 +2738,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "334e04b4d781f436dc315cb1e7515bd96826426345d498149e4bde36b67f8ee9" dependencies = [ "async-channel", - "castaway 0.1.2", + "castaway", "crossbeam-utils", "curl", "curl-sys", @@ -4585,26 +4481,6 @@ dependencies = [ "thiserror", ] -[[package]] -name = "ref-cast" -version = "1.0.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c78fb8c9293bcd48ef6fce7b4ca950ceaf21210de6e105a883ee280c0f7b9ed" -dependencies = [ - "ref-cast-impl", -] - -[[package]] -name = "ref-cast-impl" -version = "1.0.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f9c0c92af03644e4806106281fe2e068ac5bc0ae74a707266d06ea27bccee5f" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "regalloc" version = "0.0.34" @@ -8049,29 +7925,6 @@ dependencies = [ "thiserror", ] -[[package]] -name = "turborepo-paths" -version = "0.1.0" -dependencies = [ - "allocative", - "anyhow", - "assert_matches", - "common-path", - "compact_str", - "derivative", - "derive_more", - "dunce", - "gazebo", - "itertools", - "memchr", - "ref-cast", - "relative-path", - "serde", - "smallvec", - "tempfile", - "thiserror", -] - [[package]] name = "turborepo-scm" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 6260bbbc7f035..69dffc1878281 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -42,7 +42,6 @@ members = [ "crates/turborepo-lib", "crates/turborepo-lockfiles", "crates/turborepo-scm", - "crates/turborepo-paths", "xtask", ] diff --git a/crates/turbopath/src/absolute_system_path_buf.rs b/crates/turbopath/src/absolute_system_path_buf.rs index 3bca94f9f813b..9b4ad18788cf5 100644 --- a/crates/turbopath/src/absolute_system_path_buf.rs +++ b/crates/turbopath/src/absolute_system_path_buf.rs @@ -5,9 +5,32 @@ use std::{ use crate::{AnchoredSystemPathBuf, IntoSystem, PathValidationError}; +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] pub struct AbsoluteSystemPathBuf(PathBuf); impl AbsoluteSystemPathBuf { + /// Create a new AbsoluteSystemPathBuf from `unchecked_path`. + /// Confirms that `unchecked_path` is absolute and converts it to a system + /// path. + /// + /// # Arguments + /// + /// * `unchecked_path`: The path to be validated and converted to an + /// `AbsoluteSystemPathBuf`. + /// + /// returns: Result + /// + /// # Examples + /// + /// ``` + /// use std::path::{Path, PathBuf}; + /// use turbopath::AbsoluteSystemPathBuf; + /// let path = PathBuf::from("/Users/user"); + /// let absolute_path = AbsoluteSystemPathBuf::new(path).unwrap(); + /// #[cfg(windows)] + /// assert_eq!(absolute_path.as_path(), Path::new("\\Users\\user")); + /// assert_eq!(absolute_path.as_path(), Path::new("/Users/user")); + /// ``` pub fn new(unchecked_path: impl Into) -> Result { let unchecked_path = unchecked_path.into(); if !unchecked_path.is_absolute() { @@ -18,44 +41,66 @@ impl AbsoluteSystemPathBuf { Ok(AbsoluteSystemPathBuf(system_path)) } + /// Converts `path` to an `AbsoluteSystemPathBuf` without validating that + /// it is either absolute or a system path. + /// + /// # Arguments + /// + /// * `path`: The path to be converted to an `AbsoluteSystemPathBuf`. + /// + /// returns: AbsoluteSystemPathBuf pub fn new_unchecked(path: impl Into) -> Self { let path = path.into(); AbsoluteSystemPathBuf(path) } - /// Resolves `path` with `self` as anchor. + /// Anchors `path` at `self`. /// /// # Arguments /// /// * `path`: The path to be anchored at `self` /// - /// returns: AbsoluteSystemPathBuf + /// returns: Result /// /// # Examples /// /// ``` + /// use std::path::Path; + /// use turbopath::{AbsoluteSystemPathBuf, AnchoredSystemPathBuf}; + /// let base = AbsoluteSystemPathBuf::new("/Users/user").unwrap(); + /// let anchored_path = AbsoluteSystemPathBuf::new("/Users/user/Documents").unwrap(); + /// let anchored_path = base.anchor(&anchored_path).unwrap(); + /// assert_eq!(anchored_path.as_path(), Path::new("Documents")); /// ``` - pub fn resolve(&self, path: &AnchoredSystemPathBuf) -> AbsoluteSystemPathBuf { - AbsoluteSystemPathBuf(self.0.join(path.as_path())) + pub fn anchor( + &self, + path: &AbsoluteSystemPathBuf, + ) -> Result { + AnchoredSystemPathBuf::strip_root(&self, path) } - /// Anchors `path` at `self`. + /// Resolves `path` with `self` as anchor. /// /// # Arguments /// - /// * `root`: + /// * `path`: The path to be anchored at `self` /// - /// returns: Result + /// returns: AbsoluteSystemPathBuf /// /// # Examples /// /// ``` + /// use std::path::Path; + /// use turbopath::{AbsoluteSystemPathBuf, AnchoredSystemPathBuf}; + /// let absolute_path = AbsoluteSystemPathBuf::new("/Users/user").unwrap(); + /// let anchored_path = AnchoredSystemPathBuf::new_unchecked("Documents"); + /// let resolved_path = absolute_path.resolve(&anchored_path); + /// #[cfg(windows)] + /// assert_eq!(resolved_path.as_path(), Path::new("\\Users\\user\\Documents")); + /// assert_eq!(resolved_path.as_path(), Path::new("/Users/user/Documents")); /// ``` - pub fn anchor( - &self, - path: &AbsoluteSystemPathBuf, - ) -> Result { - AnchoredSystemPathBuf::strip_root(&self, path) + pub fn resolve(&self, path: &AnchoredSystemPathBuf) -> AbsoluteSystemPathBuf { + AbsoluteSystemPathBuf(self.0.join(path.as_path())) } pub fn as_path(&self) -> &Path { @@ -102,15 +147,3 @@ impl fmt::Display for AbsoluteSystemPathBuf { self.0.display().fmt(f) } } - -impl fmt::Debug for AbsoluteSystemPathBuf { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.0.fmt(f) - } -} - -impl PartialEq for AbsoluteSystemPathBuf { - fn eq(&self, other: &Self) -> bool { - self.0 == other.0 - } -} diff --git a/crates/turbopath/src/relative_system_path_buf.rs b/crates/turbopath/src/relative_system_path_buf.rs index fee3042ce8d34..75fa30fe9be95 100644 --- a/crates/turbopath/src/relative_system_path_buf.rs +++ b/crates/turbopath/src/relative_system_path_buf.rs @@ -9,6 +9,27 @@ use crate::{IntoSystem, PathValidationError}; pub struct RelativeSystemPathBuf(PathBuf); impl RelativeSystemPathBuf { + /// Create a new RelativeSystemPathBuf from `unchecked_path`. + /// Validates that `unchecked_path` is relative and converts it to a system + /// path + /// + /// # Arguments + /// + /// * `unchecked_path`: Path to be converted to a RelativeSystemPathBuf + /// + /// returns: Result + /// + /// # Examples + /// + /// ``` + /// use std::path::{Path, PathBuf}; + /// use turbopath::RelativeSystemPathBuf; + /// let path = PathBuf::from("Users/user"); + /// let relative_path = RelativeSystemPathBuf::new(path).unwrap(); + /// #[cfg(windows)] + /// assert_eq!(relative_path.as_path(), Path::new("Users\\user")); + /// assert_eq!(relative_path.as_path(), Path::new("Users/user")); + /// ``` pub fn new(unchecked_path: impl Into) -> Result { let unchecked_path = unchecked_path.into(); if unchecked_path.is_absolute() { diff --git a/crates/turborepo-lib/Cargo.toml b/crates/turborepo-lib/Cargo.toml index aa3280d9de0cb..9e411a73864f1 100644 --- a/crates/turborepo-lib/Cargo.toml +++ b/crates/turborepo-lib/Cargo.toml @@ -73,7 +73,6 @@ webbrowser = { workspace = true } const_format = "0.2.30" turbo-updater = { workspace = true } turborepo-api-client = { workspace = true } -turborepo-paths = { version = "0.1.0", path = "../turborepo-paths" } [target.'cfg(target_os = "windows")'.dependencies] uds_windows = "1.0.2" diff --git a/crates/turborepo-paths/Cargo.toml b/crates/turborepo-paths/Cargo.toml deleted file mode 100644 index 2b9dc1a1e3453..0000000000000 --- a/crates/turborepo-paths/Cargo.toml +++ /dev/null @@ -1,26 +0,0 @@ -[package] -name = "turborepo-paths" -version = "0.1.0" -edition = "2021" -license = "MIT" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -allocative = "0.2.0" -anyhow = { workspace = true } -assert_matches = "1.5.0" -common-path = "1.0.0" -compact_str = "0.7.0" -derivative = "2.2.0" -derive_more = "0.99.17" -dunce = { workspace = true } -gazebo = "0.8.1" -itertools = { workspace = true } -memchr = "2.5.0" -ref-cast = "1.0.14" -relative-path = "1.7.3" -serde = { workspace = true } -smallvec = "1.10.0" -tempfile = { workspace = true } -thiserror = { workspace = true } diff --git a/crates/turborepo-paths/LICENSE b/crates/turborepo-paths/LICENSE deleted file mode 100644 index b93be90515ccd..0000000000000 --- a/crates/turborepo-paths/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) Meta Platforms, Inc. and affiliates. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/crates/turborepo-paths/src/absolute_normalized_path.rs b/crates/turborepo-paths/src/absolute_normalized_path.rs deleted file mode 100644 index 96d4a689b95e7..0000000000000 --- a/crates/turborepo-paths/src/absolute_normalized_path.rs +++ /dev/null @@ -1,982 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under both the MIT license found in the - * LICENSE-MIT file in the root directory of this source tree and the Apache - * License, Version 2.0 found in the LICENSE-APACHE file in the root - * directory of this source tree. - */ - -use std::{ - borrow::{Borrow, Cow}, - ffi::{OsStr, OsString}, - ops::Deref, - path::{Path, PathBuf}, -}; - -use derive_more::Display; -use ref_cast::RefCast; -use relative_path::RelativePath; -use serde::{de::Error, Deserialize, Serialize}; -use thiserror::Error; - -use crate::{ - absolute_path::{AbsolutePath, AbsolutePathBuf}, - forward_relative_path::{ForwardRelativePath, ForwardRelativePathNormalizer}, -}; - -/// An absolute path. This path is not platform agnostic. -/// -/// The path is normalized: -/// * it is absolute -/// * not dot in path -/// * no dot-dot in path -/// * TODO(nga): normalize slashes on Windows -/// * TODO(nga): validate UTF-8 -/// * the path is **not** canonicalized -#[derive(Display, Debug, Hash, PartialEq, Eq, PartialOrd, Ord, RefCast)] -#[display(fmt = "{}", "_0.display()")] -#[repr(transparent)] -pub struct AbsoluteNormalizedPath(AbsolutePath); - -/// The owned version of [`AbsoluteNormalizedPath`]. -#[derive(Clone, Display, Debug, Hash, PartialEq, Eq, Ord, PartialOrd)] -#[display(fmt = "{}", "_0.display()")] -pub struct AbsoluteNormalizedPathBuf(AbsolutePathBuf); - -impl AsRef for AbsoluteNormalizedPath { - fn as_ref(&self) -> &Path { - &self.0 - } -} - -impl AsRef for AbsoluteNormalizedPathBuf { - fn as_ref(&self) -> &Path { - &self.0 - } -} - -impl AsRef for AbsoluteNormalizedPath { - fn as_ref(&self) -> &AbsolutePath { - &self.0 - } -} - -impl AsRef for AbsoluteNormalizedPathBuf { - fn as_ref(&self) -> &AbsolutePath { - &self.0 - } -} - -impl Deref for AbsoluteNormalizedPath { - type Target = AbsolutePath; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl Serialize for AbsoluteNormalizedPathBuf { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - self.0.serialize(serializer) - } -} - -impl<'de> Deserialize<'de> for AbsoluteNormalizedPathBuf { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - AbsoluteNormalizedPathBuf::new(PathBuf::deserialize(deserializer)?) - .map_err(D::Error::custom) - } -} - -impl AbsoluteNormalizedPath { - /// Creates an 'AbsPath' if the given path represents an absolute path, - /// otherwise error. - /// - /// ``` - /// # use turborepo_paths::absolute_normalized_path::AbsoluteNormalizedPath; - /// - /// assert!(AbsoluteNormalizedPath::new("foo/bar").is_err()); - /// if cfg!(windows) { - /// assert!(AbsoluteNormalizedPath::new("C:\\foo\\bar").is_ok()); - /// } else { - /// assert!(AbsoluteNormalizedPath::new("/foo/bar").is_ok()); - /// } - /// # anyhow::Ok(()) - /// ``` - pub fn new>(p: &P) -> anyhow::Result<&AbsoluteNormalizedPath> { - let path = AbsolutePath::new(p.as_ref())?; - verify_abs_path(path)?; - Ok(AbsoluteNormalizedPath::ref_cast(path)) - } - - /// Creates an owned 'AbsPathBuf' with path adjoined to self. - /// - /// ``` - /// use std::path::Path; - /// use turborepo_paths::absolute_normalized_path::{AbsoluteNormalizedPath, AbsoluteNormalizedPathBuf}; - /// use turborepo_paths::forward_relative_path::ForwardRelativePath; - /// - /// if cfg!(not(windows)) { - /// let abs_path = AbsoluteNormalizedPath::new("/my")?; - /// assert_eq!(AbsoluteNormalizedPathBuf::from("/my/foo/bar".into())?, abs_path.join(ForwardRelativePath::new("foo/bar")?)); - /// } else { - /// let abs_path = AbsoluteNormalizedPath::new("C:\\my")?; - /// assert_eq!("C:\\my\\foo\\bar", abs_path.join(ForwardRelativePath::new("foo/bar")?).to_string()); - /// } - /// # anyhow::Ok(()) - /// ``` - #[allow(clippy::collapsible_else_if)] - pub fn join>(&self, path: P) -> AbsoluteNormalizedPathBuf { - let path = path.as_ref(); - if path.is_empty() { - self.to_buf() - } else { - if cfg!(windows) { - AbsoluteNormalizedPathBuf(self.0.join(path.as_str().replace('/', "\\"))) - } else { - AbsoluteNormalizedPathBuf(self.0.join(path.as_str())) - } - } - } - - /// Returns a relative path of the parent directory - /// - /// ``` - /// use std::path::Path; - /// use turborepo_paths::absolute_normalized_path::AbsoluteNormalizedPath; - /// - /// if cfg!(not(windows)) { - /// assert_eq!( - /// Some(AbsoluteNormalizedPath::new("/")?), - /// AbsoluteNormalizedPath::new("/my")?.parent() - /// ); - /// assert_eq!( - /// None, - /// AbsoluteNormalizedPath::new("/")?.parent() - /// ); - /// } else { - /// assert_eq!( - /// Some(AbsoluteNormalizedPath::new("c:/")?), - /// AbsoluteNormalizedPath::new("c:/my")?.parent() - /// ); - /// assert_eq!( - /// None, - /// AbsoluteNormalizedPath::new("c:/")?.parent() - /// ); - /// } - /// # anyhow::Ok(()) - /// ``` - pub fn parent(&self) -> Option<&AbsoluteNormalizedPath> { - self.0.parent().map(AbsoluteNormalizedPath::ref_cast) - } - - /// Returns a 'ForwardRelativePath' that, when joined onto `base`, yields - /// `self`. - /// - /// Error if `base` is not a prefix of `self` or the returned - /// path is not a 'ForwardRelativePath' - /// - /// ``` - /// use std::{borrow::Cow, path::Path}; - /// use turborepo_paths::absolute_normalized_path::AbsoluteNormalizedPath; - /// use turborepo_paths::forward_relative_path::ForwardRelativePath; - /// - /// if cfg!(not(windows)) { - /// let path = AbsoluteNormalizedPath::new("/test/foo/bar.txt")?; - /// - /// assert_eq!( - /// path.strip_prefix(AbsoluteNormalizedPath::new("/test")?)?, - /// Cow::Borrowed(ForwardRelativePath::new("foo/bar.txt")?) - /// ); - /// assert!(path.strip_prefix(AbsoluteNormalizedPath::new("/asdf")?).is_err()); - /// } else { - /// let path = AbsoluteNormalizedPath::new(r"C:\test\foo\bar.txt")?; - /// - /// // strip_prefix will return Cow::Owned here but we still - /// // can compare it to Cow::Borrowed. - /// assert_eq!( - /// path.strip_prefix(AbsoluteNormalizedPath::new("c:/test")?)?, - /// Cow::Borrowed(ForwardRelativePath::new("foo/bar.txt")?) - /// ); - /// assert_eq!( - /// path.strip_prefix(AbsoluteNormalizedPath::new(r"c:\test")?)?, - /// Cow::Borrowed(ForwardRelativePath::new("foo/bar.txt")?) - /// ); - /// assert_eq!( - /// path.strip_prefix(AbsoluteNormalizedPath::new(r"\\?\c:\test")?)?, - /// Cow::Borrowed(ForwardRelativePath::new("foo/bar.txt")?) - /// ); - /// assert!(path.strip_prefix(AbsoluteNormalizedPath::new("c:/asdf")?).is_err()); - /// - /// let shared_path = AbsoluteNormalizedPath::new(r"\\server\share\foo\bar.txt")?; - /// assert_eq!( - /// shared_path.strip_prefix(AbsoluteNormalizedPath::new(r"\\server\share\")?)?, - /// Cow::Borrowed(ForwardRelativePath::new("foo/bar.txt")?) - /// ); - /// assert_eq!( - /// shared_path.strip_prefix(AbsoluteNormalizedPath::new(r"\\server\share\foo")?)?, - /// Cow::Borrowed(ForwardRelativePath::new("bar.txt")?) - /// ); - /// assert_eq!( - /// shared_path.strip_prefix(AbsoluteNormalizedPath::new(r"\\?\UNC\server\share\foo")?)?, - /// Cow::Borrowed(ForwardRelativePath::new("bar.txt")?) - /// ); - /// assert!(shared_path.strip_prefix(AbsoluteNormalizedPath::new(r"\\server\share2\foo")?).is_err()); - /// assert!(shared_path.strip_prefix(AbsoluteNormalizedPath::new(r"\\server\share\fo")?).is_err()); - /// } - /// # anyhow::Ok(()) - /// ``` - pub fn strip_prefix>( - &self, - base: P, - ) -> anyhow::Result> { - let stripped_path = self.strip_prefix_impl(base.as_ref())?; - ForwardRelativePathNormalizer::normalize_path(stripped_path) - } - - #[cfg(not(windows))] - fn strip_prefix_impl(&self, base: &AbsoluteNormalizedPath) -> anyhow::Result<&Path> { - self.0.strip_prefix(&base.0).map_err(anyhow::Error::from) - } - - #[cfg(windows)] - fn strip_prefix_impl(&self, base: &AbsoluteNormalizedPath) -> anyhow::Result<&Path> { - if self.windows_prefix()? == base.windows_prefix()? { - self.strip_windows_prefix()? - .strip_prefix(base.strip_windows_prefix()?) - .map_err(anyhow::Error::from) - } else { - Err(anyhow::anyhow!("Path is not a prefix")) - } - } - - /// Determines whether `base` is a prefix of `self`. - /// - /// ``` - /// use std::path::Path; - /// use turborepo_paths::absolute_normalized_path::AbsoluteNormalizedPath; - /// - /// if cfg!(not(windows)) { - /// let abs_path = AbsoluteNormalizedPath::new("/some/foo")?; - /// assert!(abs_path.starts_with(AbsoluteNormalizedPath::new("/some")?)); - /// assert!(!abs_path.starts_with(AbsoluteNormalizedPath::new("/som")?)); - /// } else { - /// let abs_path = AbsoluteNormalizedPath::new("c:/some/foo")?; - /// assert!(abs_path.starts_with(AbsoluteNormalizedPath::new("c:/some")?)); - /// assert!(!abs_path.starts_with(AbsoluteNormalizedPath::new("c:/som")?)); - /// assert!(abs_path.starts_with(AbsoluteNormalizedPath::new(r"\\?\C:\some")?)); - /// - /// let shared_path = AbsoluteNormalizedPath::new(r"\\server\share\foo\bar.txt")?; - /// assert!(shared_path.starts_with(AbsoluteNormalizedPath::new(r"\\server\share\")?)); - /// assert!(shared_path.starts_with(AbsoluteNormalizedPath::new(r"\\server\share\foo")?)); - /// assert!(shared_path.starts_with(AbsoluteNormalizedPath::new(r"\\?\UNC\server\share\foo")?)); - /// assert!(!shared_path.starts_with(AbsoluteNormalizedPath::new(r"\\server\share2\foo")?)); - /// assert!(!shared_path.starts_with(AbsoluteNormalizedPath::new(r"\\server\share\fo")?)); - /// } - /// # anyhow::Ok(()) - /// ``` - pub fn starts_with>(&self, base: P) -> bool { - self.starts_with_impl(base.as_ref()) - } - - #[cfg(not(windows))] - fn starts_with_impl(&self, base: &AbsoluteNormalizedPath) -> bool { - self.0.starts_with(&base.0) - } - - #[cfg(windows)] - fn starts_with_impl(&self, base: &AbsoluteNormalizedPath) -> bool { - let prefix = self.windows_prefix(); - let base_prefix = base.windows_prefix(); - if let (Ok(prefix), Ok(base_prefix)) = (prefix, base_prefix) { - if prefix == base_prefix { - let stripped = self.strip_windows_prefix(); - let base_stripped = base.strip_windows_prefix(); - if let (Ok(stripped), Ok(base_stripped)) = (stripped, base_stripped) { - return stripped.starts_with(base_stripped); - } - } - } - false - } - - /// Determines whether `child` is a suffix of `self`. - /// Only considers whole path components to match. - /// - /// ``` - /// use std::path::Path; - /// use turborepo_paths::absolute_normalized_path::AbsoluteNormalizedPath; - /// - /// if cfg!(not(windows)) { - /// let abs_path = AbsoluteNormalizedPath::new("/some/foo")?; - /// assert!(abs_path.ends_with("foo")); - /// } else { - /// let abs_path = AbsoluteNormalizedPath::new("c:/some/foo")?; - /// assert!(abs_path.ends_with("foo")); - /// } - /// # anyhow::Ok(()) - /// ``` - pub fn ends_with>(&self, child: P) -> bool { - self.0.ends_with(child.as_ref()) - } - - /// Build an owned `AbsPathBuf`, joined with the given path and normalized. - /// - /// ``` - /// use turborepo_paths::absolute_normalized_path::{AbsoluteNormalizedPath, AbsoluteNormalizedPathBuf}; - /// - /// if cfg!(not(windows)) { - /// assert_eq!( - /// AbsoluteNormalizedPathBuf::from("/foo/baz.txt".into())?, - /// AbsoluteNormalizedPath::new("/foo/bar")?.join_normalized("../baz.txt")? - /// ); - /// - /// assert_eq!( - /// AbsoluteNormalizedPath::new("/foo")?.join_normalized("../../baz.txt").is_err(), - /// true - /// ); - /// } else { - /// assert_eq!( - /// AbsoluteNormalizedPathBuf::from("c:/foo/baz.txt".into())?, - /// AbsoluteNormalizedPath::new("c:/foo/bar")?.join_normalized("../baz.txt")? - /// ); - /// - /// assert_eq!( - /// AbsoluteNormalizedPath::new("c:/foo")?.join_normalized("../../baz.txt").is_err(), - /// true - /// ); - /// } - /// # anyhow::Ok(()) - /// ``` - pub fn join_normalized>( - &self, - path: P, - ) -> anyhow::Result { - let mut stack = Vec::new(); - for c in self - .0 - .components() - .chain(path.as_ref().components().map(|c| match c { - relative_path::Component::Normal(s) => std::path::Component::Normal(OsStr::new(s)), - relative_path::Component::CurDir => std::path::Component::CurDir, - relative_path::Component::ParentDir => std::path::Component::ParentDir, - })) - { - match c { - std::path::Component::Normal(_) => stack.push(c), - std::path::Component::Prefix(_) => stack.push(c), - std::path::Component::RootDir => stack.push(c), - std::path::Component::CurDir => {} - std::path::Component::ParentDir => { - if stack.pop().is_none() { - return Err(anyhow::anyhow!(PathNormalizationError::OutOfBounds( - self.as_os_str().into(), - path.as_ref().as_str().into(), - ))); - } - } - } - } - let path_buf = stack.iter().collect::(); - - AbsoluteNormalizedPathBuf::try_from(path_buf) - } - - /// Convert to an owned [`AbsoluteNormalizedPathBuf`]. - pub fn to_buf(&self) -> AbsoluteNormalizedPathBuf { - self.to_owned() - } - - #[cfg(windows)] - /// Get Windows path prefix which is either disk drive letter, device or UNC - /// name. - /// - /// ``` - /// use turborepo_paths::absolute_normalized_path::AbsoluteNormalizedPath; - /// - /// assert_eq!("D", AbsoluteNormalizedPath::new("d:/foo/bar")?.windows_prefix()?); - /// assert_eq!("D", AbsoluteNormalizedPath::new(r"D:\foo\bar")?.windows_prefix()?); - /// assert_eq!("E", AbsoluteNormalizedPath::new(r"\\?\E:\foo\bar")?.windows_prefix()?); - /// assert_eq!("server\\share", AbsoluteNormalizedPath::new(r"\\server\share")?.windows_prefix()?); - /// assert_eq!("server\\share", AbsoluteNormalizedPath::new(r"\\server\share\foo\bar")?.windows_prefix()?); - /// assert_eq!("server\\share", AbsoluteNormalizedPath::new(r"\\?\UNC\server\share")?.windows_prefix()?); - /// assert_eq!("COM42", AbsoluteNormalizedPath::new(r"\\.\COM42")?.windows_prefix()?); - /// assert_eq!("COM42", AbsoluteNormalizedPath::new(r"\\.\COM42\foo\bar")?.windows_prefix()?); - /// ``` - pub fn windows_prefix(&self) -> anyhow::Result { - use std::{os::windows::ffi::OsStringExt, path::Prefix}; - - match self - .0 - .components() - .next() - .ok_or_else(|| anyhow::anyhow!("AbsPath is empty."))? - { - std::path::Component::Prefix(prefix_component) => match prefix_component.kind() { - Prefix::Disk(disk) | Prefix::VerbatimDisk(disk) => { - Ok(OsString::from_wide(&[disk.into()])) - } - Prefix::UNC(server, share) | Prefix::VerbatimUNC(server, share) => { - let mut server = server.to_owned(); - server.push("\\"); - server.push(share); - Ok(server) - } - Prefix::DeviceNS(device) => Ok(device.to_owned()), - prefix => Err(anyhow::anyhow!("Unknown prefix kind: {:?}.", prefix)), - }, - _ => Err(anyhow::anyhow!("AbsPath doesn't have prefix.")), - } - } - - #[cfg(windows)] - /// Strip Windows path prefix which is either disk drive letter, device or - /// UNC name. - /// - /// ``` - /// use std::path::Path; - /// use turborepo_paths::absolute_normalized_path::AbsoluteNormalizedPath; - /// - /// assert_eq!(Path::new(""), AbsoluteNormalizedPath::new("C:/")?.strip_windows_prefix()?); - /// assert_eq!(Path::new(""), AbsoluteNormalizedPath::new("C:\\")?.strip_windows_prefix()?); - /// assert_eq!(Path::new("foo/bar"), AbsoluteNormalizedPath::new("d:/foo/bar")?.strip_windows_prefix()?); - /// assert_eq!(Path::new("foo\\bar"), AbsoluteNormalizedPath::new(r"D:\foo\bar")?.strip_windows_prefix()?); - /// assert_eq!(Path::new("foo\\bar"), AbsoluteNormalizedPath::new(r"\\?\D:\foo\bar")?.strip_windows_prefix()?); - /// assert_eq!(Path::new("path"), AbsoluteNormalizedPath::new(r"\\server\share\path")?.strip_windows_prefix()?); - /// assert_eq!(Path::new("path"), AbsoluteNormalizedPath::new(r"\\?\UNC\server\share\path")?.strip_windows_prefix()?); - /// assert_eq!(Path::new("abc"), AbsoluteNormalizedPath::new(r"\\.\COM42\abc")?.strip_windows_prefix()?); - /// - /// # anyhow::Ok(()) - /// ``` - pub fn strip_windows_prefix(&self) -> anyhow::Result<&Path> { - let mut iter = self.0.iter(); - let prefix = iter - .next() - .ok_or_else(|| anyhow::anyhow!("AbsPath is empty."))?; - let mut prefix = prefix.to_owned(); - // Strip leading path separator as well. - if let Some(component) = iter.next() { - prefix.push(component); - } - Ok(self.as_path().strip_prefix(&prefix)?) - } - - pub fn as_path(&self) -> &Path { - self.0.as_path() - } - - pub fn as_abs_path(&self) -> &AbsolutePath { - &self.0 - } -} - -impl AbsoluteNormalizedPathBuf { - pub fn new(path: PathBuf) -> anyhow::Result { - let path = AbsolutePathBuf::try_from(path)?; - verify_abs_path(&path)?; - Ok(AbsoluteNormalizedPathBuf(path)) - } - - pub(crate) fn unchecked_new(path: PathBuf) -> Self { - AbsoluteNormalizedPathBuf(AbsolutePathBuf::try_from(path).unwrap()) - } - - pub fn into_path_buf(self) -> PathBuf { - self.0.into_path_buf() - } - - pub fn into_abs_path_buf(self) -> AbsolutePathBuf { - self.0 - } - - pub fn from(s: String) -> anyhow::Result { - AbsoluteNormalizedPathBuf::try_from(s) - } - - /// Creates a new 'AbsPathBuf' with a given capacity used to create the - /// internal 'String'. See 'with_capacity' defined on 'PathBuf' - pub fn with_capacity>(cap: usize, base: P) -> Self { - let mut path = PathBuf::with_capacity(cap); - path.push(base.as_ref()); - AbsoluteNormalizedPathBuf(AbsolutePathBuf::try_from(path).unwrap()) - } - - /// Returns the capacity of the underlying 'PathBuf' - pub fn capacity(&self) -> usize { - self.0.capacity() - } - - /// Invokes 'reserve' on the underlying 'PathBuf' - pub fn reserve(&mut self, additional: usize) { - self.0.reserve(additional) - } - - /// Invokes 'shrink_to_fit' on the underlying 'PathBuf' - pub fn shrink_to_fit(&mut self) { - self.0.shrink_to_fit() - } - - /// Invokes 'shrink_to' on the underlying 'PathBuf' - pub fn shrink_to(&mut self, min_capacity: usize) { - self.0.shrink_to(min_capacity) - } - - /// Pushes a `ForwardRelativePath` to the existing buffer - /// ``` - /// - /// use std::path::PathBuf; - /// use turborepo_paths::absolute_normalized_path::AbsoluteNormalizedPathBuf; - /// use turborepo_paths::forward_relative_path::ForwardRelativePath; - /// - /// let prefix = if cfg!(windows) { - /// "C:" - /// } else { - /// "" - /// }; - /// - /// let mut path = AbsoluteNormalizedPathBuf::try_from(format!("{prefix}/foo")).unwrap(); - /// path.push(ForwardRelativePath::unchecked_new("bar")); - /// - /// assert_eq!(AbsoluteNormalizedPathBuf::try_from(format!("{prefix}/foo/bar")).unwrap(), path); - /// - /// path.push(ForwardRelativePath::unchecked_new("more/file.rs")); - /// assert_eq!(AbsoluteNormalizedPathBuf::try_from(format!("{prefix}/foo/bar/more/file.rs")).unwrap(), path); - /// ``` - pub fn push>(&mut self, path: P) { - if cfg!(windows) { - self.0.push(path.as_ref().as_str().replace('/', "\\")) - } else { - self.0.push(path.as_ref().as_str()) - } - } - - /// Pushes a `RelativePath` to the existing buffer, normalizing it. - /// Note that this does not visit the filesystem to resolve `..`s. Instead, - /// it cancels out the components directly, similar to - /// `join_normalized`. ``` - /// - /// use turborepo_paths::abs_norm_path::AbsNormPathBuf; - /// use turborepo_paths::RelativePath; - /// - /// let prefix = if cfg!(windows) { - /// "C:" - /// } else { - /// "" - /// }; - /// - /// let mut path = - /// AbsNormPathBuf::try_from(format!("{prefix}/foo")).unwrap(); - /// path.push_normalized(RelativePath::new("bar"))?; - /// - /// assert_eq!(AbsNormPathBuf::try_from(format!("{prefix}/foo/bar")). - /// unwrap(), path); - /// - /// path.push_normalized(RelativePath::new("more/file.rs"))?; - /// assert_eq!(AbsNormPathBuf::try_from(format!("{prefix}/foo/bar/more/file. - /// rs")).unwrap(), path); - /// - /// path.push_normalized(RelativePath::new("../other.rs"))?; - /// assert_eq!(AbsNormPathBuf::try_from(format!("{prefix}/foo/bar/more/ - /// other.rs")).unwrap(), path); - /// - /// path.push_normalized(RelativePath::new(".."))?; - /// assert_eq!(AbsNormPathBuf::try_from(format!("{prefix}/foo/bar/more")). - /// unwrap(), path); - /// - /// path.push_normalized(RelativePath::new("../.."))?; - /// assert_eq!(AbsNormPathBuf::try_from(format!("{prefix}/foo")).unwrap(), - /// path); - /// - /// path.push_normalized(RelativePath::new(".."))?; - /// assert_eq!(AbsNormPathBuf::try_from(format!("{prefix}/")).unwrap(), - /// path); - /// - /// assert!(path.push_normalized(RelativePath::new("..")).is_err()); - /// - /// # anyhow::Ok(()) - /// ``` - pub fn push_normalized>(&mut self, path: P) -> anyhow::Result<()> { - for c in path.as_ref().components() { - match c { - relative_path::Component::Normal(s) => { - self.0.push(s); - } - relative_path::Component::CurDir => {} - relative_path::Component::ParentDir => { - if !self.0.pop() { - return Err(anyhow::anyhow!(PathNormalizationError::OutOfBounds( - self.as_os_str().into(), - path.as_ref().as_str().into(), - ))); - } - } - } - } - - Ok(()) - } - - pub fn pop(&mut self) -> bool { - self.0.pop() - } -} - -impl TryFrom for AbsoluteNormalizedPathBuf { - type Error = anyhow::Error; - - /// no allocation conversion - /// - /// ``` - /// - /// use turborepo_paths::absolute_normalized_path::AbsoluteNormalizedPathBuf; - /// use std::convert::TryFrom; - /// - /// assert!(AbsoluteNormalizedPathBuf::try_from("relative/bar".to_owned()).is_err()); - /// - /// if cfg!(not(windows)) { - /// assert!(AbsoluteNormalizedPathBuf::try_from("/foo/bar".to_owned()).is_ok()); - /// assert!(AbsoluteNormalizedPathBuf::try_from("/".to_owned()).is_ok()); - /// assert!(AbsoluteNormalizedPathBuf::try_from("/normalize/./bar".to_owned()).is_err()); - /// assert!(AbsoluteNormalizedPathBuf::try_from("/normalize/../bar".to_owned()).is_err()); - /// } else { - /// assert!(AbsoluteNormalizedPathBuf::try_from("c:/foo/bar".to_owned()).is_ok()); - /// assert!(AbsoluteNormalizedPathBuf::try_from("c:/".to_owned()).is_ok()); - /// assert!(AbsoluteNormalizedPathBuf::try_from("c:/normalize/./bar".to_owned()).is_err()); - /// assert!(AbsoluteNormalizedPathBuf::try_from("c:/normalize/../bar".to_owned()).is_err()); - /// } - /// ``` - fn try_from(s: String) -> anyhow::Result { - AbsoluteNormalizedPathBuf::try_from(OsString::from(s)) - } -} - -impl TryFrom for AbsoluteNormalizedPathBuf { - type Error = anyhow::Error; - - // no allocation - fn try_from(s: OsString) -> anyhow::Result { - AbsoluteNormalizedPathBuf::try_from(PathBuf::from(s)) - } -} - -impl TryFrom for AbsoluteNormalizedPathBuf { - type Error = anyhow::Error; - - /// no allocation conversion - /// - /// ``` - /// - /// use turborepo_paths::absolute_normalized_path::AbsoluteNormalizedPathBuf; - /// use std::convert::TryFrom; - /// use std::path::PathBuf; - /// - /// assert!(AbsoluteNormalizedPathBuf::try_from(PathBuf::from("relative/bar")).is_err()); - /// - /// if cfg!(not(windows)) { - /// assert!(AbsoluteNormalizedPathBuf::try_from(PathBuf::from("/foo/bar")).is_ok()); - /// assert!(AbsoluteNormalizedPathBuf::try_from(PathBuf::from("/")).is_ok()); - /// assert!(AbsoluteNormalizedPathBuf::try_from(PathBuf::from("/normalize/./bar")).is_err()); - /// assert!(AbsoluteNormalizedPathBuf::try_from(PathBuf::from("/normalize/../bar")).is_err()); - /// } else { - /// assert!(AbsoluteNormalizedPathBuf::try_from(PathBuf::from("c:/foo/bar")).is_ok()); - /// assert!(AbsoluteNormalizedPathBuf::try_from(PathBuf::from("c:/")).is_ok()); - /// assert!(AbsoluteNormalizedPathBuf::try_from(PathBuf::from("c:/normalize/./bar")).is_err()); - /// assert!(AbsoluteNormalizedPathBuf::try_from(PathBuf::from("c:/normalize/../bar")).is_err()); - /// } - /// ``` - fn try_from(p: PathBuf) -> anyhow::Result { - let p = AbsolutePathBuf::try_from(p)?; - verify_abs_path(&p)?; - Ok(AbsoluteNormalizedPathBuf(p)) - } -} - -impl ToOwned for AbsoluteNormalizedPath { - type Owned = AbsoluteNormalizedPathBuf; - - fn to_owned(&self) -> AbsoluteNormalizedPathBuf { - AbsoluteNormalizedPathBuf(self.0.to_owned()) - } -} - -impl AsRef for AbsoluteNormalizedPath { - fn as_ref(&self) -> &AbsoluteNormalizedPath { - self - } -} - -impl AsRef for AbsoluteNormalizedPathBuf { - fn as_ref(&self) -> &AbsoluteNormalizedPath { - AbsoluteNormalizedPath::ref_cast(&self.0) - } -} - -impl Borrow for AbsoluteNormalizedPathBuf { - fn borrow(&self) -> &AbsoluteNormalizedPath { - self.as_ref() - } -} - -impl Deref for AbsoluteNormalizedPathBuf { - type Target = AbsoluteNormalizedPath; - - fn deref(&self) -> &AbsoluteNormalizedPath { - AbsoluteNormalizedPath::ref_cast(&self.0) - } -} - -// Separate function so windows path verification can be tested on Unix. -fn verify_abs_path_windows_part(path: &str) -> bool { - // UNC device path. - // TODO(nga): behavior of UNC paths is under-specified in `AbsPath`. - let path = path.strip_prefix("\\\\.\\").unwrap_or(path); - - for component in path.split(|c| c == '/' || c == '\\') { - if component == "." || component == ".." { - return false; - } - } - - true -} - -/// Verifier for AbsPath to ensure the path is absolute -fn verify_abs_path(path: &AbsolutePath) -> anyhow::Result<()> { - // `Path::components` normalizes '.'s away so we cannot iterate with it. - // TODO maybe we actually want to allow "."s and just - // normalize them away entirely. - - #[cfg(unix)] - { - use std::os::unix::ffi::OsStrExt; - - // `as_bytes` function is not available on Windows. - // This is equivalent but faster to generic code below. - for component in path.as_os_str().as_bytes().split(|c| *c == b'/') { - if component == b"." || component == b".." { - return Err(anyhow::anyhow!(AbsNormPathError::PathNotNormalized( - path.to_owned() - ))); - } - } - } - - if !cfg!(unix) { - let path_str = path.to_string_lossy(); - if !verify_abs_path_windows_part(&path_str) { - return Err(anyhow::anyhow!(AbsNormPathError::PathNotNormalized( - path.to_owned() - ))); - } - } - - Ok(()) -} - -/// Errors from 'AbsPath' creation -#[derive(Error, Debug)] -enum AbsNormPathError { - #[error("expected a normalized path, but found a non-normalized path instead: `{0}`")] - PathNotNormalized(AbsolutePathBuf), -} - -/// Errors from normalizing paths -#[derive(Error, Debug)] -enum PathNormalizationError { - #[error( - "no such path: normalizing `{}` requires the parent directory of the root of `{}`", - .1.to_string_lossy(), - .0.to_string_lossy() - )] - OutOfBounds(OsString, OsString), -} - -#[cfg(test)] -mod tests { - use std::{ - collections::HashMap, - path::{Path, PathBuf}, - }; - - use crate::{ - absolute_normalized_path::{ - verify_abs_path_windows_part, AbsoluteNormalizedPath, AbsoluteNormalizedPathBuf, - }, - forward_relative_path::ForwardRelativePath, - }; - - #[cfg(not(windows))] - fn make_absolute(s: &str) -> String { - s.to_owned() - } - - #[cfg(windows)] - fn make_absolute(s: &str) -> String { - let mut abs_path = "c:".to_owned(); - abs_path.push_str(s); - abs_path - } - - #[test] - fn abs_paths_work_in_maps() -> anyhow::Result<()> { - let mut map = HashMap::new(); - let foo_string = make_absolute("/foo"); - let bar_string = make_absolute("/bar"); - - let p1 = AbsoluteNormalizedPath::new(foo_string.as_str())?; - let p2 = AbsoluteNormalizedPath::new(bar_string.as_str())?; - - map.insert(p1.to_buf(), p2.to_buf()); - - assert_eq!(Some(p2), map.get(p1).map(|p| p.as_ref())); - - Ok(()) - } - - #[test] - fn abs_path_is_comparable() -> anyhow::Result<()> { - let foo_string = make_absolute("/foo"); - let bar_string = make_absolute("/bar"); - let path1_buf = AbsoluteNormalizedPathBuf::from(foo_string.clone())?; - let path2_buf = AbsoluteNormalizedPathBuf::from(foo_string.clone())?; - let path3_buf = AbsoluteNormalizedPathBuf::from(bar_string.clone())?; - - let path1 = AbsoluteNormalizedPath::new(foo_string.as_str())?; - let path2 = AbsoluteNormalizedPath::new(foo_string.as_str())?; - let path3 = AbsoluteNormalizedPath::new(bar_string.as_str())?; - - let str2 = foo_string.as_str(); - let str3 = bar_string.as_str(); - let str_not_abs = "ble"; - - let string_not_abs = "ble".to_owned(); - - assert_eq!(path1_buf, path2_buf); - assert_ne!(path1_buf, path3_buf); - - assert_eq!(path1, path2); - assert_ne!(path1, path3); - - assert_eq!(path1_buf, path2); - assert_ne!(path1, path3_buf); - - assert_eq!(path1_buf, str2); - assert_ne!(path1_buf, str3); - assert_ne!(path1_buf, str_not_abs); - - assert_eq!(path1, str2); - assert_ne!(path1, str3); - assert_ne!(path1, str_not_abs); - - assert_eq!(path1_buf, foo_string); - assert_ne!(path1_buf, bar_string); - assert_ne!(path1_buf, string_not_abs); - - assert_eq!(path1, foo_string); - assert_ne!(path1, bar_string); - assert_ne!(path1, string_not_abs); - - Ok(()) - } - - #[test] - fn test_verify() { - assert!(AbsoluteNormalizedPath::new("relative/bar").is_err()); - assert!(AbsoluteNormalizedPath::new(Path::new("relative/bar")).is_err()); - - if cfg!(not(windows)) { - assert!(AbsoluteNormalizedPath::new("/foo/bar").is_ok()); - assert!(AbsoluteNormalizedPath::new("/").is_ok()); - assert!(AbsoluteNormalizedPath::new("/normalize/./bar").is_err()); - assert!(AbsoluteNormalizedPath::new("/normalize/../bar").is_err()); - - assert!(AbsoluteNormalizedPath::new(Path::new("/foo/bar")).is_ok()); - assert!(AbsoluteNormalizedPath::new(Path::new("/")).is_ok()); - assert!(AbsoluteNormalizedPath::new(Path::new("/normalize/./bar")).is_err()); - assert!(AbsoluteNormalizedPath::new(Path::new("/normalize/../bar")).is_err()); - } else { - assert!(AbsoluteNormalizedPath::new("c:/foo/bar").is_ok()); - assert!(AbsoluteNormalizedPath::new("c:/").is_ok()); - assert!(AbsoluteNormalizedPath::new("c:/normalize/./bar").is_err()); - assert!(AbsoluteNormalizedPath::new("c:/normalize/../bar").is_err()); - assert!(AbsoluteNormalizedPath::new("c:\\normalize\\.\\bar").is_err()); - assert!(AbsoluteNormalizedPath::new("c:\\normalize\\..\\bar").is_err()); - assert!(AbsoluteNormalizedPath::new("/foo/bar").is_err()); - - assert!(AbsoluteNormalizedPath::new(Path::new("c:/foo/bar")).is_ok()); - assert!(AbsoluteNormalizedPath::new(Path::new("c:/")).is_ok()); - assert!(AbsoluteNormalizedPath::new(Path::new("c:/normalize/./bar")).is_err()); - assert!(AbsoluteNormalizedPath::new(Path::new("c:/normalize/../bar")).is_err()); - - // UNC paths. - assert!(AbsoluteNormalizedPath::new(Path::new(r"\\.\COM42")).is_ok()); - assert!(AbsoluteNormalizedPath::new(Path::new(r"\\?\c:\test")).is_ok()); - } - } - - #[test] - fn test_verify_windows() { - assert!(verify_abs_path_windows_part(r"c:\foo\bar")); - assert!(verify_abs_path_windows_part(r"\\.\COM42")); - assert!(verify_abs_path_windows_part(r"\\?\c:\test")); - assert!(!verify_abs_path_windows_part(r"\\?\c:\.\test")); - } - - #[test] - fn test_pop() { - let mut path = if cfg!(not(windows)) { - PathBuf::from("/foo/bar") - } else { - PathBuf::from("c:/foo/bar") - }; - let mut abs_path = AbsoluteNormalizedPath::new(&path).unwrap().to_buf(); - - assert!(path.pop()); - assert!(abs_path.pop()); - assert_eq!(path, abs_path.as_path()); - - assert!(path.pop()); - assert!(abs_path.pop()); - assert_eq!(path, abs_path.as_path()); - - assert!(!path.pop()); - assert!(!abs_path.pop()); - assert_eq!(path, abs_path.as_path()); - } - - #[test] - fn test_join() { - let path = if cfg!(windows) { - AbsoluteNormalizedPathBuf::try_from("c:\\foo\\bar".to_owned()).unwrap() - } else { - AbsoluteNormalizedPathBuf::try_from("/foo/bar".to_owned()).unwrap() - }; - - let path = path.join(ForwardRelativePath::new("baz").unwrap()); - assert_eq!( - path.to_str().unwrap(), - if cfg!(windows) { - "c:\\foo\\bar\\baz" - } else { - "/foo/bar/baz" - } - ); - - let path = path.join(ForwardRelativePath::empty()); - assert_eq!( - path.to_str().unwrap(), - if cfg!(windows) { - "c:\\foo\\bar\\baz" - } else { - "/foo/bar/baz" - } - ); - } -} diff --git a/crates/turborepo-paths/src/absolute_path.rs b/crates/turborepo-paths/src/absolute_path.rs deleted file mode 100644 index 4d0aa3ccd7fd5..0000000000000 --- a/crates/turborepo-paths/src/absolute_path.rs +++ /dev/null @@ -1,194 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under both the MIT license found in the - * LICENSE-MIT file in the root directory of this source tree and the Apache - * License, Version 2.0 found in the LICENSE-APACHE file in the root - * directory of this source tree. - */ - -use std::{ - borrow::Borrow, - ffi::OsString, - fmt, - ops::Deref, - path::{Path, PathBuf}, -}; - -use thiserror::Error; - -#[derive(Error, Debug)] -enum AbsPathError { - #[error("expected an absolute path but got a relative path instead: `{0}`")] - PathNotAbsolute(PathBuf), - #[error("Cannot convert path to UTF-8, `{0:?}`")] - PathCannotBeConvertedToUtf8(OsString), -} - -#[derive(Hash, Eq, PartialEq, PartialOrd, Ord)] -#[repr(transparent)] -pub struct AbsolutePath(Path); - -#[derive(Hash, Eq, PartialEq, Ord, PartialOrd, Clone)] -pub struct AbsolutePathBuf(PathBuf); - -impl fmt::Debug for AbsolutePath { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Debug::fmt(&self.0, f) - } -} - -impl fmt::Debug for AbsolutePathBuf { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Debug::fmt(&self.0, f) - } -} - -impl AsRef for AbsolutePath { - fn as_ref(&self) -> &Path { - &self.0 - } -} - -impl AsRef for AbsolutePath { - fn as_ref(&self) -> &AbsolutePath { - self - } -} - -impl AsRef for AbsolutePathBuf { - fn as_ref(&self) -> &Path { - &self.0 - } -} - -impl AsRef for AbsolutePathBuf { - fn as_ref(&self) -> &AbsolutePath { - self - } -} - -impl Deref for AbsolutePath { - type Target = Path; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl Deref for AbsolutePathBuf { - type Target = AbsolutePath; - - fn deref(&self) -> &Self::Target { - unsafe { &*(self.0.as_path() as *const Path as *const AbsolutePath) } - } -} - -impl Borrow for AbsolutePathBuf { - fn borrow(&self) -> &AbsolutePath { - self - } -} - -impl ToOwned for AbsolutePath { - type Owned = AbsolutePathBuf; - - fn to_owned(&self) -> Self::Owned { - AbsolutePathBuf(self.0.to_owned()) - } -} - -impl AbsolutePath { - pub fn new(path: &Path) -> anyhow::Result<&AbsolutePath> { - if path.is_absolute() { - // SAFETY: repr transparent. - Ok(unsafe { &*(path as *const Path as *const AbsolutePath) }) - } else { - Err(AbsPathError::PathNotAbsolute(path.to_path_buf()).into()) - } - } - - pub fn as_path(&self) -> &Path { - &self.0 - } - - pub fn join>(&self, other: P) -> AbsolutePathBuf { - let path = self.0.join(other); - assert!(path.is_absolute()); - AbsolutePathBuf(path) - } - - pub fn parent(&self) -> Option<&AbsolutePath> { - self.0.parent().map(|p| AbsolutePath::new(p).unwrap()) - } - - pub fn strip_prefix>(&self, prefix: P) -> anyhow::Result<&Path> { - Ok(self.0.strip_prefix(prefix.as_ref())?) - } -} - -impl AbsolutePathBuf { - pub fn into_path_buf(self) -> PathBuf { - self.0 - } - - pub fn into_os_string(self) -> OsString { - self.0.into_os_string() - } - - /// Convert a path into a String. Fails if the path is not UTF8. - pub fn into_string(self) -> anyhow::Result { - self.into_os_string() - .into_string() - .map_err(|x| AbsPathError::PathCannotBeConvertedToUtf8(x).into()) - } - - pub fn capacity(&self) -> usize { - self.0.capacity() - } - - pub fn reserve(&mut self, additional: usize) { - self.0.reserve(additional) - } - - pub fn shrink_to_fit(&mut self) { - self.0.shrink_to_fit() - } - - pub fn shrink_to(&mut self, min_capacity: usize) { - self.0.shrink_to(min_capacity) - } - - pub fn push>(&mut self, path: P) { - self.0.push(path); - assert!(self.0.is_absolute()); - } - - pub fn pop(&mut self) -> bool { - let r = self.0.pop(); - assert!(self.0.is_absolute()); - r - } - - pub fn set_extension>(&mut self, extension: S) { - self.0.set_extension(extension.as_ref()); - assert!(self.0.is_absolute()); - } -} - -impl TryFrom for AbsolutePathBuf { - type Error = anyhow::Error; - - fn try_from(path: PathBuf) -> Result { - AbsolutePath::new(&path)?; - Ok(AbsolutePathBuf(path)) - } -} - -impl TryFrom for AbsolutePathBuf { - type Error = anyhow::Error; - - fn try_from(path: String) -> Result { - AbsolutePathBuf::try_from(PathBuf::from(path)) - } -} diff --git a/crates/turborepo-paths/src/cmp_impls.rs b/crates/turborepo-paths/src/cmp_impls.rs deleted file mode 100644 index 1f5d8f145a92e..0000000000000 --- a/crates/turborepo-paths/src/cmp_impls.rs +++ /dev/null @@ -1,161 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under both the MIT license found in the - * LICENSE-MIT file in the root directory of this source tree and the Apache - * License, Version 2.0 found in the LICENSE-APACHE file in the root - * directory of this source tree. - */ - -//! -//! General macros useful for path declaration - -use std::cmp; - -/// -/// Generates ['cmp::PartialEq'] and ['cmp::PartialOrd'] for the `lhs` and `rhs` -/// types, where `ty` is the unowned, reference path type. -macro_rules! impl_cmp { - ($lhs:ty, $rhs:ty, $ty:ty) => { - impl cmp::PartialEq<$rhs> for $lhs { - #[inline] - fn eq(&self, other: &$rhs) -> bool { - <$ty as cmp::PartialEq>::eq(self, other) - } - } - - impl cmp::PartialEq<$lhs> for $rhs { - #[inline] - fn eq(&self, other: &$lhs) -> bool { - <$ty as cmp::PartialEq>::eq(self, other) - } - } - - impl cmp::PartialOrd<$rhs> for $lhs { - #[inline] - fn partial_cmp(&self, other: &$rhs) -> Option { - <$ty as cmp::PartialOrd>::partial_cmp(self, other) - } - } - - impl cmp::PartialOrd<$lhs> for $rhs { - #[inline] - fn partial_cmp(&self, other: &$lhs) -> Option { - <$ty as cmp::PartialOrd>::partial_cmp(self, other) - } - } - }; -} - -/// -/// Generates ['cmp::PartialEq'] and ['cmp::PartialOrd'] for the `lhs` and `rhs` -/// string types, where `ty` is the unowned, reference path type. -macro_rules! impl_cmp_str { - ($lhs:ty, $rhs:ty, $ty:ty) => { - impl cmp::PartialEq<$rhs> for $lhs { - #[inline] - fn eq(&self, other: &$rhs) -> bool { - match <$ty>::new(other) { - Ok(other) => <$ty as cmp::PartialEq>::eq(self, other), - _ => false, - } - } - } - - impl cmp::PartialEq<$lhs> for $rhs { - #[inline] - fn eq(&self, other: &$lhs) -> bool { - match <$ty>::new(self) { - Ok(this) => <$ty as cmp::PartialEq>::eq(this, other), - _ => false, - } - } - } - - impl cmp::PartialOrd<$rhs> for $lhs { - #[inline] - fn partial_cmp(&self, other: &$rhs) -> Option { - match <$ty>::new(other) { - Ok(other) => <$ty as cmp::PartialOrd>::partial_cmp(self, other), - _ => None, - } - } - } - - impl cmp::PartialOrd<$lhs> for $rhs { - #[inline] - fn partial_cmp(&self, other: &$lhs) -> Option { - match <$ty>::new(self) { - Ok(this) => <$ty as cmp::PartialOrd>::partial_cmp(this, other), - _ => None, - } - } - } - }; -} - -use crate::forward_relative_path::{ForwardRelativePath, ForwardRelativePathBuf}; - -impl_cmp!( - ForwardRelativePathBuf, - ForwardRelativePath, - ForwardRelativePath -); -impl_cmp!( - ForwardRelativePathBuf, - &'_ ForwardRelativePath, - ForwardRelativePath -); - -impl_cmp_str!(ForwardRelativePathBuf, str, ForwardRelativePath); -impl_cmp_str!(ForwardRelativePathBuf, &'_ str, ForwardRelativePath); -impl_cmp_str!(ForwardRelativePathBuf, String, ForwardRelativePath); -impl_cmp_str!(ForwardRelativePath, str, ForwardRelativePath); -impl_cmp_str!(ForwardRelativePath, &'_ str, ForwardRelativePath); -impl_cmp_str!(ForwardRelativePath, String, ForwardRelativePath); -impl_cmp_str!(&'_ ForwardRelativePath, str, ForwardRelativePath); -impl_cmp_str!(&'_ ForwardRelativePath, String, ForwardRelativePath); - -use crate::absolute_normalized_path::{AbsoluteNormalizedPath, AbsoluteNormalizedPathBuf}; - -impl_cmp!( - AbsoluteNormalizedPathBuf, - AbsoluteNormalizedPath, - AbsoluteNormalizedPath -); -impl_cmp!( - AbsoluteNormalizedPathBuf, - &'_ AbsoluteNormalizedPath, - AbsoluteNormalizedPath -); - -impl_cmp_str!(AbsoluteNormalizedPathBuf, str, AbsoluteNormalizedPath); -impl_cmp_str!(AbsoluteNormalizedPathBuf, &'_ str, AbsoluteNormalizedPath); -impl_cmp_str!(AbsoluteNormalizedPathBuf, String, AbsoluteNormalizedPath); -impl_cmp_str!(AbsoluteNormalizedPath, str, AbsoluteNormalizedPath); -impl_cmp_str!(AbsoluteNormalizedPath, &'_ str, AbsoluteNormalizedPath); -impl_cmp_str!(AbsoluteNormalizedPath, String, AbsoluteNormalizedPath); -impl_cmp_str!(&'_ AbsoluteNormalizedPath, str, AbsoluteNormalizedPath); -impl_cmp_str!(&'_ AbsoluteNormalizedPath, String, AbsoluteNormalizedPath); - -use crate::project_relative_path::{ProjectRelativePath, ProjectRelativePathBuf}; - -impl_cmp!( - ProjectRelativePathBuf, - ProjectRelativePath, - ProjectRelativePath -); -impl_cmp!( - ProjectRelativePathBuf, - &'_ ProjectRelativePath, - ProjectRelativePath -); - -impl_cmp_str!(ProjectRelativePathBuf, str, ProjectRelativePath); -impl_cmp_str!(ProjectRelativePathBuf, &'_ str, ProjectRelativePath); -impl_cmp_str!(ProjectRelativePathBuf, String, ProjectRelativePath); -impl_cmp_str!(ProjectRelativePath, str, ProjectRelativePath); -impl_cmp_str!(ProjectRelativePath, &'_ str, ProjectRelativePath); -impl_cmp_str!(ProjectRelativePath, String, ProjectRelativePath); -impl_cmp_str!(&'_ ProjectRelativePath, str, ProjectRelativePath); -impl_cmp_str!(&'_ ProjectRelativePath, String, ProjectRelativePath); diff --git a/crates/turborepo-paths/src/file_name.rs b/crates/turborepo-paths/src/file_name.rs deleted file mode 100644 index bc71688be69c7..0000000000000 --- a/crates/turborepo-paths/src/file_name.rs +++ /dev/null @@ -1,329 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under both the MIT license found in the - * LICENSE-MIT file in the root directory of this source tree and the Apache - * License, Version 2.0 found in the LICENSE-APACHE file in the root - * directory of this source tree. - */ - -use std::{ - borrow::Borrow, - hash::{Hash, Hasher}, - ops::Deref, - path::Path, -}; - -use compact_str::CompactString; -use derive_more::Display; -use ref_cast::RefCast; -use relative_path::RelativePath; -use thiserror::Error; - -use crate::forward_relative_path::ForwardRelativePath; - -/// Errors from ForwardRelativePath creation -#[derive(Error, Debug)] -enum FileNameError { - #[error("file name is empty")] - Empty, - #[error("file name is current directory")] - Dot, - #[error("file name is parent directory")] - DotDot, - #[error("slashes in path: `{0}`")] - Slashes(String), -} - -fn verify_file_name(file_name: &str) -> anyhow::Result<()> { - if file_name.is_empty() { - Err(FileNameError::Empty.into()) - } else if file_name == "." { - Err(FileNameError::Dot.into()) - } else if file_name == ".." { - Err(FileNameError::DotDot.into()) - } else if file_name.contains('/') || file_name.contains('\\') { - // Note we do not allow backslashes in file names - // even if it is valid file name on Linux. - Err(FileNameError::Slashes(file_name.to_owned()).into()) - } else { - // At the moment we allow characters like '\0' - // even if they are not valid at least on Linux. - Ok(()) - } -} - -/// File name. Cannot be empty, cannot contain slashes, '.' or '..'. -#[repr(transparent)] -#[derive(Display, Debug, RefCast, PartialOrd, Ord, Eq)] -pub struct FileName(str); - -impl PartialEq for FileName { - #[inline] - fn eq(&self, other: &str) -> bool { - &self.0 == other - } -} - -impl PartialEq for str { - #[inline] - fn eq(&self, other: &FileName) -> bool { - self == &other.0 - } -} - -impl AsRef for FileName { - #[inline] - fn as_ref(&self) -> &Path { - Path::new(&self.0) - } -} - -impl AsRef for FileName { - #[inline] - fn as_ref(&self) -> &str { - &self.0 - } -} - -impl AsRef for FileName { - #[inline] - fn as_ref(&self) -> &RelativePath { - RelativePath::new(&self.0) - } -} - -impl AsRef for FileName { - #[inline] - fn as_ref(&self) -> &ForwardRelativePath { - ForwardRelativePath::unchecked_new(&self.0) - } -} - -impl FileName { - /// Creates an `FileName` if the given path represents a correct - /// platform-indepentent file name, otherwise error. - /// - /// ``` - /// use turborepo_paths::file_name::FileName; - /// assert!(FileName::new("foo").is_ok()); - /// assert!(FileName::new("").is_err()); - /// assert!(FileName::new(".").is_err()); - /// assert!(FileName::new("..").is_err()); - /// assert!(FileName::new(".x").is_ok()); - /// assert!(FileName::new("foo/bar").is_err()); - /// assert!(FileName::new("foo/").is_err()); - /// assert!(FileName::new("foo\\bar").is_err()); - /// ``` - #[inline] - pub fn new>(s: &S) -> anyhow::Result<&Self> { - verify_file_name(s.as_ref())?; - Ok(Self::unchecked_new(s.as_ref())) - } - - #[inline] - pub const fn unchecked_new(s: &str) -> &FileName { - unsafe { - // SAFETY: `FileName` is `repr(transparent)` over `str`. - &*(s as *const str as *const FileName) - } - } - - #[inline] - pub fn as_str(&self) -> &str { - &self.0 - } - - /// Extracts the stem (non-extension) portion of [`self.file_name`]. - /// - /// The stem is: - /// - /// * [`None`], if there is no file name; - /// * The entire file name if there is no embedded `.`; - /// * The entire file name if the file name begins with `.` and has no other - /// `.`s within; - /// * Otherwise, the portion of the file name before the final `.` - /// - /// ``` - /// use turborepo_paths::file_name::FileName; - /// - /// let path = FileName::new("foo.rs")?; - /// - /// assert_eq!(Some("foo"), path.file_stem()); - /// - /// # anyhow::Ok(()) - /// ``` - #[inline] - pub fn file_stem(&self) -> Option<&str> { - ForwardRelativePath::unchecked_new(&self.0).file_stem() - } - - /// Extracts the extension of [`self.file_name`], if possible. - /// - /// ``` - /// - /// use turborepo_paths::file_name::FileName; - /// - /// assert_eq!(Some("rs"), FileName::new("foo.rs")?.extension()); - /// - /// # anyhow::Ok(()) - /// ``` - #[inline] - pub fn extension(&self) -> Option<&str> { - ForwardRelativePath::unchecked_new(&self.0).extension() - } -} - -impl PartialEq for FileName { - #[inline] - fn eq(&self, other: &Self) -> bool { - self.as_str() == other.as_str() - } -} - -impl Hash for FileName { - #[inline] - fn hash(&self, state: &mut H) { - self.0.hash(state) - } -} - -impl ToOwned for FileName { - type Owned = FileNameBuf; - - #[inline] - fn to_owned(&self) -> FileNameBuf { - FileNameBuf(CompactString::new(&self.0)) - } -} - -/// Owned version of [`FileName`]. -#[derive(Ord, PartialOrd, Eq, Display, Debug, Clone)] -pub struct FileNameBuf(CompactString); - -impl FileNameBuf { - #[inline] - pub fn unchecked_new(s: T) -> Self - where - T: Into, - { - // NOTE: This does not turn a String into an inlined string if it's already - // allocated. - Self(s.into()) - } - - #[inline] - pub fn try_from_or_get_back(s: CompactString) -> Result { - if verify_file_name(&s).is_ok() { - Ok(FileNameBuf(s)) - } else { - Err(s) - } - } - - #[inline] - pub fn into_inner(self) -> CompactString { - self.0 - } - - #[inline] - pub fn as_str(&self) -> &str { - self.0.as_str() - } -} - -impl PartialEq for FileNameBuf -where - T: AsRef, -{ - #[inline] - fn eq(&self, other: &T) -> bool { - self.0 == other.as_ref() - } -} - -impl Hash for FileNameBuf { - #[inline] - fn hash(&self, state: &mut H) { - self.0.as_str().hash(state) - } -} - -impl Borrow for FileNameBuf { - #[inline] - fn borrow(&self) -> &FileName { - self.as_ref() - } -} - -impl Deref for FileNameBuf { - type Target = FileName; - - #[inline] - fn deref(&self) -> &Self::Target { - self.as_ref() - } -} - -impl Borrow for FileNameBuf { - #[inline] - fn borrow(&self) -> &str { - self.as_ref() - } -} - -impl AsRef for FileNameBuf { - #[inline] - fn as_ref(&self) -> &FileName { - FileName::unchecked_new(self.0.as_str()) - } -} - -impl AsRef for FileNameBuf { - #[inline] - fn as_ref(&self) -> &Path { - Path::new(self.0.as_str()) - } -} - -impl AsRef for FileNameBuf { - #[inline] - fn as_ref(&self) -> &str { - self.0.as_str() - } -} - -impl AsRef for FileNameBuf { - #[inline] - fn as_ref(&self) -> &RelativePath { - RelativePath::new(self.0.as_str()) - } -} - -impl AsRef for FileNameBuf { - #[inline] - fn as_ref(&self) -> &ForwardRelativePath { - ForwardRelativePath::unchecked_new(self.0.as_str()) - } -} - -impl TryFrom for FileNameBuf { - type Error = anyhow::Error; - - #[inline] - fn try_from(value: String) -> anyhow::Result { - // NOTE: This does not turn a String into an inlined string. - verify_file_name(value.as_str())?; - Ok(FileNameBuf(value.into())) - } -} - -impl TryFrom for FileNameBuf { - type Error = anyhow::Error; - - #[inline] - fn try_from(value: CompactString) -> anyhow::Result { - verify_file_name(value.as_str())?; - Ok(FileNameBuf(value)) - } -} diff --git a/crates/turborepo-paths/src/fmt.rs b/crates/turborepo-paths/src/fmt.rs deleted file mode 100644 index 7b50401ca5b25..0000000000000 --- a/crates/turborepo-paths/src/fmt.rs +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under both the MIT license found in the - * LICENSE-MIT file in the root directory of this source tree and the Apache - * License, Version 2.0 found in the LICENSE-APACHE file in the root - * directory of this source tree. - */ - -//! Format related utilities of the core path types - -use std::{ - fmt, - fmt::{Display, Formatter}, -}; - -/// formats the path as a quoted string -pub(crate) fn quoted_display(d: &D, f: &mut Formatter) -> fmt::Result -where - D: Display + ?Sized, -{ - write!(f, "\"{:}\"", d) -} diff --git a/crates/turborepo-paths/src/forward_relative_path.rs b/crates/turborepo-paths/src/forward_relative_path.rs deleted file mode 100644 index 21586696e3a07..0000000000000 --- a/crates/turborepo-paths/src/forward_relative_path.rs +++ /dev/null @@ -1,1306 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under both the MIT license found in the - * LICENSE-MIT file in the root directory of this source tree and the Apache - * License, Version 2.0 found in the LICENSE-APACHE file in the root - * directory of this source tree. - */ - -use std::{ - borrow::{Borrow, Cow}, - hash::Hash, - ops::Deref, - path::{Path, PathBuf}, -}; - -use derive_more::Display; -use gazebo::transmute; -use ref_cast::RefCast; -use relative_path::{RelativePath, RelativePathBuf}; -use serde::Serialize; -use smallvec::SmallVec; -use thiserror::Error; - -use crate::{ - absolute_normalized_path::{AbsoluteNormalizedPath, AbsoluteNormalizedPathBuf}, - file_name::{FileName, FileNameBuf}, - fs_util, -}; - -/// A forward pointing, fully normalized relative path and owned pathbuf. -/// This means that there is no '.' or '..' in this path, and does not begin -/// with '/'. -/// -/// This path is platform agnostic, so path separators are always '/'. -#[derive(Display, Debug, RefCast, PartialEq, Eq, PartialOrd, Ord, Hash)] -#[repr(transparent)] -pub struct ForwardRelativePath( - // Note we transmute between `ForwardRelativePath` and `str`. - str, -); - -/// The owned version of 'ForwardRelativePath', like how 'PathBuf' relates to -/// 'Path' -#[derive(Clone, Display, Debug, Serialize, PartialEq, Eq, PartialOrd, Ord, Hash)] -#[repr(transparent)] -pub struct ForwardRelativePathBuf(String); - -impl AsRef for ForwardRelativePath { - #[inline] - fn as_ref(&self) -> &RelativePath { - RelativePath::new(&self.0) - } -} - -impl AsRef for ForwardRelativePathBuf { - #[inline] - fn as_ref(&self) -> &RelativePath { - RelativePath::new(&self.0) - } -} - -pub struct ForwardRelativePathIter<'a>(&'a ForwardRelativePath); - -impl<'a> Iterator for ForwardRelativePathIter<'a> { - type Item = &'a FileName; - - #[inline] - fn next(&mut self) -> Option<&'a FileName> { - let (first, rem) = self.0.split_first()?; - self.0 = rem; - Some(first) - } -} - -impl<'a> Clone for ForwardRelativePathIter<'a> { - fn clone(&self) -> Self { - ForwardRelativePathIter(ForwardRelativePath::unchecked_new(self.0.as_str())) - } -} - -impl ForwardRelativePath { - #[inline] - pub fn unchecked_new>(s: &S) -> &Self { - ForwardRelativePath::ref_cast(s.as_ref()) - } - - #[inline] - pub fn unchecked_new_box(s: Box) -> Box { - unsafe { - // SAFETY: `ForwardRelativePath` is a transparent wrapper around `str`. - transmute!(Box, Box, s) - } - } - - #[inline] - pub fn empty() -> &'static Self { - ForwardRelativePath::unchecked_new("") - } - - /// Creates an 'ForwardRelativePath' if the given path represents a forward, - /// normalized relative path, otherwise error. - /// - /// ``` - /// use turborepo_paths::forward_relative_path::ForwardRelativePath; - /// use std::path::Path; - /// - /// assert!(ForwardRelativePath::new("foo/bar").is_ok()); - /// assert!(ForwardRelativePath::new("").is_ok()); - /// assert!(ForwardRelativePath::new("./bar").is_err()); - /// assert!(ForwardRelativePath::new("normalize/./bar").is_err()); - /// assert!(ForwardRelativePath::new("/abs/bar").is_err()); - /// assert!(ForwardRelativePath::new("foo//bar").is_err()); - /// assert!(ForwardRelativePath::new("normalize/../bar").is_err()); - /// - /// assert!(ForwardRelativePath::new(Path::new("foo/bar")).is_ok()); - /// assert!(ForwardRelativePath::new(Path::new("")).is_ok()); - /// assert!(ForwardRelativePath::new(Path::new("./bar")).is_err()); - /// assert!(ForwardRelativePath::new(Path::new("normalize/./bar")).is_err()); - /// assert!(ForwardRelativePath::new(Path::new("/abs/bar")).is_err()); - /// assert!(ForwardRelativePath::new(Path::new("normalize/../bar")).is_err()); - /// assert!(ForwardRelativePath::new(Path::new("normalize\\bar")).is_err()); - /// assert!(ForwardRelativePath::new(Path::new("normalize/bar/")).is_err()); - /// - /// # anyhow::Ok(()) - /// ``` - // TODO(nga): this accepts `Path`, but should accept `str` - // because paths can have backslashes. - // Conversion of `Path` to `ForwardRelativePath` should be done via - // `ForwardRelativePathBuf` which should normalize slashes. - #[inline] - pub fn new>(s: &S) -> anyhow::Result<&ForwardRelativePath> { - <&ForwardRelativePath>::try_from(s.as_ref()) - } - - /// `ForwardRelativePath` requires no trailing slashes. This function - /// constructs a path ignoring trailing slashes. - /// - /// ``` - /// use turborepo_paths::forward_relative_path::ForwardRelativePath; - /// - /// assert!(ForwardRelativePath::new_trim_trailing_slashes("foo/bar").is_ok()); - /// assert!(ForwardRelativePath::new_trim_trailing_slashes("foo/bar/").is_ok()); - /// assert!(ForwardRelativePath::new_trim_trailing_slashes("foo/bar//").is_ok()); - /// assert!(ForwardRelativePath::new_trim_trailing_slashes("foo//bar").is_err()); - /// ``` - pub fn new_trim_trailing_slashes>( - path: &S, - ) -> anyhow::Result<&ForwardRelativePath> { - let path = path.as_ref(); - let path = path - .to_str() - .ok_or_else(|| ForwardRelativePathError::PathNotUtf8(path.display().to_string()))?; - let path = path.trim_end_matches('/'); - ForwardRelativePath::new(path) - } - - /// Build an owned `AbsPathBuf` relative to `path` for the current relative - /// path based on the supplied root. - /// - /// ``` - /// - /// use std::path::Path; - /// use turborepo_paths::absolute_normalized_path::{AbsoluteNormalizedPath, AbsoluteNormalizedPathBuf}; - /// use turborepo_paths::forward_relative_path::ForwardRelativePath; - /// - /// if cfg!(not(windows)) { - /// let path = ForwardRelativePath::new("foo/bar")?.resolve(AbsoluteNormalizedPath::new("/some")?); - /// assert_eq!(AbsoluteNormalizedPathBuf::from("/some/foo/bar".into())?, path); - /// } else { - /// let path = ForwardRelativePath::new("foo/bar")?.resolve(AbsoluteNormalizedPath::new("c:/some")?); - /// assert_eq!(AbsoluteNormalizedPathBuf::from("c:/some/foo/bar".into())?, path); - /// } - /// - /// # anyhow::Ok(()) - /// ``` - #[inline] - pub fn resolve>( - &self, - relative_to: P, - ) -> AbsoluteNormalizedPathBuf { - relative_to.as_ref().join(self) - } - - #[inline] - pub fn as_str(&self) -> &str { - &self.0 - } - - #[inline] - pub fn as_path(&self) -> &Path { - Path::new(&self.0) - } - - #[inline] - pub fn is_empty(&self) -> bool { - self.0.is_empty() - } - - /// Creates an owned 'ForwardRelativePathBuf' with path adjoined to self. - /// - /// ``` - /// use std::path::Path; - /// use turborepo_paths::forward_relative_path::{ForwardRelativePathBuf, ForwardRelativePath}; - /// - /// let path = ForwardRelativePath::new("foo/bar")?; - /// let other = ForwardRelativePath::new("baz")?; - /// assert_eq!(ForwardRelativePathBuf::unchecked_new("foo/bar/baz".to_owned()), path.join(other)); - /// - /// # anyhow::Ok(()) - /// ``` - #[inline] - pub fn join>(&self, path: P) -> ForwardRelativePathBuf { - let path = path.as_ref(); - if self.0.is_empty() { - path.to_buf() - } else if path.0.is_empty() { - self.to_buf() - } else { - let mut buf = String::with_capacity(self.0.len() + 1 + path.0.len()); - buf.push_str(&self.0); - buf.push('/'); - buf.push_str(&path.0); - ForwardRelativePathBuf::unchecked_new(buf) - } - } - - /// Returns a relative path of the parent directory - /// - /// ``` - /// use turborepo_paths::forward_relative_path::ForwardRelativePath; - /// - /// assert_eq!( - /// Some(ForwardRelativePath::new("foo")?), - /// ForwardRelativePath::new("foo/bar")?.parent() - /// ); - /// assert_eq!( - /// Some(ForwardRelativePath::new("")?), - /// ForwardRelativePath::new("foo")?.parent() - /// ); - /// assert_eq!( - /// None, - /// ForwardRelativePath::new("")?.parent() - /// ); - /// - /// # anyhow::Ok(()) - /// ``` - pub fn parent(&self) -> Option<&ForwardRelativePath> { - let s = &self.0; - for i in (0..s.len()).rev() { - if s.as_bytes()[i] == b'/' { - return Some(ForwardRelativePath::unchecked_new(&s[..i])); - } - } - if s.is_empty() { - None - } else { - Some(ForwardRelativePath::empty()) - } - } - - /// Returns the final component of the `ForwardRelativePath`, if there is - /// one. - /// - /// If the path is a normal file, this is the file name. If it's the path of - /// a directory, this is the directory name. - /// - /// ``` - /// use turborepo_paths::forward_relative_path::ForwardRelativePath; - /// use turborepo_paths::file_name::FileName; - /// - /// assert_eq!(Some(FileName::unchecked_new("ls")), ForwardRelativePath::new("usr/bin/ls")?.file_name()); - /// assert_eq!(Some(FileName::unchecked_new("bin")), ForwardRelativePath::new("usr/bin")?.file_name()); - /// assert_eq!(Some(FileName::unchecked_new("usr")), ForwardRelativePath::new("usr")?.file_name()); - /// assert_eq!(None, ForwardRelativePath::new("")?.file_name()); - /// - /// # anyhow::Ok(()) - /// ``` - pub fn file_name(&self) -> Option<&FileName> { - let s = &self.0; - for (i, b) in s.bytes().enumerate().rev() { - if b == b'/' { - return Some(FileName::unchecked_new(&s[i + 1..])); - } - } - if s.is_empty() { - None - } else { - Some(FileName::unchecked_new(s)) - } - } - - /// Get the first component of the path and the remaining path, - /// of `None` if the path is empty. - pub fn split_first(&self) -> Option<(&FileName, &ForwardRelativePath)> { - let s = &self.0; - for (i, b) in s.bytes().enumerate() { - if b == b'/' { - return Some(( - FileName::unchecked_new(&s[..i]), - ForwardRelativePath::unchecked_new(&s[i + 1..]), - )); - } - } - if s.is_empty() { - None - } else { - Some((FileName::unchecked_new(s), ForwardRelativePath::empty())) - } - } - - /// Returns a 'ForwardRelativePath' that, when joined onto `base`, yields - /// `self`. - /// - /// Error if `base` is not a prefix of `self` or the returned - /// path is not a 'ForwardRelativePath' - /// - /// ``` - /// use turborepo_paths::forward_relative_path::ForwardRelativePath; - /// - /// let path = ForwardRelativePath::new("test/haha/foo.txt")?; - /// - /// assert_eq!( - /// path.strip_prefix(ForwardRelativePath::new("test/haha/foo.txt")?)?, - /// ForwardRelativePath::new("")? - /// ); - /// assert_eq!( - /// path.strip_prefix(ForwardRelativePath::new("test/haha")?)?, - /// ForwardRelativePath::new("foo.txt")? - /// ); - /// assert_eq!( - /// path.strip_prefix(ForwardRelativePath::new("test")?)?, - /// ForwardRelativePath::new("haha/foo.txt")? - /// ); - /// assert_eq!( - /// path.strip_prefix(ForwardRelativePath::new("")?)?, - /// ForwardRelativePath::new("test/haha/foo.txt")? - /// ); - /// assert_eq!(path.strip_prefix(ForwardRelativePath::new("asdf")?).is_err(), true); - /// - /// # anyhow::Ok(()) - /// ``` - pub fn strip_prefix>( - &self, - base: P, - ) -> anyhow::Result<&ForwardRelativePath> { - let base = base.as_ref(); - if base.0.is_empty() { - Ok(self) - } else if self.starts_with(base) { - if self.0.len() == base.0.len() { - Ok(ForwardRelativePath::empty()) - } else { - Ok(ForwardRelativePath::unchecked_new( - &self.0[base.0.len() + 1..], - )) - } - } else { - Err(StripPrefixError(base.as_str().to_owned(), self.0.to_owned()).into()) - } - } - - /// Determines whether `base` is a prefix of `self`. - /// - /// ``` - /// - /// use turborepo_paths::forward_relative_path::ForwardRelativePath; - /// - /// let path = ForwardRelativePath::new("some/foo")?; - /// - /// assert!(path.starts_with(ForwardRelativePath::new("some/foo")?)); - /// assert!(path.starts_with(ForwardRelativePath::new("some")?)); - /// assert!(!path.starts_with(ForwardRelativePath::new("som")?)); - /// assert!(path.starts_with(ForwardRelativePath::new("")?)); - /// - /// # anyhow::Ok(()) - /// ``` - pub fn starts_with>(&self, base: P) -> bool { - let base = base.as_ref(); - base.0.is_empty() - || (self.0.starts_with(&base.0) - && (self.0.len() == base.0.len() || self.0.as_bytes()[base.0.len()] == b'/')) - } - - /// Determines whether `child` is a suffix of `self`. - /// Only considers whole path components to match. - /// - /// ``` - /// use std::path::Path; - /// use turborepo_paths::forward_relative_path::ForwardRelativePath; - /// - /// let path = ForwardRelativePath::new("some/foo")?; - /// - /// assert!(path.ends_with(ForwardRelativePath::new("some/foo")?)); - /// assert!(path.ends_with(ForwardRelativePath::new("foo")?)); - /// assert!(!path.ends_with(ForwardRelativePath::new("oo")?)); - /// assert!(path.ends_with(ForwardRelativePath::new("")?)); - /// - /// # anyhow::Ok(()) - /// ``` - pub fn ends_with>(&self, child: P) -> bool { - let child = child.as_ref(); - child.0.is_empty() - || (self.0.ends_with(&child.0) - && (self.0.len() == child.0.len() - || self.0.as_bytes()[self.0.len() - child.0.len() - 1] == b'/')) - } - - /// Extracts the stem (non-extension) portion of [`self.file_name`]. - /// - /// The stem is: - /// - /// * [`None`], if there is no file name; - /// * The entire file name if there is no embedded `.`; - /// * The entire file name if the file name begins with `.` and has no other - /// `.`s within; - /// * Otherwise, the portion of the file name before the final `.` - /// - /// ``` - /// use turborepo_paths::forward_relative_path::ForwardRelativePath; - /// - /// let path = ForwardRelativePath::new("foo.rs")?; - /// - /// assert_eq!(Some("foo"), path.file_stem()); - /// assert_eq!(Some("foo.bar"), ForwardRelativePath::new("hi/foo.bar.rs")?.file_stem()); - /// - /// # anyhow::Ok(()) - /// ``` - pub fn file_stem(&self) -> Option<&str> { - let file = self.file_name(); - file.map(|f| { - let f = f.as_str(); - for (i, b) in f.bytes().enumerate().rev() { - if b == b'.' && i > 0 { - return &f[0..i]; - } - } - - f - }) - } - - /// Extracts the extension of [`self.file_name`], if possible. - /// - /// ``` - /// - /// use turborepo_paths::forward_relative_path::ForwardRelativePath; - /// - /// assert_eq!(Some("rs"), ForwardRelativePath::new("hi/foo.rs")?.extension()); - /// assert_eq!(Some("rs"), ForwardRelativePath::new("hi/foo.bar.rs")?.extension()); - /// assert_eq!(None, ForwardRelativePath::new(".git")?.extension()); - /// assert_eq!(None, ForwardRelativePath::new("foo/.git")?.extension()); - /// assert_eq!(None, ForwardRelativePath::new("")?.extension()); - /// - /// # anyhow::Ok(()) - /// ``` - pub fn extension(&self) -> Option<&str> { - let s = &self.0; - let bytes = s.as_bytes(); - let mut i = s.len().checked_sub(1)?; - while i > 0 { - let b = bytes[i]; - if b == b'/' { - return None; - } - if b == b'.' { - if bytes[i - 1] == b'/' { - return None; - } - return Some(&s[i + 1..]); - } - - i -= 1; - } - None - } - - /// Build an owned `ForwardRelativePathBuf`, joined with the given path and - /// normalized. - /// - /// ``` - /// - /// use turborepo_paths::forward_relative_path::{ForwardRelativePath, ForwardRelativePathBuf}; - /// - /// assert_eq!( - /// ForwardRelativePathBuf::unchecked_new("foo/baz.txt".into()), - /// ForwardRelativePath::new("foo/bar")?.join_normalized("../baz.txt")?, - /// ); - /// - /// assert_eq!( - /// ForwardRelativePath::new("foo")?.join_normalized("../../baz.txt").is_err(), - /// true - /// ); - /// - /// # anyhow::Ok(()) - /// ``` - pub fn join_normalized>( - &self, - path: P, - ) -> anyhow::Result { - let self_rel_path: &RelativePath = self.as_ref(); - let inner = self_rel_path.join_normalized(path.as_ref()); - ForwardRelativePathBuf::try_from(inner) - } - - /// Append a relative system path, obtained frome e.g. `read_link`. - /// - /// The path will be converted to an internal path (i.e. forward slashes) - /// before joining. - pub fn join_system(&self, path: &Path) -> anyhow::Result { - let path = fs_util::relative_path_from_system(path)?; - self.join_normalized(path) - } - - /// Iterator over the components of this path - /// - /// ``` - /// use turborepo_paths::file_name::FileName; - /// use turborepo_paths::forward_relative_path::ForwardRelativePath; - /// - /// let p = ForwardRelativePath::new("foo/bar/baz")?; - /// let mut it = p.iter(); - /// - /// assert_eq!( - /// it.next(), - /// Some(FileName::unchecked_new("foo")) - /// ); - /// assert_eq!( - /// it.next(), - /// Some(FileName::unchecked_new("bar")) - /// ); - /// assert_eq!( - /// it.next(), - /// Some(FileName::unchecked_new("baz")) - /// ); - /// assert_eq!( - /// it.next(), - /// None - /// ); - /// assert_eq!( - /// it.next(), - /// None - /// ); - /// - /// # anyhow::Ok(()) - /// ``` - #[inline] - pub fn iter(&self) -> ForwardRelativePathIter<'_> { - ForwardRelativePathIter(self) - } - - /// Strip a given number of components from the prefix of a path, - /// returning the remaining path or `None` if there were none left. - /// - /// ``` - /// use turborepo_paths::forward_relative_path::ForwardRelativePath; - /// - /// let p = ForwardRelativePath::new("foo/bar/baz")?; - /// assert_eq!( - /// p.strip_prefix_components(0), - /// Some(ForwardRelativePath::new("foo/bar/baz")?), - /// ); - /// assert_eq!( - /// p.strip_prefix_components(1), - /// Some(ForwardRelativePath::new("bar/baz")?), - /// ); - /// assert_eq!( - /// p.strip_prefix_components(2), - /// Some(ForwardRelativePath::new("baz")?), - /// ); - /// assert_eq!( - /// p.strip_prefix_components(3), - /// Some(ForwardRelativePath::new("")?), - /// ); - /// assert_eq!( - /// p.strip_prefix_components(4), - /// None, - /// ); - /// # anyhow::Ok(()) - /// ``` - pub fn strip_prefix_components(&self, components: usize) -> Option<&Self> { - let mut rem = self; - for _ in 0..components { - rem = rem.split_first()?.1; - } - Some(rem) - } - - #[inline] - pub fn to_buf(&self) -> ForwardRelativePathBuf { - self.to_owned() - } - - pub fn to_box(&self) -> Box { - self.to_buf().into_box() - } - - /// Return a RelativePath represenation of this ForwardRelativePath. - #[inline] - pub fn as_relative_path(&self) -> &RelativePath { - RelativePath::new(&self.0) - } -} - -impl ForwardRelativePathBuf { - #[inline] - pub fn new(s: String) -> anyhow::Result { - ForwardRelativePath::new(&s)?; - Ok(ForwardRelativePathBuf(s)) - } - - #[inline] - pub fn empty() -> Self { - Self("".to_owned()) - } - - #[inline] - pub fn unchecked_new(s: String) -> Self { - Self(s) - } - - /// Creates a new 'ForwardRelativePathBuf' with a given capacity used to - /// create the internal 'String'. See 'with_capacity' defined on - /// 'String' - #[inline] - pub fn with_capacity(cap: usize) -> Self { - Self(String::with_capacity(cap)) - } - - /// Returns the capacity of the underlying 'String' - #[inline] - pub fn capacity(&self) -> usize { - self.0.capacity() - } - - /// Invokes 'reserve' on the underlying 'String' - #[inline] - pub fn reserve(&mut self, additional: usize) { - self.0.reserve(additional) - } - - /// Invokes 'shrink_to_fit' on the underlying 'String' - #[inline] - pub fn shrink_to_fit(&mut self) { - self.0.shrink_to_fit() - } - - /// Invokes 'shrink_to' on the underlying 'String' - #[inline] - pub fn shrink_to(&mut self, min_capacity: usize) { - self.0.shrink_to(min_capacity) - } - - /// Pushes a `ForwardRelativePath` to the existing buffer - /// - /// ``` - /// use turborepo_paths::forward_relative_path::{ForwardRelativePath, ForwardRelativePathBuf}; - /// - /// let mut path = ForwardRelativePathBuf::unchecked_new("foo".to_owned()); - /// path.push(ForwardRelativePath::unchecked_new("bar")); - /// - /// assert_eq!(ForwardRelativePathBuf::unchecked_new("foo/bar".to_owned()), path); - /// - /// path.push(ForwardRelativePath::unchecked_new("more/file.rs")); - /// assert_eq!(ForwardRelativePathBuf::unchecked_new("foo/bar/more/file.rs".to_owned()), path); - /// - /// path.push(ForwardRelativePath::empty()); - /// assert_eq!(ForwardRelativePathBuf::unchecked_new("foo/bar/more/file.rs".to_owned()), path); - /// - /// let mut path = ForwardRelativePathBuf::unchecked_new("".to_owned()); - /// path.push(ForwardRelativePath::unchecked_new("foo")); - /// assert_eq!(ForwardRelativePathBuf::unchecked_new("foo".to_owned()), path); - /// - /// # anyhow::Ok(()) - /// ``` - pub fn push>(&mut self, path: P) { - if path.as_ref().0.is_empty() { - return; - } - - if !self.0.is_empty() { - self.0.push('/'); - } - self.0.push_str(path.as_ref().as_str()) - } - - pub fn concat<'a, I: IntoIterator + Copy>( - items: I, - ) -> ForwardRelativePathBuf { - let mut cap = 0; - for item in items { - if !item.is_empty() { - if cap != 0 { - // `/`. - cap += 1; - } - cap += item.0.len(); - } - } - let mut path = ForwardRelativePathBuf::with_capacity(cap); - for item in items { - path.push(item); - } - // Cheap self-test. - assert!(path.0.len() == cap); - path - } - - /// Pushes a `RelativePath` to the existing buffer, normalizing it. - /// Note that this does not visit the filesystem to resolve `..`s. Instead, - /// it cancels out the components directly, similar to - /// `join_normalized`. - /// - /// ``` - /// - /// use turborepo_paths::forward_relative_path::ForwardRelativePathBuf; - /// use turborepo_paths::RelativePath; - /// - /// let mut path = ForwardRelativePathBuf::unchecked_new("foo".to_owned()); - /// path.push_normalized(RelativePath::new("bar"))?; - /// - /// assert_eq!(ForwardRelativePathBuf::unchecked_new("foo/bar".to_owned()), path); - /// - /// path.push_normalized(RelativePath::new("more/file.rs"))?; - /// assert_eq!(ForwardRelativePathBuf::unchecked_new("foo/bar/more/file.rs".to_owned()), path); - /// - /// path.push_normalized(RelativePath::new("../other.rs"))?; - /// assert_eq!(ForwardRelativePathBuf::unchecked_new("foo/bar/more/other.rs".to_owned()), path); - /// - /// path.push_normalized(RelativePath::new(".."))?; - /// assert_eq!(ForwardRelativePathBuf::unchecked_new("foo/bar/more".to_owned()), path); - /// - /// path.push_normalized(RelativePath::new("../.."))?; - /// assert_eq!(ForwardRelativePathBuf::unchecked_new("foo".to_owned()), path); - /// - /// path.push_normalized(RelativePath::new(".."))?; - /// assert_eq!(ForwardRelativePathBuf::unchecked_new("".to_owned()), path); - /// - /// assert!(path.push_normalized(RelativePath::new("..")).is_err()); - /// - /// # anyhow::Ok(()) - /// ``` - pub fn push_normalized>(&mut self, path: P) -> anyhow::Result<()> { - let buf = &mut self.0; - let mut insert_idx = buf.len(); - let bytes = path.as_ref().as_str().as_bytes(); - - let mut i = 0; - let mut j = i; - while j < bytes.len() { - assert!(i <= bytes.len()); - j = i; - while j != bytes.len() { - if bytes[j] == b'/' { - break; - } - j += 1; - } - if i == j { - // Double slashes or trailing slash. - unreachable!("not a relative path"); - } else if j == i + 1 && bytes[i] == b'.' { - // Current directory. Skip this segment and do nothing - } else if j == i + 2 && bytes[i] == b'.' && bytes[i + 1] == b'.' { - // Parent directory. Move the insert index - - if insert_idx == 0 { - // if we are already at 0, then we cannot move towards the parent without - // having this path still be forward pointing - return Err(anyhow::anyhow!( - ForwardRelativePathError::RelativizationError(path.as_ref().to_string()) - )); - } - - let mut buf_i = insert_idx; - let buf_bytes = buf.as_bytes(); - // note we don't bother checking when buf_i is 0, because that would imply our - // current forward relative path starts with '/', which would imply that it's - // not relative, which is unreachable code. - while buf_i > 0 { - buf_i -= 1; - - if buf_bytes[buf_i] == b'/' { - break; - } - } - // we got here because we either found a '/', or we got to the beginning of the - // current path, but starting with something in it, which means that we are now - // at the beginning segment, so insert_idx can be the beginning - insert_idx = buf_i; - } else { - // just a path segment to add - - // first add the '/' since our path representation does not have ending slashes - if insert_idx < buf.len() { - buf.replace_range(insert_idx..=insert_idx, "/"); - } else { - buf.push('/'); - } - - let seg_to_add = unsafe { - // safe because this is the buf from a `RelativePath`, which enforces `utf8` - - // also `j` rather than `j+1` to exclude the ending `/`, - // or not run out of bounds if `j = bytes.len()` - std::str::from_utf8_unchecked(&bytes[i..j]) - }; - if insert_idx + 1 < buf.len() { - buf.replace_range(insert_idx + 1.., seg_to_add); - } else { - buf.push_str(seg_to_add); - } - - insert_idx = buf.len(); - } - i = j + 1; - } - - if insert_idx < buf.len() { - buf.replace_range(insert_idx.., ""); - } - - Ok(()) - } - - #[inline] - pub fn into_string(self) -> String { - self.0 - } - - pub fn into_box(self) -> Box { - let s: Box = self.0.into_boxed_str(); - ForwardRelativePath::unchecked_new_box(s) - } -} - -/// Errors from ForwardRelativePath creation -#[derive(Error, Debug)] -enum ForwardRelativePathError { - #[error("expected a relative path but got an absolute path instead: `{0}`")] - PathNotRelative(String), - #[error("expected a normalized path but got an un-normalized path instead: `{0}`")] - PathNotNormalized(String), - #[error("Path is not UTF-8: `{0}`")] - PathNotUtf8(String), - #[error("relativizing path `{0}` results would result in a non-forward relative path")] - RelativizationError(String), -} - -/// Error from 'strip_prefix' -#[derive(Error, Debug)] -#[error("`{0}` is not a base of `{1}`")] -pub struct StripPrefixError(String, String); - -impl<'a> IntoIterator for &'a ForwardRelativePath { - type Item = &'a FileName; - type IntoIter = ForwardRelativePathIter<'a>; - - #[inline] - fn into_iter(self) -> Self::IntoIter { - self.iter() - } -} - -impl<'a> TryFrom<&'a str> for &'a ForwardRelativePath { - type Error = anyhow::Error; - - /// no allocation conversion - /// - /// ``` - /// - /// use turborepo_paths::forward_relative_path::ForwardRelativePath; - /// use std::convert::TryFrom; - /// - /// assert!(<&ForwardRelativePath>::try_from("foo/bar").is_ok()); - /// assert!(<&ForwardRelativePath>::try_from("").is_ok()); - /// assert!(<&ForwardRelativePath>::try_from("./bar").is_err()); - /// assert!(<&ForwardRelativePath>::try_from("normalize/./bar").is_err()); - /// assert!(<&ForwardRelativePath>::try_from("/abs/bar").is_err()); - /// assert!(<&ForwardRelativePath>::try_from("normalize/../bar").is_err()); - /// - /// # anyhow::Ok(()) - /// ``` - #[inline] - fn try_from(s: &'a str) -> anyhow::Result<&'a ForwardRelativePath> { - ForwardRelativePathVerifier::verify_str(s)?; - Ok(ForwardRelativePath::ref_cast(s)) - } -} - -impl<'a> From<&'a FileName> for &'a ForwardRelativePath { - #[inline] - fn from(p: &'a FileName) -> Self { - ForwardRelativePath::unchecked_new(p.as_str()) - } -} - -impl<'a> TryFrom<&'a Path> for &'a ForwardRelativePath { - type Error = anyhow::Error; - - /// no allocation conversion - /// - /// ``` - /// - /// use turborepo_paths::forward_relative_path::ForwardRelativePath; - /// use std::convert::TryFrom; - /// use std::path::Path; - /// - /// assert!(<&ForwardRelativePath>::try_from(Path::new("foo/bar")).is_ok()); - /// assert!(<&ForwardRelativePath>::try_from(Path::new("")).is_ok()); - /// assert!(<&ForwardRelativePath>::try_from(Path::new("./bar")).is_err()); - /// assert!(<&ForwardRelativePath>::try_from(Path::new("normalize/./bar")).is_err()); - /// assert!(<&ForwardRelativePath>::try_from(Path::new("/abs/bar")).is_err()); - /// assert!(<&ForwardRelativePath>::try_from(Path::new("normalize/../bar")).is_err()); - /// - /// # anyhow::Ok(()) - /// ``` - fn try_from(s: &'a Path) -> anyhow::Result<&'a ForwardRelativePath> { - let s = s - .as_os_str() - .to_str() - .ok_or_else(|| ForwardRelativePathError::PathNotUtf8(s.display().to_string()))?; - ForwardRelativePathVerifier::verify_str(s)?; - Ok(ForwardRelativePath::unchecked_new(s)) - } -} - -impl<'a> TryFrom<&'a RelativePath> for &'a ForwardRelativePath { - type Error = anyhow::Error; - - /// no allocation conversion - /// - /// ``` - /// - /// use turborepo_paths::forward_relative_path::ForwardRelativePath; - /// use std::convert::TryFrom; - /// use turborepo_paths::RelativePath; - /// - /// assert!(<&ForwardRelativePath>::try_from(RelativePath::new("foo/bar")).is_ok()); - /// assert!(<&ForwardRelativePath>::try_from(RelativePath::new("")).is_ok()); - /// assert!(<&ForwardRelativePath>::try_from(RelativePath::new("./bar")).is_err()); - /// assert!(<&ForwardRelativePath>::try_from(RelativePath::new("normalize/./bar")).is_err()); - /// assert!(<&ForwardRelativePath>::try_from(RelativePath::new("normalize/../bar")).is_err()); - /// - /// # anyhow::Ok(()) - /// ``` - #[inline] - fn try_from(p: &'a RelativePath) -> anyhow::Result<&'a ForwardRelativePath> { - ForwardRelativePathVerifier::verify_str(p.as_str())?; - Ok(ForwardRelativePath::unchecked_new(p.as_str())) - } -} - -impl From for RelativePathBuf { - fn from(p: ForwardRelativePathBuf) -> Self { - RelativePathBuf::from(p.0) - } -} - -impl TryFrom for ForwardRelativePathBuf { - type Error = anyhow::Error; - - /// no allocation conversion - /// - /// ``` - /// - /// use turborepo_paths::forward_relative_path::ForwardRelativePathBuf; - /// use std::convert::TryFrom; - /// - /// assert!(ForwardRelativePathBuf::try_from("foo/bar".to_owned()).is_ok()); - /// assert!(ForwardRelativePathBuf::try_from("".to_owned()).is_ok()); - /// assert!(ForwardRelativePathBuf::try_from("./bar".to_owned()).is_err()); - /// assert!(ForwardRelativePathBuf::try_from("normalize/./bar".to_owned()).is_err()); - /// assert!(ForwardRelativePathBuf::try_from("/abs/bar".to_owned()).is_err()); - /// assert!(ForwardRelativePathBuf::try_from("normalize/../bar".to_owned()).is_err()); - /// - /// # anyhow::Ok(()) - /// ``` - #[inline] - fn try_from(s: String) -> anyhow::Result { - ForwardRelativePathVerifier::verify_str(&s)?; - Ok(ForwardRelativePathBuf(s)) - } -} - -impl TryFrom for ForwardRelativePathBuf { - type Error = anyhow::Error; - - /// no allocation conversion - /// - /// ``` - /// use turborepo_paths::forward_relative_path::ForwardRelativePathBuf; - /// use turborepo_paths::RelativePathBuf; - /// use std::convert::TryFrom; - /// use std::path::PathBuf; - /// - /// assert!(ForwardRelativePathBuf::try_from(PathBuf::from("foo/bar")).is_ok()); - /// assert!(ForwardRelativePathBuf::try_from(PathBuf::from("")).is_ok()); - /// assert!(ForwardRelativePathBuf::try_from(PathBuf::from("./bar")).is_err()); - /// assert!(ForwardRelativePathBuf::try_from(PathBuf::from("normalize/./bar")).is_err()); - /// assert!(ForwardRelativePathBuf::try_from(PathBuf::from("/abs/bar")).is_err()); - /// assert!(ForwardRelativePathBuf::try_from(PathBuf::from("normalize/../bar")).is_err()); - /// - /// # anyhow::Ok(()) - /// ``` - fn try_from(p: PathBuf) -> anyhow::Result { - // RelativePathBuf::from_path actually creates a copy. - // avoid the copy by constructing RelativePathBuf from the underlying String - ForwardRelativePathBuf::try_from(p.into_os_string().into_string().map_err(|_| { - relative_path::FromPathError::from(relative_path::FromPathErrorKind::NonUtf8) - })?) - } -} - -impl TryFrom for ForwardRelativePathBuf { - type Error = anyhow::Error; - - /// no allocation conversion - /// - /// ``` - /// use turborepo_paths::forward_relative_path::ForwardRelativePathBuf; - /// use turborepo_paths::RelativePathBuf; - /// use std::convert::TryFrom; - /// - /// assert!(ForwardRelativePathBuf::try_from(RelativePathBuf::from("foo/bar")).is_ok()); - /// assert!(ForwardRelativePathBuf::try_from(RelativePathBuf::from("")).is_ok()); - /// assert!(ForwardRelativePathBuf::try_from(RelativePathBuf::from("./bar")).is_err()); - /// assert!(ForwardRelativePathBuf::try_from(RelativePathBuf::from("normalize/./bar")).is_err()); - /// assert!(ForwardRelativePathBuf::try_from(RelativePathBuf::from("normalize/../bar")).is_err()); - /// - /// # anyhow::Ok(()) - /// ``` - #[inline] - fn try_from(p: RelativePathBuf) -> anyhow::Result { - ForwardRelativePathBuf::try_from(p.into_string()) - } -} - -impl ToOwned for ForwardRelativePath { - type Owned = ForwardRelativePathBuf; - - #[inline] - fn to_owned(&self) -> ForwardRelativePathBuf { - ForwardRelativePathBuf::unchecked_new(self.0.to_owned()) - } -} - -impl AsRef for ForwardRelativePath { - #[inline] - fn as_ref(&self) -> &ForwardRelativePath { - self - } -} - -impl AsRef for ForwardRelativePathBuf { - #[inline] - fn as_ref(&self) -> &ForwardRelativePath { - ForwardRelativePath::unchecked_new(&self.0) - } -} - -impl Borrow for ForwardRelativePathBuf { - #[inline] - fn borrow(&self) -> &ForwardRelativePath { - self.as_ref() - } -} - -impl Deref for ForwardRelativePathBuf { - type Target = ForwardRelativePath; - - #[inline] - fn deref(&self) -> &ForwardRelativePath { - ForwardRelativePath::unchecked_new(&self.0) - } -} - -/// Normalize ForwardRelativePath path if needed. -pub struct ForwardRelativePathNormalizer {} - -impl ForwardRelativePathNormalizer { - pub fn normalize_path + ?Sized>( - rel_path: &P, - ) -> anyhow::Result> { - let rel_path = rel_path.as_ref(); - if !rel_path.is_relative() { - return Err(anyhow::anyhow!(ForwardRelativePathError::PathNotRelative( - rel_path.display().to_string(), - ))); - } - let path_str = rel_path - .to_str() - .ok_or_else(|| ForwardRelativePathError::PathNotUtf8(rel_path.display().to_string()))?; - let bytes = path_str.as_bytes(); - if cfg!(windows) && memchr::memchr(b'\\', bytes).is_some() { - let normalized_path = path_str.replace('\\', "/"); - Ok(Cow::Owned(ForwardRelativePathBuf::try_from( - normalized_path, - )?)) - } else { - Ok(Cow::Borrowed(ForwardRelativePath::new(path_str)?)) - } - } -} - -/// Verifier for ForwardRelativePath to ensure the path is fully relative, and -/// normalized -struct ForwardRelativePathVerifier {} - -impl ForwardRelativePathVerifier { - fn verify_str(rel_path: &str) -> anyhow::Result<()> { - #[cold] - #[inline(never)] - fn err(rel_path: &str) -> anyhow::Error { - anyhow::anyhow!(ForwardRelativePathError::PathNotNormalized( - rel_path.to_owned() - )) - } - - let bytes = rel_path.as_bytes(); - if bytes.is_empty() { - return Ok(()); - } - if bytes[0] == b'/' { - return Err(anyhow::anyhow!(ForwardRelativePathError::PathNotRelative( - rel_path.to_owned() - ))); - } - - if memchr::memchr(b'\\', bytes).is_some() { - return Err(err(rel_path)); - } - - let mut i = 0; - loop { - assert!(i <= bytes.len()); - let mut j = i; - while j != bytes.len() { - if bytes[j] == b'/' { - break; - } - j += 1; - } - if i == j { - // Double slashes or trailing slash. - return Err(err(rel_path)); - } - if j == i + 1 && bytes[i] == b'.' { - // Current directory. - return Err(err(rel_path)); - } - if j == i + 2 && bytes[i] == b'.' && bytes[i + 1] == b'.' { - // Parent directory. - return Err(err(rel_path)); - } - if j == bytes.len() { - return Ok(()); - } - i = j + 1; - } - } -} - -impl<'a> FromIterator<&'a FileName> for Option { - fn from_iter(iter: I) -> Self - where - I: IntoIterator, - { - from_iter::<20, _>(iter) - } -} - -impl<'a> FromIterator<&'a FileNameBuf> for Option { - fn from_iter(iter: I) -> Self - where - I: IntoIterator, - { - iter.into_iter() - .map(>::as_ref) - .collect() - } -} - -fn from_iter<'a, const N: usize, I>(iter: I) -> Option -where - I: IntoIterator, -{ - // Collect up to 20 pointers to the stack. This avoids a reallocation when - // joining paths of up to 20 components. - let parts = iter.into_iter().collect::>(); - - let mut first = true; - let mut size = 0; - for part in &parts { - if !first { - size += 1; // For `/` - } - size += part.as_str().len(); - first = false; - } - - let mut ret = String::with_capacity(size); - for part in &parts { - if !ret.is_empty() { - ret.push('/'); - } - ret.push_str(part.as_ref()); - } - - if ret.is_empty() { - None - } else { - Some(ForwardRelativePathBuf(ret)) - } -} - -#[cfg(test)] -mod tests { - use crate::forward_relative_path::{ - from_iter, FileName, ForwardRelativePath, ForwardRelativePathBuf, - }; - - #[test] - fn forward_path_is_comparable() -> anyhow::Result<()> { - let path1_buf = ForwardRelativePathBuf::unchecked_new("foo".into()); - let path2_buf = ForwardRelativePathBuf::unchecked_new("foo".into()); - let path3_buf = ForwardRelativePathBuf::unchecked_new("bar".into()); - - let path1 = ForwardRelativePath::new("foo")?; - let path2 = ForwardRelativePath::new("foo")?; - let path3 = ForwardRelativePath::new("bar")?; - - let str2 = "foo"; - let str3 = "bar"; - let str_abs = "/ble"; - - let string2 = "foo".to_owned(); - let string3 = "bar".to_owned(); - let string_abs = "/ble".to_owned(); - - assert_eq!(path1_buf, path2_buf); - assert_ne!(path1_buf, path3_buf); - - assert_eq!(path1, path2); - assert_ne!(path1, path3); - - assert_eq!(path1_buf, path2); - assert_ne!(path1, path3_buf); - - assert_eq!(path1_buf, str2); - assert_ne!(path1_buf, str3); - assert_ne!(path1_buf, str_abs); - - assert_eq!(path1, str2); - assert_ne!(path1, str3); - assert_ne!(path1, str_abs); - - assert_eq!(path1_buf, string2); - assert_ne!(path1_buf, string3); - assert_ne!(path1_buf, string_abs); - - assert_eq!(path1, string2); - assert_ne!(path1, string3); - assert_ne!(path1, string_abs); - - Ok(()) - } - - #[test] - fn test_concat() { - assert_eq!( - ForwardRelativePath::new("").unwrap(), - AsRef::::as_ref(&ForwardRelativePathBuf::concat([])) - ); - assert_eq!( - ForwardRelativePath::new("foo/bar/baz").unwrap(), - AsRef::::as_ref(&ForwardRelativePathBuf::concat([ - ForwardRelativePath::new("foo").unwrap(), - ForwardRelativePath::new("bar/baz").unwrap(), - ])) - ); - assert_eq!( - ForwardRelativePath::new("foo/bar/baz").unwrap(), - AsRef::::as_ref(&ForwardRelativePathBuf::concat([ - ForwardRelativePath::new("").unwrap(), - ForwardRelativePath::new("foo").unwrap(), - ForwardRelativePath::new("bar/baz").unwrap(), - ])) - ); - } - - #[test] - fn test_from_iter() { - let parts = &["foo", "bar", "baz"] - .into_iter() - .map(FileName::unchecked_new) - .collect::>(); - - let expected = Some(ForwardRelativePath::unchecked_new("foo/bar/baz").to_buf()); - - assert_eq!(from_iter::<1, _>(parts.iter().copied()), expected); - assert_eq!(from_iter::<2, _>(parts.iter().copied()), expected); - assert_eq!(from_iter::<3, _>(parts.iter().copied()), expected); - assert_eq!(from_iter::<4, _>(parts.iter().copied()), expected); - } -} diff --git a/crates/turborepo-paths/src/fs_util.rs b/crates/turborepo-paths/src/fs_util.rs deleted file mode 100644 index 929cefddce84d..0000000000000 --- a/crates/turborepo-paths/src/fs_util.rs +++ /dev/null @@ -1,883 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under both the MIT license found in the - * LICENSE-MIT file in the root directory of this source tree and the Apache - * License, Version 2.0 found in the LICENSE-APACHE file in the root - * directory of this source tree. - */ - -// We'd love to use fs-err instead, but that code gives bad error messages and -// doesn't wrap all functions. Various bugs have been raised - if they all get -// fixed we can migrate. -use std::{ - borrow::Cow, - fs, - fs::File, - io, - io::Write, - ops::Deref, - path::{Path, PathBuf}, -}; - -use anyhow::Context; -#[allow(unused)] // Keep the unused deps linter happy even though we only use on Windows. -use common_path::common_path; -use relative_path::{RelativePath, RelativePathBuf}; - -use crate::{ - absolute_normalized_path::{AbsoluteNormalizedPath, AbsoluteNormalizedPathBuf}, - absolute_path::AbsolutePath, - io_counters::{IoCounterGuard, IoCounterKey}, -}; - -pub fn symlink(original: P, link: Q) -> anyhow::Result<()> -where - P: AsRef, - Q: AsRef, -{ - let _guard = IoCounterKey::Symlink.guard(); - symlink_impl(original.as_ref(), link.as_ref()).with_context(|| { - format!( - "symlink(original={}, link={})", - original.as_ref().display(), - link.as_ref().display() - ) - }) -} - -#[cfg(unix)] -fn symlink_impl(original: &Path, link: &Path) -> anyhow::Result<()> { - std::os::unix::fs::symlink(original, link).map_err(|e| e.into()) -} - -/// Create symlink on Windows. -#[cfg(windows)] -fn symlink_impl(original: &Path, link: &Path) -> anyhow::Result<()> { - use std::io::ErrorKind; - - // If original is a relative path, fix it up to be absolute - let target_abspath = if original.is_absolute() { - Cow::Borrowed(original) - } else { - Cow::Owned( - link.parent() - .ok_or_else(|| anyhow::anyhow!("Expected path with a parent in symlink target"))? - .join(original), - ) - }; - let target_abspath = std::path::absolute(&target_abspath)?; - - // Relative symlinks in Windows are relative from the real/canonical path of the - // link, so it can get messy when the symlink lives in buck-out. For - // Windows, we'll canonicalize, or if the target doesn't exist yet simulate - // canonicalize() by canonicalizing the common ancestor between the target - // and link and appending the rest. The common ancestor should - // live somewhere between repo root / buck-out and link's directory, which - // should both exist. Canonicalize() will also handle adding the verbatim - // prefix \\?\, which is required for supporting paths longer than 260 - // In general, it should be OK to opt for absolute / canonical paths when - // possible as buck will not read any of these paths. - let target_canonical = if let Ok(path) = target_abspath.canonicalize() { - path - } else { - // target doesn't exist yet, try to guess the canonical path - if let Some(common_path) = common_path(&target_abspath, &link) { - let from_common = target_abspath.strip_prefix(&common_path)?; - let common_canonicalized = common_path - .canonicalize() - .context(format!("Failed to get canonical path of {:?}", common_path))?; - common_canonicalized.join(from_common) - } else { - target_abspath - } - }; - - let target_metadata = target_canonical.metadata(); - match target_metadata { - Ok(meta) if meta.is_dir() => { - std::os::windows::fs::symlink_dir(&target_canonical, link)?; - Ok(()) - } - Err(e) if e.kind() != ErrorKind::NotFound => Err(e.into()), - _ => { - // Either file or not existent. Default to file. - // TODO(T144443238): This will cause issues if the file type turns out to be - // directory, fix this - std::os::windows::fs::symlink_file(&target_canonical, link)?; - Ok(()) - } - } -} - -pub fn create_dir_all>(path: P) -> anyhow::Result<()> { - let _guard = IoCounterKey::MkDir.guard(); - fs::create_dir_all(&path) - .with_context(|| format!("create_dir_all({})", P::as_ref(&path).display()))?; - Ok(()) -} - -pub fn create_dir>(path: P) -> anyhow::Result<()> { - let _guard = IoCounterKey::MkDir.guard(); - fs::create_dir(&path).with_context(|| format!("create_dir({})", P::as_ref(&path).display()))?; - Ok(()) -} - -/// Create directory if not exists. -/// -/// Fail if exists but is not a directory or creation failed. -pub fn create_dir_if_not_exists>(path: P) -> anyhow::Result<()> { - let path = path.as_ref(); - let _guard = IoCounterKey::MkDir.guard(); - let e = match fs::create_dir(path).with_context(|| format!("create_dir({})", path.display())) { - Ok(()) => return Ok(()), - Err(e) => e, - }; - - match symlink_metadata(path) { - Ok(metadata) => { - if metadata.is_dir() { - Ok(()) - } else { - // File exists but not a directory, return original error. - Err(e) - } - } - Err(_) => { - // `lstat` failed, means something like permission denied, return original - // error. - Err(e) - } - } -} - -/// `DirEntry` which is known to contain absolute path. -pub struct DirEntry { - dir_entry: fs::DirEntry, -} - -impl DirEntry { - pub fn path(&self) -> AbsoluteNormalizedPathBuf { - // This is safe, because `read_dir` is called with absolute path, - // and filename is not dot or dot-dot. - AbsoluteNormalizedPathBuf::unchecked_new(self.dir_entry.path()) - } -} - -impl Deref for DirEntry { - type Target = fs::DirEntry; - - fn deref(&self) -> &Self::Target { - &self.dir_entry - } -} - -pub struct ReadDir { - read_dir: fs::ReadDir, - /// We store guard in the iterator, so if an iteration does non-trivial - /// operations, these non-trivial operations will be considered part of - /// read dir in IO counters. - _guard: IoCounterGuard, -} - -impl Iterator for ReadDir { - type Item = io::Result; - - fn next(&mut self) -> Option { - self.read_dir - .next() - .map(|res| res.map(|dir_entry| DirEntry { dir_entry })) - } -} - -pub fn read_dir>(path: P) -> anyhow::Result { - let _guard = IoCounterKey::ReadDir.guard(); - fs::read_dir(path.as_ref()) - .with_context(|| format!("read_dir({})", P::as_ref(&path).display())) - .map(|read_dir| ReadDir { read_dir, _guard }) -} - -pub fn read_dir_if_exists>( - path: P, -) -> anyhow::Result> { - let _guard = IoCounterKey::ReadDir.guard(); - let read_dir = fs::read_dir(path.as_ref()); - let read_dir = match read_dir { - Err(e) if e.kind() == std::io::ErrorKind::NotFound => return Ok(None), - Err(e) => { - return Err(e) - .with_context(|| format!("read_dir_if_exists({})", P::as_ref(&path).display())); - } - Ok(x) => x, - }; - Ok(Some(ReadDir { read_dir, _guard })) -} - -pub fn try_exists>(path: P) -> anyhow::Result { - let _guard = IoCounterKey::Stat.guard(); - fs::try_exists(&path).with_context(|| format!("try_exists({})", P::as_ref(&path).display())) -} - -pub fn remove_file>(path: P) -> anyhow::Result<()> { - let _guard = IoCounterKey::Remove.guard(); - remove_file_impl(path.as_ref()) - .with_context(|| format!("remove_file({})", P::as_ref(&path).display())) -} - -#[cfg(unix)] -fn remove_file_impl(path: &Path) -> anyhow::Result<()> { - fs::remove_file(path)?; - Ok(()) -} - -#[cfg(windows)] -fn remove_file_impl(path: &Path) -> anyhow::Result<()> { - use std::os::windows::fs::FileTypeExt; - - let file_type = path.symlink_metadata()?.file_type(); - if !file_type.is_symlink() || file_type.is_symlink_file() { - fs::remove_file(path)?; - } else { - fs::remove_dir(path)?; - } - Ok(()) -} - -pub fn hard_link, Q: AsRef>(src: P, dst: Q) -> anyhow::Result<()> { - let _guard = IoCounterKey::Hardlink.guard(); - fs::hard_link(&src, &dst).with_context(|| { - format!( - "hard_link(src={}, dst={})", - P::as_ref(&src).display(), - Q::as_ref(&dst).display() - ) - })?; - Ok(()) -} - -pub fn copy, Q: AsRef>(from: P, to: Q) -> anyhow::Result { - let _guard = IoCounterKey::Copy.guard(); - fs::copy(&from, &to).with_context(|| { - format!( - "copy(from={}, to={})", - P::as_ref(&from).display(), - Q::as_ref(&to).display() - ) - }) -} - -pub fn read_link>(path: P) -> anyhow::Result { - let _guard = IoCounterKey::ReadLink.guard(); - fs::read_link(&path).with_context(|| format!("read_link({})", P::as_ref(&path).display())) -} - -pub fn rename, Q: AsRef>(from: P, to: Q) -> anyhow::Result<()> { - let _guard = IoCounterKey::Rename.guard(); - fs::rename(&from, &to).with_context(|| { - format!( - "rename(from={}, to={})", - P::as_ref(&from).display(), - Q::as_ref(&to).display() - ) - })?; - Ok(()) -} - -pub fn write, C: AsRef<[u8]>>(path: P, contents: C) -> anyhow::Result<()> { - let _guard = IoCounterKey::Write.guard(); - fs::write(&path, &contents) - .with_context(|| format!("write({}, _)", P::as_ref(&path).display()))?; - Ok(()) -} - -pub fn metadata>(path: P) -> anyhow::Result { - let _guard = IoCounterKey::Stat.guard(); - fs::metadata(&path).with_context(|| format!("metadata({})", P::as_ref(&path).display())) -} - -pub fn symlink_metadata>(path: P) -> anyhow::Result { - let _guard = IoCounterKey::Stat.guard(); - fs::symlink_metadata(&path) - .with_context(|| format!("symlink_metadata({})", P::as_ref(&path).display())) -} - -pub fn set_permissions>(path: P, perm: fs::Permissions) -> anyhow::Result<()> { - let _guard = IoCounterKey::Chmod.guard(); - fs::set_permissions(&path, perm) - .with_context(|| format!("set_permissions({}, _)", P::as_ref(&path).display()))?; - Ok(()) -} - -pub fn remove_dir_all>(path: P) -> anyhow::Result<()> { - let _guard = IoCounterKey::RmDirAll.guard(); - fs::remove_dir_all(&path) - .with_context(|| format!("remove_dir_all({})", P::as_ref(&path).display()))?; - Ok(()) -} - -/// `None` if file does not exist. -pub fn symlink_metadata_if_exists>(path: P) -> anyhow::Result> { - let _guard = IoCounterKey::Stat.guard(); - match fs::symlink_metadata(&path) { - Ok(metadata) => Ok(Some(metadata)), - Err(e) if e.kind() == io::ErrorKind::NotFound => Ok(None), - Err(err) => { - Err(err).with_context(|| format!("symlink_metadata({})", path.as_ref().display())) - } - } -} - -/// Remove whatever exists at `path`, be it a file, directory, pipe, broken -/// symlink, etc. Do nothing if `path` does not exist. -pub fn remove_all>(path: P) -> anyhow::Result<()> { - let guard = IoCounterKey::RmDirAll.guard(); - let metadata = match symlink_metadata_if_exists(&path)? { - Some(s) => s, - None => return Ok(()), - }; - - drop(guard); - - let r = if metadata.is_dir() { - remove_dir_all(&path) - } else { - remove_file(&path) - }; - if r.is_err() && symlink_metadata_if_exists(&path)?.is_none() { - // Other process removed it, our goal is achieved. - return Ok(()); - } - r -} - -pub fn read>(path: P) -> anyhow::Result> { - let _guard = IoCounterKey::Read.guard(); - fs::read(&path).with_context(|| format!("read({})", P::as_ref(&path).display())) -} - -pub fn read_to_string>(path: P) -> anyhow::Result { - let _guard = IoCounterKey::Read.guard(); - fs::read_to_string(&path) - .with_context(|| format!("read_to_string({})", P::as_ref(&path).display())) -} - -/// Read a file, if it exists. Returns `None` when the file does not exist. -pub fn read_to_string_opt>(path: P) -> anyhow::Result> { - let _guard = IoCounterKey::Read.guard(); - match fs::read_to_string(&path) { - Ok(d) => Ok(Some(d)), - Err(e) if e.kind() == io::ErrorKind::NotFound => Ok(None), - Err(e) => Err(anyhow::Error::from(e).context(format!( - "read_to_string_opt({})", - P::as_ref(&path).display() - ))), - } -} - -pub fn canonicalize>(path: P) -> anyhow::Result { - let _guard = IoCounterKey::Canonicalize.guard(); - let path = dunce::canonicalize(&path) - .with_context(|| format!("canonicalize({})", P::as_ref(&path).display()))?; - AbsoluteNormalizedPathBuf::new(path) -} - -/// Convert Windows UNC path to regular path. -pub fn simplified(path: &AbsolutePath) -> anyhow::Result<&AbsolutePath> { - let path = dunce::simplified(path.as_ref()); - // This should not fail, but better not panic. - AbsolutePath::new(path) -} - -pub fn remove_dir>(path: P) -> anyhow::Result<()> { - let _guard = IoCounterKey::RmDir.guard(); - fs::remove_dir(&path).with_context(|| format!("remove_dir({})", P::as_ref(&path).display())) -} - -pub struct FileGuard { - file: File, - _guard: IoCounterGuard, -} - -impl Write for FileGuard { - fn write(&mut self, buf: &[u8]) -> io::Result { - self.file.write(buf) - } - - fn flush(&mut self) -> io::Result<()> { - self.file.flush() - } -} - -pub fn create_file>(path: P) -> anyhow::Result { - let guard = IoCounterKey::Write.guard(); - let file = File::create(path.as_ref()) - .with_context(|| format!("create_file({})", P::as_ref(&path).display()))?; - Ok(FileGuard { - file, - _guard: guard, - }) -} - -// Create a relative path in a cross-platform way, we need this since -// RelativePath fails when converting backslashes which means windows paths end -// up failing. RelativePathBuf doesn't have this problem and we can easily -// coerce it into a RelativePath. TODO(T143971518) Avoid RelativePath usage in -// buck2 -pub fn relative_path_from_system(path: &Path) -> anyhow::Result> { - let res = if cfg!(windows) { - Cow::Owned(RelativePathBuf::from_path(path)?) - } else { - Cow::Borrowed(RelativePath::from_path(path)?) - }; - Ok(res) -} - -#[cfg(test)] -mod tests { - use std::{ - fs, - fs::File, - io, - path::{Path, PathBuf}, - }; - - use assert_matches::assert_matches; - use relative_path::RelativePath; - - use crate::{ - absolute_normalized_path::AbsoluteNormalizedPath, - forward_relative_path::ForwardRelativePath, - fs_util, - fs_util::{ - create_dir_all, metadata, read_dir_if_exists, read_to_string, remove_all, - remove_dir_all, remove_file, symlink, symlink_metadata, write, - }, - }; - - #[test] - fn if_exists_read_dir() -> anyhow::Result<()> { - let binding = std::env::temp_dir(); - let existing_path = AbsoluteNormalizedPath::new(&binding)?; - let res = read_dir_if_exists(existing_path)?; - assert!(res.is_some()); - let not_existing_dir = existing_path.join(ForwardRelativePath::unchecked_new("dir")); - let res = read_dir_if_exists(not_existing_dir)?; - assert!(res.is_none()); - Ok(()) - } - - #[test] - fn create_and_remove_symlink_dir() -> anyhow::Result<()> { - let tempdir = tempfile::tempdir()?; - let root = tempdir.path().join("root"); - create_dir_all(&root)?; - let dir1 = root.join("dir1"); - let symlink_dir1 = root.join("symlink_dir1"); - - // Create dir1 and link symlink_dir1 to dir1 - create_dir_all(&dir1)?; - assert!(symlink_metadata(&dir1)?.is_dir()); - symlink(&dir1, &symlink_dir1)?; - assert!(symlink_metadata(&symlink_dir1)?.is_symlink()); - - // Remove the symlink, dir1 should still be in tact - remove_file(&symlink_dir1)?; - assert!(symlink_metadata(&dir1)?.is_dir()); - assert_matches!(symlink_metadata(&symlink_dir1), Err(..)); - - // Clean up - remove_dir_all(&root)?; - Ok(()) - } - - #[test] - fn create_and_remove_symlink_file() -> anyhow::Result<()> { - let tempdir = tempfile::tempdir()?; - let root = tempdir.path().join("root"); - create_dir_all(&root)?; - let file1 = root.join("file1"); - let symlink_file1 = root.join("symlink_file1"); - - // Create file1 and link symlink_file1 to file1 - File::create(&file1)?; - assert!(symlink_metadata(&file1)?.is_file()); - symlink(&file1, &symlink_file1)?; - assert!(symlink_metadata(&symlink_file1)?.is_symlink()); - - // Remove the symlink, file1 should still be in tact - remove_file(&symlink_file1)?; - assert!(symlink_metadata(&file1)?.is_file()); - assert_matches!(symlink_metadata(&symlink_file1), Err(..)); - - // Clean up - remove_dir_all(&root)?; - Ok(()) - } - - #[test] - fn test_symlink_with_target_length_over_max_path() -> anyhow::Result<()> { - // In Windows, the maximum length of a path is 260. - // To allow extended path lengths, canonicalize the paths - // so that they are prefixed with '\\?' - let max_path = 260; - - // 1. Test a case where we create a simple simlink to a long file path - let tempdir = tempfile::tempdir()?; - let symlink1 = tempdir.path().join("symlink1"); - // Create a path that looks like /subdir/subdir/.../subdir/simple_file - let mut long_sub_path = "subdir/".repeat(max_path / 7); - long_sub_path.push_str("simple_file"); - let target_path1 = tempdir.path().join(long_sub_path); - assert!(target_path1.to_str().unwrap().len() > max_path); - - create_dir_all(target_path1.parent().unwrap())?; - symlink(&target_path1, &symlink1)?; - write(&target_path1, b"This is File1")?; - assert_eq!(read_to_string(&symlink1)?, "This is File1"); - - // 2. Test a case where we create a symlink to an absolute path target with some - // relative ../../ - let symlink2 = tempdir.path().join("symlink2"); - // Create a path that looks like - // /subdir/subdir/.../subdir/../abs_with_relative - let long_sub_path = "subdir/".repeat(max_path / 7); - let target_path2 = tempdir - .path() - .join(long_sub_path) - .join("../abs_with_relative"); - assert!(target_path2.is_absolute()); - assert!(target_path2.to_str().unwrap().len() > max_path); - create_dir_all(&target_path2)?; - let target_path2 = target_path2.join("file2"); - symlink(&target_path2, &symlink2)?; - write(&target_path2, b"This is File2")?; - assert_eq!(read_to_string(&symlink2)?, "This is File2"); - Ok(()) - } - - #[test] - fn symlink_to_file_which_doesnt_exist() -> anyhow::Result<()> { - let tempdir = tempfile::tempdir()?; - let symlink_path = tempdir.path().join("symlink"); - let target_path = tempdir.path().join("file"); - symlink(&target_path, &symlink_path)?; - write(&target_path, b"File content")?; - assert_eq!(read_to_string(&symlink_path)?, "File content"); - Ok(()) - } - - #[test] - fn symlink_to_symlinked_dir() -> anyhow::Result<()> { - let tempdir = tempfile::tempdir()?; - let dir_path = tempdir.path().join("dir"); - let file_path = dir_path.join("file"); - create_dir_all(&dir_path)?; - write(file_path, b"Content")?; - let symlink1_path = tempdir.path().join("symlink1"); - let symlink2_path = tempdir.path().join("symlink2"); - symlink(&dir_path, &symlink1_path)?; - symlink(&symlink1_path, &symlink2_path)?; - assert_eq!(read_to_string(symlink2_path.join("file"))?, "Content"); - assert!(metadata(&symlink1_path)?.is_dir()); - assert!(metadata(&symlink2_path)?.is_dir()); - Ok(()) - } - - #[test] - fn relative_symlink_to_nonexistent_file() -> anyhow::Result<()> { - // tmp -- dir1 (exists) -- file1 (doesn't exist) - // \ - // \ symlink1 to dir1/file1 - let tempdir = tempfile::tempdir()?; - let dir_path = tempdir.path().join("dir1"); - create_dir_all(dir_path)?; - let relative_symlink1_path = tempdir.path().join("relative_symlink1"); - symlink("dir1/file1", &relative_symlink1_path)?; - write(tempdir.path().join("dir1/file1"), b"File content")?; - assert_eq!(read_to_string(&relative_symlink1_path)?, "File content"); - Ok(()) - } - - #[test] - fn relative_symlink_to_nonexistent_dir() -> anyhow::Result<()> { - // tmp -- dir1 (doesn't exists) -- file1 (doesn't exist) - // \ - // \ dir2 -- relative_symlink1 to ../dir1/file1 - let tempdir = tempfile::tempdir()?; - let dir1 = tempdir.path().join("dir1"); - let dir2 = tempdir.path().join("dir2"); - // Only create dir2 for the symlink creation - create_dir_all(&dir2)?; - let relative_symlink1_path = dir2.as_path().join("relative_symlink1"); - // Symlink creation should still work even if dir1 doesn't exist yet - symlink("../dir1/file1", &relative_symlink1_path)?; - // Create dir1, test that we can write into file1 and symlink1 - create_dir_all(dir1)?; - write(tempdir.path().join("dir1/file1"), b"File content")?; - assert_eq!(read_to_string(&relative_symlink1_path)?, "File content"); - write(&relative_symlink1_path, b"File content 2")?; - assert_eq!( - read_to_string(tempdir.path().join("dir1/file1"))?, - "File content 2" - ); - Ok(()) - } - - #[cfg(windows)] - #[test] - fn relative_symlink_from_symlinked_dir_windows() -> anyhow::Result<()> { - use crate::fs_util::read_link; - - // tmp1 -- dir1 -- subdir1 -- file1 - // tmp2 -- symlink_to_subdir1 (points to /tmp1/dir1/subdir1) -- (file1) - // (points to /tmp1/dir1/subdir1/file1) \ - // \-- symlink_to_file2 (want to symlink to ../dir2/file2, which is actually - // /tmp2/dir2/file2) \ - // \-- symlink_to_file3 (want to symlink to ../dir2/file3, which is actually - // /tmp2/dir2/file3) \ - // \-- symlink_to_symlink1 (want to symlink to file1, which is actually - // /tmp/2/dir1/subdir1/file1) \ dir2 -- file2 - // \-- file3 (doesn't exist yet) - - // Construct the directory structure - let tempdir1 = tempfile::tempdir()?; - let tempdir2 = tempfile::tempdir()?; - let dir1 = tempdir1.path().join("dir1"); - let subdir1 = dir1.join("subdir1"); - let file1 = subdir1.join("file1"); - let dir2 = tempdir2.path().join("dir2"); - let file2 = dir2.join("file2"); - let file3 = dir2.join("file3"); - create_dir_all(&subdir1)?; - create_dir_all(&dir2)?; - write(&file1, b"File content 1")?; - write(&file2, b"File content 2")?; - - // Simple symlink to a directory - let symlink_to_subdir1 = tempdir2.path().join("symlink_to_subdir1"); - symlink(&subdir1, &symlink_to_subdir1)?; - assert_eq!( - read_link(&symlink_to_subdir1)?.canonicalize()?, - subdir1.canonicalize()? - ); - assert_eq!( - read_to_string(symlink_to_subdir1.join("file1"))?, - "File content 1" - ); - - // Test1: A relative symlink that needs to get converted to the realpath - // correctly /tmp2/symlink_to_subdir1/symlink_to_file2 would live in - // /tmp1/dir1/subdir1/file2, which means the relative symlink is incorrect - // Test that symlink properly converts to canonicalized target path - let symlink_to_file2 = symlink_to_subdir1.join("symlink_to_file2"); - symlink("../dir2/file2", &symlink_to_file2)?; - assert_eq!( - read_link(&symlink_to_file2)?.canonicalize()?, - file2.canonicalize()? - ); - assert_eq!(read_to_string(symlink_to_file2)?, "File content 2"); - - // Test2: Same case as test1, but target file doesn't exist yet - let symlink_to_file3 = symlink_to_subdir1.join("symlink_to_file3"); - symlink("../dir2/file3", &symlink_to_file3)?; - write(&file3, b"File content 3")?; - assert_eq!( - read_link(&symlink_to_file3)?.canonicalize()?, - file3.canonicalize()? - ); - assert_eq!(read_to_string(&file3)?, "File content 3"); - assert_eq!(read_to_string(symlink_to_file3)?, "File content 3"); - - // Test3: Create a symlink from a symlinked directory to another symlink in the - // same directory - let symlink_to_symlink1 = symlink_to_subdir1.join("symlink_to_symlink1"); - symlink("../symlink_to_subdir1/file1", &symlink_to_symlink1)?; - assert_eq!( - read_link(&symlink_to_symlink1)?.canonicalize()?, - file1.canonicalize()? - ); - assert_eq!(read_to_string(symlink_to_symlink1)?, "File content 1"); - Ok(()) - } - - #[test] - fn absolute_symlink_to_nonexistent_file_in_nonexistent_dir() -> anyhow::Result<()> { - // tmp -- dir1 (doesn't exists) -- file1 (doesn't exist) - // \ - // \ symlink1 to /tmp/dir1/file1 - let tempdir = tempfile::tempdir()?; - let abs_symlink1_path = tempdir.path().join("abs_symlink1"); - let target_abs_path = tempdir.path().join("dir1/file1"); - symlink(&target_abs_path, &abs_symlink1_path)?; - create_dir_all(tempdir.path().join("dir1"))?; - write(&target_abs_path, b"File content")?; - assert_eq!(read_to_string(&abs_symlink1_path)?, "File content"); - Ok(()) - } - - #[test] - fn remove_file_removes_symlink_to_directory() -> anyhow::Result<()> { - let tempdir = tempfile::tempdir()?; - let symlink_path = tempdir.path().join("symlink_dir"); - let dir_path = tempdir.path().join("dir"); - let file_path = dir_path.join("file"); - create_dir_all(&dir_path)?; - write(file_path, b"File content")?; - symlink(&dir_path, &symlink_path)?; - let symlinked_path = symlink_path.join("file"); - assert_eq!(read_to_string(symlinked_path)?, "File content"); - remove_file(&symlink_path)?; - assert_eq!( - io::ErrorKind::NotFound, - fs::metadata(&symlink_path).unwrap_err().kind() - ); - Ok(()) - } - - #[test] - fn remove_file_does_not_remove_directory() -> anyhow::Result<()> { - let tempdir = tempfile::tempdir()?; - let dir_path = tempdir.path().join("dir"); - create_dir_all(&dir_path)?; - assert_matches!(remove_file(&dir_path), Err(..)); - assert!(fs::try_exists(&dir_path)?); - Ok(()) - } - - #[test] - fn remove_file_broken_symlink() -> anyhow::Result<()> { - let tempdir = tempfile::tempdir()?; - let symlink_path = tempdir.path().join("symlink"); - symlink("path_which_doesnt_exist", &symlink_path)?; - remove_file(&symlink_path)?; - assert_eq!( - io::ErrorKind::NotFound, - fs::symlink_metadata(&symlink_path).unwrap_err().kind() - ); - Ok(()) - } - - #[test] - fn remove_file_non_existing_file() -> anyhow::Result<()> { - let tempdir = tempfile::tempdir()?; - let file_path = tempdir.path().join("file_doesnt_exist"); - assert_matches!(remove_file(file_path), Err(..)); - Ok(()) - } - - #[test] - fn remove_all_nonexistent() -> anyhow::Result<()> { - let tempdir = tempfile::tempdir()?; - remove_all(tempdir.path().join("nonexistent"))?; - Ok(()) - } - - #[test] - fn remove_all_regular() -> anyhow::Result<()> { - let tempdir = tempfile::tempdir()?; - let path = tempdir.path().join("file"); - fs::write(&path, b"regular")?; - remove_all(&path)?; - assert!(!fs::try_exists(&path)?); - Ok(()) - } - - #[test] - fn remove_all_dir() -> anyhow::Result<()> { - let tempdir = tempfile::tempdir()?; - let path = tempdir.path().join("dir"); - fs::create_dir(&path)?; - fs::write(path.join("file"), b"regular file in a dir")?; - remove_all(&path)?; - assert!(!fs::try_exists(&path)?); - Ok(()) - } - - #[test] - fn remove_all_broken_symlink() -> anyhow::Result<()> { - fn ls(path: &Path) -> anyhow::Result> { - let mut entries = fs::read_dir(path)? - .map(|entry| Ok(entry.map(|entry| entry.path())?)) - .collect::>>()?; - entries.sort(); - Ok(entries) - } - - let tempdir = tempfile::tempdir()?; - let target = tempdir.path().join("non-existent-target"); - let path = tempdir.path().join("symlink"); - symlink(target, &path)?; - - assert_eq!(vec![path.clone()], ls(tempdir.path())?, "Sanity check"); - - remove_all(&path)?; - - // We cannot use `exists` here because it does not report what we need on broken - // symlink. - assert_eq!(Vec::::new(), ls(tempdir.path())?); - - Ok(()) - } - - #[test] - fn remove_dir_all_does_not_remove_file() -> anyhow::Result<()> { - let tempdir = tempfile::tempdir()?; - let file_path = tempdir.path().join("file"); - fs::write(&file_path, b"File content")?; - assert!(remove_dir_all(&file_path).is_err()); - assert!(fs::try_exists(&file_path)?); - Ok(()) - } - - #[test] - fn create_dir_if_not_exists() { - let tempdir = tempfile::tempdir().unwrap(); - fs_util::create_dir_if_not_exists(tempdir.path().join("dir1")).unwrap(); - assert!(fs_util::symlink_metadata(tempdir.path().join("dir1")) - .unwrap() - .is_dir()); - fs_util::create_dir_if_not_exists(tempdir.path().join("dir1")).unwrap(); - assert!(fs_util::symlink_metadata(tempdir.path().join("dir1")) - .unwrap() - .is_dir()); - - assert!(fs_util::create_dir_if_not_exists(tempdir.path().join("dir2/file")).is_err()); - assert!(!fs_util::try_exists(tempdir.path().join("dir2")).unwrap()); - - fs_util::write(tempdir.path().join("file"), b"rrr").unwrap(); - assert!(fs_util::create_dir_if_not_exists(tempdir.path().join("file")).is_err()); - } - - #[test] - fn test_read_to_string_opt() -> anyhow::Result<()> { - let tempdir = tempfile::tempdir()?; - let f1 = tempdir.path().join("f1"); - let f2 = tempdir.path().join("f2"); - - fs_util::write(&f1, b"data")?; - assert_eq!(fs_util::read_to_string_opt(&f1)?.as_deref(), Some("data")); - assert_eq!(fs_util::read_to_string_opt(f2)?, None); - - Ok(()) - } - - #[cfg(windows)] - #[test] - fn test_windows_relative_path() -> anyhow::Result<()> { - assert_eq!( - fs_util::relative_path_from_system(Path::new("foo\\bar"))?, - RelativePath::new("foo/bar") - ); - Ok(()) - } - - #[cfg(unix)] - #[test] - fn test_relative_path() -> anyhow::Result<()> { - assert_eq!( - fs_util::relative_path_from_system(Path::new("foo/bar"))?, - RelativePath::new("foo/bar") - ); - Ok(()) - } -} diff --git a/crates/turborepo-paths/src/into_filename_buf_iterator.rs b/crates/turborepo-paths/src/into_filename_buf_iterator.rs deleted file mode 100644 index c988e488d8f8f..0000000000000 --- a/crates/turborepo-paths/src/into_filename_buf_iterator.rs +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under both the MIT license found in the - * LICENSE-MIT file in the root directory of this source tree and the Apache - * License, Version 2.0 found in the LICENSE-APACHE file in the root - * directory of this source tree. - */ - -use gazebo::prelude::IterOwned; - -use crate::{ - file_name::{FileName, FileNameBuf}, - forward_relative_path::{ForwardRelativePath, ForwardRelativePathBuf}, -}; - -/// Provide an iterator of FileNameBuf from inputs that can produce one. This is -/// useful for methods that insert into directory mappings. -pub trait IntoFileNameBufIterator { - type Iterator: Iterator; - - fn into_iter(self) -> Self::Iterator; -} - -impl<'a> IntoFileNameBufIterator for &'a ForwardRelativePath { - type Iterator = impl Iterator + 'a; - - fn into_iter(self) -> Self::Iterator { - self.iter().owned() - } -} - -impl<'a> IntoFileNameBufIterator for &'a ForwardRelativePathBuf { - type Iterator = impl Iterator + 'a; - - fn into_iter(self) -> Self::Iterator { - self.iter().owned() - } -} - -impl<'a> IntoFileNameBufIterator for &'a FileName { - type Iterator = impl Iterator; - - fn into_iter(self) -> Self::Iterator { - std::iter::once(self.to_owned()) - } -} - -impl<'a> IntoFileNameBufIterator for &'a FileNameBuf { - type Iterator = impl Iterator; - - fn into_iter(self) -> Self::Iterator { - std::iter::once(self.clone()) - } -} - -impl IntoFileNameBufIterator for FileNameBuf { - type Iterator = impl Iterator; - - fn into_iter(self) -> Self::Iterator { - std::iter::once(self) - } -} diff --git a/crates/turborepo-paths/src/io_counters.rs b/crates/turborepo-paths/src/io_counters.rs deleted file mode 100644 index e55b62de17ec8..0000000000000 --- a/crates/turborepo-paths/src/io_counters.rs +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under both the MIT license found in the - * LICENSE-MIT file in the root directory of this source tree and the Apache - * License, Version 2.0 found in the LICENSE-APACHE file in the root - * directory of this source tree. - */ - -use std::sync::atomic::{AtomicU32, Ordering}; - -use gazebo::prelude::Dupe; - -#[derive(Copy, Clone, Dupe, Debug)] -pub enum IoCounterKey { - Copy, - Symlink, - Hardlink, - MkDir, - ReadDir, - ReadDirEden, - RmDir, - RmDirAll, - Stat, - StatEden, - Chmod, - ReadLink, - Remove, - Rename, - Read, - Write, - Canonicalize, - EdenSettle, -} - -static IN_PROGRESS: [AtomicU32; IoCounterKey::COUNT] = [ - AtomicU32::new(0), - AtomicU32::new(0), - AtomicU32::new(0), - AtomicU32::new(0), - AtomicU32::new(0), - AtomicU32::new(0), - AtomicU32::new(0), - AtomicU32::new(0), - AtomicU32::new(0), - AtomicU32::new(0), - AtomicU32::new(0), - AtomicU32::new(0), - AtomicU32::new(0), - AtomicU32::new(0), - AtomicU32::new(0), - AtomicU32::new(0), - AtomicU32::new(0), - AtomicU32::new(0), -]; - -impl IoCounterKey { - pub const ALL: &'static [IoCounterKey] = &[ - IoCounterKey::Copy, - IoCounterKey::Symlink, - IoCounterKey::Hardlink, - IoCounterKey::MkDir, - IoCounterKey::ReadDir, - IoCounterKey::ReadDirEden, - IoCounterKey::RmDir, - IoCounterKey::RmDirAll, - IoCounterKey::Stat, - IoCounterKey::StatEden, - IoCounterKey::Chmod, - IoCounterKey::ReadLink, - IoCounterKey::Remove, - IoCounterKey::Rename, - IoCounterKey::Read, - IoCounterKey::Write, - IoCounterKey::Canonicalize, - IoCounterKey::EdenSettle, - ]; - - const COUNT: usize = IoCounterKey::ALL.len(); - - #[allow(dead_code)] - pub fn get(&self) -> u32 { - IN_PROGRESS[*self as usize].load(Ordering::Relaxed) - } - - pub fn guard(&self) -> IoCounterGuard { - IN_PROGRESS[*self as usize].fetch_add(1, Ordering::Relaxed); - IoCounterGuard(*self) - } -} - -#[must_use] -pub struct IoCounterGuard(IoCounterKey); - -impl Drop for IoCounterGuard { - fn drop(&mut self) { - IN_PROGRESS[self.0 as usize].fetch_sub(1, Ordering::Relaxed); - } -} - -#[cfg(test)] -mod tests { - use crate::io_counters::IoCounterKey; - - #[test] - fn test_keys() { - for k in IoCounterKey::ALL { - // Check `IN_PROGRESS` is correct size. - k.get(); - } - } -} diff --git a/crates/turborepo-paths/src/lib.rs b/crates/turborepo-paths/src/lib.rs deleted file mode 100644 index 748c2505a3f94..0000000000000 --- a/crates/turborepo-paths/src/lib.rs +++ /dev/null @@ -1,136 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under both the MIT license found in the - * LICENSE-MIT file in the root directory of this source tree and the Apache - * License, Version 2.0 found in the LICENSE-APACHE file in the root - * directory of this source tree. - */ - -//! -//! The paths crate for turborepo. Adapted from buck2's paths module. -//! -//! Changes from buck2: -//! - Removed abbreviations such as `AbsPath` -> `AbsolutePath` to fit with the -//! turbo codebase's conventions -//! -//! Introduces 'ForwardRelativePath', 'ForwardRelativePathBuf', 'AbsolutePath', -//! and 'AbsolutePathBuf', which are equivalents of 'Path' and 'PathBuf'. -//! -//! ForwardRelativePaths are fully normalized relative platform agnostic paths -//! that only points forward. This means that there is no `.` or `..` in this -//! path, and does not begin with `/`. These are resolved to the 'PathBuf' by -//! resolving them against an 'AbsPath'. -//! -//! 'AbsPath' are absolute paths, meaning they must start with a directory root -//! of either `/` or some windows root directory like `c:`. These behave -//! roughly like 'Path'. - -#![feature(type_alias_impl_trait)] -#![feature(fs_try_exists)] -#![feature(absolute_path)] - -pub mod absolute_normalized_path; -pub mod absolute_path; -mod cmp_impls; -pub mod file_name; -pub(crate) mod fmt; -pub mod forward_relative_path; -pub mod fs_util; -mod into_filename_buf_iterator; -mod io_counters; -pub mod project; -pub mod project_relative_path; - -pub use absolute_normalized_path::{AbsoluteNormalizedPath, AbsoluteNormalizedPathBuf}; -pub use absolute_path::{AbsolutePath, AbsolutePathBuf}; -pub use forward_relative_path::{ForwardRelativePath, ForwardRelativePathBuf}; -pub use into_filename_buf_iterator::*; -pub use project_relative_path::{ProjectRelativePath, ProjectRelativePathBuf}; -pub use relative_path::{RelativePath, RelativePathBuf}; - -#[cfg(test)] -mod tests { - use std::collections::HashMap; - - use crate::{ - absolute_normalized_path::{AbsoluteNormalizedPath, AbsoluteNormalizedPathBuf}, - forward_relative_path::{ForwardRelativePath, ForwardRelativePathBuf}, - project_relative_path::ProjectRelativePath, - }; - - #[test] - fn wrapped_paths_work_in_maps() -> anyhow::Result<()> { - let mut map = HashMap::new(); - - let p1 = ForwardRelativePath::new("foo")?; - let p2 = ProjectRelativePath::new("bar")?; - - map.insert(p1.to_buf(), p2.to_buf()); - - assert_eq!(Some(p2), map.get(p1).map(|p| p.as_ref())); - - Ok(()) - } - - #[test] - fn path_buf_is_clonable() -> anyhow::Result<()> { - let buf = ForwardRelativePathBuf::unchecked_new("foo".into()); - let buf_ref = &buf; - - let cloned: ForwardRelativePathBuf = buf_ref.clone(); - assert_eq!(buf, cloned); - - Ok(()) - } - - #[test] - fn relative_path_display_is_readable() -> anyhow::Result<()> { - let buf = ForwardRelativePathBuf::unchecked_new("foo/bar".into()); - assert_eq!("foo/bar", format!("{}", buf)); - assert_eq!("ForwardRelativePathBuf(\"foo/bar\")", format!("{:?}", buf)); - let refpath: &ForwardRelativePath = &buf; - assert_eq!("foo/bar", format!("{}", refpath)); - assert_eq!("ForwardRelativePath(\"foo/bar\")", format!("{:?}", refpath)); - - Ok(()) - } - - #[cfg(not(windows))] - #[test] - fn absolute_path_display_is_readable() -> anyhow::Result<()> { - let buf = AbsoluteNormalizedPathBuf::from("/foo/bar".into())?; - assert_eq!("/foo/bar", format!("{}", buf)); - assert_eq!( - "AbsoluteNormalizedPathBuf(\"/foo/bar\")", - format!("{:?}", buf) - ); - let refpath: &AbsoluteNormalizedPath = &buf; - assert_eq!("/foo/bar", format!("{}", refpath)); - assert_eq!( - "AbsoluteNormalizedPath(\"/foo/bar\")", - format!("{:?}", refpath) - ); - - Ok(()) - } - - #[cfg(windows)] - #[test] - fn absolute_path_display_is_readable() -> anyhow::Result<()> { - let buf = AbsoluteNormalizedPathBuf::from("C:/foo/bar".into())?; - assert_eq!("C:/foo/bar", format!("{}", buf)); - assert_eq!( - "AbsoluteNormalizedPathBuf(\"C:/foo/bar\")", - format!("{:?}", buf) - ); - let refpath: &AbsoluteNormalizedPath = &buf; - assert_eq!("C:/foo/bar", format!("{}", refpath)); - assert_eq!( - "AbsoluteNormalizedPath(\"C:/foo/bar\")", - format!("{:?}", refpath) - ); - - Ok(()) - } -} diff --git a/crates/turborepo-paths/src/project.rs b/crates/turborepo-paths/src/project.rs deleted file mode 100644 index c850e002e689b..0000000000000 --- a/crates/turborepo-paths/src/project.rs +++ /dev/null @@ -1,1051 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under both the MIT license found in the - * LICENSE-MIT file in the root directory of this source tree and the Apache - * License, Version 2.0 found in the LICENSE-APACHE file in the root - * directory of this source tree. - */ - -use std::{ - borrow::Cow, - fs::File, - path::{Component, Path, PathBuf}, - sync::Arc, -}; - -use anyhow::Context; -use gazebo::prelude::Dupe; -use ref_cast::RefCast; -use relative_path::RelativePathBuf; - -use crate::{ - absolute_normalized_path::{AbsoluteNormalizedPath, AbsoluteNormalizedPathBuf}, - forward_relative_path::ForwardRelativePath, - fs_util, RelativePath, -}; - -#[derive(Debug, thiserror::Error)] -enum ProjectRootError { - #[error("Provided project root `{0}` is not equal to the canonicalized path `{1}`")] - NotCanonical(AbsoluteNormalizedPathBuf, AbsoluteNormalizedPathBuf), - #[error("Project root `{0}` not found in path `{1}`")] - ProjectRootNotFound(ProjectRoot, AbsolutePathBuf), -} - -/// The 'ProjectFilesystem' that contains the root path and the current working -/// directory (cwd). The root path is the project root as defined in this -/// library. The cwd will be the directory from which the command was invoked, -/// which is within the project root and hence relativized against it. -#[derive(Clone, Debug, Dupe, PartialEq, derive_more::Display)] -#[display(fmt = "{root}")] -pub struct ProjectRoot { - root: Arc, -} - -pub struct ProjectRootTemp { - path: ProjectRoot, - // Important field as we want to keep this alive while the path is in use - _temp: tempfile::TempDir, -} - -impl ProjectRootTemp { - /// creates a filesystem at a temporary root where the cwd is set to the - /// same root - pub fn new() -> anyhow::Result { - let temp = tempfile::tempdir()?; - let path = fs_util::canonicalize(temp.path())?; - let path = ProjectRoot::new(path)?; - Ok(Self { path, _temp: temp }) - } - - pub fn path(&self) -> &ProjectRoot { - &self.path - } - - pub fn write_file(&self, path: &str, content: &str) { - let path = ProjectRelativePath::new(path).unwrap(); - self.path().write_file(path, content, false).unwrap(); - } -} - -impl ProjectRoot { - pub fn new(root: AbsoluteNormalizedPathBuf) -> anyhow::Result { - let canon = fs_util::canonicalize(&root).context("canonicalize project root")?; - if canon != root { - return Err(ProjectRootError::NotCanonical(root, canon).into()); - } - Ok(ProjectRoot { - // We store the canonicalized path here because even if path - // is equal to the canonicalized path, it may differ in the slashes or the case. - root: Arc::new(canon), - }) - } - - pub fn new_unchecked(root: AbsoluteNormalizedPathBuf) -> ProjectRoot { - ProjectRoot { - root: Arc::new(root), - } - } - - pub fn root(&self) -> &AbsoluteNormalizedPath { - &self.root - } - - /// - /// Takes a 'ProjectRelativePath' and resolves it against the current - /// `project root`, yielding a 'AbsPathBuf' - /// - /// ``` - /// use turborepo_paths::project::ProjectRoot; - /// use turborepo_paths::project_relative_path::ProjectRelativePath; - /// use turborepo_paths::absolute_normalized_path::AbsoluteNormalizedPathBuf; - /// - /// if cfg!(not(windows)) { - /// let root = AbsoluteNormalizedPathBuf::from("/usr/local/vercel/".into())?; - /// let fs = ProjectRoot::new_unchecked(root); - /// - /// assert_eq!( - /// AbsoluteNormalizedPathBuf::from("/usr/local/vercel/turbo/turbo.json".into())?, - /// fs.resolve(ProjectRelativePath::new("turbo/turbo.json")?) - /// ); - /// } else { - /// let root = AbsoluteNormalizedPathBuf::from("c:/open/vercel/".into())?; - /// let fs = ProjectRoot::new_unchecked(root); - /// - /// assert_eq!( - /// AbsoluteNormalizedPathBuf::from("c:/open/vercel/turbo/turbo.json".into())?, - /// fs.resolve(ProjectRelativePath::new("turbo/turbo.json")?) - /// ); - /// } - /// # anyhow::Ok(()) - /// ``` - pub fn resolve(&self, path: impl PathLike) -> AbsoluteNormalizedPathBuf { - path.resolve(self).into_owned() - } - - /// - /// Takes a 'ProjectRelativePath' and converts it to a 'Path' that is - /// relative to the project root. - /// - /// ``` - /// use turborepo_paths::project::{ProjectRoot}; - /// use turborepo_paths::absolute_normalized_path::AbsoluteNormalizedPathBuf; - /// use std::path::PathBuf; - /// use turborepo_paths::project_relative_path::ProjectRelativePath; - /// - /// let root = if cfg!(not(windows)) { - /// AbsoluteNormalizedPathBuf::from("/usr/local/vercel/".into())? - /// } else { - /// AbsoluteNormalizedPathBuf::from("c:/open/vercel/".into())? - /// }; - /// let fs = ProjectRoot::new_unchecked(root); - /// - /// assert_eq!( - /// PathBuf::from("turbo/turbo.json"), - /// fs.as_relative_path(ProjectRelativePath::new("turbo/turbo.json")?) - /// ); - /// - /// # anyhow::Ok(()) - /// ``` - pub fn as_relative_path>(&self, path: P) -> PathBuf { - let rel: &RelativePath = (path.as_ref().0).as_ref(); - PathBuf::from(rel.as_str()) - } - - /// - /// Given an 'AbsPath', attempts to relativize the 'AbsPath' against the - /// `project root` by stripping the prefix of the given paths. - /// - /// Errors if the given path is not a sub directory of the root. - /// - /// ``` - /// use std::borrow::Cow; - /// use turborepo_paths::project_relative_path::ProjectRelativePath; - /// use turborepo_paths::absolute_normalized_path::{AbsoluteNormalizedPathBuf, AbsoluteNormalizedPath}; - /// use turborepo_paths::project::ProjectRoot; - /// - /// if cfg!(not(windows)) { - /// let root = AbsoluteNormalizedPathBuf::from("/usr/local/vercel/".into())?; - /// let fs = ProjectRoot::new_unchecked(root); - /// - /// assert_eq!( - /// Cow::Borrowed(ProjectRelativePath::new("src/turbo.js")?), - /// fs.relativize(AbsoluteNormalizedPath::new("/usr/local/vercel/src/turbo.js")?)? - /// ); - /// assert!(fs.relativize(AbsoluteNormalizedPath::new("/other/path")?).is_err()); - /// } else { - /// let root = AbsoluteNormalizedPathBuf::from("c:/open/vercel/".into())?; - /// let fs = ProjectRoot::new_unchecked(root); - /// - /// assert_eq!( - /// Cow::Borrowed(ProjectRelativePath::new("src/turbo.js")?), - /// fs.relativize(AbsoluteNormalizedPath::new("c:/open/vercel/src/turbo.js")?)? - /// ); - /// assert_eq!( - /// Cow::Borrowed(ProjectRelativePath::new("src/turbo.js")?), - /// fs.relativize(AbsoluteNormalizedPath::new(r"C:\open\vercel\src\turbo.js")?)? - /// ); - /// assert_eq!( - /// Cow::Borrowed(ProjectRelativePath::new("src/turbo.js")?), - /// fs.relativize(AbsoluteNormalizedPath::new(r"\\?\C:\open\vercel\src\turbo.js")?)? - /// ); - /// assert!(fs.relativize(AbsoluteNormalizedPath::new("c:/other/path")?).is_err()); - /// } - /// - /// # anyhow::Ok(()) - /// ``` - pub fn relativize<'a, P: ?Sized + AsRef>( - &self, - p: &'a P, - ) -> anyhow::Result> { - let relative_path = p.as_ref().strip_prefix(self.root()).map_err(|_| { - anyhow::anyhow!( - "Error relativizing: `{}` is not relative to project root `{}`", - p.as_ref(), - self.root() - ) - })?; - match relative_path { - Cow::Borrowed(p) => Ok(Cow::Borrowed(ProjectRelativePath::ref_cast(p))), - Cow::Owned(p) => Ok(Cow::Owned(ProjectRelativePathBuf::from(p))), - } - } - - /// Remove project root prefix from path (even if path is not canonical) - /// and return the remaining path. - /// - /// Fail if canonicalized path does not start with project root. - fn strip_project_root<'a>(&'a self, path: &'a AbsolutePath) -> anyhow::Result { - let path = fs_util::simplified(path)?; - - if let Ok(rem) = Path::strip_prefix(path, &*self.root) { - // Fast code path. - return Ok(rem.to_path_buf()); - } - - // Now try to canonicalize the path. We cannot call `canonicalize` on the full - // path because we should only resolve symlinks found in the past that - // point into the project, but - // * not symlink found inside the project that point outside of it - // * not even symlinks found in the project unless we need to to resolve ".." - - let mut current_prefix = PathBuf::new(); - - let mut components = path.components(); - while let Some(comp) = components.next() { - current_prefix.push(comp); - - // This is not very efficient, but efficient cross-platform implementation is - // not easy. - let canonicalized_current_prefix = fs_util::canonicalize(¤t_prefix)?; - - if let Ok(rem) = canonicalized_current_prefix - .as_path() - .strip_prefix(self.root.as_path()) - { - // We found the project root. - return Ok(rem.join(components.as_path())); - } - } - - Err(ProjectRootError::ProjectRootNotFound(self.dupe(), path.to_owned()).into()) - } - - fn relativize_any_impl(&self, path: &AbsolutePath) -> anyhow::Result { - let project_relative = self.strip_project_root(path)?; - // TODO(nga): this does not treat `..` correctly. - // See the test below for an example. - // This must use `RelativePathBuf`, not `RelativePath`, - // because `RelativePathBuf` handles backslashes on Windows, and `RelativePath` - // does not. - ProjectRelativePath::empty().join_normalized(RelativePathBuf::from_path(project_relative)?) - } - - /// Relativize an absolute path which may be not normalized or not - /// canonicalize. This operation may involve disk access. - pub fn relativize_any>( - &self, - path: P, - ) -> anyhow::Result { - let path = path.as_ref(); - self.relativize_any_impl(path.as_ref()).with_context(|| { - format!( - "relativize path `{}` against project root `{}`", - path.display(), - self - ) - }) - } - - // TODO(nga): refactor this to global function. - pub fn write_file( - &self, - path: impl PathLike, - contents: impl AsRef<[u8]>, - executable: bool, - ) -> anyhow::Result<()> { - let abs_path = path.resolve(self); - if let Some(parent) = abs_path.parent() { - fs_util::create_dir_all(parent).with_context(|| { - format!( - "`write_file` for `{}` creating directory `{}`", - abs_path.as_ref(), - parent - ) - })?; - } - fs_util::write(abs_path.as_ref(), contents) - .with_context(|| format!("`write_file` writing `{}`", abs_path.as_ref()))?; - if executable { - self.set_executable(abs_path.as_ref()).with_context(|| { - format!("`write_file` setting executable `{}`", abs_path.as_ref()) - })?; - } - Ok(()) - } - - // TODO(nga): refactor this to global function. - pub fn create_file(&self, path: impl PathLike, executable: bool) -> anyhow::Result { - let abs_path = path.resolve(self); - if let Some(parent) = abs_path.parent() { - fs_util::create_dir_all(parent).with_context(|| { - format!( - "`create_file` for `{}` creating directory `{}`", - abs_path.as_ref(), - parent - ) - })?; - } - let file = File::create(abs_path.as_ref()) - .with_context(|| format!("`create_file` creating `{}`", abs_path.as_ref()))?; - if executable { - self.set_executable(abs_path.as_ref()).with_context(|| { - format!("`create_file` setting executable `{}`", abs_path.as_ref()) - })?; - } - Ok(file) - } - - // TODO(nga): refactor this to global function. - #[cfg(unix)] - pub fn set_executable(&self, path: impl PathLike) -> anyhow::Result<()> { - use std::os::unix::fs::PermissionsExt; - // Unix permission bits - let mut perms = fs_util::metadata(path.resolve(self).as_ref())?.permissions(); - // Add ugo+x - perms.set_mode(perms.mode() | 0o111); - fs_util::set_permissions(path.resolve(self).as_ref(), perms)?; - Ok(()) - } - - // TODO(nga): refactor this to global function. - #[cfg(not(unix))] - pub fn set_executable(&self, _path: impl PathLike) -> anyhow::Result<()> { - // Nothing to do - Ok(()) - } - - /// Create a soft link from one location to another. - /// - /// There is no requirement that `src` must exist, - /// and `src` can be either an absolute or relative path. - /// - /// This function is "raw" in the sense that it passes the `src` - /// directly to underlying fs function calls. We do not verify - /// anything about the incoming path. Other functions generally - /// require things like "is a project relative path", etc. - /// - /// Filesystems that do not support soft links will return `Err`. - // TODO(nga): refactor this to global function. - pub fn soft_link_raw(&self, src: impl AsRef, dest: impl PathLike) -> anyhow::Result<()> { - let dest_abs = self.resolve(dest); - - if let Some(parent) = dest_abs.parent() { - fs_util::create_dir_all(parent)?; - } - fs_util::symlink(src, dest_abs) - } - - /// Create a relative symlink between two relative paths - /// - /// This changes the path that `dest` is linked to, to the - /// relative path to get to `src` from `dest`. Useful when - /// one wants to link together, e.g. to `ProjectRelativePath`s - /// - /// e.g. given a `src` of `foo/bar1/baz1/out` and `dest` of - /// `foo/bar2/baz2/out`, `readlink` on `dest` would yield - /// `../../bar1/baz1/out` - /// - /// `src`: Relative path that does not need to exist - /// `dest`: Relative path that will be linked to `src` - /// using the relative traversal between the two - /// - /// Errors if the link could not be created (generally due to FS support of - /// symlinks) - // TODO(nga): refactor this to global function. - pub fn soft_link_relativized( - &self, - src: impl PathLike, - dest: impl PathLike, - ) -> anyhow::Result<()> { - let target_abs = self.resolve(src); - let dest_abs = self.resolve(dest); - - let target_relative = Self::find_relative_path(&target_abs, &dest_abs); - if let Some(parent) = dest_abs.parent() { - fs_util::create_dir_all(parent)?; - } - fs_util::symlink(target_relative, dest_abs) - } - - /// Copy from one path to another. This works for both files and - /// directories. - /// - /// This copy works by: - /// - Copying directories recursively - /// - Re-writing relative symlinks. That is, a link to `foo/bar` might end - /// up as `../../../other/foo/bar` in the destination. Absolute symlinks - /// are not changed. - // TODO(nga): refactor this to global function. - pub fn copy(&self, src: impl PathLike, dest: impl PathLike) -> anyhow::Result<()> { - let src_abs = self.resolve(src); - let dest_abs = self.resolve(dest); - - let result = self.copy_resolved(&src_abs, &dest_abs); - result.with_context(|| { - format!( - "When copying from src path `{}` to dest path `{}`", - src_abs, dest_abs - ) - }) - } - - fn copy_resolved( - &self, - src_abs: &AbsoluteNormalizedPathBuf, - dest_abs: &AbsoluteNormalizedPathBuf, - ) -> anyhow::Result<()> { - let src_type = fs_util::symlink_metadata(src_abs)?.file_type(); - - if let Some(parent) = dest_abs.parent() { - fs_util::create_dir_all(parent)?; - } - if src_type.is_dir() { - Self::copy_dir(src_abs, dest_abs) - } else if src_type.is_file() { - Self::copy_file(src_abs, dest_abs) - } else if src_type.is_symlink() { - Self::copy_symlink(src_abs, dest_abs) - } else { - // If we want to handle special files, we'll need to use special traits - // https://doc.rust-lang.org/std/os/unix/fs/trait.FileTypeExt.html - Err(anyhow::anyhow!( - "Attempted to copy a path ({}) of an unknown type", - src_abs - )) - } - } - - /// Remove a path recursively, regardless of it being a file or a directory - /// (all contents deleted). - /// This does not follow symlinks, and only removes the link itself. - // TODO(nga): refactor this to global function. - pub fn remove_path_recursive(&self, path: impl PathLike) -> anyhow::Result<()> { - let path = self.resolve(path); - if !path.exists() { - return Ok(()); - } - let path_type = fs_util::symlink_metadata(&path)?.file_type(); - - if path_type.is_dir() { - fs_util::remove_dir_all(&path) - .with_context(|| format!("remove_path_recursive({}) on directory", &path))?; - } else if path_type.is_file() || path_type.is_symlink() { - fs_util::remove_file(&path) - .with_context(|| format!("remove_path_recursive({}) on file", &path))?; - } else { - // If we want to handle special files, we'll need to use special traits - // https://doc.rust-lang.org/std/os/unix/fs/trait.FileTypeExt.html - return Err(anyhow::anyhow!( - "remove_path_recursive, attempted to delete a path ({}) of an unknown type", - path - )); - } - - Ok(()) - } - - /// Find the relative path between two paths within the project - pub fn relative_path(&self, target: impl PathLike, dest: impl PathLike) -> PathBuf { - Self::find_relative_path(&self.resolve(target), &self.resolve(dest)) - } - - /// Find the relative path between two absolute ones - /// - /// Given two absolute paths, get the relative path from `dest` to `target`. - /// - /// e.g. given a `target` of `/foo/bar1/baz1/out` and `dest` of - /// `/foo/bar2/baz2/out`, the result would be `../../bar1/baz1/out` - fn find_relative_path( - target: &AbsoluteNormalizedPathBuf, - dest: &AbsoluteNormalizedPathBuf, - ) -> PathBuf { - use itertools::{EitherOrBoth::*, Itertools}; - // Assemble both the '../' traversal, and the component that will come after - // that - let mut upward_traversal = PathBuf::new(); - let mut relative_to_common_path = PathBuf::new(); - // So that /foo/bar/quz and /baz/bar/quz don't look like the same path - // in the second component - let mut diverged = false; - - for component in target - .iter() - .zip_longest(dest.parent().expect("a path with a parent in dest").iter()) - { - match component { - Both(t, d) => { - if diverged || t != d { - diverged = true; - upward_traversal.push(Component::ParentDir); - relative_to_common_path.push(t); - } - } - Left(t) => { - diverged = true; - relative_to_common_path.push(t) - } - Right(_) => { - diverged = true; - upward_traversal.push(Component::ParentDir) - } - } - } - upward_traversal.push(relative_to_common_path); - upward_traversal - } - - /// Creates symbolic link `dest` which points at the same location as - /// symlink `src`. - fn copy_symlink( - src: &AbsoluteNormalizedPathBuf, - dest: &AbsoluteNormalizedPathBuf, - ) -> anyhow::Result<()> { - let mut target = fs_util::read_link(src)?; - if target.is_relative() { - // Grab the absolute path, then re-relativize the path to the destination - let relative_target = fs_util::relative_path_from_system(target.as_path())?; - let absolute_target = relative_target.normalize().to_path( - src.parent() - .expect("a path with a parent in symlink target"), - ); - target = Self::find_relative_path( - &AbsoluteNormalizedPathBuf::try_from(absolute_target)?, - dest, - ); - } - fs_util::symlink(target, dest) - } - - fn copy_file( - src: &AbsoluteNormalizedPathBuf, - dst: &AbsoluteNormalizedPathBuf, - ) -> anyhow::Result<()> { - fs_util::copy(src, dst).map(|_| ()) - } - - fn copy_dir( - src_dir: &AbsoluteNormalizedPathBuf, - dest_dir: &AbsoluteNormalizedPathBuf, - ) -> anyhow::Result<()> { - fs_util::create_dir_all(dest_dir)?; - for file in fs_util::read_dir(src_dir)? { - let file = file?; - let filetype = file.file_type()?; - let src_file = file.path(); - let dest_file = dest_dir.join(ForwardRelativePath::new(&file.file_name())?); - if filetype.is_dir() { - Self::copy_dir(&src_file, &dest_file)?; - } else if filetype.is_symlink() { - Self::copy_symlink(&src_file, &dest_file)?; - } else if filetype.is_file() { - Self::copy_file(&src_file, &dest_file)?; - } - } - Ok(()) - } -} - -pub use internals::PathLike; - -use crate::{ - absolute_path::{AbsolutePath, AbsolutePathBuf}, - project_relative_path::{ProjectRelativePath, ProjectRelativePathBuf}, -}; - -mod internals { - use std::borrow::Cow; - - use crate::{ - absolute_normalized_path::{AbsoluteNormalizedPath, AbsoluteNormalizedPathBuf}, - project::ProjectRoot, - project_relative_path::{ProjectRelativePath, ProjectRelativePathBuf}, - }; - - pub trait PathLike: PathLikeResolvable {} - - impl PathLike for T where T: PathLikeResolvable {} - - pub trait PathLikeResolvable { - fn resolve(&self, fs: &ProjectRoot) -> Cow<'_, AbsoluteNormalizedPath>; - } - - impl PathLikeResolvable for &AbsoluteNormalizedPath { - fn resolve(&self, _fs: &ProjectRoot) -> Cow<'_, AbsoluteNormalizedPath> { - Cow::Borrowed(self) - } - } - - impl PathLikeResolvable for &AbsoluteNormalizedPathBuf { - fn resolve(&self, _fs: &ProjectRoot) -> Cow<'_, AbsoluteNormalizedPath> { - Cow::Borrowed(self) - } - } - - impl PathLikeResolvable for &ProjectRelativePath { - fn resolve(&self, fs: &ProjectRoot) -> Cow<'_, AbsoluteNormalizedPath> { - Cow::Owned(self.0.resolve(fs.root())) - } - } - - impl PathLikeResolvable for &ProjectRelativePathBuf { - fn resolve(&self, fs: &ProjectRoot) -> Cow<'_, AbsoluteNormalizedPath> { - Cow::Owned(self.0.resolve(fs.root())) - } - } -} - -#[cfg(test)] -mod tests { - use std::path::{Path, PathBuf}; - - use crate::{ - absolute_path::AbsolutePath, - forward_relative_path::ForwardRelativePath, - fs_util, - project::{ProjectRoot, ProjectRootTemp}, - project_relative_path::ProjectRelativePath, - }; - - #[test] - fn copy_works() -> anyhow::Result<()> { - let fs = ProjectRootTemp::new()?; - let dir1 = ProjectRelativePath::new("dir1")?; - let dir2 = ProjectRelativePath::new("dir1/dir2")?; - let dir3 = ProjectRelativePath::new("dir1/dir2/dir3")?; - let link_dir2 = ProjectRelativePath::new("dir1/link_dir2")?; - let link_dir3 = ProjectRelativePath::new("dir1/link_dir3")?; - let link_file3 = ProjectRelativePath::new("dir1/link_file3")?; - let file1 = ProjectRelativePath::new("dir1/file1")?; - let file2 = ProjectRelativePath::new("dir1/dir2/file2")?; - let file3 = ProjectRelativePath::new("dir1/dir2/dir3/file3")?; - let file4 = ProjectRelativePath::new("dir1/dir2/dir3/file4")?; - let out_dir = ProjectRelativePath::new("out")?; - - fs_util::create_dir_all(fs.path.resolve(dir1))?; - fs_util::create_dir_all(fs.path.resolve(dir2))?; - fs_util::create_dir_all(fs.path.resolve(dir3))?; - fs_util::create_dir_all(fs.path.resolve(out_dir))?; - - fs_util::write(fs.path.resolve(file1), "file1 contents")?; - fs_util::write(fs.path.resolve(file2), "file2 contents")?; - fs_util::write(fs.path.resolve(file3), "file3 contents")?; - fs_util::write(fs.path.resolve(file4), "file4 contents")?; - // Absolute path - fs_util::symlink(fs.path.resolve(dir2), fs.path.resolve(link_dir2))?; - // Relative path - fs_util::symlink(Path::new("dir2/dir3"), fs.path.resolve(link_dir3))?; - fs_util::symlink(Path::new("dir2/dir3/file3"), fs.path.resolve(link_file3))?; - - fs.path - .copy( - ProjectRelativePath::new("dir1/file1")?, - ProjectRelativePath::new("out")?, - ) - .expect_err("should fail because out exists"); - - let expected_dir1 = ProjectRelativePath::new("out/dir1")?; - let expected_dir2 = ProjectRelativePath::new("out/dir1/dir2")?; - let expected_dir3 = ProjectRelativePath::new("out/dir1/dir2/dir3")?; - let expected_link_dir2 = ProjectRelativePath::new("out/dir1/link_dir2")?; - let expected_link_dir3 = ProjectRelativePath::new("out/dir1/link_dir3")?; - let expected_link_file3 = ProjectRelativePath::new("out/dir1/link_file3")?; - let expected_file1 = ProjectRelativePath::new("out/dir1/file1")?; - let expected_file2 = ProjectRelativePath::new("out/dir1/dir2/file2")?; - let expected_file3 = ProjectRelativePath::new("out/dir1/dir2/dir3/file3")?; - let expected_file4 = ProjectRelativePath::new("out/other1/file4")?; - - fs.path.copy(dir1, ProjectRelativePath::new("out/dir1")?)?; - - // Ensure copying a file creates any parent dirs properly - fs.path - .copy(file4, ProjectRelativePath::new("out/other1/file4")?)?; - - assert!(std::path::Path::is_dir( - fs.path.resolve(expected_dir1).as_ref() - )); - assert!(std::path::Path::is_dir( - fs.path.resolve(expected_dir2).as_ref() - )); - assert!(std::path::Path::is_dir( - fs.path.resolve(expected_dir3).as_ref() - )); - // Absolute link path - assert_eq!( - fs.path.resolve(dir2).as_ref() as &Path, - fs_util::read_link(fs.path.resolve(expected_link_dir2))?.as_path(), - ); - // Make sure out/dir1/link_dir3 links to the relative path to dir1/dir2/dir3 - let link_dir3_target = fs_util::read_link(fs.path.resolve(expected_link_dir3))?; - if cfg!(unix) { - assert_eq!( - Path::new("../../dir1/dir2/dir3"), - link_dir3_target.as_path(), - ); - } else { - // In Windows we use absolute path - assert_eq!(fs.path.resolve(dir3).as_path(), link_dir3_target.as_path()); - } - - // Make sure we can read through; that the relative path actually works - fs_util::write(fs.path.resolve(file3), "file3 new contents")?; - let link_file3_target = fs_util::read_link(fs.path.resolve(expected_link_file3))?; - if cfg!(unix) { - assert_eq!( - Path::new("../../dir1/dir2/dir3/file3"), - link_file3_target.as_path(), - ); - } else { - // In Windows we use absolute path - assert_eq!( - fs.path.resolve(file3).as_path(), - link_file3_target.as_path() - ); - } - assert_eq!( - "file3 new contents", - fs_util::read_to_string(fs.path.resolve(expected_link_file3))? - ); - - assert_eq!( - "file1 contents", - fs_util::read_to_string(fs.path.resolve(expected_file1))? - ); - assert_eq!( - "file2 contents", - fs_util::read_to_string(fs.path.resolve(expected_file2))? - ); - // Independent copy; no hard links made (previous behavior) - assert_eq!( - "file3 contents", - fs_util::read_to_string(fs.path.resolve(expected_file3))? - ); - assert_eq!( - "file4 contents", - fs_util::read_to_string(fs.path.resolve(expected_file4))? - ); - Ok(()) - } - - #[test] - fn test_copy_symlink() -> anyhow::Result<()> { - let fs = ProjectRootTemp::new()?; - let symlink1 = ProjectRelativePath::new("symlink1")?; - let symlink2 = ProjectRelativePath::new("symlink2")?; - let file = ProjectRelativePath::new("file")?; - fs.path.write_file(file, "hello", false)?; - fs.path.soft_link_raw(fs.path.resolve(file), symlink1)?; - fs.path.copy(symlink1, symlink2)?; - - assert_eq!("hello", fs_util::read_to_string(fs.path.resolve(symlink1))?); - assert_eq!("hello", fs_util::read_to_string(fs.path.resolve(symlink2))?); - Ok(()) - } - - #[test] - fn test_symlink_relativized() -> anyhow::Result<()> { - let fs = ProjectRootTemp::new()?; - - let target1 = ProjectRelativePath::new("foo1/bar1/target")?; - let target2 = ProjectRelativePath::new("foo2/bar")?; - let file = target2.join(ForwardRelativePath::new("file")?); - - let dest1 = ProjectRelativePath::new("foo1/target-link")?; - let dest2 = ProjectRelativePath::new("foo1/bar2/target")?; - let dest3 = ProjectRelativePath::new("foo1-link/bar1/target")?; - let dest4 = ProjectRelativePath::new("foo2/bar-link")?; - let dest5 = ProjectRelativePath::new("foo2-link/bar")?; - - fs.path.write_file(target1, "foo1 contents", false)?; - fs.path.write_file(&file, "foo2 contents", false)?; - - fs.path.soft_link_relativized(target1, dest1)?; - fs.path.soft_link_relativized(target1, dest2)?; - fs.path.soft_link_relativized(target1, dest3)?; - fs.path.soft_link_relativized(target2, dest4)?; - fs.path.soft_link_relativized(target2, dest5)?; - - fs.path.write_file(target1, "new foo1 contents", false)?; - fs.path.write_file(&file, "new foo2 contents", false)?; - - let dest1_expected = PathBuf::from("bar1/target"); - let dest2_expected = PathBuf::from("../bar1/target"); - let dest3_expected = PathBuf::from("../../foo1/bar1/target"); - let dest4_expected = PathBuf::from("bar"); - let dest5_expected = PathBuf::from("../foo2/bar"); - - let dest1_value = fs_util::read_link(fs.path.resolve(dest1))?; - let dest2_value = fs_util::read_link(fs.path.resolve(dest2))?; - let dest3_value = fs_util::read_link(fs.path.resolve(dest3))?; - let dest4_value = fs_util::read_link(fs.path.resolve(dest4))?; - let dest5_value = fs_util::read_link(fs.path.resolve(dest5))?; - - let contents1 = fs_util::read_to_string(fs.path.resolve(dest1))?; - let contents2 = fs_util::read_to_string(fs.path.resolve(dest2))?; - let contents3 = fs_util::read_to_string(fs.path.resolve(dest3))?; - let contents4 = fs_util::read_to_string( - fs.path - .resolve(dest4) - .join(ForwardRelativePath::new("file")?), - )?; - let contents5 = fs_util::read_to_string( - fs.path - .resolve(dest5) - .join(ForwardRelativePath::new("file")?), - )?; - - if cfg!(unix) { - assert_eq!(dest1_expected, dest1_value); - assert_eq!(dest2_expected, dest2_value); - assert_eq!(dest3_expected, dest3_value); - assert_eq!(dest4_expected, dest4_value); - assert_eq!(dest5_expected, dest5_value); - } else { - // In Windows we use absolute path - assert_eq!(fs.path.resolve(target1).as_path(), dest1_value); - assert_eq!(fs.path.resolve(target1).as_path(), dest2_value); - assert_eq!(fs.path.resolve(target1).as_path(), dest3_value); - assert_eq!(fs.path.resolve(target2).as_path(), dest4_value); - assert_eq!(fs.path.resolve(target2).as_path(), dest5_value); - } - - assert_eq!("new foo1 contents", contents1); - assert_eq!("new foo1 contents", contents2); - assert_eq!("new foo1 contents", contents3); - assert_eq!("new foo2 contents", contents4); - assert_eq!("new foo2 contents", contents5); - - Ok(()) - } - - #[test] - fn test_symlink_to_directory() -> anyhow::Result<()> { - let fs = ProjectRootTemp::new()?; - let source_dir = ProjectRelativePath::new("foo")?; - let source_file = ProjectRelativePath::new("foo/file")?; - let dest_dir = ProjectRelativePath::new("bar")?; - let dest_file = ProjectRelativePath::new("bar/file")?; - let new_file1 = ProjectRelativePath::new("bar/new_file")?; - let new_file2 = ProjectRelativePath::new("foo/new_file")?; - - fs.path.write_file(source_file, "file content", false)?; - fs.path.soft_link_relativized(source_dir, dest_dir)?; - fs.path.write_file(new_file1, "new file content", false)?; - - let content = fs_util::read_to_string(fs.path.resolve(dest_file))?; - let new_content = fs_util::read_to_string(fs.path.resolve(new_file2))?; - - assert_eq!("file content", content); - assert_eq!("new file content", new_content); - Ok(()) - } - - #[cfg(unix)] - #[test] - fn test_remove_readonly_path_recursive() -> anyhow::Result<()> { - let fs = ProjectRootTemp::new()?; - - // We can delete a read-only file - let file = ProjectRelativePath::new("foo/bar/link")?; - fs.path.write_file(file, "Hello", false)?; - let real_file = fs.path.resolve(file); - let mut perm = fs_util::metadata(&real_file)?.permissions(); - perm.set_readonly(true); - fs_util::set_permissions(&real_file, perm)?; - fs.path.remove_path_recursive(file)?; - assert!(!fs.path.resolve(file).exists()); - Ok(()) - } - - #[test] - fn test_relativizes_paths_correct() -> anyhow::Result<()> { - let fs = ProjectRootTemp::new()?; - - let test_cases = vec![ - ("foo/bar/baz", "notfoo/bar/quz", "../../foo/bar/baz"), - ( - "foo/bar/baz", - "notfoo/some/deep/tree/out", - "../../../../foo/bar/baz", - ), - ( - "notfoo/bar/quz", - "notfoo/some/deep/tree/out", - "../../../bar/quz", - ), - ("foo/bar", "foo/baz", "bar"), - ("bar", "foo/baz", "../bar"), - ("foo/bar", "baz", "foo/bar"), - ]; - - for (target_str, dest_str, expected_str) in test_cases { - let expected = PathBuf::from(expected_str); - let target = ProjectRelativePath::new(target_str)?; - let dest = ProjectRelativePath::new(dest_str)?; - - let actual = - ProjectRoot::find_relative_path(&fs.path.resolve(target), &fs.path.resolve(dest)); - assert_eq!( - expected, - actual, - "Expected path from {} to {} to be {}, got {}", - target_str, - dest_str, - expected_str, - actual.as_path().to_string_lossy() - ); - } - - Ok(()) - } - - #[cfg(unix)] - #[test] - fn test_set_executable() -> anyhow::Result<()> { - use std::os::unix::fs::PermissionsExt; - - let fs = ProjectRootTemp::new()?; - - // We can delete a read-only file - let file = ProjectRelativePath::new("foo/bar/file")?; - let real_file = fs.path.resolve(file); - - fs.path.write_file(file, "Hello", false)?; - let perm = fs_util::metadata(&real_file)?.permissions(); - assert_eq!(perm.mode() & 0o111, 0); - - fs.path.set_executable(file)?; - let perm = fs_util::metadata(&real_file)?.permissions(); - assert_eq!(perm.mode() & 0o111, 0o111); - - Ok(()) - } - - #[test] - fn test_strip_project_root_simple() { - let project_root = ProjectRootTemp::new().unwrap(); - assert_eq!( - Path::new(""), - project_root - .path() - .strip_project_root(project_root.path.root().as_abs_path()) - .unwrap() - ); - assert_eq!( - Path::new("foo"), - project_root - .path() - .strip_project_root(&project_root.path.root().as_abs_path().join("foo")) - .unwrap() - ); - assert_eq!( - Path::new("foo/bar"), - project_root - .path() - .strip_project_root(&project_root.path.root().as_abs_path().join("foo/bar")) - .unwrap() - ); - } - - #[test] - fn test_strip_project_root_complex() { - if cfg!(windows) { - return; - } - - let project_root = ProjectRootTemp::new().unwrap(); - let temp_dir = tempfile::tempdir().unwrap(); - let temp_dir = AbsolutePath::new(temp_dir.path()).unwrap(); - - fs_util::symlink(project_root.path.root(), temp_dir.join("foo")).unwrap(); - assert_eq!( - Path::new(""), - project_root - .path() - .strip_project_root(&temp_dir.join("foo")) - .unwrap() - ); - assert_eq!( - Path::new("bar"), - project_root - .path() - .strip_project_root(&temp_dir.join("foo/bar")) - .unwrap() - ); - } - - #[test] - fn test_relativize_any_bug() { - if cfg!(windows) { - return; - } - - let project_root = ProjectRootTemp::new().unwrap(); - let project_root = project_root.path(); - - fs_util::create_dir(project_root.root().as_path().join("foo")).unwrap(); - fs_util::create_dir(project_root.root().as_path().join("foo/bar")).unwrap(); - fs_util::create_dir(project_root.root().as_path().join("link-target")).unwrap(); - fs_util::write( - project_root.root().as_path().join("link-target/fff"), - "hello", - ) - .unwrap(); - fs_util::symlink( - Path::new("../../link-target"), - project_root.root().as_path().join("foo/bar/baz"), - ) - .unwrap(); - - // Now explaining why the assertion in the end of the test is incorrect: - // Existing path is resolved to non-existing path. - - let existing_path = "foo/bar/baz/../link-target/fff"; - let non_exist_path = "foo/bar/link-target/fff"; - assert!(fs_util::try_exists(project_root.root().as_path().join(existing_path)).unwrap()); - assert!(!fs_util::try_exists(project_root.root().as_path().join(non_exist_path)).unwrap()); - - assert_eq!( - ProjectRelativePath::new(non_exist_path).unwrap(), - project_root - .relativize_any( - project_root - .root() - .as_abs_path() - .join(Path::new(existing_path)) - ) - .unwrap() - ); - } -} diff --git a/crates/turborepo-paths/src/project_relative_path.rs b/crates/turborepo-paths/src/project_relative_path.rs deleted file mode 100644 index d0d539c7d3714..0000000000000 --- a/crates/turborepo-paths/src/project_relative_path.rs +++ /dev/null @@ -1,669 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under both the MIT license found in the - * LICENSE-MIT file in the root directory of this source tree and the Apache - * License, Version 2.0 found in the LICENSE-APACHE file in the root - * directory of this source tree. - */ - -//! -//! 'ProjectRelativePath's are normalized, platform agnostic, forward pointing -//! relative paths based at the `project root`. -//! The `project root` is an 'AbsPath' that corresponds to the root of the turbo -//! process. This is not the current directory where the turbo process is -//! invoked. It is the path of the root of the turborepo, which defines the -//! turbo version and configurations. -//! -//! The 'ProjectFilesystem' is the filesystem containing the `project root` -//! information. This file system is used to interact with the -//! 'ProjectRelativePath', and resolve the paths into a [`std::path::Path`] to -//! perform IO. -//! -//! Sample uses -//! ``` -//! use turborepo_paths::project::ProjectRoot; -//! use turborepo_paths::project_relative_path::{ProjectRelativePathBuf, ProjectRelativePath}; -//! use turborepo_paths::absolute_normalized_path::{AbsoluteNormalizedPathBuf, AbsoluteNormalizedPath}; -//! use turborepo_paths::forward_relative_path::ForwardRelativePath; -//! use relative_path::RelativePath; -//! use std::{borrow::Cow, convert::TryFrom}; -//! -//! let root = if cfg!(not(windows)) { -//! AbsoluteNormalizedPathBuf::from("/usr/local/vercel/".into())? -//! } else { -//! AbsoluteNormalizedPathBuf::from("C:\\open\\vercel\\".into())? -//! }; -//! let some_path = if cfg!(not(windows)) { -//! AbsoluteNormalizedPath::new("/usr/local/vercel/turbo/turbo.json")? -//! } else { -//! AbsoluteNormalizedPath::new("c:/open/vercel/turbo/turbo.json")? -//! }; -//! -//! let fs = ProjectRoot::new_unchecked(root); -//! let project_rel = fs.relativize(some_path)?; -//! -//! assert_eq!(Cow::Borrowed(ProjectRelativePath::new("turbo/turbo.json")?), project_rel); -//! assert_eq!(some_path.to_buf(), fs.resolve(project_rel.as_ref())); -//! -//! let rel_path = RelativePath::new("../src"); -//! let project_rel_2 = project_rel.join_normalized(rel_path)?; -//! assert_eq!(ProjectRelativePathBuf::try_from("turbo/src".to_owned())?, project_rel_2); -//! -//! assert_eq!(some_path.join_normalized(rel_path)?, fs.resolve(&project_rel_2).to_buf()); -//! -//! # anyhow::Ok(()) -//! ``` - -use std::{ - borrow::Borrow, - ops::Deref, - path::{Path, PathBuf}, -}; - -use derivative::Derivative; -use ref_cast::RefCast; -use relative_path::{RelativePath, RelativePathBuf}; -use serde::Serialize; - -use crate::{ - file_name::FileName, - fmt::quoted_display, - forward_relative_path::{ForwardRelativePath, ForwardRelativePathBuf, ForwardRelativePathIter}, -}; - -/// A un-owned forward pointing, fully normalized path that is relative to the -/// project root. -#[derive(derive_more::Display, Derivative, Hash, PartialEq, Eq, PartialOrd, Ord, RefCast)] -#[derivative(Debug)] -#[repr(transparent)] -pub struct ProjectRelativePath( - // TODO(nga): make private. - #[derivative(Debug(format_with = "quoted_display"))] pub(crate) ForwardRelativePath, -); - -/// The owned version of the 'ProjectRelativePath' -#[derive(Clone, derive_more::Display, Derivative)] -// split in two because formatters don't agree -#[derive(Hash, PartialEq, Eq, PartialOrd, Ord, Serialize)] -#[derivative(Debug)] -pub struct ProjectRelativePathBuf( - #[derivative(Debug(format_with = "quoted_display"))] ForwardRelativePathBuf, -); - -impl AsRef for ProjectRelativePath { - fn as_ref(&self) -> &ForwardRelativePath { - &self.0 - } -} - -impl AsRef for ProjectRelativePath { - fn as_ref(&self) -> &RelativePath { - self.0.as_ref() - } -} - -impl AsRef for ProjectRelativePathBuf { - fn as_ref(&self) -> &ForwardRelativePath { - &self.0 - } -} - -impl AsRef for ProjectRelativePathBuf { - fn as_ref(&self) -> &RelativePath { - self.0.as_ref() - } -} - -impl AsRef for ProjectRelativePathBuf { - fn as_ref(&self) -> &ForwardRelativePathBuf { - &self.0 - } -} - -impl ProjectRelativePath { - pub fn unchecked_new>(s: &S) -> &Self { - ProjectRelativePath::ref_cast(ForwardRelativePath::unchecked_new(s)) - } - - pub fn empty() -> &'static Self { - ProjectRelativePath::unchecked_new("") - } - - /// Creates an 'ProjectRelativePath' if the given string represents a - /// forward, normalized relative path, otherwise error. - /// - /// ``` - /// use std::path::Path; - /// use turborepo_paths::project_relative_path::ProjectRelativePath; - /// - /// assert!(ProjectRelativePath::new("foo/bar").is_ok()); - /// assert!(ProjectRelativePath::new("").is_ok()); - /// assert!(ProjectRelativePath::new("/abs/bar").is_err()); - /// assert!(ProjectRelativePath::new("normalize/./bar").is_err()); - /// assert!(ProjectRelativePath::new("normalize/../bar").is_err()); - /// - /// assert!(ProjectRelativePath::new(Path::new("foo/bar")).is_ok()); - /// assert!(ProjectRelativePath::new(Path::new("")).is_ok()); - /// assert!(ProjectRelativePath::new(Path::new("/abs/bar")).is_err()); - /// assert!(ProjectRelativePath::new(Path::new("normalize/./bar")).is_err()); - /// assert!(ProjectRelativePath::new(Path::new("normalize/../bar")).is_err()); - /// ``` - pub fn new>(p: &P) -> anyhow::Result<&ProjectRelativePath> { - Ok(ProjectRelativePath::ref_cast(ForwardRelativePath::new(p)?)) - } - - pub fn as_str(&self) -> &str { - self.0.as_str() - } - - pub fn as_forward_relative_path(&self) -> &ForwardRelativePath { - &self.0 - } - - /// Creates an owned 'ProjectRelativePathBuf' with path adjoined to self. - /// - /// ``` - /// use std::path::Path; - /// use turborepo_paths::forward_relative_path::ForwardRelativePath; - /// use turborepo_paths::project_relative_path::{ProjectRelativePathBuf, ProjectRelativePath}; - /// - /// let path = ProjectRelativePath::new("foo/bar")?; - /// let other = ForwardRelativePath::new("baz")?; - /// assert_eq!(ProjectRelativePathBuf::unchecked_new("foo/bar/baz".to_owned()), path.join(other)); - /// - /// # anyhow::Ok(()) - /// ``` - pub fn join>(&self, path: P) -> ProjectRelativePathBuf { - ProjectRelativePathBuf(self.0.join(path.as_ref())) - } - - /// Returns a relative path of the parent directory - /// - /// ``` - /// use turborepo_paths::project_relative_path::ProjectRelativePath; - /// - /// assert_eq!( - /// Some(ProjectRelativePath::new("foo")?), - /// ProjectRelativePath::new("foo/bar")?.parent() - /// ); - /// - /// # anyhow::Ok(()) - /// ``` - pub fn parent(&self) -> Option<&ProjectRelativePath> { - self.0.parent().map(ProjectRelativePath::ref_cast) - } - - /// Returns the final component of the `ProjectRelativePath`, if there is - /// one. - /// - /// If the path is a normal file, this is the file name. If it's the path of - /// a directory, this is the directory name. - /// - /// ``` - /// use turborepo_paths::file_name::FileName; - /// use turborepo_paths::project_relative_path::ProjectRelativePath; - /// - /// assert_eq!(Some(FileName::unchecked_new("bin")), ProjectRelativePath::new("usr/bin")?.file_name()); - /// - /// # anyhow::Ok(()) - /// ``` - pub fn file_name(&self) -> Option<&FileName> { - self.0.file_name() - } - - /// Returns a 'ForwardRelativePath' that, when joined onto `base`, yields - /// `self`. - /// - /// Error if `base` is not a prefix of `self` or the returned - /// path is not a 'ForwardRelativePath' - /// - /// ``` - /// use turborepo_paths::forward_relative_path::ForwardRelativePath; - /// - /// use turborepo_paths::project_relative_path::ProjectRelativePath; - /// - /// let path = ProjectRelativePath::new("test/haha/foo.txt")?; - /// - /// assert_eq!( - /// path.strip_prefix(ProjectRelativePath::new("test")?)?, - /// ForwardRelativePath::new("haha/foo.txt")? - /// ); - /// assert_eq!(path.strip_prefix(ProjectRelativePath::new("asdf")?).is_err(), true); - /// - /// # anyhow::Ok(()) - /// ``` - pub fn strip_prefix<'a, P: ?Sized>( - &'a self, - base: &'a P, - ) -> anyhow::Result<&'a ForwardRelativePath> - where - P: AsRef, - { - self.0.strip_prefix(&base.as_ref().0) - } - - /// Determines whether `base` is a prefix of `self`. - /// - /// ``` - /// use turborepo_paths::project_relative_path::ProjectRelativePath; - /// - /// let path = ProjectRelativePath::new("some/foo")?; - /// - /// assert!(path.starts_with(ProjectRelativePath::new("some")?)); - /// - /// # anyhow::Ok(()) - /// ``` - pub fn starts_with>(&self, base: P) -> bool { - self.0.starts_with(&base.as_ref().0) - } - - /// Determines whether `child` is a suffix of `self`. - /// Only considers whole path components to match. - /// - /// ``` - /// use std::path::Path; - /// use turborepo_paths::forward_relative_path::ForwardRelativePath; - /// - /// use turborepo_paths::project_relative_path::ProjectRelativePath; - /// - /// let path = ProjectRelativePath::new("some/foo")?; - /// - /// assert!(path.ends_with(ForwardRelativePath::new("foo").unwrap())); - /// - /// # anyhow::Ok(()) - /// ``` - pub fn ends_with>(&self, child: P) -> bool { - self.0.ends_with(child.as_ref()) - } - - /// Extracts the stem (non-extension) portion of [`self.file_name`]. - /// - /// The stem is: - /// - /// * [`None`], if there is no file name; - /// * The entire file name if there is no embedded `.`; - /// * The entire file name if the file name begins with `.` and has no other - /// `.`s within; - /// * Otherwise, the portion of the file name before the final `.` - /// - /// ``` - /// use turborepo_paths::project_relative_path::ProjectRelativePath; - /// - /// let path = ProjectRelativePath::new("foo.rs")?; - /// - /// assert_eq!(Some("foo"), path.file_stem()); - /// - /// # anyhow::Ok(()) - /// ``` - pub fn file_stem(&self) -> Option<&str> { - self.0.file_stem() - } - - /// Extracts the extension of [`self.file_name`], if possible. - /// - /// ``` - /// use turborepo_paths::project_relative_path::ProjectRelativePath; - /// - /// assert_eq!(Some("rs"), ProjectRelativePath::new("hi/foo.rs")?.extension()); - /// - /// # anyhow::Ok(()) - /// ``` - pub fn extension(&self) -> Option<&str> { - self.0.extension() - } - - /// Build an owned `ProjectRelativePathBuf`, joined with the given path and - /// normalized. - /// - /// ``` - /// use std::convert::TryFrom; - /// use turborepo_paths::project_relative_path::{ProjectRelativePath, ProjectRelativePathBuf}; - /// - /// assert_eq!( - /// ProjectRelativePath::new("foo/bar")?.join_normalized("../baz.txt")?, - /// ProjectRelativePathBuf::unchecked_new("foo/baz.txt".into()), - /// ); - /// - /// assert_eq!( - /// ProjectRelativePath::new("foo")?.join_normalized("../../baz.txt").is_err(), - /// true - /// ); - /// - /// # anyhow::Ok(()) - /// ``` - pub fn join_normalized>( - &self, - path: P, - ) -> anyhow::Result { - let inner = self.0.join_normalized(path)?; - // TODO need verify? - Ok(ProjectRelativePathBuf(inner)) - } - - /// Iterator over the components of this path - /// - /// ``` - /// use turborepo_paths::file_name::FileName; - /// use turborepo_paths::project_relative_path::ProjectRelativePath; - /// - /// let p = ProjectRelativePath::new("foo/bar/baz")?; - /// let mut it = p.iter(); - /// - /// assert_eq!( - /// it.next(), - /// Some(FileName::unchecked_new("foo")) - /// ); - /// assert_eq!( - /// it.next(), - /// Some(FileName::unchecked_new("bar")) - /// ); - /// assert_eq!( - /// it.next(), - /// Some(FileName::unchecked_new("baz")) - /// ); - /// assert_eq!( - /// it.next(), - /// None - /// ); - /// - /// # anyhow::Ok(()) - /// ``` - pub fn iter(&self) -> ForwardRelativePathIter { - self.0.iter() - } - - pub fn to_buf(&self) -> ProjectRelativePathBuf { - self.to_owned() - } -} - -impl<'a> From<&'a ForwardRelativePath> for &'a ProjectRelativePath { - /// - /// ``` - /// use turborepo_paths::forward_relative_path::ForwardRelativePath; - /// use std::convert::From; - /// use turborepo_paths::project_relative_path::ProjectRelativePath; - /// - /// let f = ForwardRelativePath::new("foo")?; - /// - /// assert_eq!(<&ProjectRelativePath>::from(f), ProjectRelativePath::new("foo")?); - /// - /// # anyhow::Ok(()) - /// ``` - fn from(p: &'a ForwardRelativePath) -> &'a ProjectRelativePath { - ProjectRelativePath::ref_cast(p) - } -} - -impl ProjectRelativePathBuf { - pub fn unchecked_new(s: String) -> Self { - Self(ForwardRelativePathBuf::unchecked_new(s)) - } - - /// Creates a new 'ProjectRelativePathBuf' with a given capacity used to - /// create the internal 'String'. See 'with_capacity' defined on - /// 'ForwardRelativePathBuf' - pub fn with_capacity(cap: usize) -> Self { - Self(ForwardRelativePathBuf::with_capacity(cap)) - } - - /// Returns the capacity of the underlying 'ForwardRelativePathBuf' - pub fn capacity(&self) -> usize { - self.0.capacity() - } - - /// Invokes 'reserve' on the underlying 'ForwardRelativePathBuf' - pub fn reserve(&mut self, additional: usize) { - self.0.reserve(additional) - } - - /// Invokes 'shrink_to_fit' on the underlying 'ForwardRelativePathBuf' - pub fn shrink_to_fit(&mut self) { - self.0.shrink_to_fit() - } - - /// Invokes 'shrink_to' on the underlying 'String' - pub fn shrink_to(&mut self, min_capacity: usize) { - self.0.shrink_to(min_capacity) - } - - /// Pushes a `ForwardRelativePath` to the existing buffer - pub fn push>(&mut self, path: P) { - self.0.push(path) - } - - /// Pushes a `RelativePath` to the existing buffer, normalizing it - pub fn push_normalized>(&mut self, path: P) -> anyhow::Result<()> { - self.0.push_normalized(path) - } - - pub fn into_forward_relative_path_buf(self) -> ForwardRelativePathBuf { - self.0 - } -} - -impl From for ProjectRelativePathBuf { - fn from(p: ForwardRelativePathBuf) -> Self { - Self(p) - } -} - -impl From for ForwardRelativePathBuf { - fn from(p: ProjectRelativePathBuf) -> Self { - p.0 - } -} - -impl From for RelativePathBuf { - fn from(p: ProjectRelativePathBuf) -> Self { - p.0.into() - } -} - -impl<'a> TryFrom<&'a str> for &'a ProjectRelativePath { - type Error = anyhow::Error; - - /// no allocation conversion - /// - /// ``` - /// use std::convert::TryFrom; - /// use turborepo_paths::forward_relative_path::ForwardRelativePath; - /// use turborepo_paths::project_relative_path::ProjectRelativePath; - /// - /// assert!(<&ProjectRelativePath>::try_from("foo/bar").is_ok()); - /// assert!(<&ProjectRelativePath>::try_from("").is_ok()); - /// assert!(<&ProjectRelativePath>::try_from("/abs/bar").is_err()); - /// assert!(<&ProjectRelativePath>::try_from("normalize/./bar").is_err()); - /// assert!(<&ProjectRelativePath>::try_from("normalize/../bar").is_err()); - /// ``` - fn try_from(s: &'a str) -> anyhow::Result<&'a ProjectRelativePath> { - Ok(ProjectRelativePath::ref_cast(ForwardRelativePath::new(s)?)) - } -} - -impl<'a> TryFrom<&'a RelativePath> for &'a ProjectRelativePath { - type Error = anyhow::Error; - - /// no allocation conversion - /// - /// ``` - /// use std::convert::TryFrom; - /// use turborepo_paths::RelativePath; - /// use turborepo_paths::project_relative_path::ProjectRelativePath; - /// - /// assert!(<&ProjectRelativePath>::try_from(RelativePath::new("foo/bar")).is_ok()); - /// assert!(<&ProjectRelativePath>::try_from(RelativePath::new("")).is_ok()); - /// assert!(<&ProjectRelativePath>::try_from(RelativePath::new("normalize/./bar")).is_err()); - /// assert!(<&ProjectRelativePath>::try_from(RelativePath::new("normalize/../bar")).is_err()); - /// ``` - fn try_from(s: &'a RelativePath) -> anyhow::Result<&'a ProjectRelativePath> { - Ok(ProjectRelativePath::ref_cast(ForwardRelativePath::new( - s.as_str(), - )?)) - } -} - -impl TryFrom for ProjectRelativePathBuf { - type Error = anyhow::Error; - - /// no allocation conversion - /// - /// ``` - /// use turborepo_paths::project_relative_path::ProjectRelativePathBuf; - /// use std::convert::TryFrom; - /// - /// assert!(ProjectRelativePathBuf::try_from("foo/bar".to_owned()).is_ok()); - /// assert!(ProjectRelativePathBuf::try_from("".to_owned()).is_ok()); - /// assert!(ProjectRelativePathBuf::try_from("/abs/bar".to_owned()).is_err()); - /// assert!(ProjectRelativePathBuf::try_from("normalize/./bar".to_owned()).is_err()); - /// assert!(ProjectRelativePathBuf::try_from("normalize/../bar".to_owned()).is_err()); - /// ``` - fn try_from(s: String) -> anyhow::Result { - Ok(ProjectRelativePathBuf::from( - ForwardRelativePathBuf::try_from(s)?, - )) - } -} - -impl TryFrom for ProjectRelativePathBuf { - type Error = anyhow::Error; - - /// no allocation conversion (TODO make ForwardRelativePath a no allocation - /// conversion) - /// - /// ``` - /// use turborepo_paths::RelativePathBuf; - /// use std::convert::TryFrom; - /// use turborepo_paths::project_relative_path::ProjectRelativePathBuf; - /// - /// assert!(ProjectRelativePathBuf::try_from(RelativePathBuf::from("foo/bar")).is_ok()); - /// assert!(ProjectRelativePathBuf::try_from(RelativePathBuf::from("")).is_ok()); - /// assert!(ProjectRelativePathBuf::try_from(RelativePathBuf::from("normalize/./bar")).is_err()); - /// assert!(ProjectRelativePathBuf::try_from(RelativePathBuf::from("normalize/../bar")).is_err()); - /// ``` - fn try_from(p: RelativePathBuf) -> anyhow::Result { - Ok(ProjectRelativePathBuf::from( - ForwardRelativePathBuf::try_from(p)?, - )) - } -} - -impl TryFrom for ProjectRelativePathBuf { - type Error = anyhow::Error; - - /// no allocation conversion - /// - /// ``` - /// - /// use std::convert::TryFrom; - /// use std::path::PathBuf; - /// use turborepo_paths::project_relative_path::ProjectRelativePathBuf; - /// - /// assert!(ProjectRelativePathBuf::try_from(PathBuf::from("foo/bar")).is_ok()); - /// assert!(ProjectRelativePathBuf::try_from(PathBuf::from("")).is_ok()); - /// assert!(ProjectRelativePathBuf::try_from(PathBuf::from("/abs/bar")).is_err()); - /// assert!(ProjectRelativePathBuf::try_from(PathBuf::from("normalize/./bar")).is_err()); - /// assert!(ProjectRelativePathBuf::try_from(PathBuf::from("normalize/../bar")).is_err()); - /// ``` - fn try_from(p: PathBuf) -> anyhow::Result { - Ok(ProjectRelativePathBuf(ForwardRelativePathBuf::try_from(p)?)) - } -} - -impl ToOwned for ProjectRelativePath { - type Owned = ProjectRelativePathBuf; - - fn to_owned(&self) -> ProjectRelativePathBuf { - ProjectRelativePathBuf(self.0.to_owned()) - } -} - -impl AsRef for ProjectRelativePath { - fn as_ref(&self) -> &ProjectRelativePath { - self - } -} - -impl AsRef for ProjectRelativePathBuf { - fn as_ref(&self) -> &ProjectRelativePath { - ProjectRelativePath::ref_cast(&self.0) - } -} - -impl Borrow for ProjectRelativePathBuf { - fn borrow(&self) -> &ProjectRelativePath { - self.as_ref() - } -} - -impl Deref for ProjectRelativePathBuf { - type Target = ProjectRelativePath; - - fn deref(&self) -> &ProjectRelativePath { - ProjectRelativePath::ref_cast(&self.0) - } -} - -#[cfg(test)] -mod tests { - use crate::project_relative_path::{ProjectRelativePath, ProjectRelativePathBuf}; - - #[test] - fn path_display_is_readable() -> anyhow::Result<()> { - let buf = ProjectRelativePathBuf::try_from("foo/bar".to_owned())?; - assert_eq!("foo/bar", format!("{}", buf)); - assert_eq!("ProjectRelativePathBuf(\"foo/bar\")", format!("{:?}", buf)); - let refpath: &ProjectRelativePath = &buf; - assert_eq!("foo/bar", format!("{}", refpath)); - assert_eq!("ProjectRelativePath(\"foo/bar\")", format!("{:?}", refpath)); - - Ok(()) - } - - #[test] - fn path_is_comparable() -> anyhow::Result<()> { - let path1_buf = ProjectRelativePathBuf::try_from("foo".to_owned())?; - let path2_buf = ProjectRelativePathBuf::try_from("foo".to_owned())?; - let path3_buf = ProjectRelativePathBuf::try_from("bar".to_owned())?; - - let path1 = ProjectRelativePath::new("foo")?; - let path2 = ProjectRelativePath::new("foo")?; - let path3 = ProjectRelativePath::new("bar")?; - - let str2 = "foo"; - let str3 = "bar"; - let str_abs = "/ble"; - - let string2 = "foo".to_owned(); - let string3 = "bar".to_owned(); - let string_abs = "/ble".to_owned(); - - assert_eq!(path1_buf, path2_buf); - assert_ne!(path1_buf, path3_buf); - - assert_eq!(path1, path2); - assert_ne!(path1, path3); - - assert_eq!(path1_buf, path2); - assert_ne!(path1, path3_buf); - - assert_eq!(path1_buf, str2); - assert_ne!(path1_buf, str3); - assert_ne!(path1_buf, str_abs); - - assert_eq!(path1, str2); - assert_ne!(path1, str3); - assert_ne!(path1, str_abs); - - assert_eq!(path1_buf, string2); - assert_ne!(path1_buf, string3); - assert_ne!(path1_buf, string_abs); - - assert_eq!(path1, string2); - assert_ne!(path1, string3); - assert_ne!(path1, string_abs); - - Ok(()) - } -} From c1d84a91557d37ce1748aba5863a86a02c3d5a42 Mon Sep 17 00:00:00 2001 From: --global Date: Tue, 4 Apr 2023 09:05:02 -0500 Subject: [PATCH 07/19] Converted daemon code to turbopath and added relevant methods --- Cargo.lock | 3 +- crates/turbopath/Cargo.toml | 2 ++ .../turbopath/src/absolute_system_path_buf.rs | 32 +++++++++++++++---- .../turbopath/src/anchored_system_path_buf.rs | 8 ++++- .../turbopath/src/relative_system_path_buf.rs | 17 +++++++--- .../turbopath/src/relative_unix_path_buf.rs | 21 ++++++++---- crates/turborepo-lib/Cargo.toml | 1 + crates/turborepo-lib/src/commands/daemon.rs | 12 +++---- crates/turborepo-lib/src/commands/mod.rs | 13 ++++---- crates/turborepo-lib/src/daemon/client.rs | 4 +-- crates/turborepo-lib/src/daemon/connector.rs | 27 ++++++++-------- 11 files changed, 93 insertions(+), 47 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cb4122e9620a4..411e9459fee43 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7835,6 +7835,7 @@ name = "turbopath" version = "0.1.0" dependencies = [ "path-slash", + "serde", "thiserror", ] @@ -7910,7 +7911,7 @@ dependencies = [ "tower", "turbo-updater", "turborepo-api-client", - "turborepo-paths", + "turbopath", "uds_windows", "url", "webbrowser", diff --git a/crates/turbopath/Cargo.toml b/crates/turbopath/Cargo.toml index 1cfd6ee68e4fe..b6fb4d764961d 100644 --- a/crates/turbopath/Cargo.toml +++ b/crates/turbopath/Cargo.toml @@ -8,4 +8,6 @@ edition = "2021" [dependencies] path-slash = "0.2.1" +# TODO: Make this a crate feature +serde = { workspace = true } thiserror = { workspace = true } diff --git a/crates/turbopath/src/absolute_system_path_buf.rs b/crates/turbopath/src/absolute_system_path_buf.rs index 9b4ad18788cf5..3ee9c89af2122 100644 --- a/crates/turbopath/src/absolute_system_path_buf.rs +++ b/crates/turbopath/src/absolute_system_path_buf.rs @@ -1,11 +1,15 @@ use std::{ + borrow::Cow, + ffi::OsStr, fmt, path::{Components, Path, PathBuf}, }; -use crate::{AnchoredSystemPathBuf, IntoSystem, PathValidationError}; +use serde::Serialize; -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] +use crate::{AnchoredSystemPathBuf, IntoSystem, PathValidationError, RelativeSystemPathBuf}; + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Serialize)] pub struct AbsoluteSystemPathBuf(PathBuf); impl AbsoluteSystemPathBuf { @@ -129,16 +133,32 @@ impl AbsoluteSystemPathBuf { AbsoluteSystemPathBuf(self.0.join(path)) } + pub fn join_relative(&self, path: RelativeSystemPathBuf) -> AbsoluteSystemPathBuf { + AbsoluteSystemPathBuf(self.0.join(path.as_path())) + } + pub fn to_str(&self) -> Result<&str, PathValidationError> { self.0.to_str().ok_or(PathValidationError::InvalidUnicode) } - pub fn file_name(&self) -> Option<&str> { - self.0.file_name().and_then(|s| s.to_str()) + pub fn to_string_lossy(&self) -> Cow<'_, str> { + self.0.to_string_lossy() + } + + pub fn file_name(&self) -> Option<&OsStr> { + self.0.file_name() + } + + pub fn exists(&self) -> bool { + self.0.exists() + } + + pub fn into_path_buf(self) -> PathBuf { + self.0 } - pub fn extension(&self) -> Option<&str> { - self.0.extension().and_then(|s| s.to_str()) + pub fn extension(&self) -> Option<&OsStr> { + self.0.extension() } } diff --git a/crates/turbopath/src/anchored_system_path_buf.rs b/crates/turbopath/src/anchored_system_path_buf.rs index d3e4945041627..3ddee3b7c7b1e 100644 --- a/crates/turbopath/src/anchored_system_path_buf.rs +++ b/crates/turbopath/src/anchored_system_path_buf.rs @@ -1,8 +1,10 @@ use std::path::{Path, PathBuf}; +use serde::Serialize; + use crate::{AbsoluteSystemPathBuf, IntoSystem, PathValidationError}; -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Serialize)] pub struct AnchoredSystemPathBuf(PathBuf); impl TryFrom<&Path> for AnchoredSystemPathBuf { @@ -39,6 +41,10 @@ impl AnchoredSystemPathBuf { self.0.as_path() } + pub fn into_path_buf(self) -> PathBuf { + self.0 + } + pub fn to_str(&self) -> Result<&str, PathValidationError> { self.0 .to_str() diff --git a/crates/turbopath/src/relative_system_path_buf.rs b/crates/turbopath/src/relative_system_path_buf.rs index 75fa30fe9be95..dfa26dfd93975 100644 --- a/crates/turbopath/src/relative_system_path_buf.rs +++ b/crates/turbopath/src/relative_system_path_buf.rs @@ -1,11 +1,14 @@ use std::{ + ffi::OsStr, fmt, path::{Components, Path, PathBuf}, }; +use serde::Serialize; + use crate::{IntoSystem, PathValidationError}; -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Serialize)] pub struct RelativeSystemPathBuf(PathBuf); impl RelativeSystemPathBuf { @@ -74,12 +77,16 @@ impl RelativeSystemPathBuf { self.0.to_str().ok_or(PathValidationError::InvalidUnicode) } - pub fn file_name(&self) -> Option<&str> { - self.0.file_name().and_then(|s| s.to_str()) + pub fn file_name(&self) -> Option<&OsStr> { + self.0.file_name() + } + + pub fn into_path_buf(self) -> PathBuf { + self.0 } - pub fn extension(&self) -> Option<&str> { - self.0.extension().and_then(|s| s.to_str()) + pub fn extension(&self) -> Option<&OsStr> { + self.0.extension() } } diff --git a/crates/turbopath/src/relative_unix_path_buf.rs b/crates/turbopath/src/relative_unix_path_buf.rs index d186e7989df7a..52cd82fd801e2 100644 --- a/crates/turbopath/src/relative_unix_path_buf.rs +++ b/crates/turbopath/src/relative_unix_path_buf.rs @@ -1,8 +1,13 @@ -use std::path::{Components, Path, PathBuf}; +use std::{ + ffi::OsStr, + path::{Components, Path, PathBuf}, +}; + +use serde::Serialize; use crate::{IntoUnix, PathValidationError}; -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Serialize)] pub struct RelativeUnixPathBuf(PathBuf); impl RelativeUnixPathBuf { @@ -66,12 +71,16 @@ impl RelativeUnixPathBuf { self.0.to_str().ok_or(PathValidationError::InvalidUnicode) } - pub fn file_name(&self) -> Option<&str> { - self.0.file_name().and_then(|s| s.to_str()) + pub fn file_name(&self) -> Option<&OsStr> { + self.0.file_name() + } + + pub fn extension(&self) -> Option<&OsStr> { + self.0.extension() } - pub fn extension(&self) -> Option<&str> { - self.0.extension().and_then(|s| s.to_str()) + pub fn into_path_buf(self) -> PathBuf { + self.0 } } diff --git a/crates/turborepo-lib/Cargo.toml b/crates/turborepo-lib/Cargo.toml index 9e411a73864f1..408b1ae6f710f 100644 --- a/crates/turborepo-lib/Cargo.toml +++ b/crates/turborepo-lib/Cargo.toml @@ -73,6 +73,7 @@ webbrowser = { workspace = true } const_format = "0.2.30" turbo-updater = { workspace = true } turborepo-api-client = { workspace = true } +turbopath = { workspace = true } [target.'cfg(target_os = "windows")'.dependencies] uds_windows = "1.0.2" diff --git a/crates/turborepo-lib/src/commands/daemon.rs b/crates/turborepo-lib/src/commands/daemon.rs index 316a470b96705..4cfdc09cade6d 100644 --- a/crates/turborepo-lib/src/commands/daemon.rs +++ b/crates/turborepo-lib/src/commands/daemon.rs @@ -14,11 +14,11 @@ pub async fn main(command: &DaemonCommand, base: &CommandBase) -> anyhow::Result let connector = DaemonConnector { can_start_server, can_kill_server, - pid_file: base.daemon_file_root().join( - turborepo_paths::ForwardRelativePath::new("turbod.pid").expect("forward relative"), + pid_file: base.daemon_file_root().join_relative( + turbopath::RelativeSystemPathBuf::new("turbod.pid").expect("relative system"), ), - sock_file: base.daemon_file_root().join( - turborepo_paths::ForwardRelativePath::new("turbod.sock").expect("forward relative"), + sock_file: base.daemon_file_root().join_relative( + turbopath::RelativeSystemPathBuf::new("turbod.sock").expect("relative system"), ), }; @@ -65,6 +65,6 @@ pub struct DaemonStatus { // this comes from the daemon server, so we trust that // it is correct pub log_file: PathBuf, - pub pid_file: turborepo_paths::AbsoluteNormalizedPathBuf, - pub sock_file: turborepo_paths::AbsoluteNormalizedPathBuf, + pub pid_file: turbopath::AbsoluteSystemPathBuf, + pub sock_file: turbopath::AbsoluteSystemPathBuf, } diff --git a/crates/turborepo-lib/src/commands/mod.rs b/crates/turborepo-lib/src/commands/mod.rs index a18b754738b65..bbacbb2f02188 100644 --- a/crates/turborepo-lib/src/commands/mod.rs +++ b/crates/turborepo-lib/src/commands/mod.rs @@ -137,13 +137,14 @@ impl CommandBase { APIClient::new(api_url, timeout, self.version) } - pub fn daemon_file_root(&self) -> turborepo_paths::AbsoluteNormalizedPathBuf { - turborepo_paths::AbsoluteNormalizedPathBuf::new(std::env::temp_dir()) + pub fn daemon_file_root(&self) -> turbopath::AbsoluteSystemPathBuf { + turbopath::AbsoluteSystemPathBuf::new(std::env::temp_dir()) .expect("temp dir is valid") - .join(turborepo_paths::ForwardRelativePath::new("turbod").expect("turbod is valid")) - .join( - turborepo_paths::ForwardRelativePath::new(&self.repo_hash()) - .expect("hash is valid"), + .join_relative( + turbopath::RelativeSystemPathBuf::new("turbod").expect("turbod is valid"), + ) + .join_relative( + turbopath::RelativeSystemPathBuf::new(&self.repo_hash()).expect("hash is valid"), ) .into() } diff --git a/crates/turborepo-lib/src/daemon/client.rs b/crates/turborepo-lib/src/daemon/client.rs index fd6b48da62845..6e8d9dee76ee1 100644 --- a/crates/turborepo-lib/src/daemon/client.rs +++ b/crates/turborepo-lib/src/daemon/client.rs @@ -108,11 +108,11 @@ impl DaemonClient { .ok_or(DaemonError::MalformedResponse) } - pub fn pid_file(&self) -> &turborepo_paths::AbsoluteNormalizedPathBuf { + pub fn pid_file(&self) -> &turbopath::AbsoluteSystemPathBuf { &self.connect_settings.pid_file } - pub fn sock_file(&self) -> &turborepo_paths::AbsoluteNormalizedPathBuf { + pub fn sock_file(&self) -> &turbopath::AbsoluteSystemPathBuf { &self.connect_settings.sock_file } } diff --git a/crates/turborepo-lib/src/daemon/connector.rs b/crates/turborepo-lib/src/daemon/connector.rs index 83a51d0fb57a5..a3e40b0e498c2 100644 --- a/crates/turborepo-lib/src/daemon/connector.rs +++ b/crates/turborepo-lib/src/daemon/connector.rs @@ -59,8 +59,8 @@ pub struct DaemonConnector { /// Whether the connector is allowed to kill a running daemon (for example, /// in the event of a version mismatch). pub can_kill_server: bool, - pub pid_file: turborepo_paths::AbsoluteNormalizedPathBuf, - pub sock_file: turborepo_paths::AbsoluteNormalizedPathBuf, + pub pid_file: turbopath::AbsoluteSystemPathBuf, + pub sock_file: turbopath::AbsoluteSystemPathBuf, } impl DaemonConnector { @@ -163,7 +163,7 @@ impl DaemonConnector { /// platform and retry in case of error. async fn get_connection( &self, - path: turborepo_paths::AbsoluteNormalizedPathBuf, + path: turbopath::AbsoluteSystemPathBuf, ) -> Result, DaemonConnectorError> { // windows doesn't treat sockets as files, so don't attempt to wait #[cfg(not(target_os = "windows"))] @@ -261,7 +261,7 @@ impl DaemonConnector { #[cfg(target_os = "windows")] fn win( - path: Arc, + path: Arc, ) -> Result { use tokio_util::compat::FuturesAsyncReadCompatExt; uds_windows::UnixStream::connect(&*path) @@ -276,7 +276,7 @@ pub enum FileWaitError { #[error("failed to wait for event {0}")] Io(#[from] std::io::Error), #[error("invalid path {0}")] - InvalidPath(turborepo_paths::AbsoluteNormalizedPathBuf), + InvalidPath(turbopath::AbsoluteSystemPathBuf), } /// Waits for a file at some path on the filesystem to be created or deleted. @@ -284,7 +284,7 @@ pub enum FileWaitError { /// It does this by watching the parent directory of the path, and waiting for /// events on that path. async fn wait_for_file( - path: &turborepo_paths::AbsoluteNormalizedPath, + path: &turbopath::AbsoluteSystemPathBuf, action: WaitAction, ) -> Result<(), FileWaitError> { let parent = path @@ -318,11 +318,10 @@ async fn wait_for_file( }), WaitAction::Deleted, ) => { - if paths.iter().any(|p| { - p.file_name() - .map(|f| file_name.as_os_str().eq(f)) - .unwrap_or_default() - }) { + if paths + .iter() + .any(|p| p.file_name().map(|f| file_name.eq(f)).unwrap_or_default()) + { futures::executor::block_on(async { // if the receiver is dropped, it is because the future has // been cancelled, so we don't need to do anything @@ -336,10 +335,10 @@ async fn wait_for_file( )?; debug!("creating {:?}", parent); - std::fs::create_dir_all(parent)?; + std::fs::create_dir_all(parent.as_path())?; debug!("watching {:?}", parent); - watcher.watch(parent, notify::RecursiveMode::NonRecursive)?; + watcher.watch(parent.as_path(), notify::RecursiveMode::NonRecursive)?; match (action, path.exists()) { (WaitAction::Exists, false) => {} @@ -375,7 +374,7 @@ mod test { select, sync::{oneshot::Sender, Mutex}, }; - use turborepo_paths::AbsoluteNormalizedPathBuf; + use turbopath::AbsoluteSystemPathBuf; use super::*; use crate::daemon::client::proto; From 47154be46d64273530bd66a21b1e0e011c5a3df0 Mon Sep 17 00:00:00 2001 From: --global Date: Tue, 4 Apr 2023 09:21:46 -0500 Subject: [PATCH 08/19] Fix clippy errors --- crates/turbopath/src/absolute_system_path_buf.rs | 2 +- crates/turbopath/src/anchored_system_path_buf.rs | 4 +--- crates/turbopath/src/lib.rs | 12 ++++++------ crates/turborepo-lib/src/commands/link.rs | 2 +- crates/turborepo-lib/src/commands/mod.rs | 3 +-- 5 files changed, 10 insertions(+), 13 deletions(-) diff --git a/crates/turbopath/src/absolute_system_path_buf.rs b/crates/turbopath/src/absolute_system_path_buf.rs index 3ee9c89af2122..c76d2530fec15 100644 --- a/crates/turbopath/src/absolute_system_path_buf.rs +++ b/crates/turbopath/src/absolute_system_path_buf.rs @@ -80,7 +80,7 @@ impl AbsoluteSystemPathBuf { &self, path: &AbsoluteSystemPathBuf, ) -> Result { - AnchoredSystemPathBuf::strip_root(&self, path) + AnchoredSystemPathBuf::strip_root(self, path) } /// Resolves `path` with `self` as anchor. diff --git a/crates/turbopath/src/anchored_system_path_buf.rs b/crates/turbopath/src/anchored_system_path_buf.rs index 3ddee3b7c7b1e..0b6c9127a9d2c 100644 --- a/crates/turbopath/src/anchored_system_path_buf.rs +++ b/crates/turbopath/src/anchored_system_path_buf.rs @@ -46,8 +46,6 @@ impl AnchoredSystemPathBuf { } pub fn to_str(&self) -> Result<&str, PathValidationError> { - self.0 - .to_str() - .ok_or_else(|| PathValidationError::InvalidUnicode) + self.0.to_str().ok_or(PathValidationError::InvalidUnicode) } } diff --git a/crates/turbopath/src/lib.rs b/crates/turbopath/src/lib.rs index a77099c012b4d..fb41596cef130 100644 --- a/crates/turbopath/src/lib.rs +++ b/crates/turbopath/src/lib.rs @@ -26,26 +26,26 @@ pub enum PathValidationError { } trait IntoSystem { - fn into_system(&self) -> Result; + fn into_system(self) -> Result; } trait IntoUnix { - fn into_unix(&self) -> Result; + fn into_unix(self) -> Result; } -impl IntoSystem for Path { - fn into_system(&self) -> Result { +impl IntoSystem for &Path { + fn into_system(self) -> Result { let path_str = self.to_str().ok_or(PathValidationError::InvalidUnicode)?; Ok(PathBuf::from_slash(path_str)) } } -impl IntoUnix for Path { +impl IntoUnix for &Path { /// NOTE: `into_unix` *only* converts Windows paths to Unix paths *on* a /// Windows system. Do not pass a Windows path on a Unix system and /// assume it'll be converted. - fn into_unix(&self) -> Result { + fn into_unix(self) -> Result { Ok(PathBuf::from( self.to_slash() .ok_or(PathValidationError::InvalidUnicode)? diff --git a/crates/turborepo-lib/src/commands/link.rs b/crates/turborepo-lib/src/commands/link.rs index dd1d7edc7d205..56e09eef5c07f 100644 --- a/crates/turborepo-lib/src/commands/link.rs +++ b/crates/turborepo-lib/src/commands/link.rs @@ -269,7 +269,7 @@ fn enable_caching(url: &str) -> Result<()> { println!("Visit {} in your browser to enable Remote Caching", url); // We return an error no matter what - return Err(anyhow!("link after enabling caching")); + Err(anyhow!("link after enabling caching")) } fn add_turbo_to_gitignore(base: &CommandBase) -> Result<()> { diff --git a/crates/turborepo-lib/src/commands/mod.rs b/crates/turborepo-lib/src/commands/mod.rs index bbacbb2f02188..db5383c3ee63b 100644 --- a/crates/turborepo-lib/src/commands/mod.rs +++ b/crates/turborepo-lib/src/commands/mod.rs @@ -144,9 +144,8 @@ impl CommandBase { turbopath::RelativeSystemPathBuf::new("turbod").expect("turbod is valid"), ) .join_relative( - turbopath::RelativeSystemPathBuf::new(&self.repo_hash()).expect("hash is valid"), + turbopath::RelativeSystemPathBuf::new(self.repo_hash()).expect("hash is valid"), ) - .into() } fn repo_hash(&self) -> String { From 2307ca77a0bf0b1c34babe9f3c473e4e45969f38 Mon Sep 17 00:00:00 2001 From: --global Date: Tue, 4 Apr 2023 09:35:42 -0500 Subject: [PATCH 09/19] More fixing --- crates/turbopath/src/absolute_system_path_buf.rs | 6 ++++++ crates/turbopath/src/relative_system_path_buf.rs | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/crates/turbopath/src/absolute_system_path_buf.rs b/crates/turbopath/src/absolute_system_path_buf.rs index c76d2530fec15..edb7005f20d8a 100644 --- a/crates/turbopath/src/absolute_system_path_buf.rs +++ b/crates/turbopath/src/absolute_system_path_buf.rs @@ -167,3 +167,9 @@ impl fmt::Display for AbsoluteSystemPathBuf { self.0.display().fmt(f) } } + +impl AsRef for AbsoluteSystemPathBuf { + fn as_ref(&self) -> &Path { + self.0.as_path() + } +} diff --git a/crates/turbopath/src/relative_system_path_buf.rs b/crates/turbopath/src/relative_system_path_buf.rs index dfa26dfd93975..2b794bd460192 100644 --- a/crates/turbopath/src/relative_system_path_buf.rs +++ b/crates/turbopath/src/relative_system_path_buf.rs @@ -95,3 +95,9 @@ impl fmt::Display for RelativeSystemPathBuf { self.0.display().fmt(f) } } + +impl AsRef for RelativeSystemPathBuf { + fn as_ref(&self) -> &Path { + self.0.as_ref() + } +} From 85ef5fc5a9dbbbbf49276eadea590a47891f3547 Mon Sep 17 00:00:00 2001 From: --global Date: Tue, 4 Apr 2023 09:54:14 -0500 Subject: [PATCH 10/19] More fixing --- crates/turbopath/src/relative_unix_path_buf.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/turbopath/src/relative_unix_path_buf.rs b/crates/turbopath/src/relative_unix_path_buf.rs index 52cd82fd801e2..dd8c6448c1f29 100644 --- a/crates/turbopath/src/relative_unix_path_buf.rs +++ b/crates/turbopath/src/relative_unix_path_buf.rs @@ -98,7 +98,7 @@ mod tests { assert!(path.ends_with("bar")); assert_eq!(path.join("baz").as_path(), Path::new("foo/bar/baz")); assert_eq!(path.to_str().unwrap(), "foo/bar"); - assert_eq!(path.file_name(), Some("bar")); + assert_eq!(path.file_name(), Some(OsStr::new("bar"))); assert_eq!(path.extension(), None); } @@ -112,8 +112,8 @@ mod tests { assert!(path.ends_with("bar.txt")); assert_eq!(path.join("baz").as_path(), Path::new("foo/bar.txt/baz")); assert_eq!(path.to_str().unwrap(), "foo/bar.txt"); - assert_eq!(path.file_name(), Some("bar.txt")); - assert_eq!(path.extension(), Some("txt")); + assert_eq!(path.file_name(), Some(OsStr::new("bar.txt"))); + assert_eq!(path.extension(), Some(OsStr::new("txt"))); } #[test] From 2abb1e4d74d60ffdd7a339019172bbc0648c1bcd Mon Sep 17 00:00:00 2001 From: --global Date: Tue, 4 Apr 2023 10:03:09 -0500 Subject: [PATCH 11/19] Even more fixing --- crates/turborepo-lib/src/daemon/connector.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/crates/turborepo-lib/src/daemon/connector.rs b/crates/turborepo-lib/src/daemon/connector.rs index a3e40b0e498c2..9e9920a5a12d1 100644 --- a/crates/turborepo-lib/src/daemon/connector.rs +++ b/crates/turborepo-lib/src/daemon/connector.rs @@ -384,12 +384,12 @@ mod test { #[cfg(target_os = "windows")] const NODE_EXE: &str = "node.exe"; - fn pid_path(tmp_path: &Path) -> AbsoluteNormalizedPathBuf { - AbsoluteNormalizedPathBuf::try_from(tmp_path.join("turbod.pid")).unwrap() + fn pid_path(tmp_path: &Path) -> AbsoluteSystemPathBuf { + AbsoluteSystemPathBuf::new(tmp_path.join("turbod.pid")).unwrap() } - fn sock_path(tmp_path: &Path) -> AbsoluteNormalizedPathBuf { - AbsoluteNormalizedPathBuf::try_from(tmp_path.join("turbod.sock")).unwrap() + fn sock_path(tmp_path: &Path) -> AbsoluteSystemPathBuf { + AbsoluteSystemPathBuf::new(tmp_path.join("turbod.sock")).unwrap() } #[tokio::test] @@ -622,13 +622,13 @@ mod test { let (pid_file, sock_file) = if cfg!(windows) { ( - AbsoluteNormalizedPathBuf::new(PathBuf::from("C:\\pid")).unwrap(), - AbsoluteNormalizedPathBuf::new(PathBuf::from("C:\\sock")).unwrap(), + AbsoluteSystemPathBuf::new(PathBuf::from("C:\\pid")).unwrap(), + AbsoluteSystemPathBuf::new(PathBuf::from("C:\\sock")).unwrap(), ) } else { ( - AbsoluteNormalizedPathBuf::new(PathBuf::from("/pid")).unwrap(), - AbsoluteNormalizedPathBuf::new(PathBuf::from("/sock")).unwrap(), + AbsoluteSystemPathBuf::new(PathBuf::from("/pid")).unwrap(), + AbsoluteSystemPathBuf::new(PathBuf::from("/sock")).unwrap(), ) }; From e038b69193122f43ab76c570871f34b686267c2b Mon Sep 17 00:00:00 2001 From: --global Date: Wed, 5 Apr 2023 11:54:40 -0400 Subject: [PATCH 12/19] PR feedback --- .../turbopath/src/absolute_system_path_buf.rs | 41 ++++++++----------- .../turbopath/src/anchored_system_path_buf.rs | 14 ++++--- crates/turbopath/src/lib.rs | 10 +++-- .../turbopath/src/relative_system_path_buf.rs | 4 +- .../turbopath/src/relative_unix_path_buf.rs | 4 +- crates/turborepo-lib/src/daemon/connector.rs | 2 +- 6 files changed, 39 insertions(+), 36 deletions(-) diff --git a/crates/turbopath/src/absolute_system_path_buf.rs b/crates/turbopath/src/absolute_system_path_buf.rs index edb7005f20d8a..3153e69fdcc01 100644 --- a/crates/turbopath/src/absolute_system_path_buf.rs +++ b/crates/turbopath/src/absolute_system_path_buf.rs @@ -29,10 +29,16 @@ impl AbsoluteSystemPathBuf { /// ``` /// use std::path::{Path, PathBuf}; /// use turbopath::AbsoluteSystemPathBuf; + /// #[cfg(windows)] + /// let path = PathBuf::from("C:/Users/user"); + /// #[cfg(not(windows))] /// let path = PathBuf::from("/Users/user"); + /// /// let absolute_path = AbsoluteSystemPathBuf::new(path).unwrap(); + /// /// #[cfg(windows)] - /// assert_eq!(absolute_path.as_path(), Path::new("\\Users\\user")); + /// assert_eq!(absolute_path.as_path(), Path::new("C:\\Users\\user")); + /// #[cfg(not(windows))] /// assert_eq!(absolute_path.as_path(), Path::new("/Users/user")); /// ``` pub fn new(unchecked_path: impl Into) -> Result { @@ -45,19 +51,6 @@ impl AbsoluteSystemPathBuf { Ok(AbsoluteSystemPathBuf(system_path)) } - /// Converts `path` to an `AbsoluteSystemPathBuf` without validating that - /// it is either absolute or a system path. - /// - /// # Arguments - /// - /// * `path`: The path to be converted to an `AbsoluteSystemPathBuf`. - /// - /// returns: AbsoluteSystemPathBuf - pub fn new_unchecked(path: impl Into) -> Self { - let path = path.into(); - AbsoluteSystemPathBuf(path) - } - /// Anchors `path` at `self`. /// /// # Arguments @@ -80,7 +73,7 @@ impl AbsoluteSystemPathBuf { &self, path: &AbsoluteSystemPathBuf, ) -> Result { - AnchoredSystemPathBuf::strip_root(self, path) + AnchoredSystemPathBuf::new(self, path) } /// Resolves `path` with `self` as anchor. @@ -129,16 +122,14 @@ impl AbsoluteSystemPathBuf { self.0.ends_with(child.as_ref()) } - pub fn join>(&self, path: P) -> AbsoluteSystemPathBuf { - AbsoluteSystemPathBuf(self.0.join(path)) - } - pub fn join_relative(&self, path: RelativeSystemPathBuf) -> AbsoluteSystemPathBuf { AbsoluteSystemPathBuf(self.0.join(path.as_path())) } pub fn to_str(&self) -> Result<&str, PathValidationError> { - self.0.to_str().ok_or(PathValidationError::InvalidUnicode) + self.0 + .to_str() + .ok_or_else(|| PathValidationError::InvalidUnicode(self.0.clone())) } pub fn to_string_lossy(&self) -> Cow<'_, str> { @@ -153,15 +144,17 @@ impl AbsoluteSystemPathBuf { self.0.exists() } - pub fn into_path_buf(self) -> PathBuf { - self.0 - } - pub fn extension(&self) -> Option<&OsStr> { self.0.extension() } } +impl Into for AbsoluteSystemPathBuf { + fn into(self) -> PathBuf { + self.0 + } +} + impl fmt::Display for AbsoluteSystemPathBuf { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.0.display().fmt(f) diff --git a/crates/turbopath/src/anchored_system_path_buf.rs b/crates/turbopath/src/anchored_system_path_buf.rs index 0b6c9127a9d2c..15a27085cb60b 100644 --- a/crates/turbopath/src/anchored_system_path_buf.rs +++ b/crates/turbopath/src/anchored_system_path_buf.rs @@ -20,7 +20,7 @@ impl TryFrom<&Path> for AnchoredSystemPathBuf { } impl AnchoredSystemPathBuf { - pub fn strip_root( + pub fn new( root: &AbsoluteSystemPathBuf, path: &AbsoluteSystemPathBuf, ) -> Result { @@ -33,7 +33,7 @@ impl AnchoredSystemPathBuf { Ok(AnchoredSystemPathBuf(stripped_path)) } - pub fn new_unchecked(path: impl Into) -> Self { + pub unsafe fn new_unchecked(path: impl Into) -> Self { AnchoredSystemPathBuf(path.into()) } @@ -41,11 +41,15 @@ impl AnchoredSystemPathBuf { self.0.as_path() } - pub fn into_path_buf(self) -> PathBuf { + pub fn to_str(&self) -> Result<&str, PathValidationError> { self.0 + .to_str() + .ok_or_else(|| PathValidationError::InvalidUnicode(self.0.clone())) } +} - pub fn to_str(&self) -> Result<&str, PathValidationError> { - self.0.to_str().ok_or(PathValidationError::InvalidUnicode) +impl Into for AnchoredSystemPathBuf { + fn into(self) -> PathBuf { + self.0 } } diff --git a/crates/turbopath/src/lib.rs b/crates/turbopath/src/lib.rs index fb41596cef130..32c54db387067 100644 --- a/crates/turbopath/src/lib.rs +++ b/crates/turbopath/src/lib.rs @@ -15,8 +15,8 @@ use thiserror::Error; // Custom error type for path validation errors #[derive(Debug, Error)] pub enum PathValidationError { - #[error("Path is non-UTF-8")] - InvalidUnicode, + #[error("Path is non-UTF-8: {0}")] + InvalidUnicode(PathBuf), #[error("Path is not absolute: {0}")] NotAbsolute(PathBuf), #[error("Path is not relative: {0}")] @@ -35,7 +35,9 @@ trait IntoUnix { impl IntoSystem for &Path { fn into_system(self) -> Result { - let path_str = self.to_str().ok_or(PathValidationError::InvalidUnicode)?; + let path_str = self + .to_str() + .ok_or_else(|| PathValidationError::InvalidUnicode(self.to_owned()))?; Ok(PathBuf::from_slash(path_str)) } @@ -48,7 +50,7 @@ impl IntoUnix for &Path { fn into_unix(self) -> Result { Ok(PathBuf::from( self.to_slash() - .ok_or(PathValidationError::InvalidUnicode)? + .ok_or_else(|| PathValidationError::InvalidUnicode(self.to_owned()))? .as_ref(), )) } diff --git a/crates/turbopath/src/relative_system_path_buf.rs b/crates/turbopath/src/relative_system_path_buf.rs index 2b794bd460192..67a2e5a03f55d 100644 --- a/crates/turbopath/src/relative_system_path_buf.rs +++ b/crates/turbopath/src/relative_system_path_buf.rs @@ -74,7 +74,9 @@ impl RelativeSystemPathBuf { } pub fn to_str(&self) -> Result<&str, PathValidationError> { - self.0.to_str().ok_or(PathValidationError::InvalidUnicode) + self.0 + .to_str() + .ok_or_else(|| PathValidationError::InvalidUnicode(self.0.clone())) } pub fn file_name(&self) -> Option<&OsStr> { diff --git a/crates/turbopath/src/relative_unix_path_buf.rs b/crates/turbopath/src/relative_unix_path_buf.rs index dd8c6448c1f29..b0e0a3f959d58 100644 --- a/crates/turbopath/src/relative_unix_path_buf.rs +++ b/crates/turbopath/src/relative_unix_path_buf.rs @@ -68,7 +68,9 @@ impl RelativeUnixPathBuf { } pub fn to_str(&self) -> Result<&str, PathValidationError> { - self.0.to_str().ok_or(PathValidationError::InvalidUnicode) + self.0 + .to_str() + .ok_or_else(|| PathValidationError::InvalidUnicode(self.0.clone())) } pub fn file_name(&self) -> Option<&OsStr> { diff --git a/crates/turborepo-lib/src/daemon/connector.rs b/crates/turborepo-lib/src/daemon/connector.rs index 9e9920a5a12d1..b2db064432cb0 100644 --- a/crates/turborepo-lib/src/daemon/connector.rs +++ b/crates/turborepo-lib/src/daemon/connector.rs @@ -255,7 +255,7 @@ impl DaemonConnector { } fn pid_lock(&self) -> pidlock::Pidlock { - pidlock::Pidlock::new(self.pid_file.clone().into_path_buf()) + pidlock::Pidlock::new(self.pid_file.clone().into()) } } From 0dba16cf64890de07e1d891416cfac3992e68cf8 Mon Sep 17 00:00:00 2001 From: --global Date: Wed, 5 Apr 2023 14:42:49 -0400 Subject: [PATCH 13/19] Removed new_unchcked and added to Rust tests --- .github/workflows/test.yml | 2 +- crates/turbopath/src/absolute_system_path_buf.rs | 2 +- crates/turbopath/src/anchored_system_path_buf.rs | 4 ---- crates/turbopath/src/relative_system_path_buf.rs | 4 ---- crates/turbopath/src/relative_unix_path_buf.rs | 5 ----- 5 files changed, 2 insertions(+), 15 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a5115565902d8..d9bbae08f1f22 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -608,7 +608,7 @@ jobs: timeout-minutes: 120 # We exclude turbo as it requires linking Go and all logic resides in turborepo-lib run: | - cargo test -p turborepo-lib -p turborepo-scm -p turborepo-lockfiles -p turborepo-api-client --features rustls-tls + cargo test -p turborepo-lib -p turborepo-scm -p turborepo-lockfiles -p turbopath -p turborepo-api-client --features rustls-tls turbopack_rust_test1: needs: [determine_jobs, rust_prepare] diff --git a/crates/turbopath/src/absolute_system_path_buf.rs b/crates/turbopath/src/absolute_system_path_buf.rs index 3153e69fdcc01..a48a1bf1e1e69 100644 --- a/crates/turbopath/src/absolute_system_path_buf.rs +++ b/crates/turbopath/src/absolute_system_path_buf.rs @@ -90,7 +90,7 @@ impl AbsoluteSystemPathBuf { /// use std::path::Path; /// use turbopath::{AbsoluteSystemPathBuf, AnchoredSystemPathBuf}; /// let absolute_path = AbsoluteSystemPathBuf::new("/Users/user").unwrap(); - /// let anchored_path = AnchoredSystemPathBuf::new_unchecked("Documents"); + /// let anchored_path = Path::new("Documents").try_into().unwrap(); /// let resolved_path = absolute_path.resolve(&anchored_path); /// #[cfg(windows)] /// assert_eq!(resolved_path.as_path(), Path::new("\\Users\\user\\Documents")); diff --git a/crates/turbopath/src/anchored_system_path_buf.rs b/crates/turbopath/src/anchored_system_path_buf.rs index 15a27085cb60b..6b9e67aed986d 100644 --- a/crates/turbopath/src/anchored_system_path_buf.rs +++ b/crates/turbopath/src/anchored_system_path_buf.rs @@ -33,10 +33,6 @@ impl AnchoredSystemPathBuf { Ok(AnchoredSystemPathBuf(stripped_path)) } - pub unsafe fn new_unchecked(path: impl Into) -> Self { - AnchoredSystemPathBuf(path.into()) - } - pub fn as_path(&self) -> &Path { self.0.as_path() } diff --git a/crates/turbopath/src/relative_system_path_buf.rs b/crates/turbopath/src/relative_system_path_buf.rs index 67a2e5a03f55d..663c18692815c 100644 --- a/crates/turbopath/src/relative_system_path_buf.rs +++ b/crates/turbopath/src/relative_system_path_buf.rs @@ -43,10 +43,6 @@ impl RelativeSystemPathBuf { Ok(RelativeSystemPathBuf(system_path)) } - pub fn new_unchecked(path: impl Into) -> Self { - RelativeSystemPathBuf(path.into()) - } - pub fn as_path(&self) -> &Path { &self.0 } diff --git a/crates/turbopath/src/relative_unix_path_buf.rs b/crates/turbopath/src/relative_unix_path_buf.rs index b0e0a3f959d58..90cf869fb927a 100644 --- a/crates/turbopath/src/relative_unix_path_buf.rs +++ b/crates/turbopath/src/relative_unix_path_buf.rs @@ -36,11 +36,6 @@ impl RelativeUnixPathBuf { Ok(RelativeUnixPathBuf(path.into_unix()?)) } - pub fn new_unchecked(path: impl Into) -> Self { - let path = path.into(); - RelativeUnixPathBuf(path) - } - pub fn as_path(&self) -> &Path { &self.0 } From 5874fefe23d6babed72eaedea73d64673cd3ea92 Mon Sep 17 00:00:00 2001 From: --global Date: Wed, 5 Apr 2023 15:29:34 -0400 Subject: [PATCH 14/19] Added specific test for Windows --- crates/turbopath/src/relative_unix_path_buf.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/crates/turbopath/src/relative_unix_path_buf.rs b/crates/turbopath/src/relative_unix_path_buf.rs index 90cf869fb927a..c20505dddd4e7 100644 --- a/crates/turbopath/src/relative_unix_path_buf.rs +++ b/crates/turbopath/src/relative_unix_path_buf.rs @@ -115,7 +115,10 @@ mod tests { #[test] fn test_relative_unix_path_buf_errors() { + #[cfg(not(windows))] assert!(RelativeUnixPathBuf::new(PathBuf::from("/foo/bar")).is_err()); + #[cfg(windows)] + assert!(RelativeUnixPathBuf::new(PathBuf::from("C:\\foo\\bar")).is_err()); } #[cfg(windows)] From c9d8351a68e9d6bed7d102cf3531e48ee97a516d Mon Sep 17 00:00:00 2001 From: --global Date: Wed, 5 Apr 2023 15:41:58 -0400 Subject: [PATCH 15/19] Adding more tests --- .../turbopath/src/absolute_system_path_buf.rs | 40 +++++++++++++++++++ crates/turbopath/src/lib.rs | 2 + 2 files changed, 42 insertions(+) diff --git a/crates/turbopath/src/absolute_system_path_buf.rs b/crates/turbopath/src/absolute_system_path_buf.rs index a48a1bf1e1e69..11c299292193a 100644 --- a/crates/turbopath/src/absolute_system_path_buf.rs +++ b/crates/turbopath/src/absolute_system_path_buf.rs @@ -166,3 +166,43 @@ impl AsRef for AbsoluteSystemPathBuf { self.0.as_path() } } + +#[cfg(test)] +mod tests { + use std::{assert_matches::assert_matches, path::Path}; + + use crate::{AbsoluteSystemPathBuf, AnchoredSystemPathBuf}; + + #[cfg(unix)] + #[test] + fn test_absolute_system_path_buf_on_unix() { + assert!(AbsoluteSystemPathBuf::new("/Users/user").is_ok()); + assert_matches!( + AbsoluteSystemPathBuf::new("./Users/user/"), + Err(PathValidationError::NotAbsolute(_)) + ); + + assert_matches!( + AbsoluteSystemPathBuf::new("Users"), + Err(PathValidationError::NotAbsolute(_)) + ); + } + + #[cfg(windows)] + #[test] + fn test_absolute_system_path_buf_on_windows() { + assert!(AbsoluteSystemPathBuf::new("C:\\Users\\user").is_ok()); + assert_matches!( + AbsoluteSystemPathBuf::new(".\\Users\\user\\"), + Err(PathValidationError::NotAbsolute(_)) + ); + assert_matches!( + AbsoluteSystemPathBuf::new("Users"), + Err(PathValidationError::NotAbsolute(_)) + ); + assert_matches!( + AbsoluteSystemPathBuf::new("/Users/home"), + Err(PathValidationError::NotAbsolute(_)) + ) + } +} diff --git a/crates/turbopath/src/lib.rs b/crates/turbopath/src/lib.rs index 32c54db387067..fa0b3c902ce3b 100644 --- a/crates/turbopath/src/lib.rs +++ b/crates/turbopath/src/lib.rs @@ -1,3 +1,5 @@ +#![feature(assert_matches)] + mod absolute_system_path_buf; mod anchored_system_path_buf; mod relative_system_path_buf; From 5eb688b182598752a22640e554da4dd864d6dd71 Mon Sep 17 00:00:00 2001 From: --global Date: Wed, 5 Apr 2023 16:04:48 -0400 Subject: [PATCH 16/19] fixed import --- crates/turbopath/src/absolute_system_path_buf.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/turbopath/src/absolute_system_path_buf.rs b/crates/turbopath/src/absolute_system_path_buf.rs index 11c299292193a..d460938a27b95 100644 --- a/crates/turbopath/src/absolute_system_path_buf.rs +++ b/crates/turbopath/src/absolute_system_path_buf.rs @@ -171,7 +171,7 @@ impl AsRef for AbsoluteSystemPathBuf { mod tests { use std::{assert_matches::assert_matches, path::Path}; - use crate::{AbsoluteSystemPathBuf, AnchoredSystemPathBuf}; + use crate::{AbsoluteSystemPathBuf, AnchoredSystemPathBuf, PathValidationError}; #[cfg(unix)] #[test] From 1b639a02bbba212e1306cc28f1f365cdc38b330c Mon Sep 17 00:00:00 2001 From: --global Date: Wed, 5 Apr 2023 16:51:38 -0400 Subject: [PATCH 17/19] More testing fixes --- .../turbopath/src/absolute_system_path_buf.rs | 31 ++++++++++++++----- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/crates/turbopath/src/absolute_system_path_buf.rs b/crates/turbopath/src/absolute_system_path_buf.rs index d460938a27b95..47ee68641983d 100644 --- a/crates/turbopath/src/absolute_system_path_buf.rs +++ b/crates/turbopath/src/absolute_system_path_buf.rs @@ -64,10 +64,21 @@ impl AbsoluteSystemPathBuf { /// ``` /// use std::path::Path; /// use turbopath::{AbsoluteSystemPathBuf, AnchoredSystemPathBuf}; - /// let base = AbsoluteSystemPathBuf::new("/Users/user").unwrap(); - /// let anchored_path = AbsoluteSystemPathBuf::new("/Users/user/Documents").unwrap(); - /// let anchored_path = base.anchor(&anchored_path).unwrap(); - /// assert_eq!(anchored_path.as_path(), Path::new("Documents")); + /// #[cfg(not(windows))] + /// { + /// let base = AbsoluteSystemPathBuf::new("/Users/user").unwrap(); + /// let anchored_path = AbsoluteSystemPathBuf::new("/Users/user/Documents").unwrap(); + /// let anchored_path = base.anchor(&anchored_path).unwrap(); + /// assert_eq!(anchored_path.as_path(), Path::new("Documents")); + /// } + /// + /// #[cfg(windows)] + /// { + /// let base = AbsoluteSystemPathBuf::new("C:\\Users\\user").unwrap(); + /// let anchored_path = AbsoluteSystemPathBuf::new("C:\\Users\\user\\Documents").unwrap(); + /// let anchored_path = base.anchor(&anchored_path).unwrap(); + /// assert_eq!(anchored_path.as_path(), Path::new("Documents")); + /// } /// ``` pub fn anchor( &self, @@ -89,11 +100,15 @@ impl AbsoluteSystemPathBuf { /// ``` /// use std::path::Path; /// use turbopath::{AbsoluteSystemPathBuf, AnchoredSystemPathBuf}; + /// #[cfg(not(windows))] /// let absolute_path = AbsoluteSystemPathBuf::new("/Users/user").unwrap(); + /// + /// #[cfg(windows)] + /// let absolute_path = AbsoluteSystemPathBuf::new("C:\\Users\\user").unwrap(); /// let anchored_path = Path::new("Documents").try_into().unwrap(); /// let resolved_path = absolute_path.resolve(&anchored_path); /// #[cfg(windows)] - /// assert_eq!(resolved_path.as_path(), Path::new("\\Users\\user\\Documents")); + /// assert_eq!(resolved_path.as_path(), Path::new("C:\\Users\\user\\Documents")); /// assert_eq!(resolved_path.as_path(), Path::new("/Users/user/Documents")); /// ``` pub fn resolve(&self, path: &AnchoredSystemPathBuf) -> AbsoluteSystemPathBuf { @@ -169,11 +184,11 @@ impl AsRef for AbsoluteSystemPathBuf { #[cfg(test)] mod tests { - use std::{assert_matches::assert_matches, path::Path}; + use std::assert_matches::assert_matches; - use crate::{AbsoluteSystemPathBuf, AnchoredSystemPathBuf, PathValidationError}; + use crate::{AbsoluteSystemPathBuf, PathValidationError}; - #[cfg(unix)] + #[cfg(not(windows))] #[test] fn test_absolute_system_path_buf_on_unix() { assert!(AbsoluteSystemPathBuf::new("/Users/user").is_ok()); From e542afae6fce4523e6257fd2af4d5a607caa029b Mon Sep 17 00:00:00 2001 From: --global Date: Thu, 6 Apr 2023 10:52:53 -0400 Subject: [PATCH 18/19] More windows fixes --- crates/turbopath/src/absolute_system_path_buf.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/crates/turbopath/src/absolute_system_path_buf.rs b/crates/turbopath/src/absolute_system_path_buf.rs index 47ee68641983d..5c0194446d09b 100644 --- a/crates/turbopath/src/absolute_system_path_buf.rs +++ b/crates/turbopath/src/absolute_system_path_buf.rs @@ -102,14 +102,16 @@ impl AbsoluteSystemPathBuf { /// use turbopath::{AbsoluteSystemPathBuf, AnchoredSystemPathBuf}; /// #[cfg(not(windows))] /// let absolute_path = AbsoluteSystemPathBuf::new("/Users/user").unwrap(); - /// /// #[cfg(windows)] /// let absolute_path = AbsoluteSystemPathBuf::new("C:\\Users\\user").unwrap(); + /// /// let anchored_path = Path::new("Documents").try_into().unwrap(); /// let resolved_path = absolute_path.resolve(&anchored_path); + /// + /// #[cfg(not(windows))] + /// assert_eq!(resolved_path.as_path(), Path::new("/Users/user/Documents")); /// #[cfg(windows)] /// assert_eq!(resolved_path.as_path(), Path::new("C:\\Users\\user\\Documents")); - /// assert_eq!(resolved_path.as_path(), Path::new("/Users/user/Documents")); /// ``` pub fn resolve(&self, path: &AnchoredSystemPathBuf) -> AbsoluteSystemPathBuf { AbsoluteSystemPathBuf(self.0.join(path.as_path())) From 3a6be5eb73841a753829254393e733f25ae88588 Mon Sep 17 00:00:00 2001 From: --global Date: Thu, 6 Apr 2023 11:42:48 -0400 Subject: [PATCH 19/19] Formatting fix --- crates/turborepo-lib/Cargo.toml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/crates/turborepo-lib/Cargo.toml b/crates/turborepo-lib/Cargo.toml index 408b1ae6f710f..fd8a80b76df8a 100644 --- a/crates/turborepo-lib/Cargo.toml +++ b/crates/turborepo-lib/Cargo.toml @@ -68,12 +68,13 @@ tonic = { version = "0.8.3", features = ["transport"] } tower = "0.4.13" uds_windows = "1.0.2" url = "2.3.1" -webbrowser = { workspace = true } const_format = "0.2.30" turbo-updater = { workspace = true } -turborepo-api-client = { workspace = true } turbopath = { workspace = true } +turborepo-api-client = { workspace = true } +webbrowser = { workspace = true } + [target.'cfg(target_os = "windows")'.dependencies] uds_windows = "1.0.2"