| @@ -3,15 +3,24 @@ use std::path::Path; · 2 unmodified lines |
| 3 | #[cfg(windows)] | 3 | #[cfg(windows)] |
| 4 | mod locations { | 4 | mod locations { |
| 5 | use std::{ | 5 | use std::{ |
| 6 | ffi::{OsStr, OsString}, | 6 | ffi::{c_void, OsStr, OsString}, |
| 7 | io::ErrorKind, | 7 | io::ErrorKind, |
| | 8 | os::windows::ffi::OsStringExt, |
| 8 | path::{Path, PathBuf}, | 9 | path::{Path, PathBuf}, |
| 9 | }; | 10 | }; |
| 10 | | 11 | |
| 11 | use known_folders::{get_known_folder_path, KnownFolder}; | | |
| 12 | use windows::{ | 12 | use windows::{ |
| 13 | core::{Result as WindowsResult, BOOL}, | 13 | core::{Result as WindowsResult, BOOL, GUID, PWSTR}, |
| 14 | Win32::System::Threading::{GetCurrentProcess, IsWow64Process}, | 14 | Win32::{ |
| | 15 | System::{ |
| | 16 | Com::CoTaskMemFree, |
| | 17 | Threading::{GetCurrentProcess, IsWow64Process}, |
| | 18 | }, |
| | 19 | UI::Shell::{ |
| | 20 | FOLDERID_LocalAppData, FOLDERID_ProgramFiles, FOLDERID_ProgramFilesX86, FOLDERID_UserProgramFiles, |
| | 21 | SHGetKnownFolderPath, KF_FLAG_DEFAULT, KF_FLAG_DONT_VERIFY, KNOWN_FOLDER_FLAG, |
| | 22 | }, |
| | 23 | }, |
| 15 | }; | 24 | }; |
| 16 | use winreg::{ | 25 | use winreg::{ |
| 17 | enums::{HKEY_LOCAL_MACHINE, KEY_QUERY_VALUE}, | 26 | enums::{HKEY_LOCAL_MACHINE, KEY_QUERY_VALUE}, |
| @@ -55,7 +64,7 @@ mod locations { · 37 unmodified lines |
| 55 | } | 64 | } |
| 56 | | 65 | |
| 57 | #[test] | 66 | #[test] |
| 58 | fn locations_under_program_files_ordinary_values_current_var_only() { | 67 | fn locations_under_program_files_global_only_ordinary_values_current_var_only() { |
| 59 | assert_eq!( | 68 | assert_eq!( |
| 60 | locations_from!( | 69 | locations_from!( |
| 61 | "ProgramFiles" => r"C:\Program Files", | 70 | "ProgramFiles" => r"C:\Program Files", |
| @@ -72,7 +81,7 @@ mod locations { · 10 unmodified lines |
| 72 | } | 81 | } |
| 73 | | 82 | |
| 74 | #[test] | 83 | #[test] |
| 75 | fn locations_under_program_files_ordinary_values_all_vars() { | 84 | fn locations_under_program_files_global_only_ordinary_values_all_vars() { |
| 76 | assert_eq!( | 85 | assert_eq!( |
| 77 | locations_from!( | 86 | locations_from!( |
| 78 | "ProgramFiles" => { | 87 | "ProgramFiles" => { |
| @@ -94,7 +103,7 @@ mod locations { · 15 unmodified lines |
| 94 | } | 103 | } |
| 95 | | 104 | |
| 96 | #[test] | 105 | #[test] |
| 97 | fn locations_under_program_files_strange_values_all_vars_distinct() { | 106 | fn locations_under_program_files_global_only_strange_values_all_vars_distinct() { |
| 98 | assert_eq!( | 107 | assert_eq!( |
| 99 | locations_from!( | 108 | locations_from!( |
| 100 | "ProgramFiles" => r"X:\cur\rent", | 109 | "ProgramFiles" => r"X:\cur\rent", |
| @@ -121,7 +130,7 @@ mod locations { · 20 unmodified lines |
| 121 | } | 130 | } |
| 122 | | 131 | |
| 123 | #[test] | 132 | #[test] |
| 124 | fn locations_under_program_files_strange_values_64bit_var_only() { | 133 | fn locations_under_program_files_global_only_strange_values_64bit_var_only() { |
| 125 | assert_eq!( | 134 | assert_eq!( |
| 126 | locations_from!( | 135 | locations_from!( |
| 127 | "ProgramW6432" => r"Z:\wi\de", | 136 | "ProgramW6432" => r"Z:\wi\de", |
| @@ -131,7 +140,7 @@ mod locations { · 3 unmodified lines |
| 131 | } | 140 | } |
| 132 | | 141 | |
| 133 | #[test] | 142 | #[test] |
| 134 | fn locations_under_program_files_strange_values_all_vars_path_cruft() { | 143 | fn locations_under_program_files_global_only_strange_values_all_vars_path_cruft() { |
| 135 | assert_eq!( | 144 | assert_eq!( |
| 136 | locations_from!( | 145 | locations_from!( |
| 137 | "ProgramFiles" => r"Z:/wi//de/", | 146 | "ProgramFiles" => r"Z:/wi//de/", |
| @@ -156,7 +165,7 @@ mod locations { · 18 unmodified lines |
| 156 | } | 165 | } |
| 157 | | 166 | |
| 158 | #[test] | 167 | #[test] |
| 159 | fn locations_under_program_files_strange_values_some_relative() { | 168 | fn locations_under_program_files_global_only_strange_values_some_relative() { |
| 160 | assert_eq!( | 169 | assert_eq!( |
| 161 | locations_from!( | 170 | locations_from!( |
| 162 | "ProgramFiles" => r"foo\bar", | 171 | "ProgramFiles" => r"foo\bar", |
| @@ -167,6 +176,235 @@ mod locations { · 4 unmodified lines |
| 167 | ); | 176 | ); |
| 168 | } | 177 | } |
| 169 | | 178 | |
| | 179 | #[test] |
| | 180 | fn locations_under_program_files_local_only_ordinary_value() { |
| | 181 | assert_eq!( |
| | 182 | locations_from!( |
| | 183 | "LocalAppData" => r"C:\Users\alice\AppData\Local", |
| | 184 | ), |
| | 185 | pathbuf_vec![ |
| | 186 | r"C:\Users\alice\AppData\Local\Programs\Git\clangarm64\bin", |
| | 187 | r"C:\Users\alice\AppData\Local\Programs\Git\mingw64\bin", |
| | 188 | r"C:\Users\alice\AppData\Local\Programs\Git\mingw32\bin", |
| | 189 | ], |
| | 190 | ); |
| | 191 | } |
| | 192 | |
| | 193 | #[test] |
| | 194 | fn locations_under_program_files_local_only_strange_value_path_cruft() { |
| | 195 | assert_eq!( |
| | 196 | locations_from!( |
| | 197 | "LocalAppData" => r"\\.\Q:\Documents and Settings/bob\/weird\.\sub//dir", |
| | 198 | ), |
| | 199 | pathbuf_vec![ |
| | 200 | r"\\.\Q:\Documents and Settings\bob\weird\sub\dir\Programs\Git\clangarm64\bin", |
| | 201 | r"\\.\Q:\Documents and Settings\bob\weird\sub\dir\Programs\Git\mingw64\bin", |
| | 202 | r"\\.\Q:\Documents and Settings\bob\weird\sub\dir\Programs\Git\mingw32\bin", |
| | 203 | ], |
| | 204 | ); |
| | 205 | } |
| | 206 | |
| | 207 | #[test] |
| | 208 | fn locations_under_program_files_local_only_strange_value_empty() { |
| | 209 | assert_eq!( |
| | 210 | locations_from!( |
| | 211 | "LocalAppData" => "", |
| | 212 | ), |
| | 213 | Vec::<PathBuf>::new(), |
| | 214 | ); |
| | 215 | } |
| | 216 | |
| | 217 | #[test] |
| | 218 | fn locations_under_program_files_local_only_strange_value_relative_nonempty() { |
| | 219 | assert_eq!( |
| | 220 | locations_from!( |
| | 221 | "LocalAppData" => r"AppData\Local", |
| | 222 | ), |
| | 223 | Vec::<PathBuf>::new(), |
| | 224 | ); |
| | 225 | } |
| | 226 | |
| | 227 | #[test] |
| | 228 | fn locations_under_program_files_local_and_global_ordinary_values_limited_vars() { |
| | 229 | assert_eq!( |
| | 230 | locations_from!( |
| | 231 | "LocalAppData" => r"C:\Users\alice\AppData\Local", |
| | 232 | "ProgramFiles" => r"C:\Program Files", |
| | 233 | ), |
| | 234 | if cfg!(target_pointer_width = "64") { |
| | 235 | pathbuf_vec![ |
| | 236 | r"C:\Users\alice\AppData\Local\Programs\Git\clangarm64\bin", |
| | 237 | r"C:\Users\alice\AppData\Local\Programs\Git\mingw64\bin", |
| | 238 | r"C:\Users\alice\AppData\Local\Programs\Git\mingw32\bin", |
| | 239 | r"C:\Program Files\Git\clangarm64\bin", |
| | 240 | r"C:\Program Files\Git\mingw64\bin", |
| | 241 | ] |
| | 242 | } else { |
| | 243 | pathbuf_vec![ |
| | 244 | r"C:\Users\alice\AppData\Local\Programs\Git\clangarm64\bin", |
| | 245 | r"C:\Users\alice\AppData\Local\Programs\Git\mingw64\bin", |
| | 246 | r"C:\Users\alice\AppData\Local\Programs\Git\mingw32\bin", |
| | 247 | r"C:\Program Files\Git\mingw32\bin", |
| | 248 | ] |
| | 249 | }, |
| | 250 | ); |
| | 251 | } |
| | 252 | |
| | 253 | #[test] |
| | 254 | fn locations_under_program_files_local_and_global_ordinary_values_all_vars() { |
| | 255 | assert_eq!( |
| | 256 | locations_from!( |
| | 257 | "LocalAppData" => r"C:\Users\bob\AppData\Local", |
| | 258 | "ProgramFiles" => { |
| | 259 | if cfg!(target_pointer_width = "64") { |
| | 260 | r"C:\Program Files" |
| | 261 | } else { |
| | 262 | r"C:\Program Files (x86)" |
| | 263 | } |
| | 264 | }, |
| | 265 | "ProgramFiles(x86)" => r"C:\Program Files (x86)", |
| | 266 | "ProgramW6432" => r"C:\Program Files", |
| | 267 | ), |
| | 268 | pathbuf_vec![ |
| | 269 | r"C:\Users\bob\AppData\Local\Programs\Git\clangarm64\bin", |
| | 270 | r"C:\Users\bob\AppData\Local\Programs\Git\mingw64\bin", |
| | 271 | r"C:\Users\bob\AppData\Local\Programs\Git\mingw32\bin", |
| | 272 | r"C:\Program Files\Git\clangarm64\bin", |
| | 273 | r"C:\Program Files\Git\mingw64\bin", |
| | 274 | r"C:\Program Files (x86)\Git\mingw32\bin", |
| | 275 | ], |
| | 276 | ); |
| | 277 | } |
| | 278 | |
| | 279 | #[test] |
| | 280 | fn locations_under_program_files_local_and_global_strange_values_all_vars_distinct() { |
| | 281 | assert_eq!( |
| | 282 | locations_from!( |
| | 283 | "LocalAppData" => r"W:\us\er", |
| | 284 | "ProgramFiles" => r"X:\cur\rent", |
| | 285 | "ProgramFiles(x86)" => r"Y:\nar\row", |
| | 286 | "ProgramW6432" => r"Z:\wi\de", |
| | 287 | ), |
| | 288 | if cfg!(target_pointer_width = "64") { |
| | 289 | pathbuf_vec![ |
| | 290 | r"W:\us\er\Programs\Git\clangarm64\bin", |
| | 291 | r"W:\us\er\Programs\Git\mingw64\bin", |
| | 292 | r"W:\us\er\Programs\Git\mingw32\bin", |
| | 293 | r"Z:\wi\de\Git\clangarm64\bin", |
| | 294 | r"Z:\wi\de\Git\mingw64\bin", |
| | 295 | r"Y:\nar\row\Git\mingw32\bin", |
| | 296 | r"X:\cur\rent\Git\clangarm64\bin", |
| | 297 | r"X:\cur\rent\Git\mingw64\bin", |
| | 298 | ] |
| | 299 | } else { |
| | 300 | pathbuf_vec![ |
| | 301 | r"W:\us\er\Programs\Git\clangarm64\bin", |
| | 302 | r"W:\us\er\Programs\Git\mingw64\bin", |
| | 303 | r"W:\us\er\Programs\Git\mingw32\bin", |
| | 304 | r"Z:\wi\de\Git\clangarm64\bin", |
| | 305 | r"Z:\wi\de\Git\mingw64\bin", |
| | 306 | r"Y:\nar\row\Git\mingw32\bin", |
| | 307 | r"X:\cur\rent\Git\mingw32\bin", |
| | 308 | ] |
| | 309 | }, |
| | 310 | ); |
| | 311 | } |
| | 312 | |
| | 313 | #[test] |
| | 314 | fn locations_under_program_files_local_and_global_strange_values_limited_64bit_var() { |
| | 315 | assert_eq!( |
| | 316 | locations_from!( |
| | 317 | "LocalAppData" => r"W:\us\er", |
| | 318 | "ProgramW6432" => r"Z:\wi\de", |
| | 319 | ), |
| | 320 | pathbuf_vec![ |
| | 321 | r"W:\us\er\Programs\Git\clangarm64\bin", |
| | 322 | r"W:\us\er\Programs\Git\mingw64\bin", |
| | 323 | r"W:\us\er\Programs\Git\mingw32\bin", |
| | 324 | r"Z:\wi\de\Git\clangarm64\bin", |
| | 325 | r"Z:\wi\de\Git\mingw64\bin", |
| | 326 | ], |
| | 327 | ); |
| | 328 | } |
| | 329 | |
| | 330 | #[test] |
| | 331 | fn locations_under_program_files_local_and_global_strange_values_crufty_cross_clash() { |
| | 332 | assert_eq!( |
| | 333 | locations_from!( |
| | 334 | "LocalAppData" => r"Y:\nar\row", |
| | 335 | "ProgramFiles" => r"Z:/wi//de/", |
| | 336 | "ProgramFiles(x86)" => r"Y:/\nar/row\Programs", |
| | 337 | "ProgramW6432" => r"Z:\wi\.\de", |
| | 338 | ), |
| | 339 | if cfg!(target_pointer_width = "64") { |
| | 340 | pathbuf_vec![ |
| | 341 | r"Y:\nar\row\Programs\Git\clangarm64\bin", |
| | 342 | r"Y:\nar\row\Programs\Git\mingw64\bin", |
| | 343 | r"Y:\nar\row\Programs\Git\mingw32\bin", |
| | 344 | r"Z:\wi\de\Git\clangarm64\bin", |
| | 345 | r"Z:\wi\de\Git\mingw64\bin", |
| | 346 | ] |
| | 347 | } else { |
| | 348 | pathbuf_vec![ |
| | 349 | r"Y:\nar\row\Programs\Git\clangarm64\bin", |
| | 350 | r"Y:\nar\row\Programs\Git\mingw64\bin", |
| | 351 | r"Y:\nar\row\Programs\Git\mingw32\bin", |
| | 352 | r"Z:\wi\de\Git\clangarm64\bin", |
| | 353 | r"Z:\wi\de\Git\mingw64\bin", |
| | 354 | r"Z:\wi\de\Git\mingw32\bin", |
| | 355 | ] |
| | 356 | }, |
| | 357 | ); |
| | 358 | } |
| | 359 | |
| | 360 | #[test] |
| | 361 | fn locations_under_program_files_local_and_global_strange_values_some_relative() { |
| | 362 | assert_eq!( |
| | 363 | locations_from!( |
| | 364 | "LocalAppData" => "dir", |
| | 365 | "ProgramFiles" => r"foo\bar", |
| | 366 | "ProgramFiles(x86)" => r"\\host\share\subdir", |
| | 367 | "ProgramW6432" => r"", |
| | 368 | ), |
| | 369 | pathbuf_vec![r"\\host\share\subdir\Git\mingw32\bin"], |
| | 370 | ); |
| | 371 | } |
| | 372 | |
| | 373 | /// Owner of a null-terminated `PWSTR` that must be freed with `CoTaskMemFree`. |
| | 374 | struct CoStr { |
| | 375 | pwstr: PWSTR, |
| | 376 | } |
| | 377 | |
| | 378 | impl CoStr { |
| | 379 | /// SAFETY: The caller must ensure `pwstr` is a non-null pointer to the beginning of a |
| | 380 | /// null-terminated (zero-codepoint-terminated) wide string releasable by `CoTaskMemFree`. |
| | 381 | unsafe fn new(pwstr: PWSTR) -> Self { |
| | 382 | Self { pwstr } |
| | 383 | } |
| | 384 | |
| | 385 | fn to_os_string(&self) -> OsString { |
| | 386 | // SAFETY: We know `pwstr` is derefrenceable and the string is null-terminated. |
| | 387 | let wide = unsafe { self.pwstr.as_wide() }; |
| | 388 | OsString::from_wide(wide) |
| | 389 | } |
| | 390 | } |
| | 391 | |
| | 392 | impl Drop for CoStr { |
| | 393 | fn drop(&mut self) { |
| | 394 | // SAFETY: `pwstr` is allowed to be passed to `CoTaskMemFree`. (We happen to know it's |
| | 395 | // non-null, but `CoTaskMemFree` permits null as well, so the cast is doubly safe.) |
| | 396 | unsafe { CoTaskMemFree(Some(self.pwstr.as_ptr().cast::<c_void>())) }; |
| | 397 | } |
| | 398 | } |
| | 399 | |
| | 400 | fn get_known_folder_path_with_flag(id: GUID, flag: KNOWN_FOLDER_FLAG) -> WindowsResult<PathBuf> { |
| | 401 | // SAFETY: `SHGetKnownFolderPath` in the `windows` crate wraps the API function and returns |
| | 402 | // a non-null pointer to a null-terminated wide string, or an error, not a null pointer. |
| | 403 | // As in the wrapped API function, the pointer it returns can be passed to `CoTaskMemFree`. |
| | 404 | let costr = unsafe { CoStr::new(SHGetKnownFolderPath(&id, flag, None)?) }; |
| | 405 | Ok(PathBuf::from(costr.to_os_string())) |
| | 406 | } |
| | 407 | |
| 170 | #[derive(Clone, Copy, Debug)] | 408 | #[derive(Clone, Copy, Debug)] |
| 171 | enum PlatformBitness { | 409 | enum PlatformBitness { |
| 172 | Is32on32, | 410 | Is32on32, |
| @@ -205,9 +443,10 @@ mod locations { · 32 unmodified lines |
| 205 | Some(folded_text.ends_with(&folded_pattern)) | 443 | Some(folded_text.ends_with(&folded_pattern)) |
| 206 | } | 444 | } |
| 207 | | 445 | |
| 208 | /// The most common global program files paths on this system, by process and system architecture. | 446 | /// The most common program files paths, as they are available in this environment. |
| 209 | /// | 447 | /// |
| 210 | /// This omits the 32-bit ARM program files directory, as Git for Windows is never installed there. | 448 | /// This omits the global 32-bit ARM program files directory, because Git for Windows is never |
| | 449 | /// installed there. |
| 211 | #[derive(Clone, Debug)] | 450 | #[derive(Clone, Debug)] |
| 212 | struct ProgramFilesPaths { | 451 | struct ProgramFilesPaths { |
| 213 | /// The program files directory used for whatever architecture this program was built for. | 452 | /// The program files directory used for whatever architecture this program was built for. |
| @@ -224,10 +463,13 @@ mod locations { · 10 unmodified lines |
| 224 | /// AMD64 programs use the same program files directory while 32-bit x86 and 32-bit ARM | 463 | /// AMD64 programs use the same program files directory while 32-bit x86 and 32-bit ARM |
| 225 | /// programs use two others. Only a 32-bit system has no 64-bit program files directory. | 464 | /// programs use two others. Only a 32-bit system has no 64-bit program files directory. |
| 226 | maybe_64bit: Option<PathBuf>, | 465 | maybe_64bit: Option<PathBuf>, |
| | 466 | |
| | 467 | /// The per-user `Programs` subdirectory of the user's local application data directory. |
| | 468 | user: PathBuf, |
| 227 | } | 469 | } |
| 228 | | 470 | |
| 229 | impl ProgramFilesPaths { | 471 | impl ProgramFilesPaths { |
| 230 | /// Get the three most common kinds of global program files paths without environment variables. | 472 | /// Get the four most common kinds of program files paths without environment variables. |
| 231 | /// | 473 | /// |
| 232 | /// The idea here is to obtain this information, which the `alternative_locations()` unit | 474 | /// The idea here is to obtain this information, which the `alternative_locations()` unit |
| 233 | /// test uses to learn the expected alternative locations, without duplicating *any* of the | 475 | /// test uses to learn the expected alternative locations, without duplicating *any* of the |
| @@ -235,9 +477,10 @@ mod locations { · 1 unmodified line |
| 235 | /// here is also more reliable than using environment variables, but it is a bit more | 477 | /// here is also more reliable than using environment variables, but it is a bit more |
| 236 | /// complex, and it requires either additional dependencies or the use of unsafe code. | 478 | /// complex, and it requires either additional dependencies or the use of unsafe code. |
| 237 | /// | 479 | /// |
| 238 | /// This gets `pf_current` and `pf_x86` by the [known folders][known-folders] system. But | 480 | /// This gets `pf_user`, `pf_current`, and `pf_x86` by the [known folders][known-folders] |
| 239 | /// it gets `maybe_pf_64bit` from the registry, as the corresponding known folder is not | 481 | /// system. But it gets `maybe_pf_64bit` from the registry, as the corresponding known |
| 240 | /// available to 32-bit processes. See the [`KNOWNFOLDDERID`][knownfolderid] documentation. | 482 | /// folder is not available to 32-bit processes. See the [`KNOWNFOLDDERID`][knownfolderid] |
| | 483 | /// documentation. |
| 241 | /// | 484 | /// |
| 242 | /// If in the future the implementation of `ALTERNATIVE_LOCATIONS` uses these techniques, | 485 | /// If in the future the implementation of `ALTERNATIVE_LOCATIONS` uses these techniques, |
| 243 | /// then this function can be changed to use environment variables and renamed accordingly. | 486 | /// then this function can be changed to use environment variables and renamed accordingly. |
| @@ -245,10 +488,10 @@ mod locations { · 1 unmodified line |
| 245 | /// [known-folders]: https://learn.microsoft.com/en-us/windows/win32/shell/known-folders | 488 | /// [known-folders]: https://learn.microsoft.com/en-us/windows/win32/shell/known-folders |
| 246 | /// [knownfolderid]: https://learn.microsoft.com/en-us/windows/win32/shell/knownfolderid#remarks | 489 | /// [knownfolderid]: https://learn.microsoft.com/en-us/windows/win32/shell/knownfolderid#remarks |
| 247 | fn obtain_envlessly() -> Self { | 490 | fn obtain_envlessly() -> Self { |
| 248 | let pf_current = get_known_folder_path(KnownFolder::ProgramFiles) | 491 | let pf_current = get_known_folder_path_with_flag(FOLDERID_ProgramFiles, KF_FLAG_DEFAULT) |
| 249 | .expect("The process architecture specific program files folder is always available"); | 492 | .expect("The process architecture specific program files folder is always available"); |
| 250 | | 493 | |
| 251 | let pf_x86 = get_known_folder_path(KnownFolder::ProgramFilesX86) | 494 | let pf_x86 = get_known_folder_path_with_flag(FOLDERID_ProgramFilesX86, KF_FLAG_DEFAULT) |
| 252 | .expect("The x86 program files folder will in practice always be available"); | 495 | .expect("The x86 program files folder will in practice always be available"); |
| 253 | | 496 | |
| 254 | let maybe_pf_64bit = RegKey::predef(HKEY_LOCAL_MACHINE) | 497 | let maybe_pf_64bit = RegKey::predef(HKEY_LOCAL_MACHINE) |
| @@ -262,10 +505,14 @@ mod locations { · 7 unmodified lines |
| 262 | }) | 505 | }) |
| 263 | .ok(); | 506 | .ok(); |
| 264 | | 507 | |
| | 508 | let pf_user = get_known_folder_path_with_flag(FOLDERID_UserProgramFiles, KF_FLAG_DONT_VERIFY) |
| | 509 | .expect("The path where the user's local Programs folder is, or would be created, is known"); |
| | 510 | |
| 265 | Self { | 511 | Self { |
| 266 | current: pf_current, | 512 | current: pf_current, |
| 267 | x86: pf_x86, | 513 | x86: pf_x86, |
| 268 | maybe_64bit: maybe_pf_64bit, | 514 | maybe_64bit: maybe_pf_64bit, |
| | 515 | user: pf_user, |
| 269 | } | 516 | } |
| 270 | } | 517 | } |
| 271 | | 518 | |
| @@ -274,6 +521,12 @@ mod locations { · 2 unmodified lines |
| 274 | /// This checks that `obtain_envlessly()` returned paths that are likely to be correct and | 521 | /// This checks that `obtain_envlessly()` returned paths that are likely to be correct and |
| 275 | /// that satisfy the most important properties based on the current system and process. | 522 | /// that satisfy the most important properties based on the current system and process. |
| 276 | fn validated(self) -> Self { | 523 | fn validated(self) -> Self { |
| | 524 | self.validate_global_folders(); |
| | 525 | self.validate_user_folder(); |
| | 526 | self |
| | 527 | } |
| | 528 | |
| | 529 | fn validate_global_folders(&self) { |
| 277 | match PlatformBitness::current().expect("Process and system 'bitness' should be available") { | 530 | match PlatformBitness::current().expect("Process and system 'bitness' should be available") { |
| 278 | PlatformBitness::Is32on32 => { | 531 | PlatformBitness::Is32on32 => { |
| 279 | assert_eq!( | 532 | assert_eq!( |
| @@ -325,59 +578,114 @@ mod locations { · 45 unmodified lines |
| 325 | ); | 578 | ); |
| 326 | } | 579 | } |
| 327 | } | 580 | } |
| | 581 | } |
| 328 | | 582 | |
| 329 | self | 583 | fn validate_user_folder(&self) { |
| | 584 | let expected = get_known_folder_path_with_flag(FOLDERID_LocalAppData, KF_FLAG_DEFAULT) |
| | 585 | .expect("The user's local application data directory is available") |
| | 586 | .join("Programs"); |
| | 587 | assert_eq!( |
| | 588 | self.user, expected, |
| | 589 | "The user program files directory is Programs in the local application data directory.", |
| | 590 | ); |
| 330 | } | 591 | } |
| 331 | } | 592 | } |
| 332 | | 593 | |
| 333 | /// Paths relative to process architecture specific program files directories. | 594 | /// Architecture-specific Git for Windows paths relative to the user program files directory. |
| 334 | #[derive(Clone, Debug)] | 595 | #[derive(Clone, Debug)] |
| 335 | struct RelativeGitBinPaths<'a> { | 596 | struct RelativeUserGitBinPaths<'a> { |
| | 597 | x86: &'a Path, |
| | 598 | x64: &'a Path, |
| | 599 | arm64: &'a Path, |
| | 600 | } |
| | 601 | |
| | 602 | impl<'a> RelativeUserGitBinPaths<'a> { |
| | 603 | /// Assert that `locations` leads with the given user path prefix, and extract the suffixes. |
| | 604 | fn assert_from(pf_user: &'a Path, locations: &'static [PathBuf]) -> Self { |
| | 605 | match locations { |
| | 606 | [path1, path2, path3, ..] => { |
| | 607 | let suffix_user_arm64 = path1 |
| | 608 | .strip_prefix(pf_user) |
| | 609 | .expect("It gives a per-user 64-bit ARM64 path and lists it first"); |
| | 610 | let suffix_user_x64 = path2 |
| | 611 | .strip_prefix(pf_user) |
| | 612 | .expect("It gives a per-user 64-bit x86 path and lists it second"); |
| | 613 | let suffix_user_x86 = path3 |
| | 614 | .strip_prefix(pf_user) |
| | 615 | .expect("It gives a per-user 32-bit x86 path and lists it third"); |
| | 616 | Self { |
| | 617 | x86: suffix_user_x86, |
| | 618 | x64: suffix_user_x64, |
| | 619 | arm64: suffix_user_arm64, |
| | 620 | } |
| | 621 | } |
| | 622 | other => panic!( |
| | 623 | "{:?} has length {}, so some expected leading user program files paths are absent", |
| | 624 | other, |
| | 625 | other.len() |
| | 626 | ), |
| | 627 | } |
| | 628 | } |
| | 629 | |
| | 630 | /// Assert that suffixes are common Git install locations relative to a program files directory. |
| | 631 | fn assert_architectures(&self) { |
| | 632 | assert_eq!(self.x86, Path::new("Git/mingw32/bin")); |
| | 633 | assert_eq!(self.x64, Path::new("Git/mingw64/bin")); |
| | 634 | assert_eq!(self.arm64, Path::new("Git/clangarm64/bin")); |
| | 635 | } |
| | 636 | } |
| | 637 | |
| | 638 | /// Architecture-specific Git for Windows paths relative to global program files directories. |
| | 639 | #[derive(Clone, Debug)] |
| | 640 | struct RelativeGlobalGitBinPaths<'a> { |
| 336 | x86: &'a Path, | 641 | x86: &'a Path, |
| 337 | maybe_x64: Option<&'a Path>, | 642 | maybe_x64: Option<&'a Path>, |
| 338 | maybe_arm64: Option<&'a Path>, | 643 | maybe_arm64: Option<&'a Path>, |
| 339 | } | 644 | } |
| 340 | | 645 | |
| 341 | impl<'a> RelativeGitBinPaths<'a> { | 646 | impl<'a> RelativeGlobalGitBinPaths<'a> { |
| 342 | /// Assert that `locations` has the given path prefixes, and extract the suffixes. | 647 | /// Assert that `locations` trails with the given global path prefixes, and extract the suffixes. |
| 343 | fn assert_from(pf: &'a ProgramFilesPaths, locations: &'static [PathBuf]) -> Self { | 648 | fn assert_from(pf: &'a ProgramFilesPaths, locations: &'static [PathBuf]) -> Self { |
| 344 | match locations { | 649 | match locations { |
| 345 | [primary, secondary, tertiary] => { | 650 | [_, _, _, path4, path5, path6] => { |
| 346 | let prefix_64bit = pf | 651 | let prefix_64bit = pf |
| 347 | .maybe_64bit | 652 | .maybe_64bit |
| 348 | .as_ref() | 653 | .as_ref() |
| 349 | .expect("It gives three paths only if some can be 64-bit"); | 654 | .expect("It gives 6 paths only if some global paths can be 64-bit"); |
| 350 | let suffix_arm64 = primary | 655 | let suffix_global_arm64 = path4 |
| 351 | .strip_prefix(prefix_64bit) | 656 | .strip_prefix(prefix_64bit) |
| 352 | .expect("It gives the 64-bit ARM64 path and lists it first"); | 657 | .expect("It gives a global 64-bit ARM64 path and lists it fourth"); |
| 353 | let suffix_x64 = secondary | 658 | let suffix_global_x64 = path5 |
| 354 | .strip_prefix(prefix_64bit) | 659 | .strip_prefix(prefix_64bit) |
| 355 | .expect("It gives the 64-bit x86 path and lists it second"); | 660 | .expect("It gives a global 64-bit x86 path and lists it fifth"); |
| 356 | let suffix_x86 = tertiary | 661 | let suffix_global_x86 = path6 |
| 357 | .strip_prefix(pf.x86.as_path()) | 662 | .strip_prefix(&pf.x86) |
| 358 | .expect("It gives the 32-bit path and lists it third"); | 663 | .expect("It gives a global 32-bit path and lists it sixth"); |
| 359 | Self { | 664 | Self { |
| 360 | x86: suffix_x86, | 665 | x86: suffix_global_x86, |
| 361 | maybe_x64: Some(suffix_x64), | 666 | maybe_x64: Some(suffix_global_x64), |
| 362 | maybe_arm64: Some(suffix_arm64), | 667 | maybe_arm64: Some(suffix_global_arm64), |
| 363 | } | 668 | } |
| 364 | } | 669 | } |
| 365 | [only] => { | 670 | [_, _, _, path4] => { |
| 366 | assert_eq!(pf.maybe_64bit, None, "It gives one path only if none can be 64-bit."); | 671 | assert_eq!( |
| 367 | let suffix_x86 = only | 672 | pf.maybe_64bit, None, |
| 368 | .strip_prefix(pf.x86.as_path()) | 673 | "It gives 4 paths only if no global paths can be 64-bit.", |
| 369 | .expect("The one path it gives is the 32-bit path"); | 674 | ); |
| | 675 | let suffix_global_x86 = path4 |
| | 676 | .strip_prefix(&pf.x86) |
| | 677 | .expect("It gives a global 32-bit path and lists it fourth"); |
| 370 | Self { | 678 | Self { |
| 371 | x86: suffix_x86, | 679 | x86: suffix_global_x86, |
| 372 | maybe_x64: None, | 680 | maybe_x64: None, |
| 373 | maybe_arm64: None, | 681 | maybe_arm64: None, |
| 374 | } | 682 | } |
| 375 | } | 683 | } |
| 376 | other => panic!("{:?} has length {}, expected 1 or 3.", other, other.len()), | 684 | other => panic!("{:?} has length {}, expected 4 or 6.", other, other.len()), |
| 377 | } | 685 | } |
| 378 | } | 686 | } |
| 379 | | 687 | |
| 380 | /// Assert that the suffixes (relative subdirectories) are the common per-architecture Git install locations. | 688 | /// Assert that suffixes are common Git install locations relative to a program files directory. |
| 381 | fn assert_architectures(&self) { | 689 | fn assert_architectures(&self) { |
| 382 | assert_eq!(self.x86, Path::new("Git/mingw32/bin")); | 690 | assert_eq!(self.x86, Path::new("Git/mingw32/bin")); |
| 383 | | 691 | |
| @@ -390,6 +698,28 @@ mod locations { · 6 unmodified lines |
| 390 | } | 698 | } |
| 391 | } | 699 | } |
| 392 | | 700 | |
| | 701 | /// Architecture-specific Git for Windows paths relative to particular program files directories. |
| | 702 | #[derive(Clone, Debug)] |
| | 703 | struct RelativeGitBinPaths<'a> { |
| | 704 | global: RelativeGlobalGitBinPaths<'a>, |
| | 705 | user: RelativeUserGitBinPaths<'a>, |
| | 706 | } |
| | 707 | |
| | 708 | impl<'a> RelativeGitBinPaths<'a> { |
| | 709 | /// Assert that `locations` has the given path prefixes, and extract the suffixes. |
| | 710 | fn assert_from(pf: &'a ProgramFilesPaths, locations: &'static [PathBuf]) -> Self { |
| | 711 | let user = RelativeUserGitBinPaths::assert_from(&pf.user, locations); |
| | 712 | let global = RelativeGlobalGitBinPaths::assert_from(pf, locations); |
| | 713 | Self { global, user } |
| | 714 | } |
| | 715 | |
| | 716 | /// Assert that global and user suffixes (relative subdirectories) are common Git install locations. |
| | 717 | fn assert_architectures(&self) { |
| | 718 | self.global.assert_architectures(); |
| | 719 | self.user.assert_architectures(); |
| | 720 | } |
| | 721 | } |
| | 722 | |
| 393 | #[test] | 723 | #[test] |
| 394 | fn alternative_locations() { | 724 | fn alternative_locations() { |
| 395 | // Obtain program files directory paths by other means and check that they seem correct. | 725 | // Obtain program files directory paths by other means and check that they seem correct. |