@@ -203,7 +203,7 @@ fn strip_extended_path_prefix(path: &str) -> String {
203203 path. to_string ( )
204204}
205205
206- /// Convert a Windows path to a container-compatible path for volume mounts .
206+ /// Convert a Windows path to a container-compatible path for use *inside* the container .
207207/// - Docker Desktop: C:\Users\foo -> /c/Users/foo
208208/// - Podman on WSL: C:\Users\foo -> /mnt/c/Users/foo
209209#[ cfg( target_os = "windows" ) ]
@@ -247,11 +247,24 @@ fn windows_path_to_container(path: &Path, use_podman_format: bool) -> String {
247247 }
248248}
249249
250+ /// Convert a Windows path to a host-side mount source.
251+ /// For Windows runtimes, the host side should be a Windows path.
252+ #[ cfg( target_os = "windows" ) ]
253+ fn windows_path_to_mount_source ( path : & Path , _use_podman_format : bool ) -> String {
254+ let canonical = path. canonicalize ( ) . unwrap_or_else ( |_| path. to_path_buf ( ) ) ;
255+ strip_extended_path_prefix ( canonical. to_string_lossy ( ) . as_ref ( ) )
256+ }
257+
250258#[ cfg( not( target_os = "windows" ) ) ]
251259fn windows_path_to_container ( _path : & Path , _use_podman_format : bool ) -> String {
252260 _path. to_string_lossy ( ) . to_string ( )
253261}
254262
263+ #[ cfg( not( target_os = "windows" ) ) ]
264+ fn windows_path_to_mount_source ( path : & Path , _use_podman_format : bool ) -> String {
265+ path. to_string_lossy ( ) . to_string ( )
266+ }
267+
255268/// Recursively remap Windows paths in JSON values to container-compatible paths
256269fn remap_json_paths_for_container ( value : & JsonValue , use_podman_format : bool ) -> JsonValue {
257270 match value {
@@ -321,6 +334,35 @@ fn normalize_windows_path_str(s: &str) -> String {
321334 . strip_prefix ( "\\ \\ ?\\ " )
322335 . or_else ( || s. strip_prefix ( "//?/" ) )
323336 . unwrap_or ( s) ;
337+ // Handle MSYS/Git Bash style paths like /c/Users/... or /mnt/c/Users/...
338+ if let Some ( rest) = stripped. strip_prefix ( "/mnt/" ) {
339+ if rest. len ( ) >= 3 && rest. as_bytes ( ) [ 1 ] == b'/' {
340+ let drive = rest
341+ . chars ( )
342+ . next ( )
343+ . unwrap_or ( 'c' )
344+ . to_ascii_uppercase ( ) ;
345+ let remainder = & rest[ 2 ..] ;
346+ return format ! (
347+ "{}:\\ {}" ,
348+ drive,
349+ remainder. replace( '/' , "\\ " ) . trim_start_matches( "\\ " )
350+ ) ;
351+ }
352+ }
353+ if stripped. len ( ) >= 3 && stripped. starts_with ( '/' ) && stripped. as_bytes ( ) [ 2 ] == b'/' {
354+ let drive = stripped
355+ . chars ( )
356+ . nth ( 1 )
357+ . unwrap_or ( 'c' )
358+ . to_ascii_uppercase ( ) ;
359+ let remainder = & stripped[ 3 ..] ;
360+ return format ! (
361+ "{}:\\ {}" ,
362+ drive,
363+ remainder. replace( '/' , "\\ " ) . trim_start_matches( "\\ " )
364+ ) ;
365+ }
324366 // Convert forward slashes to backslashes
325367 stripped. replace ( '/' , "\\ " )
326368}
@@ -492,6 +534,18 @@ fn looks_like_windows_absolute_path(s: &str) -> bool {
492534 . or_else ( || s. strip_prefix ( "//?/" ) )
493535 . unwrap_or ( s) ;
494536
537+ // Accept MSYS/Git Bash style: /c/... or /mnt/c/...
538+ if let Some ( rest) = stripped. strip_prefix ( "/mnt/" ) {
539+ if rest. len ( ) >= 3 && rest. as_bytes ( ) [ 1 ] == b'/' {
540+ let drive = rest. chars ( ) . next ( ) . unwrap_or ( 'c' ) ;
541+ return drive. is_ascii_alphabetic ( ) ;
542+ }
543+ }
544+ if stripped. len ( ) >= 3 && stripped. starts_with ( '/' ) && stripped. as_bytes ( ) [ 2 ] == b'/' {
545+ let drive = stripped. chars ( ) . nth ( 1 ) . unwrap_or ( 'c' ) ;
546+ return drive. is_ascii_alphabetic ( ) ;
547+ }
548+
495549 if stripped. len ( ) < 3 {
496550 return false ;
497551 }
@@ -1913,35 +1967,37 @@ pub async fn execute_dynamic(
19131967 . arg ( "-v" )
19141968 . arg ( format ! (
19151969 "{}:{}" ,
1916- windows_path_to_container ( & biovault_home, using_podman) ,
1970+ windows_path_to_mount_source ( & biovault_home, using_podman) ,
19171971 docker_biovault_home
19181972 ) )
19191973 // Mount the project path (may be same as above, Docker handles duplicates)
19201974 . arg ( "-v" )
19211975 . arg ( format ! (
19221976 "{}:{}" ,
1923- windows_path_to_container ( project_path, using_podman) ,
1977+ windows_path_to_mount_source ( project_path, using_podman) ,
19241978 docker_project_path
19251979 ) ) ;
19261980
19271981 // Mount additional data directories discovered from inputs
19281982 for mount_path in & mount_roots {
19291983 let container_mount = windows_path_to_container ( mount_path, using_podman) ;
1984+ let host_mount = windows_path_to_mount_source ( mount_path, using_podman) ;
19301985 append_desktop_log ( & format ! (
19311986 "[Pipeline] Adding mount: {} -> {}" ,
19321987 mount_path. display( ) ,
19331988 container_mount
19341989 ) ) ;
19351990 docker_cmd
19361991 . arg ( "-v" )
1937- . arg ( format ! ( "{}:{}" , container_mount , container_mount) ) ;
1992+ . arg ( format ! ( "{}:{}" , host_mount , container_mount) ) ;
19381993 }
19391994 }
19401995
19411996 // Generate runtime-specific Nextflow config (Docker vs Podman)
19421997 let runtime_config_path = if !dry_run {
1943- let config_path =
1944- generate_runtime_config ( & project_abs, using_podman, hyperv_host_mount) ?;
1998+ // On Windows with Podman, prefer copying inputs to avoid broken symlinks on /mnt/c mounts.
1999+ let stage_in_copy = hyperv_host_mount || ( cfg ! ( target_os = "windows" ) && using_podman) ;
2000+ let config_path = generate_runtime_config ( & project_abs, using_podman, stage_in_copy) ?;
19452001 // For Hyper-V mode, the config file was copied to VM along with project
19462002 // Reference it via the VM project path
19472003 #[ cfg( target_os = "windows" ) ]
0 commit comments