feat: merge fs and std crate (#1203)

* feat: merge fs and std crate

* fix: errors
This commit is contained in:
Myriad-Dreamin 2025-01-20 23:00:31 +08:00 committed by GitHub
parent 04f688e122
commit 8ca6c8118c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
41 changed files with 449 additions and 325 deletions

View file

@ -1,6 +1,6 @@
[package]
name = "tinymist-std"
description = "Additional functions wrapping Rust's std library."
description = "Additional functions wrapping Rust's standard library."
authors.workspace = true
version.workspace = true
license.workspace = true
@ -10,32 +10,54 @@ repository.workspace = true
[dependencies]
comemo.workspace = true
ecow.workspace = true
parking_lot.workspace = true
web-time.workspace = true
wasm-bindgen = { workspace = true, optional = true }
js-sys = { workspace = true, optional = true }
bitvec = { version = "1" }
dashmap = { version = "5" }
# tiny-skia-path.workspace = true
path-clean.workspace = true
anyhow.workspace = true
base64.workspace = true
bitvec.workspace = true
comemo.workspace = true
dashmap.workspace = true
ecow.workspace = true
fxhash.workspace = true
log.workspace = true
path-clean.workspace = true
parking_lot.workspace = true
rustc-hash.workspace = true
siphasher.workspace = true
serde = { workspace = true, features = ["derive"] }
serde_repr = "0.1"
serde_repr.workspace = true
serde_json.workspace = true
serde_with.workspace = true
siphasher.workspace = true
web-time.workspace = true
tempfile = { workspace = true, optional = true }
same-file = { workspace = true, optional = true }
# feature = "web"
js-sys = { workspace = true, optional = true }
wasm-bindgen = { workspace = true, optional = true }
# feature = "rkyv"
rkyv = { workspace = true, optional = true }
# feature = "typst"
typst = { workspace = true, optional = true }
typst-shim = { workspace = true, optional = true }
[target.'cfg(target_os = "macos")'.dependencies]
core-foundation.workspace = true
[target.'cfg(unix)'.dependencies]
libc.workspace = true
[target.'cfg(windows)'.dependencies]
windows-sys = { workspace = true, features = [
"Win32_Foundation",
"Win32_Security",
"Win32_Storage_FileSystem",
"Win32_System_IO",
"Win32_System_Console",
"Win32_System_JobObjects",
"Win32_System_Threading",
] }
[dev-dependencies]
hex.workspace = true
@ -48,13 +70,11 @@ typst = ["dep:typst", "dep:typst-shim"]
rkyv = ["dep:rkyv", "rkyv/alloc", "rkyv/archive_le"]
rkyv-validation = ["dep:rkyv", "rkyv/validation"]
# flat-vector = ["rkyv", "rkyv-validation"]
__web = ["dep:wasm-bindgen", "dep:js-sys"]
web = ["__web"]
system = []
system = ["dep:tempfile", "dep:same-file"]
bi-hash = []
item-dashmap = []
[lints]
workspace = true

View file

@ -1,3 +1,5 @@
//! A map that shards items by their fingerprint.
use std::{collections::HashMap, num::NonZeroU32};
use crate::hash::Fingerprint;
@ -31,12 +33,13 @@ fn default_shard_size() -> NonZeroU32 {
type FMapBase<V> = parking_lot::RwLock<HashMap<Fingerprint, V>>;
/// A map that shards items by their fingerprint.
/// A map that shards items by their fingerprint. This is faster
/// than the dashmap in some cases.
///
/// It is fast since a fingerprint could split items into different shards
/// efficiently.
///
/// Note: If a fingerprint is calculated from a hash function, it is not
/// Note: If a fingerprint is not calculated from a hash function, it is not
/// guaranteed that the fingerprint is evenly distributed. Thus, in that case,
/// the performance of this map is not guaranteed.
pub struct FingerprintMap<V> {
@ -76,6 +79,7 @@ impl<V> FingerprintMap<V> {
.flat_map(|shard| shard.into_inner().into_iter())
}
/// Get the shard
pub fn shard(&self, fg: Fingerprint) -> &FMapBase<V> {
let shards = &self.shards;
let route_idx = (fg.lower32() & self.mask) as usize;
@ -92,6 +96,7 @@ impl<V> FingerprintMap<V> {
&mut self.shards
}
/// Checks if the map is empty.
pub fn contains_key(&self, fg: &Fingerprint) -> bool {
self.shard(*fg).read().contains_key(fg)
}

View file

@ -1,3 +1,5 @@
//! This module contains the implementation of the abstract data types.
pub mod fmap;
pub use fmap::FingerprintMap;

View file

@ -1,6 +1,16 @@
//! <https://github.com/rust-analyzer/rowan/blob/v0.16.1/src/cow_mut.rs>
//!
//! This module provides a `CowMut` type, which is a mutable version of `Cow`.
//! Although it is strange that we can have a `CowMut`, because it should "copy
//! on write", we also don't love the `Cow` API and use `Cow` without even
//! touching its `DerefMut` feature.
/// A mutable version of [Cow][`std::borrow::Cow`].
#[derive(Debug)]
pub enum CowMut<'a, T> {
/// An owned data.
Owned(T),
/// A borrowed mut data.
Borrowed(&'a mut T),
}

View file

@ -1,61 +0,0 @@
//todo: move to core/src/hash.rs
use std::{
hash::{Hash, Hasher},
ops::Deref,
};
use crate::hash::item_hash128;
pub trait StaticHash128 {
fn get_hash(&self) -> u128;
}
impl Hash for dyn StaticHash128 {
#[inline]
fn hash<H: Hasher>(&self, state: &mut H) {
state.write_u128(self.get_hash());
}
}
pub struct HashedTrait<T: ?Sized> {
hash: u128,
t: Box<T>,
}
impl<T: ?Sized> HashedTrait<T> {
pub fn new(hash: u128, t: Box<T>) -> Self {
Self { hash, t }
}
}
impl<T: ?Sized> Deref for HashedTrait<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.t
}
}
impl<T> Hash for HashedTrait<T> {
#[inline]
fn hash<H: Hasher>(&self, state: &mut H) {
state.write_u128(self.hash);
}
}
impl<T: Hash + Default + 'static> Default for HashedTrait<T> {
fn default() -> Self {
let t = T::default();
Self {
hash: item_hash128(&t),
t: Box::new(t),
}
}
}
impl<T: ?Sized> StaticHash128 for HashedTrait<T> {
fn get_hash(&self) -> u128 {
self.hash
}
}

View file

@ -7,6 +7,7 @@ use serde_with::{
};
use serde_with::{DeserializeAs, SerializeAs};
/// A marker type for serializing and deserializing `Cow<[u8]>` as base64.
pub struct AsCowBytes;
type StdBase64 = Base64<Standard, Padded>;

View file

@ -3,9 +3,6 @@ use std::{path::Path, sync::Arc};
pub use takable::*;
mod hash;
pub use hash::*;
pub mod cow_mut;
mod query;
@ -17,6 +14,9 @@ pub use read::*;
mod marker;
pub use marker::*;
/// An immutable string.
pub type ImmutStr = Arc<str>;
/// An immutable byte slice.
pub type ImmutBytes = Arc<[u8]>;
/// An immutable path.
pub type ImmutPath = Arc<Path>;

View file

@ -15,6 +15,7 @@ pub struct QueryRef<Res, Err, QueryContext = ()> {
}
impl<T, E, QC> QueryRef<T, E, QC> {
/// Create a new query reference with the given value.
pub fn with_value(value: T) -> Self {
let cell = OnceLock::new();
cell.get_or_init(|| Ok(value));
@ -24,6 +25,8 @@ impl<T, E, QC> QueryRef<T, E, QC> {
}
}
/// Create a new query reference with the given context to execute the
/// query.
pub fn with_context(ctx: QC) -> Self {
Self {
ctx: Mutex::new(Some(ctx)),

View file

@ -1,3 +1,5 @@
/// A trait for reading all data from a source into a buffer.
pub trait ReadAllOnce {
/// Reads all data from the source into the buffer.
fn read_all(self, buf: &mut Vec<u8>) -> std::io::Result<usize>;
}

View file

@ -1,3 +1,6 @@
//! The debug location that can be used to locate a position in a document or a
//! file.
use core::fmt;
use serde::{Deserialize, Serialize};
@ -38,6 +41,7 @@ pub type RawSourceSpan = u64;
/// See [`CharPosition`] for the definition of the position inside a file.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FileLocation {
/// The file path.
pub filepath: String,
}
@ -73,11 +77,14 @@ impl From<Option<(usize, usize)>> for CharPosition {
/// See [`CharPosition`] for the definition of the position inside a file.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SourceLocation {
/// The file path.
pub filepath: String,
/// The position in the file.
pub pos: CharPosition,
}
impl SourceLocation {
/// Create a new source location.
pub fn from_flat(
flat: FlatSourceLocation,
i: &impl std::ops::Index<usize, Output = FileLocation>,
@ -94,16 +101,20 @@ impl SourceLocation {
/// See [`CharPosition`] for the definition of the position inside a file.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FlatSourceLocation {
/// The file path.
pub filepath: u32,
/// The position in the file.
pub pos: CharPosition,
}
// /// A resolved file range.
// ///
// /// See [`CharPosition`] for the definition of the position inside a file.
/// A resolved file range.
///
/// See [`CharPosition`] for the definition of the position inside a file.
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct CharRange {
/// The start position.
pub start: CharPosition,
/// The end position.
pub end: CharPosition,
}
@ -117,12 +128,14 @@ impl fmt::Display for CharRange {
}
}
// /// A resolved source (text) range.
// ///
// /// See [`CharPosition`] for the definition of the position inside a file.
/// A resolved source (text) range.
///
/// See [`CharPosition`] for the definition of the position inside a file.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SourceRange {
/// The file path.
pub path: String,
/// The range in the file.
pub range: CharRange,
}
@ -144,7 +157,10 @@ mod typst_ext {
/// text or string content.
#[derive(Debug, Clone, Copy)]
pub struct SourceSpanOffset {
/// The source span.
pub span: SourceSpan,
/// The offset relative to the start of the span. This is usually useful
/// if the location is not a span created by the parser.
pub offset: usize,
}

View file

@ -1,3 +1,5 @@
//! Error handling utilities for the `tinymist` crate.
use core::fmt;
use ecow::EcoString;
@ -5,12 +7,17 @@ use serde::{Deserialize, Serialize};
use crate::debug_loc::CharRange;
/// The severity of a diagnostic message, following the LSP specification.
#[derive(serde_repr::Serialize_repr, serde_repr::Deserialize_repr, Debug, Clone)]
#[repr(u8)]
pub enum DiagSeverity {
/// An error message.
Error = 1,
/// A warning message.
Warning = 2,
/// An information message.
Information = 3,
/// A hint message.
Hint = 4,
}
@ -26,30 +33,41 @@ impl fmt::Display for DiagSeverity {
}
/// <https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#diagnostic>
/// The `owner` and `source` fields are not included in the struct, but they
/// could be added to `ErrorImpl::arguments`.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DiagMessage {
/// The typst package specifier.
pub package: String,
/// The file path relative to the root of the workspace or the package.
pub path: String,
/// The diagnostic message.
pub message: String,
/// The severity of the diagnostic message.
pub severity: DiagSeverity,
/// The char range in the file. The position encoding must be negotiated.
pub range: Option<CharRange>,
// These field could be added to ErrorImpl::arguments
// owner: Option<ImmutStr>,
// source: ImmutStr,
}
impl DiagMessage {}
/// ALl kind of errors that can occur in the `tinymist` crate.
#[derive(Debug, Clone)]
#[non_exhaustive]
pub enum ErrKind {
/// No message.
None,
/// A string message.
Msg(EcoString),
/// A source diagnostic message.
Diag(Box<DiagMessage>),
/// An inner error.
Inner(Error),
}
/// A trait to convert an error kind into an error kind.
pub trait ErrKindExt {
/// Convert the error kind into an error kind.
fn to_error_kind(self) -> ErrKind;
}
@ -65,6 +83,12 @@ impl ErrKindExt for std::io::Error {
}
}
impl ErrKindExt for std::str::Utf8Error {
fn to_error_kind(self) -> ErrKind {
ErrKind::Msg(self.to_string().into())
}
}
impl ErrKindExt for String {
fn to_error_kind(self) -> ErrKind {
ErrKind::Msg(self.into())
@ -101,11 +125,27 @@ impl ErrKindExt for serde_json::Error {
}
}
impl ErrKindExt for anyhow::Error {
fn to_error_kind(self) -> ErrKind {
ErrKind::Msg(self.to_string().into())
}
}
impl ErrKindExt for Error {
fn to_error_kind(self) -> ErrKind {
ErrKind::Msg(self.to_string().into())
}
}
/// The internal error implementation.
#[derive(Debug, Clone)]
pub struct ErrorImpl {
/// A static error identifier.
loc: &'static str,
/// The kind of error.
kind: ErrKind,
arguments: Box<[(&'static str, String)]>,
/// Additional extractable arguments for the error.
args: Option<Box<[(&'static str, String)]>>,
}
/// This type represents all possible errors that can occur in typst.ts
@ -118,43 +158,65 @@ pub struct Error {
}
impl Error {
pub fn new(loc: &'static str, kind: ErrKind, arguments: Box<[(&'static str, String)]>) -> Self {
/// Creates a new error.
pub fn new(
loc: &'static str,
kind: ErrKind,
args: Option<Box<[(&'static str, String)]>>,
) -> Self {
Self {
err: Box::new(ErrorImpl {
loc,
kind,
arguments,
}),
err: Box::new(ErrorImpl { loc, kind, args }),
}
}
/// Returns the location of the error.
pub fn loc(&self) -> &'static str {
self.err.loc
}
/// Returns the kind of the error.
pub fn kind(&self) -> &ErrKind {
&self.err.kind
}
/// Returns the arguments of the error.
pub fn arguments(&self) -> &[(&'static str, String)] {
&self.err.arguments
self.err.args.as_deref().unwrap_or_default()
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let err = &self.err;
match &err.kind {
ErrKind::Msg(msg) => write!(f, "{}: {} with {:?}", err.loc, msg, err.arguments),
ErrKind::Diag(diag) => {
write!(f, "{}: {} with {:?}", err.loc, diag.message, err.arguments)
if err.loc.is_empty() {
match &err.kind {
ErrKind::Msg(msg) => write!(f, "{msg} with {:?}", err.args),
ErrKind::Diag(diag) => {
write!(f, "{} with {:?}", diag.message, err.args)
}
ErrKind::Inner(e) => write!(f, "{e} with {:?}", err.args),
ErrKind::None => write!(f, "error with {:?}", err.args),
}
} else {
match &err.kind {
ErrKind::Msg(msg) => write!(f, "{}: {} with {:?}", err.loc, msg, err.args),
ErrKind::Diag(diag) => {
write!(f, "{}: {} with {:?}", err.loc, diag.message, err.args)
}
ErrKind::Inner(e) => write!(f, "{}: {} with {:?}", err.loc, e, err.args),
ErrKind::None => write!(f, "{}: with {:?}", err.loc, err.args),
}
ErrKind::Inner(e) => write!(f, "{}: {} with {:?}", err.loc, e, err.arguments),
ErrKind::None => write!(f, "{}: with {:?}", err.loc, err.arguments),
}
}
}
impl From<anyhow::Error> for Error {
fn from(e: anyhow::Error) -> Self {
Error::new("", e.to_string().to_error_kind(), None)
}
}
impl std::error::Error for Error {}
#[cfg(feature = "web")]
@ -178,47 +240,94 @@ impl From<&Error> for wasm_bindgen::JsValue {
}
}
/// The result type used in the `tinymist` crate.
pub type Result<T, Err = Error> = std::result::Result<T, Err>;
/// A trait to add context to a result.
pub trait WithContext<T>: Sized {
/// Add a context to the result.
fn context(self, loc: &'static str) -> Result<T>;
/// Add a context to the result with additional arguments.
fn with_context<F>(self, loc: &'static str, f: F) -> Result<T>
where
F: FnOnce() -> Option<Box<[(&'static str, String)]>>;
}
impl<T, E: ErrKindExt> WithContext<T> for Result<T, E> {
fn context(self, loc: &'static str) -> Result<T> {
self.map_err(|e| Error::new(loc, e.to_error_kind(), None))
}
fn with_context<F>(self, loc: &'static str, f: F) -> Result<T>
where
F: FnOnce() -> Option<Box<[(&'static str, String)]>>,
{
self.map_err(|e| Error::new(loc, e.to_error_kind(), f()))
}
}
impl<T> WithContext<T> for Option<T> {
fn context(self, loc: &'static str) -> Result<T> {
self.ok_or_else(|| Error::new(loc, ErrKind::None, None))
}
fn with_context<F>(self, loc: &'static str, f: F) -> Result<T>
where
F: FnOnce() -> Option<Box<[(&'static str, String)]>>,
{
self.ok_or_else(|| Error::new(loc, ErrKind::None, f()))
}
}
/// A trait to add context to a result without a specific error type.
pub trait WithContextUntyped<T>: Sized {
/// Add a context to the result.
fn context_ut(self, loc: &'static str) -> Result<T>;
/// Add a context to the result with additional arguments.
fn with_context_ut<F>(self, loc: &'static str, f: F) -> Result<T>
where
F: FnOnce() -> Option<Box<[(&'static str, String)]>>;
}
impl<T, E: std::fmt::Display> WithContextUntyped<T> for Result<T, E> {
fn context_ut(self, loc: &'static str) -> Result<T> {
self.map_err(|e| Error::new(loc, ErrKind::Msg(ecow::eco_format!("{e}")), None))
}
fn with_context_ut<F>(self, loc: &'static str, f: F) -> Result<T>
where
F: FnOnce() -> Option<Box<[(&'static str, String)]>>,
{
self.map_err(|e| Error::new(loc, ErrKind::Msg(ecow::eco_format!("{e}")), f()))
}
}
/// The error prelude.
pub mod prelude {
#![allow(missing_docs)]
use super::ErrKindExt;
use crate::Error;
pub type ZResult<T> = Result<T, Error>;
pub trait WithContext<T>: Sized {
fn context(self, loc: &'static str) -> ZResult<T>;
fn with_context<F>(self, loc: &'static str, f: F) -> ZResult<T>
where
F: FnOnce() -> Box<[(&'static str, String)]>;
}
impl<T, E: ErrKindExt> WithContext<T> for Result<T, E> {
fn context(self, loc: &'static str) -> ZResult<T> {
self.map_err(|e| Error::new(loc, e.to_error_kind(), Box::new([])))
}
fn with_context<F>(self, loc: &'static str, f: F) -> ZResult<T>
where
F: FnOnce() -> Box<[(&'static str, String)]>,
{
self.map_err(|e| Error::new(loc, e.to_error_kind(), f()))
}
}
pub use super::{WithContext, WithContextUntyped};
pub use crate::Result;
pub fn map_string_err<T: ToString>(loc: &'static str) -> impl Fn(T) -> Error {
move |e| Error::new(loc, e.to_string().to_error_kind(), Box::new([]))
move |e| Error::new(loc, e.to_string().to_error_kind(), None)
}
pub fn map_into_err<S: ErrKindExt, T: Into<S>>(loc: &'static str) -> impl Fn(T) -> Error {
move |e| Error::new(loc, e.into().to_error_kind(), Box::new([]))
move |e| Error::new(loc, e.into().to_error_kind(), None)
}
pub fn map_err<T: ErrKindExt>(loc: &'static str) -> impl Fn(T) -> Error {
move |e| Error::new(loc, e.to_error_kind(), Box::new([]))
move |e| Error::new(loc, e.to_error_kind(), None)
}
pub fn wrap_err(loc: &'static str) -> impl Fn(Error) -> Error {
move |e| Error::new(loc, crate::ErrKind::Inner(e), Box::new([]))
move |e| Error::new(loc, crate::ErrKind::Inner(e), None)
}
pub fn map_string_err_with_args<
@ -226,13 +335,13 @@ pub mod prelude {
Args: IntoIterator<Item = (&'static str, String)>,
>(
loc: &'static str,
arguments: Args,
args: Args,
) -> impl FnOnce(T) -> Error {
move |e| {
Error::new(
loc,
e.to_string().to_error_kind(),
arguments.into_iter().collect::<Vec<_>>().into_boxed_slice(),
Some(args.into_iter().collect::<Vec<_>>().into_boxed_slice()),
)
}
}
@ -243,49 +352,49 @@ pub mod prelude {
Args: IntoIterator<Item = (&'static str, String)>,
>(
loc: &'static str,
arguments: Args,
args: Args,
) -> impl FnOnce(T) -> Error {
move |e| {
Error::new(
loc,
e.into().to_error_kind(),
arguments.into_iter().collect::<Vec<_>>().into_boxed_slice(),
Some(args.into_iter().collect::<Vec<_>>().into_boxed_slice()),
)
}
}
pub fn map_err_with_args<T: ErrKindExt, Args: IntoIterator<Item = (&'static str, String)>>(
loc: &'static str,
arguments: Args,
args: Args,
) -> impl FnOnce(T) -> Error {
move |e| {
Error::new(
loc,
e.to_error_kind(),
arguments.into_iter().collect::<Vec<_>>().into_boxed_slice(),
Some(args.into_iter().collect::<Vec<_>>().into_boxed_slice()),
)
}
}
pub fn wrap_err_with_args<Args: IntoIterator<Item = (&'static str, String)>>(
loc: &'static str,
arguments: Args,
args: Args,
) -> impl FnOnce(Error) -> Error {
move |e| {
Error::new(
loc,
crate::ErrKind::Inner(e),
arguments.into_iter().collect::<Vec<_>>().into_boxed_slice(),
Some(args.into_iter().collect::<Vec<_>>().into_boxed_slice()),
)
}
}
pub fn _error_once(loc: &'static str, args: Box<[(&'static str, String)]>) -> Error {
Error::new(loc, crate::ErrKind::None, args)
Error::new(loc, crate::ErrKind::None, Some(args))
}
pub fn _msg(loc: &'static str, msg: EcoString) -> Error {
Error::new(loc, crate::ErrKind::Msg(msg), Box::new([]))
Error::new(loc, crate::ErrKind::Msg(msg), None)
}
pub use ecow::eco_format as _eco_format;

View file

@ -0,0 +1,6 @@
//! Filesystem support for tinymist.
#[cfg(feature = "system")]
pub mod flock;
#[cfg(feature = "system")]
pub mod paths;

View file

@ -0,0 +1,563 @@
//! Upstream: <https://github.com/rust-lang/cargo/blob/rust-1.83.0/src/cargo/util/flock.rs>
//! File-locking support.
//!
//! This module defines the [`Filesystem`] type which is an abstraction over a
//! filesystem, ensuring that access to the filesystem is only done through
//! coordinated locks.
//!
//! The [`FileLock`] type represents a locked file, and provides access to the
//! file.
use std::fs::{File, OpenOptions};
use std::io;
use std::io::{Read, Seek, SeekFrom, Write};
use std::path::{Display, Path, PathBuf};
use self::sys::*;
use super::paths;
use anyhow::Context as _;
use anyhow::Result;
/// A locked file.
///
/// This provides access to file while holding a lock on the file. This type
/// implements the [`Read`], [`Write`], and [`Seek`] traits to provide access
/// to the underlying file.
///
/// Locks are either shared (multiple processes can access the file) or
/// exclusive (only one process can access the file).
///
/// This type is created via methods on the [`Filesystem`] type.
///
/// When this value is dropped, the lock will be released.
#[derive(Debug)]
pub struct FileLock {
f: Option<File>,
path: PathBuf,
}
impl FileLock {
/// Returns the underlying file handle of this lock.
pub fn file(&self) -> &File {
self.f.as_ref().unwrap()
}
/// Returns the underlying path that this lock points to.
///
/// Note that special care must be taken to ensure that the path is not
/// referenced outside the lifetime of this lock.
pub fn path(&self) -> &Path {
&self.path
}
/// Returns the parent path containing this file
pub fn parent(&self) -> &Path {
self.path.parent().unwrap()
}
/// Removes all sibling files to this locked file.
///
/// This can be useful if a directory is locked with a sentinel file but it
/// needs to be cleared out as it may be corrupt.
pub fn remove_siblings(&self) -> Result<()> {
let path = self.path();
for entry in path.parent().unwrap().read_dir()? {
let entry = entry?;
if Some(&entry.file_name()[..]) == path.file_name() {
continue;
}
let kind = entry.file_type()?;
if kind.is_dir() {
paths::remove_dir_all(entry.path())?;
} else {
paths::remove_file(entry.path())?;
}
}
Ok(())
}
}
impl Read for FileLock {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
self.file().read(buf)
}
}
impl Seek for FileLock {
fn seek(&mut self, to: SeekFrom) -> io::Result<u64> {
self.file().seek(to)
}
}
impl Write for FileLock {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.file().write(buf)
}
fn flush(&mut self) -> io::Result<()> {
self.file().flush()
}
}
impl Drop for FileLock {
fn drop(&mut self) {
if let Some(f) = self.f.take() {
if let Err(e) = unlock(&f) {
log::warn!("failed to release lock: {e:?}");
}
}
}
}
/// A "filesystem" is intended to be a globally shared, hence locked, resource
/// in Cargo.
///
/// The `Path` of a filesystem cannot be learned unless it's done in a locked
/// fashion, and otherwise functions on this structure are prepared to handle
/// concurrent invocations across multiple instances of Cargo.
///
/// The methods on `Filesystem` that open files return a [`FileLock`] which
/// holds the lock, and that type provides methods for accessing the
/// underlying file.
///
/// If the blocking methods (like [`Filesystem::open_ro_shared`]) detect that
/// they will block, then they will display a message to the user letting them
/// know it is blocked. There are non-blocking variants starting with the
/// `try_` prefix like [`Filesystem::try_open_ro_shared_create`].
///
/// The behavior of locks acquired by the `Filesystem` depend on the operating
/// system. On unix-like system, they are advisory using [`flock`], and thus
/// not enforced against processes which do not try to acquire the lock. On
/// Windows, they are mandatory using [`LockFileEx`], enforced against all
/// processes.
///
/// This **does not** guarantee that a lock is acquired. In some cases, for
/// example on filesystems that don't support locking, it will return a
/// [`FileLock`] even though the filesystem lock was not acquired. This is
/// intended to provide a graceful fallback instead of refusing to work.
/// Usually there aren't multiple processes accessing the same resource. In
/// that case, it is the user's responsibility to not run concurrent
/// processes.
///
/// [`flock`]: https://linux.die.net/man/2/flock
/// [`LockFileEx`]: https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-lockfileex
#[derive(Clone, Debug)]
pub struct Filesystem {
root: PathBuf,
}
impl Filesystem {
/// Creates a new filesystem to be rooted at the given path.
pub fn new(path: PathBuf) -> Filesystem {
Filesystem { root: path }
}
/// Like `Path::join`, creates a new filesystem rooted at this filesystem
/// joined with the given path.
pub fn join<T: AsRef<Path>>(&self, other: T) -> Filesystem {
Filesystem::new(self.root.join(other))
}
/// Like `Path::push`, pushes a new path component onto this filesystem.
pub fn push<T: AsRef<Path>>(&mut self, other: T) {
self.root.push(other);
}
/// Consumes this filesystem and returns the underlying `PathBuf`.
///
/// Note that this is a relatively dangerous operation and should be used
/// with great caution!.
pub fn into_path_unlocked(self) -> PathBuf {
self.root
}
/// Returns the underlying `Path`.
///
/// Note that this is a relatively dangerous operation and should be used
/// with great caution!.
pub fn as_path_unlocked(&self) -> &Path {
&self.root
}
/// Creates the directory pointed to by this filesystem.
///
/// Handles errors where other Cargo processes are also attempting to
/// concurrently create this directory.
pub fn create_dir(&self) -> Result<()> {
paths::create_dir_all(&self.root)
}
/// Returns an adaptor that can be used to print the path of this
/// filesystem.
pub fn display(&self) -> Display<'_> {
self.root.display()
}
/// Opens read-write exclusive access to a file, returning the locked
/// version of a file.
///
/// This function will create a file at `path` if it doesn't already exist
/// (including intermediate directories), and then it will acquire an
/// exclusive lock on `path`. If the process must block waiting for the
/// lock, the `msg` is printed to stderr.
///
/// The returned file can be accessed to look at the path and also has
/// read/write access to the underlying file.
pub fn open_rw_exclusive_create<P>(&self, path: P, msg: &str) -> Result<FileLock>
where
P: AsRef<Path>,
{
let mut opts = OpenOptions::new();
opts.read(true).write(true).create(true);
let (path, f) = self.open(path.as_ref(), &opts, true)?;
acquire(msg, &path, &|| try_lock_exclusive(&f), &|| {
lock_exclusive(&f)
})?;
Ok(FileLock { f: Some(f), path })
}
/// A non-blocking version of [`Filesystem::open_rw_exclusive_create`].
///
/// Returns `None` if the operation would block due to another process
/// holding the lock.
pub fn try_open_rw_exclusive_create<P: AsRef<Path>>(
&self,
path: P,
) -> Result<Option<FileLock>> {
let mut opts = OpenOptions::new();
opts.read(true).write(true).create(true);
let (path, f) = self.open(path.as_ref(), &opts, true)?;
if try_acquire(&path, &|| try_lock_exclusive(&f))? {
Ok(Some(FileLock { f: Some(f), path }))
} else {
Ok(None)
}
}
/// Opens read-only shared access to a file, returning the locked version of
/// a file.
///
/// This function will fail if `path` doesn't already exist, but if it does
/// then it will acquire a shared lock on `path`. If the process must block
/// waiting for the lock, the `msg` is printed to stderr.
///
/// The returned file can be accessed to look at the path and also has read
/// access to the underlying file. Any writes to the file will return an
/// error.
pub fn open_ro_shared<P>(&self, path: P, msg: &str) -> Result<FileLock>
where
P: AsRef<Path>,
{
let (path, f) = self.open(path.as_ref(), OpenOptions::new().read(true), false)?;
acquire(msg, &path, &|| try_lock_shared(&f), &|| lock_shared(&f))?;
Ok(FileLock { f: Some(f), path })
}
/// Opens read-only shared access to a file, returning the locked version of
/// a file.
///
/// Compared to [`Filesystem::open_ro_shared`], this will create the file
/// (and any directories in the parent) if the file does not already
/// exist.
pub fn open_ro_shared_create<P: AsRef<Path>>(&self, path: P, msg: &str) -> Result<FileLock> {
let mut opts = OpenOptions::new();
opts.read(true).write(true).create(true);
let (path, f) = self.open(path.as_ref(), &opts, true)?;
acquire(msg, &path, &|| try_lock_shared(&f), &|| lock_shared(&f))?;
Ok(FileLock { f: Some(f), path })
}
/// A non-blocking version of [`Filesystem::open_ro_shared_create`].
///
/// Returns `None` if the operation would block due to another process
/// holding the lock.
pub fn try_open_ro_shared_create<P: AsRef<Path>>(&self, path: P) -> Result<Option<FileLock>> {
let mut opts = OpenOptions::new();
opts.read(true).write(true).create(true);
let (path, f) = self.open(path.as_ref(), &opts, true)?;
if try_acquire(&path, &|| try_lock_shared(&f))? {
Ok(Some(FileLock { f: Some(f), path }))
} else {
Ok(None)
}
}
fn open(&self, path: &Path, opts: &OpenOptions, create: bool) -> Result<(PathBuf, File)> {
let path = self.root.join(path);
let f = opts
.open(&path)
.or_else(|e| {
// If we were requested to create this file, and there was a
// NotFound error, then that was likely due to missing
// intermediate directories. Try creating them and try again.
if e.kind() == io::ErrorKind::NotFound && create {
paths::create_dir_all(path.parent().unwrap())?;
Ok(opts.open(&path)?)
} else {
Err(anyhow::Error::from(e))
}
})
.with_context(|| format!("failed to open: {}", path.display()))?;
Ok((path, f))
}
}
impl PartialEq<Path> for Filesystem {
fn eq(&self, other: &Path) -> bool {
self.root == other
}
}
impl PartialEq<Filesystem> for Path {
fn eq(&self, other: &Filesystem) -> bool {
self == other.root
}
}
fn try_acquire(path: &Path, lock_try: &dyn Fn() -> io::Result<()>) -> Result<bool> {
// File locking on Unix is currently implemented via `flock`, which is known
// to be broken on NFS. We could in theory just ignore errors that happen on
// NFS, but apparently the failure mode [1] for `flock` on NFS is **blocking
// forever**, even if the "non-blocking" flag is passed!
//
// As a result, we just skip all file locks entirely on NFS mounts. That
// should avoid calling any `flock` functions at all, and it wouldn't work
// there anyway.
//
// [1]: https://github.com/rust-lang/cargo/issues/2615
if is_on_nfs_mount(path) {
log::info!("{path:?} appears to be an NFS mount, not trying to lock");
return Ok(true);
}
match lock_try() {
Ok(()) => return Ok(true),
// In addition to ignoring NFS which is commonly not working we also
// just ignore locking on filesystems that look like they don't
// implement file locking.
Err(e) if error_unsupported(&e) => return Ok(true),
Err(e) => {
if !error_contended(&e) {
let e = anyhow::Error::from(e);
let cx = format!("failed to lock file: {}", path.display());
return Err(e.context(cx));
}
}
}
Ok(false)
}
/// Acquires a lock on a file in a "nice" manner.
///
/// Almost all long-running blocking actions in Cargo have a status message
/// associated with them as we're not sure how long they'll take. Whenever a
/// conflicted file lock happens, this is the case (we're not sure when the lock
/// will be released).
///
/// This function will acquire the lock on a `path`, printing out a nice message
/// to the console if we have to wait for it. It will first attempt to use `try`
/// to acquire a lock on the crate, and in the case of contention it will emit a
/// status message based on `msg` to [`GlobalContext`]'s shell, and then use
/// `block` to block waiting to acquire a lock.
///
/// Returns an error if the lock could not be acquired or if any error other
/// than a contention error happens.
fn acquire(
msg: &str,
path: &Path,
lock_try: &dyn Fn() -> io::Result<()>,
lock_block: &dyn Fn() -> io::Result<()>,
) -> Result<()> {
if try_acquire(path, lock_try)? {
return Ok(());
}
log::info!("waiting for file lock on {msg}");
lock_block().with_context(|| format!("failed to lock file: {}", path.display()))?;
Ok(())
}
#[cfg(all(target_os = "linux", not(target_env = "musl")))]
fn is_on_nfs_mount(path: &Path) -> bool {
use std::ffi::CString;
use std::mem;
use std::os::unix::prelude::*;
let Ok(path) = CString::new(path.as_os_str().as_bytes()) else {
return false;
};
// SAFETY: this is implemented by the cargo
unsafe {
let mut buf: libc::statfs = mem::zeroed();
let r = libc::statfs(path.as_ptr(), &mut buf);
r == 0 && buf.f_type as u32 == libc::NFS_SUPER_MAGIC as u32
}
}
#[cfg(any(not(target_os = "linux"), target_env = "musl"))]
fn is_on_nfs_mount(_path: &Path) -> bool {
false
}
#[cfg(unix)]
mod sys {
use std::fs::File;
use std::io::{Error, Result};
use std::os::unix::io::AsRawFd;
pub(super) fn lock_shared(file: &File) -> Result<()> {
flock(file, libc::LOCK_SH)
}
pub(super) fn lock_exclusive(file: &File) -> Result<()> {
flock(file, libc::LOCK_EX)
}
pub(super) fn try_lock_shared(file: &File) -> Result<()> {
flock(file, libc::LOCK_SH | libc::LOCK_NB)
}
pub(super) fn try_lock_exclusive(file: &File) -> Result<()> {
flock(file, libc::LOCK_EX | libc::LOCK_NB)
}
pub(super) fn unlock(file: &File) -> Result<()> {
flock(file, libc::LOCK_UN)
}
pub(super) fn error_contended(err: &Error) -> bool {
err.raw_os_error() == Some(libc::EWOULDBLOCK)
}
pub(super) fn error_unsupported(err: &Error) -> bool {
match err.raw_os_error() {
// Unfortunately, depending on the target, these may or may not be the same.
// For targets in which they are the same, the duplicate pattern causes a warning.
#[allow(unreachable_patterns)]
Some(libc::ENOTSUP | libc::EOPNOTSUPP) => true,
Some(libc::ENOSYS) => true,
_ => false,
}
}
#[cfg(not(target_os = "solaris"))]
fn flock(file: &File, flag: libc::c_int) -> Result<()> {
// SAFETY: this is implemented by the cargo
let ret = unsafe { libc::flock(file.as_raw_fd(), flag) };
if ret < 0 {
Err(Error::last_os_error())
} else {
Ok(())
}
}
#[cfg(target_os = "solaris")]
fn flock(file: &File, flag: libc::c_int) -> Result<()> {
// Solaris lacks flock(), so try to emulate using fcntl()
let mut flock = libc::flock {
l_type: 0,
l_whence: 0,
l_start: 0,
l_len: 0,
l_sysid: 0,
l_pid: 0,
l_pad: [0, 0, 0, 0],
};
flock.l_type = if flag & libc::LOCK_UN != 0 {
libc::F_UNLCK
} else if flag & libc::LOCK_EX != 0 {
libc::F_WRLCK
} else if flag & libc::LOCK_SH != 0 {
libc::F_RDLCK
} else {
panic!("unexpected flock() operation")
};
let mut cmd = libc::F_SETLKW;
if (flag & libc::LOCK_NB) != 0 {
cmd = libc::F_SETLK;
}
let ret = unsafe { libc::fcntl(file.as_raw_fd(), cmd, &flock) };
if ret < 0 {
Err(Error::last_os_error())
} else {
Ok(())
}
}
}
#[cfg(windows)]
mod sys {
use std::fs::File;
use std::io::{Error, Result};
use std::mem;
use std::os::windows::io::AsRawHandle;
use windows_sys::Win32::Foundation::HANDLE;
use windows_sys::Win32::Foundation::{ERROR_INVALID_FUNCTION, ERROR_LOCK_VIOLATION};
use windows_sys::Win32::Storage::FileSystem::{
LockFileEx, UnlockFile, LOCKFILE_EXCLUSIVE_LOCK, LOCKFILE_FAIL_IMMEDIATELY,
};
pub(super) fn lock_shared(file: &File) -> Result<()> {
lock_file(file, 0)
}
pub(super) fn lock_exclusive(file: &File) -> Result<()> {
lock_file(file, LOCKFILE_EXCLUSIVE_LOCK)
}
pub(super) fn try_lock_shared(file: &File) -> Result<()> {
lock_file(file, LOCKFILE_FAIL_IMMEDIATELY)
}
pub(super) fn try_lock_exclusive(file: &File) -> Result<()> {
lock_file(file, LOCKFILE_EXCLUSIVE_LOCK | LOCKFILE_FAIL_IMMEDIATELY)
}
pub(super) fn error_contended(err: &Error) -> bool {
err.raw_os_error() == Some(ERROR_LOCK_VIOLATION as i32)
}
pub(super) fn error_unsupported(err: &Error) -> bool {
err.raw_os_error() == Some(ERROR_INVALID_FUNCTION as i32)
}
pub(super) fn unlock(file: &File) -> Result<()> {
// SAFETY: this is implemented by the cargo
unsafe {
let ret = UnlockFile(file.as_raw_handle() as HANDLE, 0, 0, !0, !0);
if ret == 0 {
Err(Error::last_os_error())
} else {
Ok(())
}
}
}
fn lock_file(file: &File, flags: u32) -> Result<()> {
// SAFETY: this is implemented by the cargo
unsafe {
let mut overlapped = mem::zeroed();
let ret = LockFileEx(
file.as_raw_handle() as HANDLE,
flags,
0,
!0,
!0,
&mut overlapped,
);
if ret == 0 {
Err(Error::last_os_error())
} else {
Ok(())
}
}
}
}

View file

@ -0,0 +1,891 @@
//! Upstream: <https://github.com/rust-lang/cargo/blob/rust-1.83.0/crates/cargo-util/src/paths.rs>
//! Various utilities for working with files and paths.
use std::env;
use std::ffi::{OsStr, OsString};
use std::fs::{self, File, Metadata, OpenOptions};
use std::io;
use std::io::prelude::*;
use std::iter;
use std::path::{Component, Path, PathBuf};
use anyhow::{Context, Result};
use tempfile::Builder as TempFileBuilder;
/// Joins paths into a string suitable for the `PATH` environment variable.
///
/// This is equivalent to [`std::env::join_paths`], but includes a more
/// detailed error message. The given `env` argument is the name of the
/// environment variable this is will be used for, which is included in the
/// error message.
pub fn join_paths<T: AsRef<OsStr>>(paths: &[T], env: &str) -> Result<OsString> {
env::join_paths(paths.iter()).with_context(|| {
let mut message = format!(
"failed to join paths from `${env}` together\n\n\
Check if any of path segments listed below contain an \
unterminated quote character or path separator:"
);
for path in paths {
use std::fmt::Write;
write!(&mut message, "\n {:?}", Path::new(path)).unwrap();
}
message
})
}
/// Returns the name of the environment variable used for searching for
/// dynamic libraries.
pub fn dylib_path_envvar() -> &'static str {
if cfg!(windows) {
"PATH"
} else if cfg!(target_os = "macos") {
// When loading and linking a dynamic library or bundle, dlopen
// searches in LD_LIBRARY_PATH, DYLD_LIBRARY_PATH, PWD, and
// DYLD_FALLBACK_LIBRARY_PATH.
// In the Mach-O format, a dynamic library has an "install path."
// Clients linking against the library record this path, and the
// dynamic linker, dyld, uses it to locate the library.
// dyld searches DYLD_LIBRARY_PATH *before* the install path.
// dyld searches DYLD_FALLBACK_LIBRARY_PATH only if it cannot
// find the library in the install path.
// Setting DYLD_LIBRARY_PATH can easily have unintended
// consequences.
//
// Also, DYLD_LIBRARY_PATH appears to have significant performance
// penalty starting in 10.13. Cargo's testsuite ran more than twice as
// slow with it on CI.
"DYLD_FALLBACK_LIBRARY_PATH"
} else if cfg!(target_os = "aix") {
"LIBPATH"
} else {
"LD_LIBRARY_PATH"
}
}
/// Returns a list of directories that are searched for dynamic libraries.
///
/// Note that some operating systems will have defaults if this is empty that
/// will need to be dealt with.
pub fn dylib_path() -> Vec<PathBuf> {
match env::var_os(dylib_path_envvar()) {
Some(var) => env::split_paths(&var).collect(),
None => Vec::new(),
}
}
/// Normalize a path, removing things like `.` and `..`.
///
/// CAUTION: This does not resolve symlinks (unlike
/// [`std::fs::canonicalize`]). This may cause incorrect or surprising
/// behavior at times. This should be used carefully. Unfortunately,
/// [`std::fs::canonicalize`] can be hard to use correctly, since it can often
/// fail, or on Windows returns annoying device paths. This is a problem Cargo
/// needs to improve on.
pub fn normalize_path(path: &Path) -> PathBuf {
let mut components = path.components().peekable();
let mut ret = if let Some(c @ Component::Prefix(..)) = components.peek().cloned() {
components.next();
PathBuf::from(c.as_os_str())
} else {
PathBuf::new()
};
for component in components {
match component {
Component::Prefix(..) => unreachable!(),
Component::RootDir => {
ret.push(component.as_os_str());
}
Component::CurDir => {}
Component::ParentDir => {
ret.pop();
}
Component::Normal(c) => {
ret.push(c);
}
}
}
ret
}
/// Returns the absolute path of where the given executable is located based
/// on searching the `PATH` environment variable.
///
/// Returns an error if it cannot be found.
pub fn resolve_executable(exec: &Path) -> Result<PathBuf> {
if exec.components().count() == 1 {
let paths = env::var_os("PATH").ok_or_else(|| anyhow::format_err!("no PATH"))?;
let candidates = env::split_paths(&paths).flat_map(|path| {
let candidate = path.join(exec);
let with_exe = if env::consts::EXE_EXTENSION.is_empty() {
None
} else {
Some(candidate.with_extension(env::consts::EXE_EXTENSION))
};
iter::once(candidate).chain(with_exe)
});
for candidate in candidates {
if candidate.is_file() {
return Ok(candidate);
}
}
anyhow::bail!("no executable for `{}` found in PATH", exec.display())
} else {
Ok(exec.into())
}
}
/// Returns metadata for a file (follows symlinks).
///
/// Equivalent to [`std::fs::metadata`] with better error messages.
pub fn metadata<P: AsRef<Path>>(path: P) -> Result<Metadata> {
let path = path.as_ref();
std::fs::metadata(path)
.with_context(|| format!("failed to load metadata for path `{}`", path.display()))
}
/// Returns metadata for a file without following symlinks.
///
/// Equivalent to [`std::fs::metadata`] with better error messages.
pub fn symlink_metadata<P: AsRef<Path>>(path: P) -> Result<Metadata> {
let path = path.as_ref();
std::fs::symlink_metadata(path)
.with_context(|| format!("failed to load metadata for path `{}`", path.display()))
}
/// Reads a file to a string.
///
/// Equivalent to [`std::fs::read_to_string`] with better error messages.
pub fn read(path: &Path) -> Result<String> {
match String::from_utf8(read_bytes(path)?) {
Ok(s) => Ok(s),
Err(_) => anyhow::bail!("path at `{}` was not valid utf-8", path.display()),
}
}
/// Reads a file into a bytes vector.
///
/// Equivalent to [`std::fs::read`] with better error messages.
pub fn read_bytes(path: &Path) -> Result<Vec<u8>> {
fs::read(path).with_context(|| format!("failed to read `{}`", path.display()))
}
/// Writes a file to disk.
///
/// Equivalent to [`std::fs::write`] with better error messages.
pub fn write<P: AsRef<Path>, C: AsRef<[u8]>>(path: P, contents: C) -> Result<()> {
let path = path.as_ref();
fs::write(path, contents.as_ref())
.with_context(|| format!("failed to write `{}`", path.display()))
}
/// Writes a file to disk atomically.
///
/// write_atomic uses tempfile::persist to accomplish atomic writes.
pub fn write_atomic<P: AsRef<Path>, C: AsRef<[u8]>>(path: P, contents: C) -> Result<()> {
let path = path.as_ref();
// On unix platforms, get the permissions of the original file. Copy only the
// user/group/other read/write/execute permission bits. The tempfile lib
// defaults to an initial mode of 0o600, and we'll set the proper
// permissions after creating the file.
#[cfg(unix)]
let perms = path.metadata().ok().map(|meta| {
use std::os::unix::fs::PermissionsExt;
// these constants are u16 on macOS
#[allow(clippy::useless_conversion)]
let mask = u32::from(libc::S_IRWXU | libc::S_IRWXG | libc::S_IRWXO);
let mode = meta.permissions().mode() & mask;
std::fs::Permissions::from_mode(mode)
});
let mut tmp = TempFileBuilder::new()
.prefix(path.file_name().unwrap())
.tempfile_in(path.parent().unwrap())?;
tmp.write_all(contents.as_ref())?;
// On unix platforms, set the permissions on the newly created file. We can use
// fchmod (called by the std lib; subject to change) which ignores the umask
// so that the new file has the same permissions as the old file.
#[cfg(unix)]
if let Some(perms) = perms {
tmp.as_file().set_permissions(perms)?;
}
tmp.persist(path)?;
Ok(())
}
/// Equivalent to [`write()`], but does not write anything if the file contents
/// are identical to the given contents.
pub fn write_if_changed<P: AsRef<Path>, C: AsRef<[u8]>>(path: P, contents: C) -> Result<()> {
(|| -> Result<()> {
let contents = contents.as_ref();
let mut f = OpenOptions::new()
.read(true)
.write(true)
.create(true)
// todo: cargo doesn't have `truncate` in the options, but it is
// suggested by clippy
.truncate(false)
.open(&path)?;
let mut orig = Vec::new();
f.read_to_end(&mut orig)?;
if orig != contents {
f.set_len(0)?;
f.seek(io::SeekFrom::Start(0))?;
f.write_all(contents)?;
}
Ok(())
})()
.with_context(|| format!("failed to write `{}`", path.as_ref().display()))?;
Ok(())
}
/// Equivalent to [`write()`], but appends to the end instead of replacing the
/// contents.
pub fn append(path: &Path, contents: &[u8]) -> Result<()> {
(|| -> Result<()> {
let mut f = OpenOptions::new().append(true).create(true).open(path)?;
f.write_all(contents)?;
Ok(())
})()
.with_context(|| format!("failed to write `{}`", path.display()))?;
Ok(())
}
/// Creates a new file.
pub fn create<P: AsRef<Path>>(path: P) -> Result<File> {
let path = path.as_ref();
File::create(path).with_context(|| format!("failed to create file `{}`", path.display()))
}
/// Opens an existing file.
pub fn open<P: AsRef<Path>>(path: P) -> Result<File> {
let path = path.as_ref();
File::open(path).with_context(|| format!("failed to open file `{}`", path.display()))
}
/// Converts a path to UTF-8 bytes.
pub fn path2bytes(path: &Path) -> Result<&[u8]> {
#[cfg(unix)]
{
use std::os::unix::prelude::*;
Ok(path.as_os_str().as_bytes())
}
#[cfg(windows)]
{
match path.as_os_str().to_str() {
Some(s) => Ok(s.as_bytes()),
None => Err(anyhow::format_err!(
"invalid non-unicode path: {}",
path.display()
)),
}
}
}
/// Converts UTF-8 bytes to a path.
pub fn bytes2path(bytes: &[u8]) -> Result<PathBuf> {
#[cfg(unix)]
{
use std::os::unix::prelude::*;
Ok(PathBuf::from(OsStr::from_bytes(bytes)))
}
#[cfg(windows)]
{
use std::str;
match str::from_utf8(bytes) {
Ok(s) => Ok(PathBuf::from(s)),
Err(..) => Err(anyhow::format_err!("invalid non-unicode path")),
}
}
}
/// Returns an iterator that walks up the directory hierarchy towards the root.
///
/// Each item is a [`Path`]. It will start with the given path, finishing at
/// the root. If the `stop_root_at` parameter is given, it will stop at the
/// given path (which will be the last item).
pub fn ancestors<'a>(path: &'a Path, stop_root_at: Option<&Path>) -> PathAncestors<'a> {
PathAncestors::new(path, stop_root_at)
}
/// An iterator that walks up the directory hierarchy towards the root.
pub struct PathAncestors<'a> {
current: Option<&'a Path>,
stop_at: Option<PathBuf>,
}
impl<'a> PathAncestors<'a> {
fn new(path: &'a Path, stop_root_at: Option<&Path>) -> PathAncestors<'a> {
let stop_at = env::var("__CARGO_TEST_ROOT")
.ok()
.map(PathBuf::from)
.or_else(|| stop_root_at.map(|p| p.to_path_buf()));
PathAncestors {
current: Some(path),
//HACK: avoid reading `~/.cargo/config` when testing Cargo itself.
stop_at,
}
}
}
impl<'a> Iterator for PathAncestors<'a> {
type Item = &'a Path;
fn next(&mut self) -> Option<&'a Path> {
if let Some(path) = self.current {
self.current = path.parent();
if let Some(ref stop_at) = self.stop_at {
if path == stop_at {
self.current = None;
}
}
Some(path)
} else {
None
}
}
}
/// Equivalent to [`std::fs::create_dir_all`] with better error messages.
pub fn create_dir_all(p: impl AsRef<Path>) -> Result<()> {
_create_dir_all(p.as_ref())
}
fn _create_dir_all(p: &Path) -> Result<()> {
fs::create_dir_all(p)
.with_context(|| format!("failed to create directory `{}`", p.display()))?;
Ok(())
}
/// Equivalent to [`std::fs::remove_dir_all`] with better error messages.
///
/// This does *not* follow symlinks.
pub fn remove_dir_all<P: AsRef<Path>>(p: P) -> Result<()> {
_remove_dir_all(p.as_ref()).or_else(|prev_err| {
// `std::fs::remove_dir_all` is highly specialized for different platforms
// and may be more reliable than a simple walk. We try the walk first in
// order to report more detailed errors.
fs::remove_dir_all(p.as_ref()).with_context(|| {
format!(
"{:?}\n\nError: failed to remove directory `{}`",
prev_err,
p.as_ref().display(),
)
})
})
}
fn _remove_dir_all(p: &Path) -> Result<()> {
if symlink_metadata(p)?.is_symlink() {
return remove_file(p);
}
let entries = p
.read_dir()
.with_context(|| format!("failed to read directory `{}`", p.display()))?;
for entry in entries {
let entry = entry?;
let path = entry.path();
if entry.file_type()?.is_dir() {
remove_dir_all(&path)?;
} else {
remove_file(&path)?;
}
}
remove_dir(p)
}
/// Equivalent to [`std::fs::remove_dir`] with better error messages.
pub fn remove_dir<P: AsRef<Path>>(p: P) -> Result<()> {
_remove_dir(p.as_ref())
}
fn _remove_dir(p: &Path) -> Result<()> {
fs::remove_dir(p).with_context(|| format!("failed to remove directory `{}`", p.display()))?;
Ok(())
}
/// Equivalent to [`std::fs::remove_file`] with better error messages.
///
/// If the file is readonly, this will attempt to change the permissions to
/// force the file to be deleted.
/// On Windows, if the file is a symlink to a directory, this will attempt to
/// remove the symlink itself.
pub fn remove_file<P: AsRef<Path>>(p: P) -> Result<()> {
_remove_file(p.as_ref())
}
fn _remove_file(p: &Path) -> Result<()> {
// For Windows, we need to check if the file is a symlink to a directory
// and remove the symlink itself by calling `remove_dir` instead of
// `remove_file`.
#[cfg(target_os = "windows")]
{
use std::os::windows::fs::FileTypeExt;
let metadata = symlink_metadata(p)?;
let file_type = metadata.file_type();
if file_type.is_symlink_dir() {
return remove_symlink_dir_with_permission_check(p);
}
}
remove_file_with_permission_check(p)
}
#[cfg(target_os = "windows")]
fn remove_symlink_dir_with_permission_check(p: &Path) -> Result<()> {
remove_with_permission_check(fs::remove_dir, p)
.with_context(|| format!("failed to remove symlink dir `{}`", p.display()))
}
fn remove_file_with_permission_check(p: &Path) -> Result<()> {
remove_with_permission_check(fs::remove_file, p)
.with_context(|| format!("failed to remove file `{}`", p.display()))
}
fn remove_with_permission_check<F, P>(remove_func: F, p: P) -> io::Result<()>
where
F: Fn(P) -> io::Result<()>,
P: AsRef<Path> + Clone,
{
match remove_func(p.clone()) {
Ok(()) => Ok(()),
Err(e) => {
if e.kind() == io::ErrorKind::PermissionDenied
&& set_not_readonly(p.as_ref()).unwrap_or(false)
{
remove_func(p)
} else {
Err(e)
}
}
}
}
fn set_not_readonly(p: &Path) -> io::Result<bool> {
let mut perms = p.metadata()?.permissions();
if !perms.readonly() {
return Ok(false);
}
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
perms.set_mode(0o640);
}
#[cfg(not(unix))]
#[allow(clippy::permissions_set_readonly_false)]
{
perms.set_readonly(false);
}
fs::set_permissions(p, perms)?;
Ok(true)
}
/// Hardlink (file) or symlink (dir) src to dst if possible, otherwise copy it.
///
/// If the destination already exists, it is removed before linking.
pub fn link_or_copy(src: impl AsRef<Path>, dst: impl AsRef<Path>) -> Result<()> {
let src = src.as_ref();
let dst = dst.as_ref();
_link_or_copy(src, dst)
}
fn _link_or_copy(src: &Path, dst: &Path) -> Result<()> {
log::debug!("linking {} to {}", src.display(), dst.display());
if same_file::is_same_file(src, dst).unwrap_or(false) {
return Ok(());
}
// NB: we can't use dst.exists(), as if dst is a broken symlink,
// dst.exists() will return false. This is problematic, as we still need to
// unlink dst in this case. symlink_metadata(dst).is_ok() will tell us
// whether dst exists *without* following symlinks, which is what we want.
if fs::symlink_metadata(dst).is_ok() {
remove_file(dst)?;
}
let link_result = if src.is_dir() {
#[cfg(target_os = "redox")]
use std::os::redox::fs::symlink;
#[cfg(unix)]
use std::os::unix::fs::symlink;
#[cfg(windows)]
// FIXME: This should probably panic or have a copy fallback. Symlinks
// are not supported in all windows environments. Currently symlinking
// is only used for .dSYM directories on macos, but this shouldn't be
// accidentally relied upon.
use std::os::windows::fs::symlink_dir as symlink;
let dst_dir = dst.parent().unwrap();
let src = if src.starts_with(dst_dir) {
src.strip_prefix(dst_dir).unwrap()
} else {
src
};
symlink(src, dst)
} else if cfg!(target_os = "macos") {
// There seems to be a race condition with APFS when hard-linking
// binaries. Gatekeeper does not have signing or hash information
// stored in kernel when running the process. Therefore killing it.
// This problem does not appear when copying files as kernel has
// time to process it. Note that: fs::copy on macos is using
// CopyOnWrite (syscall fclonefileat) which should be as fast as
// hardlinking. See these issues for the details:
//
// * https://github.com/rust-lang/cargo/issues/7821
// * https://github.com/rust-lang/cargo/issues/10060
fs::copy(src, dst).map_or_else(
|e| {
if e.raw_os_error()
.map_or(false, |os_err| os_err == 35 /* libc::EAGAIN */)
{
log::info!("copy failed {e:?}. falling back to fs::hard_link");
// Working around an issue copying too fast with zfs (probably related to
// https://github.com/openzfsonosx/zfs/issues/809)
// See https://github.com/rust-lang/cargo/issues/13838
fs::hard_link(src, dst)
} else {
Err(e)
}
},
|_| Ok(()),
)
} else {
fs::hard_link(src, dst)
};
link_result
.or_else(|err| {
log::debug!("link failed {err}. falling back to fs::copy");
fs::copy(src, dst).map(|_| ())
})
.with_context(|| {
format!(
"failed to link or copy `{}` to `{}`",
src.display(),
dst.display()
)
})?;
Ok(())
}
/// Copies a file from one location to another.
///
/// Equivalent to [`std::fs::copy`] with better error messages.
pub fn copy<P: AsRef<Path>, Q: AsRef<Path>>(from: P, to: Q) -> Result<u64> {
let from = from.as_ref();
let to = to.as_ref();
fs::copy(from, to)
.with_context(|| format!("failed to copy `{}` to `{}`", from.display(), to.display()))
}
/// Strips `base` from `path`.
///
/// This canonicalizes both paths before stripping. This is useful if the
/// paths are obtained in different ways, and one or the other may or may not
/// have been normalized in some way.
pub fn strip_prefix_canonical<P: AsRef<Path>>(
path: P,
base: P,
) -> Result<PathBuf, std::path::StripPrefixError> {
// Not all filesystems support canonicalize. Just ignore if it doesn't work.
let safe_canonicalize = |path: &Path| match path.canonicalize() {
Ok(p) => p,
Err(e) => {
log::warn!("cannot canonicalize {path:?}: {e:?}");
path.to_path_buf()
}
};
let canon_path = safe_canonicalize(path.as_ref());
let canon_base = safe_canonicalize(base.as_ref());
canon_path.strip_prefix(canon_base).map(|p| p.to_path_buf())
}
/// Creates an excluded from cache directory atomically with its parents as
/// needed.
///
/// The atomicity only covers creating the leaf directory and exclusion from
/// cache. Any missing parent directories will not be created in an atomic
/// manner.
///
/// This function is idempotent and in addition to that it won't exclude ``p``
/// from cache if it already exists.
pub fn create_dir_all_excluded_from_backups_atomic(p: impl AsRef<Path>) -> Result<()> {
let path = p.as_ref();
if path.is_dir() {
return Ok(());
}
let parent = path.parent().unwrap();
let base = path.file_name().unwrap();
create_dir_all(parent)?;
// We do this in two steps (first create a temporary directory and exclude
// it from backups, then rename it to the desired name. If we created the
// directory directly where it should be and then excluded it from backups
// we would risk a situation where cargo is interrupted right after the
// directory creation but before the exclusion the directory would remain
// non-excluded from backups because we only perform exclusion right after
// we created the directory ourselves.
//
// We need the tempdir created in parent instead of $TMP, because only then we
// can be easily sure that rename() will succeed (the new name needs to be
// on the same mount point as the old one).
let tempdir = TempFileBuilder::new().prefix(base).tempdir_in(parent)?;
exclude_from_backups(tempdir.path());
exclude_from_content_indexing(tempdir.path());
// Previously std::fs::create_dir_all() (through paths::create_dir_all()) was
// used here to create the directory directly and fs::create_dir_all()
// explicitly treats the directory being created concurrently by another
// thread or process as success, hence the check below to follow the
// existing behavior. If we get an error at rename() and suddenly the
// directory (which didn't exist a moment earlier) exists we can infer from
// it's another cargo process doing work.
if let Err(e) = fs::rename(tempdir.path(), path) {
if !path.exists() {
return Err(anyhow::Error::from(e))
.with_context(|| format!("failed to create directory `{}`", path.display()));
}
}
Ok(())
}
/// Mark an existing directory as excluded from backups and indexing.
///
/// Errors in marking it are ignored.
pub fn exclude_from_backups_and_indexing(p: impl AsRef<Path>) {
let path = p.as_ref();
exclude_from_backups(path);
exclude_from_content_indexing(path);
}
/// Marks the directory as excluded from archives/backups.
///
/// This is recommended to prevent derived/temporary files from bloating
/// backups. There are two mechanisms used to achieve this right now:
///
/// * A dedicated resource property excluding from Time Machine backups on macOS
/// * CACHEDIR.TAG files supported by various tools in a platform-independent
/// way
fn exclude_from_backups(path: &Path) {
exclude_from_time_machine(path);
let file = path.join("CACHEDIR.TAG");
if !file.exists() {
let _ = std::fs::write(
file,
"Signature: 8a477f597d28d172789f06886806bc55
# This file is a cache directory tag created by cargo.
# For information about cache directory tags see https://bford.info/cachedir/
",
);
// Similarly to exclude_from_time_machine() we ignore errors here as
// it's an optional feature.
}
}
/// Marks the directory as excluded from content indexing.
///
/// This is recommended to prevent the content of derived/temporary files from
/// being indexed. This is very important for Windows users, as the live content
/// indexing significantly slows cargo's I/O operations.
///
/// This is currently a no-op on non-Windows platforms.
fn exclude_from_content_indexing(path: &Path) {
#[cfg(windows)]
{
use std::iter::once;
use std::os::windows::prelude::OsStrExt;
use windows_sys::Win32::Storage::FileSystem::{
GetFileAttributesW, SetFileAttributesW, FILE_ATTRIBUTE_NOT_CONTENT_INDEXED,
};
let path: Vec<u16> = path.as_os_str().encode_wide().chain(once(0)).collect();
// SAFETY: this is implemented by the cargo
unsafe {
SetFileAttributesW(
path.as_ptr(),
GetFileAttributesW(path.as_ptr()) | FILE_ATTRIBUTE_NOT_CONTENT_INDEXED,
);
}
}
#[cfg(not(windows))]
{
let _ = path;
}
}
#[cfg(not(target_os = "macos"))]
fn exclude_from_time_machine(_: &Path) {}
#[cfg(target_os = "macos")]
/// Marks files or directories as excluded from Time Machine on macOS
fn exclude_from_time_machine(path: &Path) {
use core_foundation::base::TCFType;
use core_foundation::{number, string, url};
use std::ptr;
// For compatibility with 10.7 a string is used instead of global
// kCFURLIsExcludedFromBackupKey
let is_excluded_key: Result<string::CFString, _> = "NSURLIsExcludedFromBackupKey".parse();
let path = url::CFURL::from_path(path, false);
if let (Some(path), Ok(is_excluded_key)) = (path, is_excluded_key) {
unsafe {
url::CFURLSetResourcePropertyForKey(
path.as_concrete_TypeRef(),
is_excluded_key.as_concrete_TypeRef(),
number::kCFBooleanTrue as *const _,
ptr::null_mut(),
);
}
}
// Errors are ignored, since it's an optional feature and failure
// doesn't prevent Cargo from working
}
#[cfg(test)]
mod tests {
use super::join_paths;
use super::write;
use super::write_atomic;
#[test]
fn write_works() {
let original_contents = "[dependencies]\nfoo = 0.1.0";
let tmpdir = tempfile::tempdir().unwrap();
let path = tmpdir.path().join("Cargo.toml");
write(&path, original_contents).unwrap();
let contents = std::fs::read_to_string(&path).unwrap();
assert_eq!(contents, original_contents);
}
#[test]
fn write_atomic_works() {
let original_contents = "[dependencies]\nfoo = 0.1.0";
let tmpdir = tempfile::tempdir().unwrap();
let path = tmpdir.path().join("Cargo.toml");
write_atomic(&path, original_contents).unwrap();
let contents = std::fs::read_to_string(&path).unwrap();
assert_eq!(contents, original_contents);
}
#[test]
#[cfg(unix)]
fn write_atomic_permissions() {
use std::os::unix::fs::PermissionsExt;
#[allow(clippy::useless_conversion)]
let perms = u32::from(libc::S_IRWXU | libc::S_IRGRP | libc::S_IWGRP | libc::S_IROTH);
let original_perms = std::fs::Permissions::from_mode(perms);
let tmp = tempfile::Builder::new().tempfile().unwrap();
// need to set the permissions after creating the file to avoid umask
tmp.as_file()
.set_permissions(original_perms.clone())
.unwrap();
// after this call, the file at `tmp.path()` will not be the same as the file
// held by `tmp`
write_atomic(tmp.path(), "new").unwrap();
assert_eq!(std::fs::read_to_string(tmp.path()).unwrap(), "new");
let new_perms = std::fs::metadata(tmp.path()).unwrap().permissions();
#[allow(clippy::useless_conversion)]
let mask = u32::from(libc::S_IRWXU | libc::S_IRWXG | libc::S_IRWXO);
assert_eq!(original_perms.mode(), new_perms.mode() & mask);
}
#[test]
fn join_paths_lists_paths_on_error() {
let valid_paths = vec!["/testing/one", "/testing/two"];
// does not fail on valid input
let _joined = join_paths(&valid_paths, "TESTING1").unwrap();
#[cfg(unix)]
{
let invalid_paths = vec!["/testing/one", "/testing/t:wo/three"];
let err = join_paths(&invalid_paths, "TESTING2").unwrap_err();
assert_eq!(
err.to_string(),
"failed to join paths from `$TESTING2` together\n\n\
Check if any of path segments listed below contain an \
unterminated quote character or path separator:\
\n \"/testing/one\"\
\n \"/testing/t:wo/three\"\
"
);
}
#[cfg(windows)]
{
let invalid_paths = vec!["/testing/one", "/testing/t\"wo/three"];
let err = join_paths(&invalid_paths, "TESTING2").unwrap_err();
assert_eq!(
err.to_string(),
"failed to join paths from `$TESTING2` together\n\n\
Check if any of path segments listed below contain an \
unterminated quote character or path separator:\
\n \"/testing/one\"\
\n \"/testing/t\\\"wo/three\"\
"
);
}
}
#[test]
#[cfg(windows)]
fn test_remove_symlink_dir() {
use super::*;
use std::fs;
use std::os::windows::fs::symlink_dir;
let tmpdir = tempfile::tempdir().unwrap();
let dir_path = tmpdir.path().join("testdir");
let symlink_path = tmpdir.path().join("symlink");
fs::create_dir(&dir_path).unwrap();
symlink_dir(&dir_path, &symlink_path).expect("failed to create symlink");
assert!(symlink_path.exists());
assert!(remove_file(symlink_path.clone()).is_ok());
assert!(!symlink_path.exists());
assert!(dir_path.exists());
}
#[test]
#[cfg(windows)]
fn test_remove_symlink_file() {
use super::*;
use std::fs;
use std::os::windows::fs::symlink_file;
let tmpdir = tempfile::tempdir().unwrap();
let file_path = tmpdir.path().join("testfile");
let symlink_path = tmpdir.path().join("symlink");
fs::write(&file_path, b"test").unwrap();
symlink_file(&file_path, &symlink_path).expect("failed to create symlink");
assert!(symlink_path.exists());
assert!(remove_file(symlink_path.clone()).is_ok());
assert!(!symlink_path.exists());
assert!(file_path.exists());
}
}

View file

@ -1,8 +1,10 @@
//! The hash extension module. It provides extra concepts like `Fingerprint` and
//! `HashedTrait`.
use core::fmt;
use std::{
any::Any,
hash::{Hash, Hasher},
};
use std::any::Any;
use std::hash::{Hash, Hasher};
use std::ops::Deref;
use base64::Engine;
use fxhash::FxHasher32;
@ -11,12 +13,13 @@ use siphasher::sip128::{Hasher128, SipHasher13};
#[cfg(feature = "rkyv")]
use rkyv::{Archive, Deserialize as rDeser, Serialize as rSer};
use crate::error::prelude::ZResult;
use crate::error::prelude::Result;
pub(crate) type FxBuildHasher = std::hash::BuildHasherDefault<FxHasher>;
pub use rustc_hash::{FxHashMap, FxHashSet, FxHasher};
// pub type FxIndexSet<K> = indexmap::IndexSet<K, FxHasher>;
// pub type FxIndexMap<K, V> = indexmap::IndexMap<K, V, FxHasher>;
/// A dashmap that uses the FxHasher as the underlying hasher.
pub type FxDashMap<K, V> = dashmap::DashMap<K, V, FxBuildHasher>;
/// See <https://github.com/rust-lang/rust/blob/master/compiler/rustc_hir/src/stable_hash_impls.rs#L22>
@ -85,7 +88,7 @@ impl Fingerprint {
}
/// Creates a new `Fingerprint` from a svg id that **doesn't have prefix**.
pub fn try_from_str(s: &str) -> ZResult<Self> {
pub fn try_from_str(s: &str) -> Result<Self> {
let bytes = base64::engine::general_purpose::STANDARD_NO_PAD
.decode(&s.as_bytes()[..11])
.expect("invalid base64 string");
@ -135,9 +138,11 @@ pub struct FingerprintSipHasher {
data: Vec<u8>,
}
/// The base hasher for the [`FingerprintSipHasher`].
pub type FingerprintSipHasherBase = SipHasher13;
impl FingerprintSipHasher {
/// Get the fast hash value and the underlying data.
pub fn fast_hash(&self) -> (u32, &Vec<u8>) {
let mut inner = FxHasher32::default();
self.data.hash(&mut inner);
@ -187,6 +192,7 @@ pub struct FingerprintBuilder {
#[cfg(not(feature = "bi-hash"))]
impl FingerprintBuilder {
/// Resolve the fingerprint without checking the conflict.
pub fn resolve_unchecked<T: Hash>(&self, item: &T) -> Fingerprint {
let mut s = FingerprintSipHasher { data: Vec::new() };
item.hash(&mut s);
@ -194,6 +200,7 @@ impl FingerprintBuilder {
fingerprint
}
/// Resolve the fingerprint and check the conflict.
pub fn resolve<T: Hash + 'static>(&self, item: &T) -> Fingerprint {
let mut s = FingerprintSipHasher { data: Vec::new() };
item.type_id().hash(&mut s);
@ -216,6 +223,7 @@ impl FingerprintBuilder {
#[cfg(feature = "bi-hash")]
impl FingerprintBuilder {
/// Resolve the fingerprint without checking the conflict.
pub fn resolve_unchecked<T: Hash>(&self, item: &T) -> Fingerprint {
let mut s = FingerprintSipHasher { data: Vec::new() };
item.hash(&mut s);
@ -233,6 +241,7 @@ impl FingerprintBuilder {
fingerprint
}
/// Resolve the fingerprint and check the conflict.
pub fn resolve<T: Hash + 'static>(&self, item: &T) -> Fingerprint {
let mut s = FingerprintSipHasher { data: Vec::new() };
item.type_id().hash(&mut s);
@ -297,6 +306,66 @@ pub fn hash64<T: Hash + ?Sized>(v: &T) -> u64 {
// todo: rustc hash doesn't have 32-bit hash
pub use fxhash::hash32;
/// A trait that provides a static prehashed 128-bit hash.
pub trait StaticHash128 {
/// Get the prehashed 128-bit hash.
fn get_hash(&self) -> u128;
}
impl Hash for dyn StaticHash128 {
#[inline]
fn hash<H: Hasher>(&self, state: &mut H) {
state.write_u128(self.get_hash());
}
}
/// A trait that provides a static prehashed 64-bit hash for any internal `T`.
///
/// Please ensure that the `T` is really mapped to the hash. Use it at your own
/// risk.
pub struct HashedTrait<T: ?Sized> {
hash: u128,
t: Box<T>,
}
impl<T: ?Sized> HashedTrait<T> {
/// Create a new `HashedTrait` with the given hash and the trait object.
pub fn new(hash: u128, t: Box<T>) -> Self {
Self { hash, t }
}
}
impl<T: ?Sized> Deref for HashedTrait<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.t
}
}
impl<T> Hash for HashedTrait<T> {
#[inline]
fn hash<H: Hasher>(&self, state: &mut H) {
state.write_u128(self.hash);
}
}
impl<T: Hash + Default + 'static> Default for HashedTrait<T> {
fn default() -> Self {
let t = T::default();
Self {
hash: item_hash128(&t),
t: Box::new(t),
}
}
}
impl<T: ?Sized> StaticHash128 for HashedTrait<T> {
fn get_hash(&self) -> u128 {
self.hash
}
}
#[test]
fn test_fingerprint() {
let t = Fingerprint::from_pair(0, 1);

View file

@ -1,8 +1,9 @@
#![allow(missing_docs)]
//! Additional functions wrapping Rust's standard library.
pub mod adt;
pub mod debug_loc;
pub mod error;
pub mod fs;
pub mod hash;
pub mod path;
pub mod time;
@ -11,7 +12,7 @@ pub(crate) mod concepts;
pub use concepts::*;
pub use error::{ErrKind, Error};
pub use error::{ErrKind, Error, Result};
#[cfg(feature = "typst")]
pub use typst_shim;
@ -19,8 +20,8 @@ pub use typst_shim;
#[cfg(feature = "rkyv")]
use rkyv::{Archive, Deserialize as rDeser, Serialize as rSer};
/// The local id of a svg item.
/// This id is only unique within the svg document.
/// The local id of an item.
/// This id is only unique within a task or process.
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
#[cfg_attr(feature = "rkyv", derive(Archive, rDeser, rSer))]
#[cfg_attr(feature = "rkyv-validation", archive(check_bytes))]

View file

@ -1,3 +1,5 @@
//! Path utilities.
use std::path::{Component, Path};
pub use path_clean::PathClean;

View file

@ -1,3 +1,5 @@
//! Cross platform time utilities.
pub use std::time::SystemTime as Time;
pub use web_time::Duration;
pub use web_time::Instant;