1+ // Copyright (c) Microsoft Corporation.
2+ // Licensed under the MIT License.
3+
4+ use pet_conda:: utils:: is_conda_env;
5+ use pet_fs:: path:: { expand_path, norm_case} ;
6+ use std:: { fs, path:: PathBuf } ;
7+
8+ /// Get the UV cache directory.
9+ /// UV uses the following priority order:
10+ /// 1. UV_CACHE_DIR environment variable
11+ /// 2. XDG cache directories on Unix / %LOCALAPPDATA% on Windows
12+ /// 3. Platform-specific cache directories
13+ fn get_uv_cache_dir (
14+ uv_cache_dir_env_var : Option < String > ,
15+ xdg_cache_home : Option < String > ,
16+ user_home : Option < PathBuf > ,
17+ ) -> Option < PathBuf > {
18+ // 1. Check UV_CACHE_DIR environment variable
19+ if let Some ( cache_dir) = uv_cache_dir_env_var {
20+ let cache_dir = norm_case ( expand_path ( PathBuf :: from ( cache_dir) ) ) ;
21+ if cache_dir. exists ( ) {
22+ return Some ( cache_dir) ;
23+ }
24+ }
25+
26+ // 2. Check XDG_CACHE_HOME on Unix
27+ if let Some ( xdg_cache) = xdg_cache_home. map ( |d| PathBuf :: from ( d) . join ( "uv" ) ) {
28+ if xdg_cache. exists ( ) {
29+ return Some ( xdg_cache) ;
30+ }
31+ }
32+
33+ // 3. Platform-specific cache directories
34+ if let Some ( home) = user_home {
35+ let cache_dirs = if cfg ! ( target_os = "windows" ) {
36+ // On Windows: %LOCALAPPDATA%\uv
37+ vec ! [ home. join( "AppData" ) . join( "Local" ) . join( "uv" ) ]
38+ } else if cfg ! ( target_os = "macos" ) {
39+ // On macOS: ~/Library/Caches/uv
40+ vec ! [ home. join( "Library" ) . join( "Caches" ) . join( "uv" ) ]
41+ } else {
42+ // On other Unix systems: ~/.cache/uv
43+ vec ! [ home. join( ".cache" ) . join( "uv" ) ]
44+ } ;
45+
46+ for cache_dir in cache_dirs {
47+ if cache_dir. exists ( ) {
48+ return Some ( cache_dir) ;
49+ }
50+ }
51+ }
52+
53+ None
54+ }
55+
56+ /// Get UV environment cache directories.
57+ /// UV stores virtual environments in {cache_dir}/environments-v2/
58+ fn get_uv_environment_dirs (
59+ uv_cache_dir_env_var : Option < String > ,
60+ xdg_cache_home : Option < String > ,
61+ user_home : Option < PathBuf > ,
62+ ) -> Vec < PathBuf > {
63+ let mut env_dirs = Vec :: new ( ) ;
64+
65+ if let Some ( cache_dir) = get_uv_cache_dir ( uv_cache_dir_env_var, xdg_cache_home, user_home) {
66+ let environments_dir = cache_dir. join ( "environments-v2" ) ;
67+ if environments_dir. exists ( ) {
68+ env_dirs. push ( environments_dir) ;
69+ }
70+ }
71+
72+ env_dirs
73+ }
74+
75+ /// List UV virtual environment paths.
76+ /// This function discovers UV cache directories and enumerates the virtual environments within them.
77+ /// It filters out conda environments to avoid conflicts.
78+ pub fn list_uv_virtual_envs_paths (
79+ uv_cache_dir_env_var : Option < String > ,
80+ xdg_cache_home : Option < String > ,
81+ user_home : Option < PathBuf > ,
82+ ) -> Vec < PathBuf > {
83+ let mut python_envs: Vec < PathBuf > = vec ! [ ] ;
84+
85+ for env_cache_dir in get_uv_environment_dirs ( uv_cache_dir_env_var, xdg_cache_home, user_home) {
86+ if let Ok ( dirs) = fs:: read_dir ( & env_cache_dir) {
87+ python_envs. append (
88+ & mut dirs
89+ . filter_map ( Result :: ok)
90+ . map ( |e| e. path ( ) )
91+ . filter ( |p| p. is_dir ( ) && !is_conda_env ( p) )
92+ . collect ( ) ,
93+ ) ;
94+ }
95+ }
96+
97+ python_envs. sort ( ) ;
98+ python_envs. dedup ( ) ;
99+
100+ python_envs
101+ }
102+
103+ #[ cfg( test) ]
104+ mod tests {
105+ use super :: * ;
106+ use std:: fs;
107+
108+ #[ test]
109+ fn test_uv_cache_dir_from_env_var ( ) {
110+ let temp_dir = std:: env:: temp_dir ( ) . join ( "test_uv_cache" ) ;
111+ fs:: create_dir_all ( & temp_dir) . unwrap ( ) ;
112+
113+ let cache_dir = get_uv_cache_dir (
114+ Some ( temp_dir. to_string_lossy ( ) . to_string ( ) ) ,
115+ None ,
116+ None ,
117+ ) ;
118+
119+ assert_eq ! ( cache_dir, Some ( temp_dir. clone( ) ) ) ;
120+ fs:: remove_dir_all ( & temp_dir) . ok ( ) ;
121+ }
122+
123+ #[ test]
124+ fn test_uv_environment_dirs ( ) {
125+ let temp_dir = std:: env:: temp_dir ( ) . join ( "test_uv_env" ) ;
126+ let env_dir = temp_dir. join ( "environments-v2" ) ;
127+ fs:: create_dir_all ( & env_dir) . unwrap ( ) ;
128+
129+ let env_dirs = get_uv_environment_dirs (
130+ Some ( temp_dir. to_string_lossy ( ) . to_string ( ) ) ,
131+ None ,
132+ None ,
133+ ) ;
134+
135+ assert_eq ! ( env_dirs, vec![ env_dir. clone( ) ] ) ;
136+ fs:: remove_dir_all ( & temp_dir) . ok ( ) ;
137+ }
138+
139+ #[ test]
140+ fn test_list_uv_virtual_envs_paths ( ) {
141+ let temp_dir = std:: env:: temp_dir ( ) . join ( "test_uv_list" ) ;
142+ let env_dir = temp_dir. join ( "environments-v2" ) ;
143+ let test_env = env_dir. join ( "test-venv" ) ;
144+ fs:: create_dir_all ( & test_env) . unwrap ( ) ;
145+
146+ let envs = list_uv_virtual_envs_paths (
147+ Some ( temp_dir. to_string_lossy ( ) . to_string ( ) ) ,
148+ None ,
149+ None ,
150+ ) ;
151+
152+ assert ! ( envs. contains( & test_env) ) ;
153+ fs:: remove_dir_all ( & temp_dir) . ok ( ) ;
154+ }
155+ }
0 commit comments