mirror of
https://github.com/astral-sh/uv.git
synced 2025-07-07 21:35:00 +00:00
Filter Windows Python executables by file version metadata during discovery
This commit is contained in:
parent
e67dff85cc
commit
e0399dafd8
6 changed files with 412 additions and 21 deletions
59
Cargo.lock
generated
59
Cargo.lock
generated
|
@ -738,7 +738,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"windows-sys 0.48.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -948,6 +948,15 @@ version = "0.2.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8d7439c3735f405729d52c3fbbe4de140eaf938a1fe47d227c27f8254d4302a5"
|
||||
|
||||
[[package]]
|
||||
name = "dataview"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "47a802a2cad0ff4dfc4f3110da174b7a6928c315cae523e88638cfb72941b4d5"
|
||||
dependencies = [
|
||||
"derive_pod",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "deadpool"
|
||||
version = "0.10.0"
|
||||
|
@ -977,6 +986,12 @@ dependencies = [
|
|||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_pod"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2ea6706d74fca54e15f1d40b5cf7fe7f764aaec61352a9fcec58fe27e042fc8"
|
||||
|
||||
[[package]]
|
||||
name = "diff"
|
||||
version = "0.1.13"
|
||||
|
@ -1115,7 +1130,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1941,7 +1956,7 @@ checksum = "e19b23d53f35ce9f56aebc7d1bb4e6ac1e9c0db7ac85c8d1760c04379edced37"
|
|||
dependencies = [
|
||||
"hermit-abi 0.4.0",
|
||||
"libc",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -2001,7 +2016,7 @@ dependencies = [
|
|||
"portable-atomic",
|
||||
"portable-atomic-util",
|
||||
"serde",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -2385,6 +2400,12 @@ dependencies = [
|
|||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "no-std-compat"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b93853da6d84c2e3c7d730d6473e8817692dd89be387eb01b94d7f108ecb5b8c"
|
||||
|
||||
[[package]]
|
||||
name = "normalize-line-endings"
|
||||
version = "0.3.0"
|
||||
|
@ -2561,6 +2582,25 @@ version = "0.2.3"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3"
|
||||
|
||||
[[package]]
|
||||
name = "pelite"
|
||||
version = "0.9.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a821dd5a5c4744099b50dc94a6a381c8b4b007f4d80da5334428e220945319b"
|
||||
dependencies = [
|
||||
"dataview",
|
||||
"libc",
|
||||
"no-std-compat",
|
||||
"pelite-macros",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pelite-macros"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a7cf3f8ecebb0f4895f4892a8be0a0dc81b498f9d56735cb769dc31bf00815b"
|
||||
|
||||
[[package]]
|
||||
name = "percent-encoding"
|
||||
version = "2.3.1"
|
||||
|
@ -2885,7 +2925,7 @@ dependencies = [
|
|||
"once_cell",
|
||||
"socket2",
|
||||
"tracing",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -3334,7 +3374,7 @@ dependencies = [
|
|||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys 0.4.15",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -3347,7 +3387,7 @@ dependencies = [
|
|||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys 0.9.2",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -3928,7 +3968,7 @@ dependencies = [
|
|||
"getrandom 0.3.1",
|
||||
"once_cell",
|
||||
"rustix 1.0.7",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -5603,6 +5643,7 @@ dependencies = [
|
|||
"itertools 0.14.0",
|
||||
"once_cell",
|
||||
"owo-colors",
|
||||
"pelite",
|
||||
"procfs",
|
||||
"regex",
|
||||
"reqwest",
|
||||
|
@ -6282,7 +6323,7 @@ version = "0.1.9"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
|
||||
dependencies = [
|
||||
"windows-sys 0.48.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
|
@ -132,6 +132,7 @@ owo-colors = { version = "4.1.0" }
|
|||
path-slash = { version = "0.2.1" }
|
||||
pathdiff = { version = "0.2.1" }
|
||||
percent-encoding = { version = "2.3.1" }
|
||||
pelite = { version = "0.9" }
|
||||
petgraph = { version = "0.8.0" }
|
||||
proc-macro2 = { version = "1.0.86" }
|
||||
procfs = { version = "0.17.0", default-features = false, features = ["flate2"] }
|
||||
|
|
|
@ -71,6 +71,7 @@ procfs = { workspace = true }
|
|||
windows-registry = { workspace = true }
|
||||
windows-result = { workspace = true }
|
||||
windows-sys = { workspace = true }
|
||||
pelite = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
anyhow = { workspace = true }
|
||||
|
|
|
@ -28,6 +28,8 @@ use crate::interpreter::{StatusCodeError, UnexpectedResponseError};
|
|||
use crate::managed::ManagedPythonInstallations;
|
||||
#[cfg(windows)]
|
||||
use crate::microsoft_store::find_microsoft_store_pythons;
|
||||
#[cfg(windows)]
|
||||
use crate::pe_version::extract_version_from_pe;
|
||||
use crate::virtualenv::Error as VirtualEnvError;
|
||||
use crate::virtualenv::{
|
||||
CondaEnvironmentKind, conda_environment_from_env, virtualenv_from_env,
|
||||
|
@ -554,6 +556,20 @@ fn python_executables_from_search_path<'a>(
|
|||
.into_iter()
|
||||
.flatten(),
|
||||
)
|
||||
.filter(|path| {
|
||||
// On Windows, try PE version extraction first as an optimization
|
||||
// Skip this executable if PE version doesn't match
|
||||
if let Some(pe_matches) = try_pe_version_check(path, Some(version)) {
|
||||
if !pe_matches {
|
||||
debug!(
|
||||
"Skipping `{}` based on PE version mismatch",
|
||||
path.display()
|
||||
);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
true
|
||||
})
|
||||
})
|
||||
.into_iter()
|
||||
.flatten()
|
||||
|
@ -668,23 +684,78 @@ fn python_interpreters<'a>(
|
|||
})
|
||||
}
|
||||
|
||||
/// On Windows, try to extract version from PE metadata to potentially skip expensive interpreter queries.
|
||||
/// Returns `None` if PE extraction fails or is not available, indicating fallback to full query.
|
||||
#[cfg(windows)]
|
||||
fn try_pe_version_check(path: &Path, request: Option<&VersionRequest>) -> Option<bool> {
|
||||
let Some(request) = request else {
|
||||
// No specific version requested, can't optimize
|
||||
return None;
|
||||
};
|
||||
|
||||
let pe_version = match extract_version_from_pe(path) {
|
||||
Ok(Some(version)) => version,
|
||||
Ok(None) => {
|
||||
debug!("No version info found in PE file: {}", path.display());
|
||||
return None;
|
||||
}
|
||||
Err(err) => {
|
||||
debug!(
|
||||
"Failed to extract PE version from {}: {}",
|
||||
path.display(),
|
||||
err
|
||||
);
|
||||
return None;
|
||||
}
|
||||
};
|
||||
|
||||
let matches = request.matches_version(&pe_version);
|
||||
|
||||
if matches {
|
||||
debug!(
|
||||
"PE version {} from {} matches request {}",
|
||||
pe_version,
|
||||
path.display(),
|
||||
request
|
||||
);
|
||||
} else {
|
||||
debug!(
|
||||
"PE version {} from {} does not match request {}",
|
||||
pe_version,
|
||||
path.display(),
|
||||
request
|
||||
);
|
||||
}
|
||||
|
||||
Some(matches)
|
||||
}
|
||||
|
||||
/// Non-Windows version always returns None (no optimization available)
|
||||
#[cfg(not(windows))]
|
||||
fn try_pe_version_check(_path: &Path, _request: Option<&VersionRequest>) -> Option<bool> {
|
||||
None
|
||||
}
|
||||
|
||||
/// Lazily convert Python executables into interpreters.
|
||||
fn python_interpreters_from_executables<'a>(
|
||||
executables: impl Iterator<Item = Result<(PythonSource, PathBuf), Error>> + 'a,
|
||||
cache: &'a Cache,
|
||||
) -> impl Iterator<Item = Result<(PythonSource, Interpreter), Error>> + 'a {
|
||||
executables.map(|result| match result {
|
||||
Ok((source, path)) => Interpreter::query(&path, cache)
|
||||
.map(|interpreter| (source, interpreter))
|
||||
.inspect(|(source, interpreter)| {
|
||||
debug!(
|
||||
"Found `{}` at `{}` ({source})",
|
||||
interpreter.key(),
|
||||
path.display()
|
||||
);
|
||||
})
|
||||
.map_err(|err| Error::Query(Box::new(err), path, source))
|
||||
.inspect_err(|err| debug!("{err}")),
|
||||
executables.map(move |result| match result {
|
||||
Ok((source, path)) => {
|
||||
// Proceed with full interpreter query
|
||||
Interpreter::query(&path, cache)
|
||||
.map(|interpreter| (source, interpreter))
|
||||
.inspect(|(source, interpreter)| {
|
||||
debug!(
|
||||
"Found `{}` at `{}` ({source})",
|
||||
interpreter.key(),
|
||||
path.display()
|
||||
);
|
||||
})
|
||||
.map_err(|err| Error::Query(Box::new(err), path, source))
|
||||
.inspect_err(|err| debug!("{err}"))
|
||||
}
|
||||
Err(err) => Err(err),
|
||||
})
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ pub use crate::environment::{InvalidEnvironmentKind, PythonEnvironment};
|
|||
pub use crate::implementation::ImplementationName;
|
||||
pub use crate::installation::{PythonInstallation, PythonInstallationKey};
|
||||
pub use crate::interpreter::{BrokenSymlink, Error as InterpreterError, Interpreter};
|
||||
pub use crate::pe_version::extract_version_from_pe;
|
||||
pub use crate::pointer_size::PointerSize;
|
||||
pub use crate::prefix::Prefix;
|
||||
pub use crate::python_version::PythonVersion;
|
||||
|
@ -35,6 +36,7 @@ pub mod macos_dylib;
|
|||
pub mod managed;
|
||||
#[cfg(windows)]
|
||||
mod microsoft_store;
|
||||
mod pe_version;
|
||||
pub mod platform;
|
||||
mod pointer_size;
|
||||
mod prefix;
|
||||
|
|
275
crates/uv-python/src/pe_version.rs
Normal file
275
crates/uv-python/src/pe_version.rs
Normal file
|
@ -0,0 +1,275 @@
|
|||
//! Extract version information from Windows PE executables.
|
||||
//!
|
||||
//! This module provides functionality to extract Python version information
|
||||
//! directly from Windows PE executable files using the pelite crate, which
|
||||
//! can be faster than executing the Python interpreter to query its version.
|
||||
|
||||
use std::path::Path;
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
use std::str::FromStr;
|
||||
|
||||
#[cfg(windows)]
|
||||
use tracing::debug;
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
use pelite::{pe32::Pe as Pe32, pe64::Pe as Pe64};
|
||||
|
||||
use crate::PythonVersion;
|
||||
|
||||
/// Extract Python version information from a Windows PE executable.
|
||||
///
|
||||
/// This function reads the PE file's version resource to extract version
|
||||
/// information without executing the Python interpreter. This can be
|
||||
/// significantly faster for version discovery.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `path` - Path to the Python executable
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// Returns `Ok(Some(PythonVersion))` if version information was successfully
|
||||
/// extracted, `Ok(None)` if no version information was found, or an error
|
||||
/// if the file could not be read or parsed.
|
||||
#[cfg(target_os = "windows")]
|
||||
pub fn extract_version_from_pe(path: &Path) -> Result<Option<PythonVersion>, std::io::Error> {
|
||||
use pelite::FileMap;
|
||||
|
||||
debug!("Extracting version info from PE file: {}", path.display());
|
||||
|
||||
// Read the PE file
|
||||
let map = FileMap::open(path)?;
|
||||
|
||||
// Parse as PE64 first, fall back to PE32 if needed
|
||||
match parse_pe64_version(&map) {
|
||||
Ok(version) => Ok(version),
|
||||
Err(_) => parse_pe32_version(&map),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
fn parse_pe64_version(map: &pelite::FileMap) -> Result<Option<PythonVersion>, std::io::Error> {
|
||||
use pelite::pe64::PeFile;
|
||||
|
||||
let pe = PeFile::from_bytes(map).map_err(|e| {
|
||||
std::io::Error::new(
|
||||
std::io::ErrorKind::InvalidData,
|
||||
format!("Failed to parse PE64 file: {e}"),
|
||||
)
|
||||
})?;
|
||||
|
||||
extract_version_from_pe64_file(&pe)
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
fn parse_pe32_version(map: &pelite::FileMap) -> Result<Option<PythonVersion>, std::io::Error> {
|
||||
use pelite::pe32::PeFile;
|
||||
|
||||
let pe = PeFile::from_bytes(map).map_err(|e| {
|
||||
std::io::Error::new(
|
||||
std::io::ErrorKind::InvalidData,
|
||||
format!("Failed to parse PE32 file: {e}"),
|
||||
)
|
||||
})?;
|
||||
|
||||
extract_version_from_pe32_file(&pe)
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
fn extract_version_from_pe64_file(
|
||||
pe: &pelite::pe64::PeFile,
|
||||
) -> Result<Option<PythonVersion>, std::io::Error> {
|
||||
// Get resources from the PE file
|
||||
let resources = pe.resources().map_err(|e| {
|
||||
std::io::Error::new(
|
||||
std::io::ErrorKind::InvalidData,
|
||||
format!("Failed to read PE resources: {e}"),
|
||||
)
|
||||
})?;
|
||||
|
||||
// Try to get version info
|
||||
let Ok(version_info) = resources.version_info() else {
|
||||
debug!("No version info found in PE file");
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
// Get the fixed file info which contains version numbers
|
||||
let Some(fixed_info) = version_info.fixed() else {
|
||||
debug!("No fixed version info found in PE file");
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
// Extract version from the file version field
|
||||
let file_version = fixed_info.dwFileVersion;
|
||||
#[allow(clippy::cast_possible_truncation)]
|
||||
let major = file_version.Major as u8;
|
||||
#[allow(clippy::cast_possible_truncation)]
|
||||
let minor = file_version.Minor as u8;
|
||||
#[allow(clippy::cast_possible_truncation)]
|
||||
let patch = file_version.Patch as u8;
|
||||
|
||||
// Validate that this looks like a Python version
|
||||
if major == 0 || major > 10 || minor > 50 {
|
||||
debug!(
|
||||
"Version {}.{}.{} doesn't look like a Python version",
|
||||
major, minor, patch
|
||||
);
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
debug!("Extracted Python version: {}.{}.{}", major, minor, patch);
|
||||
|
||||
match PythonVersion::from_str(&format!("{major}.{minor}.{patch}")) {
|
||||
Ok(version) => Ok(Some(version)),
|
||||
Err(e) => {
|
||||
debug!(
|
||||
"Failed to parse version {}.{}.{}: {}",
|
||||
major, minor, patch, e
|
||||
);
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
fn extract_version_from_pe32_file(
|
||||
pe: &pelite::pe32::PeFile,
|
||||
) -> Result<Option<PythonVersion>, std::io::Error> {
|
||||
// Get resources from the PE file
|
||||
let resources = pe.resources().map_err(|e| {
|
||||
std::io::Error::new(
|
||||
std::io::ErrorKind::InvalidData,
|
||||
format!("Failed to read PE resources: {e}"),
|
||||
)
|
||||
})?;
|
||||
|
||||
// Try to get version info
|
||||
let Ok(version_info) = resources.version_info() else {
|
||||
debug!("No version info found in PE file");
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
// Get the fixed file info which contains version numbers
|
||||
let Some(fixed_info) = version_info.fixed() else {
|
||||
debug!("No fixed version info found in PE file");
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
// Extract version from the file version field
|
||||
let file_version = fixed_info.dwFileVersion;
|
||||
#[allow(clippy::cast_possible_truncation)]
|
||||
let major = file_version.Major as u8;
|
||||
#[allow(clippy::cast_possible_truncation)]
|
||||
let minor = file_version.Minor as u8;
|
||||
#[allow(clippy::cast_possible_truncation)]
|
||||
let patch = file_version.Patch as u8;
|
||||
|
||||
// Validate that this looks like a Python version
|
||||
if major == 0 || major > 10 || minor > 50 {
|
||||
debug!(
|
||||
"Version {}.{}.{} doesn't look like a Python version",
|
||||
major, minor, patch
|
||||
);
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
debug!("Extracted Python version: {}.{}.{}", major, minor, patch);
|
||||
|
||||
match PythonVersion::from_str(&format!("{major}.{minor}.{patch}")) {
|
||||
Ok(version) => Ok(Some(version)),
|
||||
Err(e) => {
|
||||
debug!(
|
||||
"Failed to parse version {}.{}.{}: {}",
|
||||
major, minor, patch, e
|
||||
);
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Extract version information from a Windows PE executable.
|
||||
///
|
||||
/// On non-Windows platforms, this function always returns `Ok(None)`.
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
pub fn extract_version_from_pe(_path: &Path) -> Result<Option<PythonVersion>, std::io::Error> {
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_basic_pe_version_functionality() {
|
||||
use std::str::FromStr;
|
||||
|
||||
// Basic test for the non-Windows version
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
{
|
||||
let result = extract_version_from_pe(Path::new("test.exe"));
|
||||
assert_eq!(result.unwrap(), None);
|
||||
}
|
||||
|
||||
// Test PythonVersion parsing
|
||||
assert!(PythonVersion::from_str("3.12.0").is_ok());
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::str::FromStr;
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
use std::io::Write;
|
||||
#[cfg(target_os = "windows")]
|
||||
use tempfile::NamedTempFile;
|
||||
|
||||
#[test]
|
||||
#[cfg(target_os = "windows")]
|
||||
fn test_extract_version_from_nonexistent_file() {
|
||||
let result = extract_version_from_pe(Path::new("nonexistent.exe"));
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(target_os = "windows")]
|
||||
fn test_extract_version_from_invalid_pe_file() {
|
||||
// Create a temporary file with invalid PE content
|
||||
let mut temp_file = NamedTempFile::new().unwrap();
|
||||
temp_file.write_all(b"Not a PE file").unwrap();
|
||||
temp_file.flush().unwrap();
|
||||
|
||||
let result = extract_version_from_pe(temp_file.path());
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
fn test_extract_version_non_windows() {
|
||||
let result = extract_version_from_pe(Path::new("python.exe"));
|
||||
assert_eq!(result.unwrap(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_version_validation() {
|
||||
// Test that valid Python versions work
|
||||
assert!(PythonVersion::from_str("3.12.0").is_ok());
|
||||
assert!(PythonVersion::from_str("3.9.18").is_ok());
|
||||
assert!(PythonVersion::from_str("3.13.1").is_ok());
|
||||
|
||||
// Test some edge cases that should still work
|
||||
assert!(PythonVersion::from_str("0.1.0").is_ok()); // PythonVersion allows this
|
||||
|
||||
// Test malformed versions
|
||||
assert!(PythonVersion::from_str("not.a.version").is_err());
|
||||
assert!(PythonVersion::from_str("").is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_always_runs() {
|
||||
// This test should always run regardless of platform
|
||||
// Test that the non-Windows version works
|
||||
let result = extract_version_from_pe(Path::new("fake.exe"));
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
assert_eq!(result.unwrap(), None);
|
||||
#[cfg(target_os = "windows")]
|
||||
assert!(result.is_err() || result.unwrap().is_none());
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue