Skip to content

Commit 1654c77

Browse files
committed
fix: stamp startup inputs with platform path rules on Windows
1 parent 3d1ccd6 commit 1654c77

4 files changed

Lines changed: 100 additions & 27 deletions

File tree

‎crates/sqlbuild-cache/src/artifacts/_helpers/files.rs‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ use std::fs::{File, OpenOptions};
55
use std::io::{ErrorKind, Read, Write};
66
use std::path::{Path, PathBuf};
77

8-
use crate::artifacts::constants::NEW_FILE_MODE;
98
use crate::artifacts::errors::ArtifactError;
109
use crate::artifacts::models::{Publication, WrittenArtifacts};
1110

@@ -261,7 +260,9 @@ fn copy_new_file(source: &Path, path: &Path) -> std::io::Result<()> {
261260
#[cfg(unix)]
262261
{
263262
use std::os::unix::fs::PermissionsExt;
264-
let _ = builder.permissions(std::fs::Permissions::from_mode(NEW_FILE_MODE));
263+
let _ = builder.permissions(std::fs::Permissions::from_mode(
264+
crate::artifacts::constants::NEW_FILE_MODE,
265+
));
265266
}
266267
let mut temporary = builder.tempfile_in(directory)?;
267268
temporary.write_all(&std::fs::read(source)?)?;
Lines changed: 76 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
1-
//! `pathlib.PurePosixPath` spelling and ordering over raw path bytes.
1+
//! `pathlib` spelling, ordering and stat reads for startup stamps on each platform.
22
3-
use std::ffi::OsString;
4-
use std::os::unix::ffi::{OsStrExt, OsStringExt};
3+
use std::ffi::OsStr;
4+
use std::fs::Metadata;
55
use std::path::{Path, PathBuf};
66

7+
use crate::startup_inputs::constants::PATH_FILE_SUFFIX;
8+
#[cfg(unix)]
79
use crate::startup_inputs::constants::{CURRENT, SEPARATOR};
810

911
/// The root a POSIX path keeps: two leading slashes stay two, more collapse to one.
12+
#[cfg(unix)]
1013
fn root(bytes: &[u8]) -> &[u8] {
1114
match bytes {
1215
[SEPARATOR, SEPARATOR, rest @ ..] if rest.first() != Some(&SEPARATOR) => &bytes[..2],
@@ -16,11 +19,14 @@ fn root(bytes: &[u8]) -> &[u8] {
1619
}
1720

1821
/// `str(PurePosixPath(path, *children))`.
19-
pub(crate) fn pure(path: &Path, children: &[&[u8]]) -> PathBuf {
22+
#[cfg(unix)]
23+
pub(crate) fn pure(path: &Path, children: &[&OsStr]) -> PathBuf {
24+
use std::os::unix::ffi::{OsStrExt, OsStringExt};
25+
2026
let bytes: &[u8] = path.as_os_str().as_bytes();
2127
let mut text: Vec<u8> = root(bytes).to_vec();
2228
let parts = std::iter::once(bytes)
23-
.chain(children.iter().copied())
29+
.chain(children.iter().map(|child| child.as_bytes()))
2430
.flat_map(|segment| segment.split(|byte| *byte == SEPARATOR))
2531
.filter(|part| !part.is_empty() && *part != CURRENT);
2632
let mut first: bool = true;
@@ -34,22 +40,82 @@ pub(crate) fn pure(path: &Path, children: &[&[u8]]) -> PathBuf {
3440
if text.is_empty() {
3541
text.extend_from_slice(CURRENT);
3642
}
37-
PathBuf::from(OsString::from_vec(text))
43+
PathBuf::from(std::ffi::OsString::from_vec(text))
44+
}
45+
46+
/// `str(PureWindowsPath(path, *children))` through the platform path rules.
47+
#[cfg(not(unix))]
48+
pub(crate) fn pure(path: &Path, children: &[&OsStr]) -> PathBuf {
49+
let mut joined: PathBuf = path.components().collect();
50+
for child in children {
51+
joined.push(child);
52+
}
53+
joined
3854
}
3955

4056
/// `str(Path(path).absolute())` against the working directory `cwd`.
4157
pub(crate) fn absolute(path: &Path, cwd: &Path) -> PathBuf {
42-
if root(path.as_os_str().as_bytes()).is_empty() {
43-
pure(cwd, &[path.as_os_str().as_bytes()])
44-
} else {
58+
if path.has_root() && (cfg!(unix) || path.is_absolute()) {
4559
pure(path, &[])
60+
} else {
61+
pure(cwd, &[path.as_os_str()])
4662
}
4763
}
4864

4965
/// The `/`-separated parts `PurePosixPath` orders paths by.
50-
pub(crate) fn order_key(path: &Path) -> Vec<&[u8]> {
66+
#[cfg(unix)]
67+
pub(crate) fn order_key(path: &Path) -> Vec<Vec<u8>> {
68+
use std::os::unix::ffi::OsStrExt;
69+
5170
path.as_os_str()
5271
.as_bytes()
5372
.split(|byte| *byte == SEPARATOR)
73+
.map(<[u8]>::to_vec)
74+
.collect()
75+
}
76+
77+
/// The case-folded parts `PureWindowsPath` orders paths by.
78+
#[cfg(not(unix))]
79+
pub(crate) fn order_key(path: &Path) -> Vec<Vec<u8>> {
80+
path.components()
81+
.map(|part| {
82+
part.as_os_str()
83+
.to_string_lossy()
84+
.to_lowercase()
85+
.into_bytes()
86+
})
5487
.collect()
5588
}
89+
90+
/// Whether `glob("*.pth")` matches a directory entry name on this platform.
91+
pub(crate) fn is_path_file(name: &OsStr) -> bool {
92+
if cfg!(unix) {
93+
name.as_encoded_bytes().ends_with(PATH_FILE_SUFFIX)
94+
} else {
95+
name.to_string_lossy()
96+
.to_lowercase()
97+
.as_bytes()
98+
.ends_with(PATH_FILE_SUFFIX)
99+
}
100+
}
101+
102+
/// `os.stat(path).st_mtime_ns`.
103+
#[cfg(unix)]
104+
pub(crate) fn modified_ns(metadata: &Metadata) -> std::io::Result<i64> {
105+
use std::os::unix::fs::MetadataExt;
106+
107+
Ok(metadata.mtime() * 1_000_000_000 + metadata.mtime_nsec())
108+
}
109+
110+
/// `os.stat(path).st_mtime_ns` from the platform modification time, negative before 1970.
111+
#[cfg(not(unix))]
112+
pub(crate) fn modified_ns(metadata: &Metadata) -> std::io::Result<i64> {
113+
let modified: std::time::SystemTime = metadata.modified()?;
114+
let (nanos, sign): (u128, i64) = match modified.duration_since(std::time::UNIX_EPOCH) {
115+
Ok(elapsed) => (elapsed.as_nanos(), 1),
116+
Err(before) => (before.duration().as_nanos(), -1),
117+
};
118+
i64::try_from(nanos)
119+
.map(|value| sign * value)
120+
.map_err(std::io::Error::other)
121+
}
Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
//! Names the startup stamps read.
22
33
/// The virtual-environment configuration file of an interpreter prefix.
4-
pub(crate) const VENV_CONFIG: &[u8] = b"pyvenv.cfg";
4+
pub(crate) const VENV_CONFIG: &str = "pyvenv.cfg";
55
/// The suffix of the path configuration files `site` processes.
66
pub(crate) const PATH_FILE_SUFFIX: &[u8] = b".pth";
7+
#[cfg(unix)]
78
pub(crate) const SEPARATOR: u8 = b'/';
9+
#[cfg(unix)]
810
pub(crate) const CURRENT: &[u8] = b".";

‎crates/sqlbuild-cache/src/startup_inputs/main/startup_stamps.rs‎

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
//! Stamp every startup directory, its `.pth` files, the interpreter and its venv configuration.
22
33
use std::collections::BTreeSet;
4-
use std::ffi::OsString;
4+
use std::ffi::{OsStr, OsString};
55
use std::io;
6-
use std::os::unix::ffi::OsStrExt;
7-
use std::os::unix::fs::MetadataExt;
86
use std::path::{Path, PathBuf};
97

10-
use crate::startup_inputs::_helpers::pure_paths::{absolute, order_key, pure};
11-
use crate::startup_inputs::constants::{PATH_FILE_SUFFIX, VENV_CONFIG};
8+
use crate::startup_inputs::_helpers::pure_paths::{
9+
absolute, is_path_file, modified_ns, order_key, pure,
10+
};
11+
use crate::startup_inputs::constants::VENV_CONFIG;
1212
use crate::startup_inputs::models::{StartupLocations, StartupStamp};
1313

1414
/// Sorted stamps of the startup paths; missing paths are unstamped and other stat failures fail.
@@ -32,29 +32,33 @@ pub fn startup_stamps(
3232
locations
3333
.prefixes
3434
.iter()
35-
.map(|prefix| pure(prefix, &[VENV_CONFIG]).into_os_string()),
35+
.map(|prefix| pure(prefix, &[OsStr::new(VENV_CONFIG)]).into_os_string()),
3636
);
3737
for directory in &directories {
3838
let directory: &Path = Path::new(directory);
3939
let Ok(listing) = std::fs::read_dir(directory) else {
4040
continue;
4141
};
4242
for entry in listing.flatten() {
43-
if entry.file_name().as_bytes().ends_with(PATH_FILE_SUFFIX) {
44-
paths.insert(pure(directory, &[entry.file_name().as_bytes()]).into_os_string());
43+
let name: OsString = entry.file_name();
44+
if is_path_file(&name) {
45+
paths.insert(pure(directory, &[name.as_os_str()]).into_os_string());
4546
}
4647
}
4748
}
4849
let mut ordered: Vec<PathBuf> = paths.into_iter().map(PathBuf::from).collect();
49-
ordered.sort_by(|left, right| order_key(left).cmp(&order_key(right)));
50+
ordered.sort_by_key(|path| order_key(path));
5051
ordered
5152
.into_iter()
5253
.map(|path| match std::fs::metadata(&path) {
53-
Ok(metadata) => Ok(StartupStamp {
54-
modified_ns: Some(metadata.mtime() * 1_000_000_000 + metadata.mtime_nsec()),
55-
size: Some(metadata.len()),
56-
path,
57-
}),
54+
Ok(metadata) => match modified_ns(&metadata) {
55+
Ok(modified) => Ok(StartupStamp {
56+
modified_ns: Some(modified),
57+
size: Some(metadata.len()),
58+
path,
59+
}),
60+
Err(error) => Err((path, error)),
61+
},
5862
Err(error) if error.kind() == io::ErrorKind::NotFound => Ok(StartupStamp {
5963
path,
6064
modified_ns: None,

0 commit comments

Comments
 (0)