diff options
author | Pedro Alves <pedro@palves.net> | 2025-08-09 00:03:28 +0100 |
---|---|---|
committer | Pedro Alves <pedro@palves.net> | 2025-08-22 21:35:12 +0100 |
commit | a63213cd374dc14da82849ddf55044b730a311f4 (patch) | |
tree | e62b9a4a966d6e019e38ffa574d7eadcd8015a24 | |
parent | 8feea8c5d8cc2573da61fee3fd42cf3392ec0f0e (diff) | |
download | binutils-a63213cd374dc14da82849ddf55044b730a311f4.zip binutils-a63213cd374dc14da82849ddf55044b730a311f4.tar.gz binutils-a63213cd374dc14da82849ddf55044b730a311f4.tar.bz2 |
MSYS2+MinGW testing: Unix <-> Windows path conversion
On an MSYS2 system, I have:
# which tclsh
/mingw64/bin/tclsh
# which tclsh86
/mingw64/bin/tclsh86
# which tclsh8.6
/usr/bin/tclsh8.6
# which expect
/usr/bin/expect
The ones under /usr/bin are MSYS2 programs (linked with msys-2.0.dll).
I.e., they are really Cygwin (unix) ports of the programs.
The ones under /mingw64 are native Windows programs (NOT linked with
msys-2.0.dll). You can check that with CYGWIN/MSYS2 ldd.
The MSYS2/Cygwin port of TCL (and thus expect) does not treat a file
name that starts with a drive letter as an absolute file name, while
the native/MinGW port does. Vis:
# cat file-join.exp
puts [file join c:/ d:/]
# /mingw64/bin/tclsh.exe file-join.exp
d:/
# /mingw64/bin/tclsh86.exe file-join.exp
d:/
# /usr/bin/expect.exe file-join.exp
c:/d:
# /usr/bin/tclsh8.6.exe file-join.exp
c:/d:
When running the testsuite under MSYS2 to test mingw32 (Windows
native) GDB, we use MSYS2 expect (there is no MinGW port of expect
AFAIK). Any TCL file manipulation routine will thus not consider
drive letters special, and just treats them as relative file names.
This results in several cases of the testsuite passing to GDB broken
file names, like:
"C:/foo/C:/foo/bar"
or:
"/c/foo/C:/foo/bar"
E.g., there is a "file join" in standard_output_file that results in
this:
(gdb) file C:/gdb/build/outputs/gdb.base/info_sources_2/C:/gdb/build/outputs/gdb.base/info_sources_2/info_sources_2
C:/gdb/build/outputs/gdb.base/info_sources_2/C:/gdb/build/outputs/gdb.base/info_sources_2/info_sources_2: No such file or directory.
(gdb) ERROR: (info_sources_2) No such file or directory
delete breakpoints
The bad "file join" comes from clean_restart $binfile, where $binfile
is an absolute host file name (thus has a drive letter), clean_restart
doing:
set binfile [standard_output_file ${executable}]
return [gdb_load ${binfile}]
and standard_output_file doing:
# If running on MinGW, replace /c/foo with c:/foo
if { [ishost *-*-mingw*] } {
set dir [exec sh -c "cd ${dir} && pwd -W"]
}
return [file join $dir $basename]
Here, BASENAME was already an absolute file name that starts with a
drive letter, but "file join" treated it as a relative file name.
Another spot where we mishandle Unix vs drive letter file names, is in
the "dir" command that we issue when starting every testcase under
GDB. We currently always pass the file name as seen from the build
machine (i.e., from MSYS2), which is a Unix file name that native
Windows GDB does not understand, resulting in:
(gdb) dir /c/gdb/src/gdb/testsuite/gdb.rocm
warning: /c/gdb/src/gdb/testsuite/gdb.rocm: No such file or directory
Source directories searched: /c/gdb/src/gdb/testsuite/gdb.rocm;$cdir;$cwd
This patch introduces a systematic approach to handle all this, by
introducing the concepts of build file names (what DejaGnu sees) vs
host file names (what GDB sees).
This patches implements that in the following way:
1) - Keep standard_output_file's host-side semantics
standard_output_file currently converts the file name to a Windows
file name, using the "cd $dir; pwd -W" trick. standard_output_file is
used pervasively, so I think it should keep the semantics that it
returns a host file name.
Note there is already a preexisting host_standard_output_file
procedure. The difference to standard_output_file is that
host_standard_output_file handles remote hosts, while
standard_output_file assumes the build and host machines share a
filesystem. The MSYS2 Unix path vs MinGW GDB drive letter case fall
in the "shared filesystem" bucket. An NFS mount on the host at the
same mount point as on the build machine falls in that bucket too.
2) - Introduce build_standard_output_file
In some places, we are calling standard_output_file to find the
build-side file name, most often just to find the standard output
directory file name, and then immediately use that file name with TCL
file manipulation procedures, to do some file manipulation on the
build machine. clean_standard_output_dir is an example of such a
case. That code path is responsible for this bogus 'rm -rf' in
current MSYS2 testing:
Running /c/gdb/src/gdb/testsuite/gdb.base/break.exp ...
Executing on build: rm -rf /c/msys2/home/alves/gdb/build-testsuite/C:/msys2/home/alves/gdb/build-tests...
For these cases, add a variant of standard_output_file called
build_standard_output_file. The main difference to
standard_output_file is that it doesn't do the "cd $dir; pwd -W"
trick. I.e., it returns a path on the build machine.
3) Introduce host_file_sanitize
In some cases, we read an absolute file name out of GDB's output, and
then want to compare it against some other file name. The file name
may originally come from the DWARF, and sometimes may have forward
slashes, and other times, it may have backward slashes. Or the drive
letter may be uppercase, or it may be lowercase. To make comparisons
easier, add a new host_file_sanitize procedure, that normalizes
slashes, and uppercases the drive letter. It does no other
normalization. Particularly, it does not turn a relative file name
into an absolute file name.
It's arguable whether GDB itself should do this sanitization. I
suspect it should. I personally dislike seeing backward slashes in
e.g., "info shared" output, or worse, mixed backward and forward
slashes. Still, I propose starting with a testsuite adjustment that
moves us forward, and handle that separately. I won't be surprised if
we need the new routine for some cases even if we adjust GDB.
4) build_file_normalize / host_file_normalize
In several places in the testsuite, we call "file normalize" on some
file name. If we pass it a drive-letter file name, that TCL procedure
treats the passed in file name as a relative file name, so produces
something like /c/foo/C:/foo/bar.txt.
If the context calls for a build file name, then the "file normalize"
call should produce /c/foo/bar.txt. If OTOH we need a host file name,
then it should produce "C:/foo/bar.txt". Handle this by adding two
procedures that wrap "file normalize":
- build_file_normalize
- host_file_normalize
Initialy I implemented them in a very simple way, calling into
cygpath:
proc build_file_normalize {filename} {
if { [ishost *-*-mingw*] } {
return [exec cygpath -ua $filename]
} else {
return [file normalize $filename]
}
}
proc host_file_normalize {filename} {
if { [ishost *-*-mingw*] } {
return [exec cygpath -ma $filename]
} else {
return [file normalize $filename]
}
}
"cygpath" is a utility that comes OOTB with both Cygwin and MSYS2,
that does Windows <-> Cygwin file name conversion.
This works well, but because running the testsuite on Windows is so
slow, I thought of trying to avoid or minimize the cost of calling an
external utility ("cygpath").
On my system, calling into cygpath takes between 200ms to 350ms, and
these smallish costs (OK, not so small!) can creep up and compound an
already bad situation. Note that the current call to "cd $dir; pwd
-W" has about the same cost as a "cygpath" call (though a little bit
cheaper).
So with this patch, we actually don't call cygpath at all, and no
longer use the "cd $dir; pwd -W" trick. Instead we run the "mount"
command once, and cache the mapping (via gdb_caching_proc) between
Windows file names and Unix mount points, and then use that mapping in
host_file_normalize and build_file_normalize, to do the Windows <=>
Unix file name conversions ourselves.
One other small advantage here is that this approach works the same
for 'cygwin x mingw' testing [1], and 'msys x mingw' testing, while
"pwd -W" only works on MSYS2.
So I think the end result is that we should end up faster (or less
slow) than the current state.
(No, I don't have actual timings for the effect over a whole testsuite
run.)
5) Introduce host_file_join
For the "file join" call done from within standard_output_file (and
probably in future other places), since that procedure works with host
file names, add a new host_file_join procedure that is a wrapper
around "file join" that is aware of Windows drive letters.
======
With the infrastructure described above in place, the "dir" case is
fixed by simply calling host_file_normalize on the directory name,
before passing it to GDB. That turns:
(gdb) dir /c/gdb/src/gdb/testsuite/gdb.base
warning: /c/gdb/src/gdb/testsuite/gdb.base: No such file or directory
Source directories searched: /c/gdb/src/gdb/testsuite/gdb.base;$cdir;$cwd
Into:
(gdb) dir C:/gdb/src/gdb/testsuite/gdb.base
Source directories searched: C:/gdb/src/gdb/testsuite/gdb.base;$cdir;$cwd
Running the testsuite on GNU/Linux reveals that that change requires
tweaks to gdb.guile/scm-parameter.exp and gdb.python/py-parameter.exp,
to run the expected directory by host_file_normalize too, so that it
matches the directory we initially pass GDB at startup time. Without
that fix, there could be a mismatch if the GDB sources path has a
symlink component, which now gets resolved by the host_file_normalize
call.
The theory is that most standard_output_file uses will not need to be
adjusted.
I grepped for "file normalize" and "file join", to find cases that
might need adjustment, and fixed those that required fixing. The
fixes are included in this patch, to make it easier to reason about
the overall change. E.g., in gdb.base/fullname.exp, without the fix,
we get:
Running /c/gdb/src/gdb/testsuite/gdb.base/fullname.exp ...
ERROR: tcl error sourcing /c/gdb/src/gdb/testsuite/gdb.base/fullname.exp.
ERROR: tcl error code NONE
ERROR: C:/msys2/home/alves/gdb/build-testsuite/outputs/gdb.base/fullname/tmp-fullname.c not a subdir of /c/msys2/home/alves/gdb/build-testsuite
In gdb.base/source-dir.exp, we have several issues. E.g., we see the
"/c/foo/c:/foo" problem there too:
dir /c/msys2/home/alves/gdb/build-testsuite/C:/msys2/home/alves/gdb/build-testsuite/outputs/gdb.base/source-dir/C:/msys2/home/alves/gdb/build-testsuite/outputs
warning: /c/msys2/home/alves/gdb/build-testsuite/C:/msys2/home/alves/gdb/build-testsuite/outputs/gdb.base/source-dir/C:/msys2/home/alves/gdb/build-testsuite/outputs: No such file or directory
Source directories searched: /c/msys2/home/alves/gdb/build-testsuite/C:/msys2/home/alves/gdb/build-testsuite/outputs/gdb.base/source-dir/C:/msys2/home/alves/gdb/build-testsuite/outputs;$cdir;$cwd
(gdb) PASS: gdb.base/source-dir.exp: setup source path search directory
...
Executing on host: x86_64-w64-mingw32-gcc \
-fno-stack-protector \
/c/msys2/home/alves/gdb/build-testsuite/C:/msys2/home/alves/gdb/build-testsuite/outputs/gdb.base/macro-source-path/cwd/macro-source-path.c ...
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
... and we need to handle Unix file names that we pass to the compiler
(on the build side), vs file names that GDB prints out (the host
side).
Similarly in the other testcases.
I haven't yet tried to do a full testsuite run on MSYS2, and I'm quite
confident there will be more places that will need similar adjustment,
but I'd like to land the infrastructure early, so that the rest of the
testsuite can be adjusted incrementally, and others can help.
Change-Id: I664dbb86d0efa4fa8db405577bea2b4b4a96a613
-rw-r--r-- | gdb/testsuite/gdb.base/annotate-symlink.exp | 2 | ||||
-rw-r--r-- | gdb/testsuite/gdb.base/fullname.exp | 2 | ||||
-rw-r--r-- | gdb/testsuite/gdb.base/info_sources_2.exp | 35 | ||||
-rw-r--r-- | gdb/testsuite/gdb.base/macro-source-path.exp | 4 | ||||
-rw-r--r-- | gdb/testsuite/gdb.base/source-dir.exp | 52 | ||||
-rw-r--r-- | gdb/testsuite/gdb.guile/scm-parameter.exp | 3 | ||||
-rw-r--r-- | gdb/testsuite/gdb.python/py-parameter.exp | 3 | ||||
-rw-r--r-- | gdb/testsuite/lib/gdb.exp | 211 |
8 files changed, 263 insertions, 49 deletions
diff --git a/gdb/testsuite/gdb.base/annotate-symlink.exp b/gdb/testsuite/gdb.base/annotate-symlink.exp index f7f6f18..a5d431e 100644 --- a/gdb/testsuite/gdb.base/annotate-symlink.exp +++ b/gdb/testsuite/gdb.base/annotate-symlink.exp @@ -20,7 +20,7 @@ standard_testfile realname-expand.c realname-expand-real.c require {!is_remote host} set srcdirabs [file join [pwd] $srcdir] -set srcfilelink [standard_output_file realname-expand-link.c] +set srcfilelink [build_standard_output_file realname-expand-link.c] remote_exec build "ln -sf ${srcdirabs}/${subdir}/${srcfile2} $srcfilelink" diff --git a/gdb/testsuite/gdb.base/fullname.exp b/gdb/testsuite/gdb.base/fullname.exp index 430d0c4..ce617d5 100644 --- a/gdb/testsuite/gdb.base/fullname.exp +++ b/gdb/testsuite/gdb.base/fullname.exp @@ -65,7 +65,7 @@ if { [gdb_breakpoint [standard_output_file tmp-${srcfile}]:${line} {no-message}] } # Build the test executable using a relative path. -if { [gdb_compile [relative_filename [pwd] [standard_output_file tmp-${srcfile}]] \ +if { [gdb_compile [relative_filename [pwd] [build_standard_output_file tmp-${srcfile}]] \ "${binfile}" executable {debug}] != "" } { return -1 } diff --git a/gdb/testsuite/gdb.base/info_sources_2.exp b/gdb/testsuite/gdb.base/info_sources_2.exp index c469049..39b594b 100644 --- a/gdb/testsuite/gdb.base/info_sources_2.exp +++ b/gdb/testsuite/gdb.base/info_sources_2.exp @@ -72,13 +72,18 @@ proc run_info_sources { extra_args args } { set objfile_name "" set source_files {} set files {} + # Note below we sanitize paths so we can compare against the + # host_file_normalize'd paths later. Note we sanitize, but + # don't normalize here, as the latter would turn a relative + # path into an absolute path, and this testcase wants to make + # sure that GDB prints the absolute path. gdb_test_multiple $cmd "" { -re "${command_regex}\r\n" { exp_continue } -re "^(\[^\r\n\]+):\r\n" { - set objfile_name $expect_out(1,string) + set objfile_name [host_file_sanitize $expect_out(1,string)] if { $is_remote_target } { set objfile_name [file tail $objfile_name] } @@ -101,7 +106,7 @@ proc run_info_sources { extra_args args } { } -re "^(\[^,\r\n\]+), " { - set f $expect_out(1,string) + set f [host_file_sanitize $expect_out(1,string)] lappend files $f exp_continue } @@ -111,7 +116,7 @@ proc run_info_sources { extra_args args } { return } - set f $expect_out(1,string) + set f [host_file_sanitize $expect_out(1,string)] lappend files $f set info_sources($objfile_name) $files set $objfile_name "" @@ -133,7 +138,7 @@ proc run_info_sources { extra_args args } { } # Figure out the path for SOURCEFILE that we're looking for. - set sourcepath [file normalize ${srcdir}/${subdir}/${sourcefile}] + set sourcepath [host_file_normalize ${srcdir}/${subdir}/${sourcefile}] if { $is_remote_target } { set objfile [file tail $objfile] @@ -156,32 +161,34 @@ proc run_info_sources { extra_args args } { # The actual tests. +set host_binfile [host_file_normalize $binfile$EXEEXT] + run_info_sources "" \ - ${binfile} ${srcfile} \ - ${binfile} ${testfile}-header.h \ + ${host_binfile} ${srcfile} \ + ${host_binfile} ${testfile}-header.h \ ${solib_name} ${srcfile2} \ ${solib_name} ${testfile}-header.h run_info_sources "-basename info_sources_2" \ - ${binfile} ${srcfile} \ - ${binfile} ${testfile}-header.h \ + ${host_binfile} ${srcfile} \ + ${host_binfile} ${testfile}-header.h \ ${solib_name} ${srcfile2} \ ${solib_name} ${testfile}-header.h run_info_sources "-basename \\.c" \ - ${binfile} ${srcfile} \ - ${binfile} !${testfile}-header.h \ + ${host_binfile} ${srcfile} \ + ${host_binfile} !${testfile}-header.h \ ${solib_name} ${srcfile2} \ ${solib_name} !${testfile}-header.h run_info_sources "-basename -- -test\\.c" \ - ${binfile} ${srcfile} \ - ${binfile} !${testfile}-header.h \ + ${host_binfile} ${srcfile} \ + ${host_binfile} !${testfile}-header.h \ ${solib_name} !${srcfile2} \ ${solib_name} !${testfile}-header.h run_info_sources "-basename -- -lib\\.c" \ - ${binfile} !${srcfile} \ - ${binfile} !${testfile}-header.h \ + ${host_binfile} !${srcfile} \ + ${host_binfile} !${testfile}-header.h \ ${solib_name} ${srcfile2} \ ${solib_name} !${testfile}-header.h diff --git a/gdb/testsuite/gdb.base/macro-source-path.exp b/gdb/testsuite/gdb.base/macro-source-path.exp index 47ad789..47d99aa 100644 --- a/gdb/testsuite/gdb.base/macro-source-path.exp +++ b/gdb/testsuite/gdb.base/macro-source-path.exp @@ -33,7 +33,7 @@ require {!is_remote host} # Set the current working directory to $out/cwd, so that we can test compiling # using relative paths. -set out_dir [standard_output_file ""] +set out_dir [build_standard_output_file ""] file mkdir $out_dir/cwd file mkdir $out_dir/other file copy -force $srcdir/$subdir/$srcfile $out_dir/cwd @@ -53,7 +53,7 @@ proc test { src name } { return } - clean_restart $binfile + clean_restart [host_file_normalize $binfile] if { ![runto_main] } { return diff --git a/gdb/testsuite/gdb.base/source-dir.exp b/gdb/testsuite/gdb.base/source-dir.exp index b2bf78c..3b0c5dd 100644 --- a/gdb/testsuite/gdb.base/source-dir.exp +++ b/gdb/testsuite/gdb.base/source-dir.exp @@ -62,7 +62,7 @@ proc test_truncated_comp_dir {} { # /some/path/to/gdb/build/testsuite/ # We are going to copy the source file out of the source tree into # a location like this: - # /some/path/to/gdb/build/testsuite/output/gdb.base/soure-dir/ + # /some/path/to/gdb/build/testsuite/output/gdb.base/source-dir/ # # We will then switch to this directory and compile the source # file, however, we will ask GCC to remove this prefix from the @@ -87,11 +87,12 @@ proc test_truncated_comp_dir {} { return } - set working_dir [standard_output_file ""] + set working_dir [build_standard_output_file ""] with_cwd $working_dir { - set strip_dir [file normalize "${working_dir}/../.."] + set strip_dir [build_file_normalize "${working_dir}/../.."] + set h_strip_dir [host_file_normalize $strip_dir] - set new_srcfile [standard_output_file ${srcfile}] + set new_srcfile [build_standard_output_file ${srcfile}] set fd [open "$new_srcfile" w] puts $fd "int main () @@ -100,8 +101,17 @@ proc test_truncated_comp_dir {} { }" close $fd + # We ask GCC to remove both the build and host views of the + # path, because we don't know which one GCC uses. E.g., we're + # testing on MSYS2 with an MSYS2 cross-compiler that targets + # MinGW, then the path GCC uses is a Unix path. If OTOH we're + # testing on MSYS2 with a native Windows compiler, then the + # path GCC uses is a Windows path. set options \ - "debug additional_flags=-fdebug-prefix-map=${strip_dir}=" + [list \ + "debug" \ + "additional_flags=-fdebug-prefix-map=${strip_dir}=" \ + "additional_flags=-fdebug-prefix-map=${h_strip_dir}="] if { [gdb_compile "${srcfile}" "${binfile}" \ executable ${options}] != "" } { untested "failed to compile" @@ -133,9 +143,9 @@ proc test_truncated_comp_dir {} { "Does not include preprocessor macro info." ] \ "info source before setting directory search list" - gdb_test "dir $strip_dir" \ + gdb_test "dir $h_strip_dir" \ [search_dir_list [list \ - "$strip_dir" \ + "$h_strip_dir" \ "\\\$cdir" \ "\\\$cwd"]] \ "setup source path search directory" @@ -146,17 +156,23 @@ proc test_truncated_comp_dir {} { "4\[ \t\]+return 0;" \ "5\[ \t\]+\\}" ] - gdb_test "info source" \ - [multi_line \ - "Current source file is ${srcfile}" \ - "Compilation directory is \[^\n\r\]+" \ - "Located in ${new_srcfile}" \ - "Contains 5 lines." \ - "Source language is c." \ - "Producer is \[^\n\r\]+" \ - "\[^\n\r\]+" \ - "\[^\n\r\]+" ] \ - "info source after setting directory search list" + set re [multi_line \ + "Current source file is ${srcfile}" \ + "Compilation directory is \[^\n\r\]+" \ + "Located in (\[^\n\r\]+)" \ + "Contains 5 lines." \ + "Source language is c." \ + "Producer is \[^\n\r\]+" \ + "\[^\n\r\]+" \ + "\[^\n\r\]+"] + set test "info source after setting directory search list" + gdb_test_multiple "info source" $test { + -re -wrap "$re" { + set host_new_srcfile [host_file_normalize $new_srcfile] + set host_location [host_file_sanitize $expect_out(1,string)] + gdb_assert {$host_new_srcfile eq $host_location} $gdb_test_name + } + } } proc test_change_search_directory_with_empty_dirname {} { diff --git a/gdb/testsuite/gdb.guile/scm-parameter.exp b/gdb/testsuite/gdb.guile/scm-parameter.exp index 0ee11e6..acd78b9 100644 --- a/gdb/testsuite/gdb.guile/scm-parameter.exp +++ b/gdb/testsuite/gdb.guile/scm-parameter.exp @@ -39,7 +39,8 @@ if { [is_remote host] } { gdb_test "guile (print (parameter-value \"directories\"))" \ "\\\$cdir.\\\$cwd" } else { - set escaped_directory [string_to_regexp "$srcdir/$subdir"] + set directory [host_file_normalize "$::srcdir/$::subdir"] + set escaped_directory [string_to_regexp $directory] gdb_test "guile (print (parameter-value \"directories\"))" \ "$escaped_directory.\\\$cdir.\\\$cwd" } diff --git a/gdb/testsuite/gdb.python/py-parameter.exp b/gdb/testsuite/gdb.python/py-parameter.exp index e676925..30a477b 100644 --- a/gdb/testsuite/gdb.python/py-parameter.exp +++ b/gdb/testsuite/gdb.python/py-parameter.exp @@ -38,7 +38,8 @@ proc_with_prefix test_directories { } { # doesn't set search directories on remote host. set directories ".*\\\$cdir.\\\$cwd" } else { - set escaped_directory [string_to_regexp "$::srcdir/$::subdir"] + set directory [host_file_normalize "$::srcdir/$::subdir"] + set escaped_directory [string_to_regexp $directory] set directories "$escaped_directory.\\\$cdir.\\\$cwd" } gdb_test "python print (gdb.parameter ('directories'))" $directories diff --git a/gdb/testsuite/lib/gdb.exp b/gdb/testsuite/lib/gdb.exp index d989314..041650a 100644 --- a/gdb/testsuite/lib/gdb.exp +++ b/gdb/testsuite/lib/gdb.exp @@ -2252,6 +2252,177 @@ proc gdb_assert { condition {message ""} } { return $res } +# Comparison command for "lsort -command". Sorts two strings by +# descending file name length. + +proc compare_length_desc {a b} { + expr {[string length $b] - [string length $a]} +} + +# Fill in and return the global cache for Windows <=> Unix mount point +# mappings, for Windows. +# +# Calling external processes on MSYS2/Cygwin is expensive so instead +# of calling "cygpath -ua $FILENAME" or "cygpath -ma $FILENAME" for +# every file name, we extract the Windows and Unix file names of each +# mount point using the 'mount' command output, and cache the +# mappings, for both directions. + +gdb_caching_proc get_mount_point_map {} { + array set win_to_unix {} + array set unix_to_win {} + + # The 'mount' command provides all mappings. The general format + # is: 'WindowsFileName on UnixFileName type ...' + # + # For example: + # 'C:/msys64 on / type ntfs (binary,auto)' + # 'C: on /c type ntfs (binary,posix=0,user,noumount,auto)' + set mount_output [exec mount] + + foreach line [split $mount_output \n] { + if {[regexp {^(.+) on (.+) type } $line -> win_filename unix_filename]} { + set win_to_unix($win_filename) $unix_filename + set unix_to_win($unix_filename) $win_filename + } + } + + # Sort each mapping's keys by descending file name length, + # otherwise we wouldn't be able to look for '/foo' in '/' (for + # example). + + set sorted_win {} + foreach k [lsort -command compare_length_desc [array names win_to_unix]] { + lappend sorted_win $k $win_to_unix($k) + } + + set sorted_unix {} + foreach k [lsort -command compare_length_desc [array names unix_to_win]] { + lappend sorted_unix $k $unix_to_win($k) + } + + # Return both sorted lists: {win => unix} {unix => win} + return [list $sorted_win $sorted_unix] +} + +# Normalize backward slashes to forward slashes. + +proc normalize_slashes {filename} { + return [string map {\\ /} $filename] +} + +# Sanitize a host file name, without making it absolute or resolving +# symlinks. On native Windows, this normalizes slashes to forward +# slashes, and makes sure that if the file name starts with a drive +# letter, it is upper case. On other systems, it just returns the +# file name unmodified. + +proc host_file_sanitize {filename} { + if {[ishost *-*-mingw*]} { + set filename [normalize_slashes $filename] + + # If the file name starts with a drive letter, uppercase it. + if {[regexp {^([a-zA-Z]):(/.*)?} $filename -> drive rest]} { + set filename "[string toupper $drive]:$rest" + } + } + + return $filename +} + +# Normalize a file name for the build machine. If running native +# Windows GDB, this converts a Windows file name to the corresponding +# Unix filename, per the mount table. For example, this replaces +# 'c:/foo' with '/c/foo' (on MSYS2) or '/cygdrive/c/foo' (on Cygwin). +# On other systems, it just wraps "file normalize". + +proc build_file_normalize {filename} { + if {[ishost *-*-mingw*]} { + set filename [host_file_sanitize $filename] + + # Handle Windows => Unix mount point conversion. We assume + # there are no symlinks to resolve, which is a reasonable + # assumption for native Windows testing. + + # Get Windows => Unix map. + lassign [get_mount_point_map] win_to_unix _ + + foreach {win_filename unix_filename} $win_to_unix { + if {[string equal -length [string length $win_filename] \ + $win_filename $filename]} { + set rest [string range $filename \ + [string length $win_filename] end] + return "${unix_filename}$rest" + } + } + } + + return [file normalize $filename] +} + +# Normalize a file name for the host machine. If running native +# Windows GDB, this converts a Unix file name to a Windows filename, +# per the mount table. E.g., '/c/foo' (on MSYS2) or '/cygdrive/c/foo' +# (on Cygwin) is converted to 'c:/foo'. + +proc host_file_normalize {filename} { + if {[ishost *-*-mingw*]} { + set filename [host_file_sanitize $filename] + + # If the file name already starts with a drive letter (e.g., + # C:/foo), we're done. Don't let it fallthrough to "file + # normalize", which would misinterpret it as a relative file + # name. + if {[regexp {^[A-Z]:/} $filename]} { + return $filename + } + + # Get Unix => Windows map. + lassign [get_mount_point_map] _ unix_to_win + + foreach {unix_filename win_filename} $unix_to_win { + set mount_len [string length $unix_filename] + if {[string equal -length $mount_len $unix_filename $filename]} { + if {[string length $filename] == $mount_len} { + return "$win_filename/" + } elseif {[string index $filename $mount_len] eq "/"} { + set rest [string range $filename $mount_len end] + return "$win_filename$rest" + } + } + } + } + + return [file normalize $filename] +} + +# Wrapper around "file join" that handles host-specific details. +# +# For Cygwin/MSYS2's Tcl, file names that start with a drive letter +# are not considered absolute file names, thus 'file join "c:/" "d:/"' +# returns "c:/d:". This procedure thus detects absolute Windows-style +# file names, and treats them as absolute, bypassing "file join". + +proc host_file_join {args} { + if {[isbuild *-*-mingw*]} { + set result "" + foreach filename $args { + set filename [host_file_sanitize $filename] + + # If the file name starts with drive letter and colon + # (e.g., "C:/"), treat it as absolute. + if {[regexp {^[A-Z]:/} $filename]} { + set result $filename + } else { + set result [file join $result $filename] + } + } + return $result + } else { + return [file join {*}$args] + } +} + proc gdb_reinitialize_dir { subdir } { global gdb_prompt @@ -2266,7 +2437,8 @@ proc gdb_reinitialize_dir { subdir } { } gdb_expect 60 { -re "Source directories searched.*$gdb_prompt $" { - send_gdb "dir $subdir\n" + set dir [host_file_normalize $subdir] + send_gdb "dir $dir\n" gdb_expect 60 { -re "Source directories searched.*$gdb_prompt $" { verbose "Dir set to $subdir" @@ -7473,13 +7645,13 @@ proc clean_standard_output_dir {} { } # Directory containing the standard output files. - set standard_output_dir [file normalize [standard_output_file ""]] + set standard_output_dir [build_standard_output_file ""] # Ensure that standard_output_dir is clean, or only contains # gdb.log / gdb.sum. set log_file_info [split [log_file -info]] set log_file [file normalize [lindex $log_file_info end]] - if { $log_file == [file normalize [standard_output_file gdb.log]] } { + if { $log_file == [file normalize [build_standard_output_file gdb.log]] } { # Dir already contains active gdb.log. Don't remove the dir, but # check that it's clean otherwise. set res [glob -directory $standard_output_dir -tails *] @@ -7734,22 +7906,39 @@ proc make_gdb_parallel_path { args } { } # Turn BASENAME into a full file name in the standard output -# directory. It is ok if BASENAME is the empty string; in this case -# the directory is returned. +# directory, as seen from the build machine. I.e., as seen from the +# system driving DejaGnu. (E.g., if DejaGnu is being driven by MSYS2 +# to test native Windows GDB, the "build" file names should be file +# names TCL understands, i.e., Unix file names.) It is OK if BASENAME +# is the empty string; in this case the directory is returned. -proc standard_output_file {basename} { +proc build_standard_output_file {basename} { global objdir subdir gdb_test_file_name set dir [make_gdb_parallel_path outputs $subdir $gdb_test_file_name] file mkdir $dir - # If running on MinGW, replace /c/foo with c:/foo - if { [ishost *-*-mingw*] } { - set dir [exec sh -c "cd ${dir} && pwd -W"] - } return [file join $dir $basename] } -# Turn BASENAME into a file name on host. +# Turn BASENAME into a full file name in the standard output +# directory, as seen from a non-remote host. I.e., assuming the build +# and the host share the filesystem. E.g., if DejaGnu is being driven +# by MSYS2 to test native Windows GDB, the "host" file names should be +# file names GDB understands, i.e., Windows file names. It is OK if +# BASENAME is the empty string; in this case the directory is +# returned. + +proc standard_output_file {basename} { + global objdir subdir gdb_test_file_name + + set dir [make_gdb_parallel_path outputs $subdir $gdb_test_file_name] + file mkdir $dir + set dir [host_file_normalize $dir] + return [host_file_join $dir $basename] +} + +# Like standard_output_file, but handles remote hosts. Turn BASENAME +# into a file name on (potentially remote) host. proc host_standard_output_file { basename } { if { [is_remote host] } { |