From 99a874406ac45934f72c4719da878a758271f3ec Mon Sep 17 00:00:00 2001 From: pb01ka Date: Tue, 28 Apr 2026 01:49:09 +0530 Subject: [PATCH 01/12] fix(source-cache): hint users to enable Developer Mode on Windows extraction failure When extracting a tar archive on Windows fails (e.g. due to symlinks requiring elevated privileges), check whether Developer Mode is enabled via the registry. If it is not, append an actionable hint pointing the user to Settings > System > For developers > Developer Mode. --- Cargo.lock | 1 + crates/rattler_build_source_cache/Cargo.toml | 3 + .../rattler_build_source_cache/src/cache.rs | 85 +++++++++++++++++-- 3 files changed, 80 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e4caa59cb..ae4031dd6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5106,6 +5106,7 @@ dependencies = [ "tracing", "url", "walkdir", + "windows-sys 0.61.2", "zip", "zstd", ] diff --git a/crates/rattler_build_source_cache/Cargo.toml b/crates/rattler_build_source_cache/Cargo.toml index 91ed5e44e..03e1a9721 100644 --- a/crates/rattler_build_source_cache/Cargo.toml +++ b/crates/rattler_build_source_cache/Cargo.toml @@ -63,6 +63,9 @@ sigstore-verify = { version = "0.6.4", default-features = false, optional = true sigstore-trust-root = { version = "0.6.4", default-features = false, optional = true } sigstore-types = { version = "0.6.4", optional = true } +[target.'cfg(target_os = "windows")'.dependencies] +windows-sys = { workspace = true, features = ["Win32_System_Registry", "Win32_Foundation"] } + [dev-dependencies] tokio = { workspace = true, features = ["rt", "macros", "test-util"] } tempfile.workspace = true diff --git a/crates/rattler_build_source_cache/src/cache.rs b/crates/rattler_build_source_cache/src/cache.rs index 6802e3b6e..5392d83d4 100644 --- a/crates/rattler_build_source_cache/src/cache.rs +++ b/crates/rattler_build_source_cache/src/cache.rs @@ -711,6 +711,73 @@ pub fn is_tarball(file_name: &str) -> bool { .any(|ext| file_name.ends_with(ext)) } +/// On Windows, checks whether Developer Mode is enabled by reading the registry key +/// `HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\AppModelUnlock`. +/// Returns `false` if the check cannot be performed or the key does not indicate enabled. +#[cfg(target_os = "windows")] +fn is_windows_developer_mode_enabled() -> bool { + use windows_sys::Win32::Foundation::ERROR_SUCCESS; + use windows_sys::Win32::System::Registry::{ + HKEY, HKEY_LOCAL_MACHINE, KEY_READ, RegCloseKey, RegOpenKeyExW, RegQueryValueExW, + }; + + let sub_key: Vec = + "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\AppModelUnlock\0" + .encode_utf16() + .collect(); + let value_name: Vec = "AllowDevelopmentWithoutDevLicense\0" + .encode_utf16() + .collect(); + + // SAFETY: We call standard Windows registry APIs with valid, null-terminated UTF-16 + // strings and correctly sized output buffers. + unsafe { + let mut hkey: HKEY = std::ptr::null_mut(); + let result = RegOpenKeyExW( + HKEY_LOCAL_MACHINE, + sub_key.as_ptr(), + 0, + KEY_READ, + std::ptr::addr_of_mut!(hkey), + ); + if result != ERROR_SUCCESS { + return false; + } + + let mut data: u32 = 0; + let mut data_size = std::mem::size_of::() as u32; + let mut reg_type: u32 = 0; + + let result = RegQueryValueExW( + hkey, + value_name.as_ptr(), + std::ptr::null(), + &mut reg_type, + std::ptr::addr_of_mut!(data) as *mut u8, + &mut data_size, + ); + RegCloseKey(hkey); + + result == ERROR_SUCCESS && data == 1 + } +} + +/// Wraps a tar unpack error, appending a Windows Developer Mode hint when on Windows and +/// Developer Mode is detected as disabled. +fn tar_unpack_error(context: &str, e: std::io::Error) -> CacheError { + #[cfg(target_os = "windows")] + if !is_windows_developer_mode_enabled() { + return CacheError::ExtractionError(format!( + "{context}: {e}\n\n\ + hint: Extracting this archive failed on Windows. The archive may contain symbolic \ + links, which require Developer Mode or administrator privileges to create. \ + Please enable Developer Mode in: \ + Settings > System > For developers > Developer Mode, then retry the build." + )); + } + CacheError::ExtractionError(format!("{context}: {e}")) +} + fn extract_tar(archive: &Path, target: &Path) -> Result<(), CacheError> { let file = fs_err::File::open(archive) .map_err(|e| CacheError::ExtractionError(format!("Failed to open archive: {}", e)))?; @@ -721,30 +788,30 @@ fn extract_tar(archive: &Path, target: &Path) -> Result<(), CacheError> { let mut archive = tar::Archive::new(GzDecoder::new(file)); archive .unpack(target) - .map_err(|e| CacheError::ExtractionError(format!("Failed to extract tar.gz: {}", e)))?; + .map_err(|e| tar_unpack_error("Failed to extract tar.gz", e))?; } else if name.ends_with(".tar.bz2") || name.ends_with(".tbz2") { let mut archive = tar::Archive::new(bzip2::read::BzDecoder::new(file)); - archive.unpack(target).map_err(|e| { - CacheError::ExtractionError(format!("Failed to extract tar.bz2: {}", e)) - })?; + archive + .unpack(target) + .map_err(|e| tar_unpack_error("Failed to extract tar.bz2", e))?; } else if name.ends_with(".tar.xz") || name.ends_with(".txz") { let mut archive = tar::Archive::new(lzma_rust2::XzReader::new(file, true)); archive .unpack(target) - .map_err(|e| CacheError::ExtractionError(format!("Failed to extract tar.xz: {}", e)))?; + .map_err(|e| tar_unpack_error("Failed to extract tar.xz", e))?; } else if name.ends_with(".tar.zst") { let decoder = zstd::stream::read::Decoder::new(file).map_err(|e| { CacheError::ExtractionError(format!("Failed to create zstd decoder: {}", e)) })?; let mut archive = tar::Archive::new(decoder); - archive.unpack(target).map_err(|e| { - CacheError::ExtractionError(format!("Failed to extract tar.zst: {}", e)) - })?; + archive + .unpack(target) + .map_err(|e| tar_unpack_error("Failed to extract tar.zst", e))?; } else { let mut archive = tar::Archive::new(file); archive .unpack(target) - .map_err(|e| CacheError::ExtractionError(format!("Failed to extract tar: {}", e)))?; + .map_err(|e| tar_unpack_error("Failed to extract tar", e))?; } Ok(()) From 303d658fbdfe436c48398114ae55a2ca96ed27ef Mon Sep 17 00:00:00 2001 From: pb01ka Date: Tue, 28 Apr 2026 21:19:37 +0530 Subject: [PATCH 02/12] test(source-cache): add symlink extraction test for Windows Developer Mode hint --- .../rattler_build_source_cache/src/cache.rs | 2 +- .../rattler_build_source_cache/src/tests.rs | 50 +++++++++++++++++++ 2 files changed, 51 insertions(+), 1 deletion(-) diff --git a/crates/rattler_build_source_cache/src/cache.rs b/crates/rattler_build_source_cache/src/cache.rs index 5392d83d4..fb51f4314 100644 --- a/crates/rattler_build_source_cache/src/cache.rs +++ b/crates/rattler_build_source_cache/src/cache.rs @@ -778,7 +778,7 @@ fn tar_unpack_error(context: &str, e: std::io::Error) -> CacheError { CacheError::ExtractionError(format!("{context}: {e}")) } -fn extract_tar(archive: &Path, target: &Path) -> Result<(), CacheError> { +pub(crate) fn extract_tar(archive: &Path, target: &Path) -> Result<(), CacheError> { let file = fs_err::File::open(archive) .map_err(|e| CacheError::ExtractionError(format!("Failed to open archive: {}", e)))?; diff --git a/crates/rattler_build_source_cache/src/tests.rs b/crates/rattler_build_source_cache/src/tests.rs index c318a345c..2448e8fe7 100644 --- a/crates/rattler_build_source_cache/src/tests.rs +++ b/crates/rattler_build_source_cache/src/tests.rs @@ -344,6 +344,56 @@ mod source_cache_tests { ); } + /// Creates a minimal `.tar.gz` at `dest` containing a single symlink entry. + fn make_tar_gz_with_symlink(dest: &std::path::Path) { + use flate2::{Compression, write::GzEncoder}; + + let file = fs_err::File::create(dest).unwrap(); + let enc = GzEncoder::new(file, Compression::default()); + let mut builder = tar::Builder::new(enc); + + let mut header = tar::Header::new_gnu(); + header.set_entry_type(tar::EntryType::Symlink); + header.set_size(0); + header.set_mode(0o777); + header.set_cksum(); + builder + .append_link(&mut header, "link_target", "real_file") + .unwrap(); + builder.finish().unwrap(); + } + + /// On Windows without Developer Mode the extraction of a symlink-containing + /// archive should fail with a hint about enabling Developer Mode. + /// On Windows with Developer Mode enabled, or on other OSes, the extraction + /// succeeds (or fails without the hint). + #[test] + fn test_symlink_extraction_error_message() { + use crate::cache::extract_tar; + + let dir = TempDir::new().unwrap(); + let archive = dir.path().join("test.tar.gz"); + make_tar_gz_with_symlink(&archive); + + let target = dir.path().join("out"); + fs_err::create_dir_all(&target).unwrap(); + + match extract_tar(&archive, &target) { + Ok(()) => { + // Symlinks extracted fine — Developer Mode is on (or not Windows) + } + Err(CacheError::ExtractionError(msg)) => { + // On Windows without Developer Mode the hint must be present + #[cfg(target_os = "windows")] + assert!( + msg.contains("Developer Mode"), + "Expected Developer Mode hint in error message, got: {msg}" + ); + } + Err(e) => panic!("Unexpected error variant: {e}"), + } + } + #[test] fn test_extract_filename_from_header_strips_path_components() { use crate::cache::extract_filename_from_header; From f837c6f9dccb2a12ffded6255a4b5e54a705a5e1 Mon Sep 17 00:00:00 2001 From: pb01ka Date: Tue, 28 Apr 2026 22:54:35 +0530 Subject: [PATCH 03/12] chore: Improvise error message --- crates/rattler_build_source_cache/src/cache.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/rattler_build_source_cache/src/cache.rs b/crates/rattler_build_source_cache/src/cache.rs index fb51f4314..3241f50d2 100644 --- a/crates/rattler_build_source_cache/src/cache.rs +++ b/crates/rattler_build_source_cache/src/cache.rs @@ -772,7 +772,7 @@ fn tar_unpack_error(context: &str, e: std::io::Error) -> CacheError { hint: Extracting this archive failed on Windows. The archive may contain symbolic \ links, which require Developer Mode or administrator privileges to create. \ Please enable Developer Mode in: \ - Settings > System > For developers > Developer Mode, then retry the build." + Developer settings > Developer Mode, then retry the build." )); } CacheError::ExtractionError(format!("{context}: {e}")) From 7fd6b43319b0cf59d5b2187af16e3cb44bca9c30 Mon Sep 17 00:00:00 2001 From: pb01ka Date: Tue, 28 Apr 2026 22:58:23 +0530 Subject: [PATCH 04/12] chore(tests): run test_symlink_extraction_error_message only on Windows --- crates/rattler_build_source_cache/src/tests.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/crates/rattler_build_source_cache/src/tests.rs b/crates/rattler_build_source_cache/src/tests.rs index 2448e8fe7..c300d664f 100644 --- a/crates/rattler_build_source_cache/src/tests.rs +++ b/crates/rattler_build_source_cache/src/tests.rs @@ -365,9 +365,9 @@ mod source_cache_tests { /// On Windows without Developer Mode the extraction of a symlink-containing /// archive should fail with a hint about enabling Developer Mode. - /// On Windows with Developer Mode enabled, or on other OSes, the extraction - /// succeeds (or fails without the hint). + /// On Windows with Developer Mode enabled the extraction succeeds. #[test] + #[cfg(target_os = "windows")] fn test_symlink_extraction_error_message() { use crate::cache::extract_tar; @@ -383,8 +383,6 @@ mod source_cache_tests { // Symlinks extracted fine — Developer Mode is on (or not Windows) } Err(CacheError::ExtractionError(msg)) => { - // On Windows without Developer Mode the hint must be present - #[cfg(target_os = "windows")] assert!( msg.contains("Developer Mode"), "Expected Developer Mode hint in error message, got: {msg}" From 99a124b90d8616726410f74448c2cd22f918d264 Mon Sep 17 00:00:00 2001 From: pb01ka Date: Tue, 28 Apr 2026 23:12:14 +0530 Subject: [PATCH 05/12] doc: Added code comments for is_windows_developer_mode_enabled implementation --- .../rattler_build_source_cache/src/cache.rs | 34 +++++++++++++------ 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/crates/rattler_build_source_cache/src/cache.rs b/crates/rattler_build_source_cache/src/cache.rs index 3241f50d2..35f520f34 100644 --- a/crates/rattler_build_source_cache/src/cache.rs +++ b/crates/rattler_build_source_cache/src/cache.rs @@ -729,35 +729,47 @@ fn is_windows_developer_mode_enabled() -> bool { .encode_utf16() .collect(); - // SAFETY: We call standard Windows registry APIs with valid, null-terminated UTF-16 - // strings and correctly sized output buffers. + // The three steps below are equivalent to running: + // reg query "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\AppModelUnlock" /v AllowDevelopmentWithoutDevLicense unsafe { + // Step 1 - Open the key (equivalent to the path argument of `reg query`). + // RegOpenKeyExW returns a handle (`hkey`) to the opened key, analogous to + // a file descriptor. On failure (key missing, no permission, etc.) it + // returns a non-ERROR_SUCCESS code and we bail out early. let mut hkey: HKEY = std::ptr::null_mut(); let result = RegOpenKeyExW( - HKEY_LOCAL_MACHINE, - sub_key.as_ptr(), + HKEY_LOCAL_MACHINE, // HKLM + sub_key.as_ptr(), // \SOFTWARE\...\AppModelUnlock 0, - KEY_READ, - std::ptr::addr_of_mut!(hkey), + KEY_READ, // read-only access + std::ptr::addr_of_mut!(hkey), // out: handle to the opened key ); if result != ERROR_SUCCESS { return false; } + // Step 2 - Read the value (equivalent to `/v AllowDevelopmentWithoutDevLicense`). + // Windows writes the raw bytes of the REG_DWORD value directly into `data`, + // so after this call `data` holds the integer stored in the registry: + // 0x1 -> Developer Mode ON + // 0x0 -> Developer Mode OFF (or key absent) let mut data: u32 = 0; let mut data_size = std::mem::size_of::() as u32; let mut reg_type: u32 = 0; let result = RegQueryValueExW( hkey, - value_name.as_ptr(), - std::ptr::null(), - &mut reg_type, - std::ptr::addr_of_mut!(data) as *mut u8, - &mut data_size, + value_name.as_ptr(), // "AllowDevelopmentWithoutDevLicense" + std::ptr::null(), // reserved, always null + &mut reg_type, // out: value type (REG_DWORD = 4) + std::ptr::addr_of_mut!(data) as *mut u8, // out: raw bytes of the value + &mut data_size, // in/out: buffer size in bytes ); + + // Step 3 - Close the handle. RegCloseKey(hkey); + // Step 4 - Check the result: success + data == 1 (0x1) means enabled. result == ERROR_SUCCESS && data == 1 } } From 59ebbe37b07177a961a1f307129eb648c5fb4eee Mon Sep 17 00:00:00 2001 From: pb01ka Date: Wed, 29 Apr 2026 01:23:13 +0530 Subject: [PATCH 06/12] chore: Run pixi run -e pre-commit lint --- crates/rattler_build_source_cache/Cargo.toml | 5 +++- .../rattler_build_source_cache/src/cache.rs | 23 +++++++++---------- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/crates/rattler_build_source_cache/Cargo.toml b/crates/rattler_build_source_cache/Cargo.toml index 03e1a9721..8585cc78c 100644 --- a/crates/rattler_build_source_cache/Cargo.toml +++ b/crates/rattler_build_source_cache/Cargo.toml @@ -64,7 +64,10 @@ sigstore-trust-root = { version = "0.6.4", default-features = false, optional = sigstore-types = { version = "0.6.4", optional = true } [target.'cfg(target_os = "windows")'.dependencies] -windows-sys = { workspace = true, features = ["Win32_System_Registry", "Win32_Foundation"] } +windows-sys = { workspace = true, features = [ + "Win32_System_Registry", + "Win32_Foundation", +] } [dev-dependencies] tokio = { workspace = true, features = ["rt", "macros", "test-util"] } diff --git a/crates/rattler_build_source_cache/src/cache.rs b/crates/rattler_build_source_cache/src/cache.rs index 35f520f34..d3fa12274 100644 --- a/crates/rattler_build_source_cache/src/cache.rs +++ b/crates/rattler_build_source_cache/src/cache.rs @@ -721,10 +721,9 @@ fn is_windows_developer_mode_enabled() -> bool { HKEY, HKEY_LOCAL_MACHINE, KEY_READ, RegCloseKey, RegOpenKeyExW, RegQueryValueExW, }; - let sub_key: Vec = - "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\AppModelUnlock\0" - .encode_utf16() - .collect(); + let sub_key: Vec = "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\AppModelUnlock\0" + .encode_utf16() + .collect(); let value_name: Vec = "AllowDevelopmentWithoutDevLicense\0" .encode_utf16() .collect(); @@ -738,11 +737,11 @@ fn is_windows_developer_mode_enabled() -> bool { // returns a non-ERROR_SUCCESS code and we bail out early. let mut hkey: HKEY = std::ptr::null_mut(); let result = RegOpenKeyExW( - HKEY_LOCAL_MACHINE, // HKLM - sub_key.as_ptr(), // \SOFTWARE\...\AppModelUnlock + HKEY_LOCAL_MACHINE, // HKLM + sub_key.as_ptr(), // \SOFTWARE\...\AppModelUnlock 0, - KEY_READ, // read-only access - std::ptr::addr_of_mut!(hkey), // out: handle to the opened key + KEY_READ, // read-only access + std::ptr::addr_of_mut!(hkey), // out: handle to the opened key ); if result != ERROR_SUCCESS { return false; @@ -759,11 +758,11 @@ fn is_windows_developer_mode_enabled() -> bool { let result = RegQueryValueExW( hkey, - value_name.as_ptr(), // "AllowDevelopmentWithoutDevLicense" - std::ptr::null(), // reserved, always null - &mut reg_type, // out: value type (REG_DWORD = 4) + value_name.as_ptr(), // "AllowDevelopmentWithoutDevLicense" + std::ptr::null(), // reserved, always null + &mut reg_type, // out: value type (REG_DWORD = 4) std::ptr::addr_of_mut!(data) as *mut u8, // out: raw bytes of the value - &mut data_size, // in/out: buffer size in bytes + &mut data_size, // in/out: buffer size in bytes ); // Step 3 - Close the handle. From a5693eadaca453c0527bdc9d2822cba4f044b835 Mon Sep 17 00:00:00 2001 From: pb01ka Date: Wed, 29 Apr 2026 01:27:35 +0530 Subject: [PATCH 07/12] chore: Compile the helper make_tar_gz_with_symlink only on windows --- crates/rattler_build_source_cache/src/tests.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/rattler_build_source_cache/src/tests.rs b/crates/rattler_build_source_cache/src/tests.rs index c300d664f..2c0478814 100644 --- a/crates/rattler_build_source_cache/src/tests.rs +++ b/crates/rattler_build_source_cache/src/tests.rs @@ -345,6 +345,7 @@ mod source_cache_tests { } /// Creates a minimal `.tar.gz` at `dest` containing a single symlink entry. + #[cfg(target_os = "windows")] fn make_tar_gz_with_symlink(dest: &std::path::Path) { use flate2::{Compression, write::GzEncoder}; From 574b8d74db6ea869a4cbafae985ac01fa5e17ee1 Mon Sep 17 00:00:00 2001 From: pb01ka Date: Wed, 29 Apr 2026 01:42:11 +0530 Subject: [PATCH 08/12] chore: Run pixi run -e pre-commit lint --- py-rattler-build/rust/Cargo.lock | 1 + 1 file changed, 1 insertion(+) diff --git a/py-rattler-build/rust/Cargo.lock b/py-rattler-build/rust/Cargo.lock index bc4b04d19..07f82485a 100644 --- a/py-rattler-build/rust/Cargo.lock +++ b/py-rattler-build/rust/Cargo.lock @@ -4954,6 +4954,7 @@ dependencies = [ "tracing", "url", "walkdir", + "windows-sys 0.61.2", "zip", "zstd", ] From 447b5bd48368f55f08b4e98927a8f98f0180f4ef Mon Sep 17 00:00:00 2001 From: pb01ka Date: Wed, 29 Apr 2026 15:58:16 +0530 Subject: [PATCH 09/12] fix: Improve error handling and code safety --- Cargo.lock | 1 - crates/rattler_build_source_cache/Cargo.toml | 6 -- .../rattler_build_source_cache/src/cache.rs | 99 +++++++------------ py-rattler-build/rust/Cargo.lock | 1 - 4 files changed, 38 insertions(+), 69 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ae4031dd6..e4caa59cb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5106,7 +5106,6 @@ dependencies = [ "tracing", "url", "walkdir", - "windows-sys 0.61.2", "zip", "zstd", ] diff --git a/crates/rattler_build_source_cache/Cargo.toml b/crates/rattler_build_source_cache/Cargo.toml index 8585cc78c..91ed5e44e 100644 --- a/crates/rattler_build_source_cache/Cargo.toml +++ b/crates/rattler_build_source_cache/Cargo.toml @@ -63,12 +63,6 @@ sigstore-verify = { version = "0.6.4", default-features = false, optional = true sigstore-trust-root = { version = "0.6.4", default-features = false, optional = true } sigstore-types = { version = "0.6.4", optional = true } -[target.'cfg(target_os = "windows")'.dependencies] -windows-sys = { workspace = true, features = [ - "Win32_System_Registry", - "Win32_Foundation", -] } - [dev-dependencies] tokio = { workspace = true, features = ["rt", "macros", "test-util"] } tempfile.workspace = true diff --git a/crates/rattler_build_source_cache/src/cache.rs b/crates/rattler_build_source_cache/src/cache.rs index d3fa12274..9ef513c49 100644 --- a/crates/rattler_build_source_cache/src/cache.rs +++ b/crates/rattler_build_source_cache/src/cache.rs @@ -711,77 +711,54 @@ pub fn is_tarball(file_name: &str) -> bool { .any(|ext| file_name.ends_with(ext)) } -/// On Windows, checks whether Developer Mode is enabled by reading the registry key -/// `HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\AppModelUnlock`. -/// Returns `false` if the check cannot be performed or the key does not indicate enabled. +/// Returns `true` if the I/O error is caused by insufficient privilege to create a symbolic +/// link. +/// +/// On Windows, `CreateSymbolicLinkW` returns `ERROR_PRIVILEGE_NOT_HELD` (1314 / 0x522) when +/// the caller does not hold `SeCreateSymbolicLinkPrivilege` and Developer Mode is disabled. #[cfg(target_os = "windows")] -fn is_windows_developer_mode_enabled() -> bool { - use windows_sys::Win32::Foundation::ERROR_SUCCESS; - use windows_sys::Win32::System::Registry::{ - HKEY, HKEY_LOCAL_MACHINE, KEY_READ, RegCloseKey, RegOpenKeyExW, RegQueryValueExW, - }; +fn is_symlink_error(e: &std::io::Error) -> bool { + e.raw_os_error() == Some(1314) // ERROR_PRIVILEGE_NOT_HELD +} - let sub_key: Vec = "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\AppModelUnlock\0" - .encode_utf16() - .collect(); - let value_name: Vec = "AllowDevelopmentWithoutDevLicense\0" - .encode_utf16() - .collect(); - - // The three steps below are equivalent to running: - // reg query "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\AppModelUnlock" /v AllowDevelopmentWithoutDevLicense - unsafe { - // Step 1 - Open the key (equivalent to the path argument of `reg query`). - // RegOpenKeyExW returns a handle (`hkey`) to the opened key, analogous to - // a file descriptor. On failure (key missing, no permission, etc.) it - // returns a non-ERROR_SUCCESS code and we bail out early. - let mut hkey: HKEY = std::ptr::null_mut(); - let result = RegOpenKeyExW( - HKEY_LOCAL_MACHINE, // HKLM - sub_key.as_ptr(), // \SOFTWARE\...\AppModelUnlock - 0, - KEY_READ, // read-only access - std::ptr::addr_of_mut!(hkey), // out: handle to the opened key - ); - if result != ERROR_SUCCESS { - return false; +/// On Windows, checks whether Developer Mode is enabled by querying the registry via the +/// built-in `reg` command. Returns `false` if the query fails or the value is not `0x1`. +/// +/// Equivalent to: +/// reg query "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\AppModelUnlock" +/// /v AllowDevelopmentWithoutDevLicense +#[cfg(target_os = "windows")] +fn is_windows_developer_mode_enabled() -> bool { + let output = std::process::Command::new("reg") + .args([ + "query", + r"HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\AppModelUnlock", + "/v", + "AllowDevelopmentWithoutDevLicense", + ]) + .output(); + + match output { + Ok(out) if out.status.success() => { + // A successful query prints a line containing the value, e.g.: + // AllowDevelopmentWithoutDevLicense REG_DWORD 0x1 + // A value of 0x1 means Developer Mode is enabled. + String::from_utf8_lossy(&out.stdout).contains("0x1") } - - // Step 2 - Read the value (equivalent to `/v AllowDevelopmentWithoutDevLicense`). - // Windows writes the raw bytes of the REG_DWORD value directly into `data`, - // so after this call `data` holds the integer stored in the registry: - // 0x1 -> Developer Mode ON - // 0x0 -> Developer Mode OFF (or key absent) - let mut data: u32 = 0; - let mut data_size = std::mem::size_of::() as u32; - let mut reg_type: u32 = 0; - - let result = RegQueryValueExW( - hkey, - value_name.as_ptr(), // "AllowDevelopmentWithoutDevLicense" - std::ptr::null(), // reserved, always null - &mut reg_type, // out: value type (REG_DWORD = 4) - std::ptr::addr_of_mut!(data) as *mut u8, // out: raw bytes of the value - &mut data_size, // in/out: buffer size in bytes - ); - - // Step 3 - Close the handle. - RegCloseKey(hkey); - - // Step 4 - Check the result: success + data == 1 (0x1) means enabled. - result == ERROR_SUCCESS && data == 1 + _ => false, } } -/// Wraps a tar unpack error, appending a Windows Developer Mode hint when on Windows and -/// Developer Mode is detected as disabled. +/// Wraps a tar unpack error, appending a Windows Developer Mode hint only when: +/// 1. The error is a symlink-privilege failure (`ERROR_PRIVILEGE_NOT_HELD`), and +/// 2. Developer Mode is detected as disabled. fn tar_unpack_error(context: &str, e: std::io::Error) -> CacheError { #[cfg(target_os = "windows")] - if !is_windows_developer_mode_enabled() { + if is_symlink_error(&e) && !is_windows_developer_mode_enabled() { return CacheError::ExtractionError(format!( "{context}: {e}\n\n\ - hint: Extracting this archive failed on Windows. The archive may contain symbolic \ - links, which require Developer Mode or administrator privileges to create. \ + hint: Extracting this archive failed because it contains symbolic links, which \ + require Developer Mode or administrator privileges to create on Windows. \ Please enable Developer Mode in: \ Developer settings > Developer Mode, then retry the build." )); diff --git a/py-rattler-build/rust/Cargo.lock b/py-rattler-build/rust/Cargo.lock index 07f82485a..bc4b04d19 100644 --- a/py-rattler-build/rust/Cargo.lock +++ b/py-rattler-build/rust/Cargo.lock @@ -4954,7 +4954,6 @@ dependencies = [ "tracing", "url", "walkdir", - "windows-sys 0.61.2", "zip", "zstd", ] From 187839909fbd9620edab21cf124e1c21ad4b1323 Mon Sep 17 00:00:00 2001 From: pb01ka Date: Wed, 29 Apr 2026 18:58:09 +0530 Subject: [PATCH 10/12] fix: remove is_symlink_error and always provide the hint for Developer mode --- .../rattler_build_source_cache/src/cache.rs | 21 +++++-------------- 1 file changed, 5 insertions(+), 16 deletions(-) diff --git a/crates/rattler_build_source_cache/src/cache.rs b/crates/rattler_build_source_cache/src/cache.rs index 9ef513c49..19bdcc8a6 100644 --- a/crates/rattler_build_source_cache/src/cache.rs +++ b/crates/rattler_build_source_cache/src/cache.rs @@ -711,16 +711,6 @@ pub fn is_tarball(file_name: &str) -> bool { .any(|ext| file_name.ends_with(ext)) } -/// Returns `true` if the I/O error is caused by insufficient privilege to create a symbolic -/// link. -/// -/// On Windows, `CreateSymbolicLinkW` returns `ERROR_PRIVILEGE_NOT_HELD` (1314 / 0x522) when -/// the caller does not hold `SeCreateSymbolicLinkPrivilege` and Developer Mode is disabled. -#[cfg(target_os = "windows")] -fn is_symlink_error(e: &std::io::Error) -> bool { - e.raw_os_error() == Some(1314) // ERROR_PRIVILEGE_NOT_HELD -} - /// On Windows, checks whether Developer Mode is enabled by querying the registry via the /// built-in `reg` command. Returns `false` if the query fails or the value is not `0x1`. /// @@ -749,16 +739,15 @@ fn is_windows_developer_mode_enabled() -> bool { } } -/// Wraps a tar unpack error, appending a Windows Developer Mode hint only when: -/// 1. The error is a symlink-privilege failure (`ERROR_PRIVILEGE_NOT_HELD`), and -/// 2. Developer Mode is detected as disabled. +/// Wraps a tar unpack error, appending a Windows Developer Mode hint when on Windows and +/// Developer Mode is detected as disabled. fn tar_unpack_error(context: &str, e: std::io::Error) -> CacheError { #[cfg(target_os = "windows")] - if is_symlink_error(&e) && !is_windows_developer_mode_enabled() { + if !is_windows_developer_mode_enabled() { return CacheError::ExtractionError(format!( "{context}: {e}\n\n\ - hint: Extracting this archive failed because it contains symbolic links, which \ - require Developer Mode or administrator privileges to create on Windows. \ + hint: Extracting this archive failed on Windows. The archive may contain symbolic \ + links, which require Developer Mode or administrator privileges to create. \ Please enable Developer Mode in: \ Developer settings > Developer Mode, then retry the build." )); From 53797682a8aa0bc28f5d05a1bea6d9fe683f8f68 Mon Sep 17 00:00:00 2001 From: pb01ka Date: Wed, 29 Apr 2026 21:33:12 +0530 Subject: [PATCH 11/12] fix: Use winreg which enables checking Developer Mode without unsafe --- Cargo.lock | 11 +++++ Cargo.toml | 1 + crates/rattler_build_source_cache/Cargo.toml | 3 ++ .../rattler_build_source_cache/src/cache.rs | 40 ++++++++----------- py-rattler-build/rust/Cargo.lock | 11 +++++ 5 files changed, 42 insertions(+), 24 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e4caa59cb..56850b223 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5106,6 +5106,7 @@ dependencies = [ "tracing", "url", "walkdir", + "winreg", "zip", "zstd", ] @@ -8749,6 +8750,16 @@ dependencies = [ "memchr", ] +[[package]] +name = "winreg" +version = "0.56.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d6f32a0ff4a9f6f01231eb2059cc85479330739333e0e58cadf03b6af2cca10" +dependencies = [ + "cfg-if", + "windows-sys 0.61.2", +] + [[package]] name = "winver" version = "1.0.0" diff --git a/Cargo.toml b/Cargo.toml index 9e14e7220..4aad8a7a0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -90,6 +90,7 @@ urlencoding = "2.1" walkdir = "2.5.0" which = "8.0.2" windows-sys = "0.61" +winreg = "0.56" zip = { version = "8.5.1", default-features = false, features = ["deflate"] } zstd = "0.13.3" spdx = "0.13.4" diff --git a/crates/rattler_build_source_cache/Cargo.toml b/crates/rattler_build_source_cache/Cargo.toml index 91ed5e44e..8314854e6 100644 --- a/crates/rattler_build_source_cache/Cargo.toml +++ b/crates/rattler_build_source_cache/Cargo.toml @@ -63,6 +63,9 @@ sigstore-verify = { version = "0.6.4", default-features = false, optional = true sigstore-trust-root = { version = "0.6.4", default-features = false, optional = true } sigstore-types = { version = "0.6.4", optional = true } +[target.'cfg(target_os = "windows")'.dependencies] +winreg = { workspace = true } + [dev-dependencies] tokio = { workspace = true, features = ["rt", "macros", "test-util"] } tempfile.workspace = true diff --git a/crates/rattler_build_source_cache/src/cache.rs b/crates/rattler_build_source_cache/src/cache.rs index 19bdcc8a6..416f5734e 100644 --- a/crates/rattler_build_source_cache/src/cache.rs +++ b/crates/rattler_build_source_cache/src/cache.rs @@ -711,32 +711,24 @@ pub fn is_tarball(file_name: &str) -> bool { .any(|ext| file_name.ends_with(ext)) } -/// On Windows, checks whether Developer Mode is enabled by querying the registry via the -/// built-in `reg` command. Returns `false` if the query fails or the value is not `0x1`. -/// -/// Equivalent to: -/// reg query "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\AppModelUnlock" -/// /v AllowDevelopmentWithoutDevLicense +/// On Windows, checks whether Developer Mode is enabled by reading the registry key +/// `HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\AppModelUnlock` value +/// `AllowDevelopmentWithoutDevLicense`. +/// Returns `false` if the key is missing, inaccessible, or the value is not `1`. #[cfg(target_os = "windows")] fn is_windows_developer_mode_enabled() -> bool { - let output = std::process::Command::new("reg") - .args([ - "query", - r"HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\AppModelUnlock", - "/v", - "AllowDevelopmentWithoutDevLicense", - ]) - .output(); - - match output { - Ok(out) if out.status.success() => { - // A successful query prints a line containing the value, e.g.: - // AllowDevelopmentWithoutDevLicense REG_DWORD 0x1 - // A value of 0x1 means Developer Mode is enabled. - String::from_utf8_lossy(&out.stdout).contains("0x1") - } - _ => false, - } + use winreg::RegKey; + use winreg::enums::HKEY_LOCAL_MACHINE; + + let hklm = RegKey::predef(HKEY_LOCAL_MACHINE); + let Ok(key) = hklm.open_subkey(r"SOFTWARE\Microsoft\Windows\CurrentVersion\AppModelUnlock") + else { + return false; + }; + let Ok(value): Result = key.get_value("AllowDevelopmentWithoutDevLicense") else { + return false; + }; + value == 1 } /// Wraps a tar unpack error, appending a Windows Developer Mode hint when on Windows and diff --git a/py-rattler-build/rust/Cargo.lock b/py-rattler-build/rust/Cargo.lock index bc4b04d19..36be17c7c 100644 --- a/py-rattler-build/rust/Cargo.lock +++ b/py-rattler-build/rust/Cargo.lock @@ -4954,6 +4954,7 @@ dependencies = [ "tracing", "url", "walkdir", + "winreg", "zip", "zstd", ] @@ -8456,6 +8457,16 @@ dependencies = [ "memchr", ] +[[package]] +name = "winreg" +version = "0.56.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d6f32a0ff4a9f6f01231eb2059cc85479330739333e0e58cadf03b6af2cca10" +dependencies = [ + "cfg-if", + "windows-sys 0.61.2", +] + [[package]] name = "winver" version = "1.0.0" From 5f060242a8457268a1e8acb0c70a018a709ebdf5 Mon Sep 17 00:00:00 2001 From: pb01ka Date: Wed, 29 Apr 2026 23:08:56 +0530 Subject: [PATCH 12/12] chore: Shorten the implementation of is_windows_developer_mode_enabled --- crates/rattler_build_source_cache/src/cache.rs | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/crates/rattler_build_source_cache/src/cache.rs b/crates/rattler_build_source_cache/src/cache.rs index 416f5734e..751c36338 100644 --- a/crates/rattler_build_source_cache/src/cache.rs +++ b/crates/rattler_build_source_cache/src/cache.rs @@ -720,15 +720,13 @@ fn is_windows_developer_mode_enabled() -> bool { use winreg::RegKey; use winreg::enums::HKEY_LOCAL_MACHINE; - let hklm = RegKey::predef(HKEY_LOCAL_MACHINE); - let Ok(key) = hklm.open_subkey(r"SOFTWARE\Microsoft\Windows\CurrentVersion\AppModelUnlock") - else { - return false; - }; - let Ok(value): Result = key.get_value("AllowDevelopmentWithoutDevLicense") else { - return false; - }; - value == 1 + (|| -> Result { + let value: u32 = RegKey::predef(HKEY_LOCAL_MACHINE) + .open_subkey(r"SOFTWARE\Microsoft\Windows\CurrentVersion\AppModelUnlock")? + .get_value("AllowDevelopmentWithoutDevLicense")?; + Ok(value == 1) + })() + .unwrap_or(false) } /// Wraps a tar unpack error, appending a Windows Developer Mode hint when on Windows and