*: misc improvements to config command and substitute-path rules (#3335)
A series of interconnected changes to both the terminal command 'config', DAP command 'dlv config', quality of life improvements to how substitute-path works, and better documentation. - Let 'config substitute-path' show the current substitute path rules - Add a -clear command to 'config substitute-path' - Support 'config-debug-info-directories' - rewrite SubstitutePath to be platform independent (see below) - document path substitution more Regarding the rewrite of SubstitutePath: the previous version used runtime.GOOS and filepath.IsAbs to determine which filepath separator to use and if matching should be case insensitive. This is wrong in all situations where the client and server run on different OSes, when examining core files and when cross-compilation is involved. The new version of SubstitutePath checks the rules and the input path to determine if Windows is involved in the process, if it looks like it is it switches to case-insensitive matching. It uses a lax version of filepath.IsAbs to determine if a path is absolute and tries to avoid having to select a path separator as much as possible Fixes #2891, #2890, #2889, #3179, #3332, #3343
This commit is contained in:
parent
9873e0ef63
commit
13ad7dc1d5
@ -230,14 +230,24 @@ Changes the value of a configuration parameter.
|
||||
|
||||
config substitute-path <from> <to>
|
||||
config substitute-path <from>
|
||||
config substitute-path -clear
|
||||
|
||||
Adds or removes a path substitution rule.
|
||||
Adds or removes a path substitution rule, if -clear is used all
|
||||
substitute-path rules are removed. Without arguments shows the current list
|
||||
of substitute-path rules.
|
||||
See also [Documentation/cli/substitutepath.md](//github.com/go-delve/delve/tree/master/Documentation/cli/substitutepath.md) for how the rules are applied.
|
||||
|
||||
config alias <command> <alias>
|
||||
config alias <alias>
|
||||
|
||||
Defines <alias> as an alias to <command> or removes an alias.
|
||||
|
||||
config debug-info-directories -add <path>
|
||||
config debug-info-directories -rm <path>
|
||||
config debug-info-directories -clear
|
||||
|
||||
Adds, removes or clears debug-info-directories.
|
||||
|
||||
|
||||
## continue
|
||||
Run until breakpoint or program termination.
|
||||
|
@ -29,6 +29,7 @@ raw_command(Name, ThreadID, GoroutineID, ReturnInfoLoadConfig, Expr, UnsafeCall)
|
||||
create_breakpoint(Breakpoint, LocExpr, SubstitutePathRules, Suspended) | Equivalent to API call [CreateBreakpoint](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.CreateBreakpoint)
|
||||
create_ebpf_tracepoint(FunctionName) | Equivalent to API call [CreateEBPFTracepoint](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.CreateEBPFTracepoint)
|
||||
create_watchpoint(Scope, Expr, Type) | Equivalent to API call [CreateWatchpoint](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.CreateWatchpoint)
|
||||
debug_info_directories(Set, List) | Equivalent to API call [DebugInfoDirectories](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.DebugInfoDirectories)
|
||||
detach(Kill) | Equivalent to API call [Detach](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.Detach)
|
||||
disassemble(Scope, StartPC, EndPC, Flavour) | Equivalent to API call [Disassemble](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.Disassemble)
|
||||
dump_cancel() | Equivalent to API call [DumpCancel](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.DumpCancel)
|
||||
|
66
Documentation/cli/substitutepath.md
Normal file
66
Documentation/cli/substitutepath.md
Normal file
@ -0,0 +1,66 @@
|
||||
## Path substitution configuration
|
||||
|
||||
Normally Delve finds the path to the source code that was used to produce an executable by looking at the debug symbols of the executable.
|
||||
However, under [some circumstances](../faq.md#substpath), the paths that end up inside the executable will be different from the paths to the source code on the machine that is running the debugger. If that is the case Delve will need extra configuration to convert the paths stored inside the executable to paths in your local filesystem.
|
||||
|
||||
This configuration is done by specifying a list of path substitution rules.
|
||||
|
||||
|
||||
### Where are path substitution rules specified
|
||||
|
||||
#### Delve command line client
|
||||
|
||||
The command line client reads the path substitution rules from Delve's YAML configuration file located at `$XDG_CONFIG_HOME/dlv/config.yml` or `.dlv/config.yml` inside the home directory on Windows.
|
||||
|
||||
The `substitute-path` entry should look like this:
|
||||
|
||||
```
|
||||
substitute-path:
|
||||
- {from: "/compiler/machine/directory", to: "/debugger/machine/directory"}
|
||||
- {from: "", to: "/mapping/for/relative/paths"}
|
||||
```
|
||||
|
||||
If you are starting a headless instance of Delve and connecting to it through `dlv connect` the configuration file that is used is the one that runs `dlv connect`.
|
||||
|
||||
The rules can also be modified while Delve is running by using the [config substitute-path command](./README.md#config):
|
||||
|
||||
```
|
||||
(dlv) config substitute-path /from/path /to/path
|
||||
```
|
||||
|
||||
Double quotes can be used to specify paths that contain spaces, or to specify empty paths:
|
||||
|
||||
```
|
||||
(dlv) config substitute-path "/path containing spaces/" /path-without-spaces/
|
||||
(dlv) config substitute-path /make/this/path/relative ""
|
||||
```
|
||||
|
||||
#### DAP server
|
||||
|
||||
If you connect to Delve using the DAP protocol then the substitute path rules are specified using the substitutePath option in [launch.json](https://github.com/golang/vscode-go/blob/master/docs/debugging.md#launchjson-attributes).
|
||||
|
||||
```
|
||||
"substitutePath": [
|
||||
{ "from": "/from/path", "to": "/to/path" }
|
||||
]
|
||||
```
|
||||
|
||||
The [debug console](https://github.com/golang/vscode-go/blob/master/docs/debugging.md#dlv-command-from-debug-console) can also be used to modify the path substitution list:
|
||||
|
||||
```
|
||||
dlv config substitutePath /from/path /to/path
|
||||
```
|
||||
|
||||
This command works similarly to the `config substitute-path` command described above.
|
||||
|
||||
### How are path substitution rules applied
|
||||
|
||||
Regardless of how they are specified the path substitution rules are an ordered list of `(from-path, to-path)` pairs. When Delve needs to convert a path P found inside the executable file into a path in the local filesystem it will scan through the list of rules looking for the first one where P starts with from-path and replace from-path with to-path.
|
||||
|
||||
Empty paths in both from-path and to-path are special, they represent relative paths:
|
||||
|
||||
- `(from="" to="/home/user/project/src")` converts all relative paths in the executable to absolute paths in `/home/user/project/src`
|
||||
- `(from="/build/dir" to="")` converts all paths in the executable that start with `/build/dir` into relative paths.
|
||||
|
||||
The path substitution code is SubstitutePath in pkg/locspec/locations.go.
|
||||
|
@ -116,6 +116,8 @@ The substitute-path feature can be used to solve this problem, see `help config`
|
||||
|
||||
The `sources` command could also be useful in troubleshooting this problem, it shows the list of file paths that has been embedded by the compiler into the executable.
|
||||
|
||||
For more informations on path substitution see [path substitution](cli/substitutepath.md).
|
||||
|
||||
If you still think this is a bug in Delve and not a configuration problem, open an [issue](https://github.com/go-delve/delve/issues), filling the issue template and including the logs produced by delve with the options `--log --log-output=rpc,dap`.
|
||||
|
||||
### <a name="runtime"></a> Using Delve to debug the Go runtime
|
||||
|
@ -278,6 +278,7 @@ aliases:
|
||||
# between compilation and debugging.
|
||||
# Note that substitution rules will not be used for paths passed to "break" and "trace"
|
||||
# commands.
|
||||
# See also Documentation/cli/substitutepath.md.
|
||||
substitute-path:
|
||||
# - {from: path, to: path}
|
||||
|
||||
|
@ -81,6 +81,9 @@ func ConfigureSetSimple(rest string, cfgname string, field reflect.Value) error
|
||||
}
|
||||
return reflect.ValueOf(&n), nil
|
||||
case reflect.Bool:
|
||||
if rest != "true" && rest != "false" {
|
||||
return reflect.ValueOf(nil), fmt.Errorf("argument to %q must be true or false", cfgname)
|
||||
}
|
||||
v := rest == "true"
|
||||
return reflect.ValueOf(&v), nil
|
||||
case reflect.String:
|
||||
|
@ -479,47 +479,99 @@ func (loc *NormalLocationSpec) findFuncCandidates(bi *proc.BinaryInfo, limit int
|
||||
return r
|
||||
}
|
||||
|
||||
func crossPlatformPath(path string) string {
|
||||
if runtime.GOOS == "windows" {
|
||||
return strings.ToLower(path)
|
||||
// isAbs returns true if path looks like an absolute path.
|
||||
func isAbs(path string) bool {
|
||||
// Unix-like absolute path
|
||||
if strings.HasPrefix(path, "/") {
|
||||
return true
|
||||
}
|
||||
return path
|
||||
return windowsAbsPath(path)
|
||||
}
|
||||
|
||||
func windowsAbsPath(path string) bool {
|
||||
// Windows UNC absolute path
|
||||
if strings.HasPrefix(path, `\\`) {
|
||||
return true
|
||||
}
|
||||
// DOS absolute paths
|
||||
if len(path) < 3 || path[1] != ':' {
|
||||
return false
|
||||
}
|
||||
return path[2] == '/' || path[2] == '\\'
|
||||
}
|
||||
|
||||
func hasPathSeparatorSuffix(path string) bool {
|
||||
return strings.HasSuffix(path, "/") || strings.HasSuffix(path, "\\")
|
||||
}
|
||||
|
||||
func hasPathSeparatorPrefix(path string) bool {
|
||||
return strings.HasPrefix(path, "/") || strings.HasPrefix(path, "\\")
|
||||
}
|
||||
|
||||
// SubstitutePath applies the specified path substitution rules to path.
|
||||
func SubstitutePath(path string, rules [][2]string) string {
|
||||
path = crossPlatformPath(path)
|
||||
// On windows paths returned from headless server are as c:/dir/dir
|
||||
// though os.PathSeparator is '\\'
|
||||
|
||||
separator := "/" // make it default
|
||||
if strings.Contains(path, "\\") { // dependent on the path
|
||||
separator = "\\"
|
||||
// Look for evidence that we are dealing with windows somewhere, if we are use case-insensitive matching
|
||||
caseInsensitive := windowsAbsPath(path)
|
||||
if !caseInsensitive {
|
||||
for i := range rules {
|
||||
if windowsAbsPath(rules[i][0]) || windowsAbsPath(rules[i][1]) {
|
||||
caseInsensitive = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, r := range rules {
|
||||
from := crossPlatformPath(r[0])
|
||||
to := r[1]
|
||||
from, to := r[0], r[1]
|
||||
|
||||
// If we have an exact match, use it directly.
|
||||
// if we have an exact match, use it directly.
|
||||
if path == from {
|
||||
return to
|
||||
}
|
||||
|
||||
// Otherwise check if it's a directory prefix.
|
||||
if from != "" && !strings.HasSuffix(from, separator) {
|
||||
from = from + separator
|
||||
}
|
||||
if to != "" && !strings.HasSuffix(to, separator) {
|
||||
to = to + separator
|
||||
match := false
|
||||
var rest string
|
||||
if from == "" {
|
||||
match = !isAbs(path)
|
||||
rest = path
|
||||
} else {
|
||||
if caseInsensitive {
|
||||
match = strings.HasPrefix(strings.ToLower(path), strings.ToLower(from))
|
||||
if match {
|
||||
path = strings.ToLower(path)
|
||||
from = strings.ToLower(from)
|
||||
}
|
||||
} else {
|
||||
match = strings.HasPrefix(path, from)
|
||||
}
|
||||
if match {
|
||||
// make sure the match ends on something that looks like a path separator boundary
|
||||
rest = path[len(from):]
|
||||
match = hasPathSeparatorSuffix(from) || hasPathSeparatorPrefix(rest)
|
||||
}
|
||||
}
|
||||
|
||||
// Expand relative paths with the specified prefix
|
||||
if from == "" && !filepath.IsAbs(path) {
|
||||
return strings.Replace(path, from, to, 1)
|
||||
}
|
||||
if match {
|
||||
if to == "" {
|
||||
// make sure we return a relative path, regardless of whether 'from' consumed a final / or not
|
||||
if hasPathSeparatorPrefix(rest) {
|
||||
return rest[1:]
|
||||
}
|
||||
return rest
|
||||
}
|
||||
|
||||
if from != "" && strings.HasPrefix(path, from) {
|
||||
return strings.Replace(path, from, to, 1)
|
||||
toEndsWithSlash := hasPathSeparatorSuffix(to)
|
||||
restStartsWithSlash := hasPathSeparatorPrefix(rest)
|
||||
|
||||
switch {
|
||||
case toEndsWithSlash && restStartsWithSlash:
|
||||
return to[:len(to)-1] + rest
|
||||
case toEndsWithSlash && !restStartsWithSlash:
|
||||
return to + rest
|
||||
case !toEndsWithSlash && restStartsWithSlash:
|
||||
return to + rest
|
||||
case !toEndsWithSlash && !restStartsWithSlash:
|
||||
return to + "/" + rest
|
||||
}
|
||||
}
|
||||
}
|
||||
return path
|
||||
|
@ -1,7 +1,6 @@
|
||||
package locspec
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"testing"
|
||||
)
|
||||
|
||||
@ -69,16 +68,13 @@ func TestFunctionLocationParsing(t *testing.T) {
|
||||
}
|
||||
|
||||
func assertSubstitutePathEqual(t *testing.T, expected string, substituted string) {
|
||||
t.Helper()
|
||||
if expected != substituted {
|
||||
t.Fatalf("Expected substitutedPath to be %s got %s instead", expected, substituted)
|
||||
t.Errorf("Expected substitutedPath to be %s got %s instead", expected, substituted)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSubstitutePathUnix(t *testing.T) {
|
||||
if runtime.GOOS == "windows" {
|
||||
t.Skip("Skipping unix SubstitutePath test in windows")
|
||||
}
|
||||
|
||||
// Relative paths mapping
|
||||
assertSubstitutePathEqual(t, "/my/asb/folder/relative/path", SubstitutePath("relative/path", [][2]string{{"", "/my/asb/folder/"}}))
|
||||
assertSubstitutePathEqual(t, "/already/abs/path", SubstitutePath("/already/abs/path", [][2]string{{"", "/my/asb/folder/"}}))
|
||||
@ -99,21 +95,17 @@ func TestSubstitutePathUnix(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestSubstitutePathWindows(t *testing.T) {
|
||||
if runtime.GOOS != "windows" {
|
||||
t.Skip("Skipping windows SubstitutePath test in unix")
|
||||
}
|
||||
|
||||
// Relative paths mapping
|
||||
assertSubstitutePathEqual(t, "c:\\my\\asb\\folder\\relative\\path", SubstitutePath("relative\\path", [][2]string{{"", "c:\\my\\asb\\folder\\"}}))
|
||||
assertSubstitutePathEqual(t, "f:\\already\\abs\\path", SubstitutePath("F:\\already\\abs\\path", [][2]string{{"", "c:\\my\\asb\\folder\\"}}))
|
||||
assertSubstitutePathEqual(t, "F:\\already\\abs\\path", SubstitutePath("F:\\already\\abs\\path", [][2]string{{"", "c:\\my\\asb\\folder\\"}}))
|
||||
assertSubstitutePathEqual(t, "relative\\path", SubstitutePath("C:\\my\\asb\\folder\\relative\\path", [][2]string{{"c:\\my\\asb\\folder\\", ""}}))
|
||||
assertSubstitutePathEqual(t, "f:\\another\\folder\\relative\\path", SubstitutePath("F:\\another\\folder\\relative\\path", [][2]string{{"c:\\my\\asb\\folder\\", ""}}))
|
||||
assertSubstitutePathEqual(t, "F:\\another\\folder\\relative\\path", SubstitutePath("F:\\another\\folder\\relative\\path", [][2]string{{"c:\\my\\asb\\folder\\", ""}}))
|
||||
assertSubstitutePathEqual(t, "my\\path", SubstitutePath("relative\\path\\my\\path", [][2]string{{"relative\\path", ""}}))
|
||||
assertSubstitutePathEqual(t, "c:\\abs\\my\\path", SubstitutePath("c:\\abs\\my\\path", [][2]string{{"abs\\my", ""}}))
|
||||
|
||||
// Absolute paths mapping
|
||||
assertSubstitutePathEqual(t, "c:\\new\\mapping\\path", SubstitutePath("D:\\original\\path", [][2]string{{"d:\\original", "c:\\new\\mapping"}}))
|
||||
assertSubstitutePathEqual(t, "f:\\no\\change\\path", SubstitutePath("F:\\no\\change\\path", [][2]string{{"d:\\original", "c:\\new\\mapping"}}))
|
||||
assertSubstitutePathEqual(t, "F:\\no\\change\\path", SubstitutePath("F:\\no\\change\\path", [][2]string{{"d:\\original", "c:\\new\\mapping"}}))
|
||||
assertSubstitutePathEqual(t, "c:\\folder\\should_not_be_replaced\\path", SubstitutePath("c:\\folder\\should_not_be_replaced\\path", [][2]string{{"should_not_be_replaced", ""}}))
|
||||
|
||||
// Mix absolute and relative mapping
|
||||
@ -121,3 +113,79 @@ func TestSubstitutePathWindows(t *testing.T) {
|
||||
assertSubstitutePathEqual(t, "c:\\my\\asb\\folder\\path\\", SubstitutePath("path\\", [][2]string{{"d:\\original", "c:\\new\\mapping"}, {"", "c:\\my\\asb\\folder\\"}, {"c:\\my\\asb\\folder\\", ""}}))
|
||||
assertSubstitutePathEqual(t, "path", SubstitutePath("C:\\my\\asb\\folder\\path", [][2]string{{"d:\\original", "c:\\new\\mapping"}, {"c:\\my\\asb\\folder\\", ""}, {"", "c:\\my\\asb\\folder\\"}}))
|
||||
}
|
||||
|
||||
type tRule struct {
|
||||
from string
|
||||
to string
|
||||
}
|
||||
|
||||
type tCase struct {
|
||||
rules []tRule
|
||||
path string
|
||||
res string
|
||||
}
|
||||
|
||||
func platformCases() []tCase {
|
||||
casesUnix := []tCase{
|
||||
// Should not depend on separator at the end of rule path
|
||||
{[]tRule{{"/tmp/path", "/new/path2"}}, "/tmp/path/file.go", "/new/path2/file.go"},
|
||||
{[]tRule{{"/tmp/path/", "/new/path2/"}}, "/tmp/path/file.go", "/new/path2/file.go"},
|
||||
{[]tRule{{"/tmp/path/", "/new/path2"}}, "/tmp/path/file.go", "/new/path2/file.go"},
|
||||
{[]tRule{{"/tmp/path", "/new/path2/"}}, "/tmp/path/file.go", "/new/path2/file.go"},
|
||||
// Should apply to directory prefixes
|
||||
{[]tRule{{"/tmp/path", "/new/path2"}}, "/tmp/path-2/file.go", "/tmp/path-2/file.go"},
|
||||
// Should apply to exact matches
|
||||
{[]tRule{{"/tmp/path/file.go", "/new/path2/file2.go"}}, "/tmp/path/file.go", "/new/path2/file2.go"},
|
||||
// First matched rule should be used
|
||||
{[]tRule{
|
||||
{"/tmp/path1", "/new/path1"},
|
||||
{"/tmp/path2", "/new/path2"},
|
||||
{"/tmp/path2", "/new/path3"}}, "/tmp/path2/file.go", "/new/path2/file.go"},
|
||||
}
|
||||
casesLinux := []tCase{
|
||||
// Should be case-sensitive
|
||||
{[]tRule{{"/tmp/path", "/new/path2"}}, "/TmP/path/file.go", "/TmP/path/file.go"},
|
||||
}
|
||||
casesFreebsd := []tCase{
|
||||
// Should be case-sensitive
|
||||
{[]tRule{{"/tmp/path", "/new/path2"}}, "/TmP/path/file.go", "/TmP/path/file.go"},
|
||||
}
|
||||
casesDarwin := []tCase{
|
||||
// Can be either case-sensitive or case-insensitive depending on
|
||||
// filesystem settings, we always treat it as case-sensitive.
|
||||
{[]tRule{{"/tmp/path", "/new/path2"}}, "/TmP/PaTh/file.go", "/TmP/PaTh/file.go"},
|
||||
}
|
||||
casesWindows := []tCase{
|
||||
// Should not depend on separator at the end of rule path
|
||||
{[]tRule{{`c:\tmp\path`, `d:\new\path2`}}, `c:\tmp\path\file.go`, `d:\new\path2\file.go`},
|
||||
{[]tRule{{`c:\tmp\path\`, `d:\new\path2\`}}, `c:\tmp\path\file.go`, `d:\new\path2\file.go`},
|
||||
{[]tRule{{`c:\tmp\path`, `d:\new\path2\`}}, `c:\tmp\path\file.go`, `d:\new\path2\file.go`},
|
||||
{[]tRule{{`c:\tmp\path\`, `d:\new\path2`}}, `c:\tmp\path\file.go`, `d:\new\path2/file.go`},
|
||||
// Should apply to directory prefixes
|
||||
{[]tRule{{`c:\tmp\path`, `d:\new\path2`}}, `c:\tmp\path-2\file.go`, `c:\tmp\path-2\file.go`},
|
||||
// Should apply to exact matches
|
||||
{[]tRule{{`c:\tmp\path\file.go`, `d:\new\path2\file2.go`}}, `c:\tmp\path\file.go`, `d:\new\path2\file2.go`},
|
||||
// Should be case-insensitive
|
||||
{[]tRule{{`c:\tmp\path`, `d:\new\path2`}}, `C:\TmP\PaTh\file.go`, `d:\new\path2\file.go`},
|
||||
}
|
||||
|
||||
r := append(casesUnix, casesLinux...)
|
||||
r = append(r, casesFreebsd...)
|
||||
r = append(r, casesDarwin...)
|
||||
r = append(r, casesWindows...)
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
func TestSubstitutePath(t *testing.T) {
|
||||
for _, c := range platformCases() {
|
||||
subRules := [][2]string{}
|
||||
for _, r := range c.rules {
|
||||
subRules = append(subRules, [2]string{r.from, r.to})
|
||||
}
|
||||
res := SubstitutePath(c.path, subRules)
|
||||
if c.res != res {
|
||||
t.Errorf("terminal.SubstitutePath(%q) => %q, want %q", c.path, res, c.res)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -51,7 +51,7 @@ type BinaryInfo struct {
|
||||
// GOOS operating system this binary is executing on.
|
||||
GOOS string
|
||||
|
||||
debugInfoDirectories []string
|
||||
DebugInfoDirectories []string
|
||||
|
||||
// BuildID of this binary.
|
||||
BuildID string
|
||||
@ -676,7 +676,7 @@ func (bi *BinaryInfo) LoadBinaryInfo(path string, entryPoint uint64, debugInfoDi
|
||||
bi.lastModified = fi.ModTime()
|
||||
}
|
||||
|
||||
bi.debugInfoDirectories = debugInfoDirs
|
||||
bi.DebugInfoDirectories = debugInfoDirs
|
||||
|
||||
return bi.AddImage(path, entryPoint)
|
||||
}
|
||||
@ -1396,7 +1396,7 @@ func loadBinaryInfoElf(bi *BinaryInfo, image *Image, path string, addr uint64, w
|
||||
if err != nil {
|
||||
var sepFile *os.File
|
||||
var serr error
|
||||
sepFile, dwarfFile, serr = bi.openSeparateDebugInfo(image, elfFile, bi.debugInfoDirectories)
|
||||
sepFile, dwarfFile, serr = bi.openSeparateDebugInfo(image, elfFile, bi.DebugInfoDirectories)
|
||||
if serr != nil {
|
||||
return serr
|
||||
}
|
||||
|
@ -516,13 +516,23 @@ Changes the value of a configuration parameter.
|
||||
|
||||
config substitute-path <from> <to>
|
||||
config substitute-path <from>
|
||||
config substitute-path -clear
|
||||
|
||||
Adds or removes a path substitution rule.
|
||||
Adds or removes a path substitution rule, if -clear is used all
|
||||
substitute-path rules are removed. Without arguments shows the current list
|
||||
of substitute-path rules.
|
||||
See also Documentation/cli/substitutepath.md for how the rules are applied.
|
||||
|
||||
config alias <command> <alias>
|
||||
config alias <alias>
|
||||
|
||||
Defines <alias> as an alias to <command> or removes an alias.`},
|
||||
Defines <alias> as an alias to <command> or removes an alias.
|
||||
|
||||
config debug-info-directories -add <path>
|
||||
config debug-info-directories -rm <path>
|
||||
config debug-info-directories -clear
|
||||
|
||||
Adds, removes or clears debug-info-directories.`},
|
||||
|
||||
{aliases: []string{"edit", "ed"}, cmdFn: edit, helpMsg: `Open where you are in $DELVE_EDITOR or $EDITOR
|
||||
|
||||
|
@ -714,37 +714,67 @@ func findCmdName(c *Commands, cmdstr string, prefix cmdPrefix) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func assertNoError(t *testing.T, err error, str string) {
|
||||
t.Helper()
|
||||
if err != nil {
|
||||
t.Fatalf("%s: %v", str, err)
|
||||
}
|
||||
}
|
||||
|
||||
func assertNoErrorConfigureCmd(t *testing.T, term *Term, cmdstr string) {
|
||||
t.Helper()
|
||||
err := configureCmd(term, callContext{}, cmdstr)
|
||||
assertNoError(t, err, fmt.Sprintf("error executing configureCmd(%s)", cmdstr))
|
||||
}
|
||||
|
||||
func assertSubstitutePath(t *testing.T, sp config.SubstitutePathRules, v ...string) {
|
||||
t.Helper()
|
||||
if len(sp) != len(v)/2 {
|
||||
t.Fatalf("wrong number of substitute path rules (expected: %d): %#v", len(v)/2, sp)
|
||||
}
|
||||
for i := range sp {
|
||||
if sp[i].From != v[i*2] || sp[i].To != v[i*2+1] {
|
||||
t.Fatalf("wrong substitute path rule %#v expected (from: %q to %q)", sp[i], v[i*2], v[i*2+1])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func assertDebugInfoDirs(t *testing.T, got []string, tgt ...string) {
|
||||
if len(got) != len(tgt) {
|
||||
t.Fatalf("wrong number of debug info directories (got %d expected %d)", len(got), len(tgt))
|
||||
}
|
||||
for i := range got {
|
||||
if got[i] != tgt[i] {
|
||||
t.Fatalf("debug info directories mismatch got: %v expected: %v", got, tgt)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfig(t *testing.T) {
|
||||
var buf bytes.Buffer
|
||||
var term Term
|
||||
term.conf = &config.Config{}
|
||||
term.cmds = DebugCommands(nil)
|
||||
term.stdout = &transcriptWriter{pw: &pagingWriter{w: &buf}}
|
||||
|
||||
err := configureCmd(&term, callContext{}, "nonexistent-parameter 10")
|
||||
if err == nil {
|
||||
t.Fatalf("expected error executing configureCmd(nonexistent-parameter)")
|
||||
}
|
||||
|
||||
err = configureCmd(&term, callContext{}, "max-string-len 10")
|
||||
if err != nil {
|
||||
t.Fatalf("error executing configureCmd(max-string-len): %v", err)
|
||||
}
|
||||
assertNoErrorConfigureCmd(t, &term, "max-string-len 10")
|
||||
if term.conf.MaxStringLen == nil {
|
||||
t.Fatalf("expected MaxStringLen 10, got nil")
|
||||
}
|
||||
if *term.conf.MaxStringLen != 10 {
|
||||
t.Fatalf("expected MaxStringLen 10, got: %d", *term.conf.MaxStringLen)
|
||||
}
|
||||
err = configureCmd(&term, callContext{}, "show-location-expr true")
|
||||
if err != nil {
|
||||
t.Fatalf("error executing configureCmd(show-location-expr true)")
|
||||
}
|
||||
assertNoErrorConfigureCmd(t, &term, "show-location-expr true")
|
||||
if term.conf.ShowLocationExpr != true {
|
||||
t.Fatalf("expected ShowLocationExpr true, got false")
|
||||
}
|
||||
err = configureCmd(&term, callContext{}, "max-variable-recurse 4")
|
||||
if err != nil {
|
||||
t.Fatalf("error executing configureCmd(max-variable-recurse): %v", err)
|
||||
}
|
||||
|
||||
assertNoErrorConfigureCmd(t, &term, "max-variable-recurse 4")
|
||||
if term.conf.MaxVariableRecurse == nil {
|
||||
t.Fatalf("expected MaxVariableRecurse 4, got nil")
|
||||
}
|
||||
@ -752,26 +782,13 @@ func TestConfig(t *testing.T) {
|
||||
t.Fatalf("expected MaxVariableRecurse 4, got: %d", *term.conf.MaxVariableRecurse)
|
||||
}
|
||||
|
||||
err = configureCmd(&term, callContext{}, "substitute-path a b")
|
||||
if err != nil {
|
||||
t.Fatalf("error executing configureCmd(substitute-path a b): %v", err)
|
||||
}
|
||||
if len(term.conf.SubstitutePath) != 1 || (term.conf.SubstitutePath[0] != config.SubstitutePathRule{From: "a", To: "b"}) {
|
||||
t.Fatalf("unexpected SubstitutePathRules after insert %v", term.conf.SubstitutePath)
|
||||
}
|
||||
assertNoErrorConfigureCmd(t, &term, "substitute-path a b")
|
||||
assertSubstitutePath(t, term.conf.SubstitutePath, "a", "b")
|
||||
|
||||
err = configureCmd(&term, callContext{}, "substitute-path a")
|
||||
if err != nil {
|
||||
t.Fatalf("error executing configureCmd(substitute-path a): %v", err)
|
||||
}
|
||||
if len(term.conf.SubstitutePath) != 0 {
|
||||
t.Fatalf("unexpected SubstitutePathRules after delete %v", term.conf.SubstitutePath)
|
||||
}
|
||||
assertNoErrorConfigureCmd(t, &term, "substitute-path a")
|
||||
assertSubstitutePath(t, term.conf.SubstitutePath)
|
||||
|
||||
err = configureCmd(&term, callContext{}, "alias print blah")
|
||||
if err != nil {
|
||||
t.Fatalf("error executing configureCmd(alias print blah): %v", err)
|
||||
}
|
||||
assertNoErrorConfigureCmd(t, &term, "alias print blah")
|
||||
if len(term.conf.Aliases["print"]) != 1 {
|
||||
t.Fatalf("aliases not changed after configure command %v", term.conf.Aliases)
|
||||
}
|
||||
@ -779,16 +796,62 @@ func TestConfig(t *testing.T) {
|
||||
t.Fatalf("new alias not found")
|
||||
}
|
||||
|
||||
err = configureCmd(&term, callContext{}, "alias blah")
|
||||
if err != nil {
|
||||
t.Fatalf("error executing configureCmd(alias blah): %v", err)
|
||||
}
|
||||
assertNoErrorConfigureCmd(t, &term, "alias blah")
|
||||
if len(term.conf.Aliases["print"]) != 0 {
|
||||
t.Fatalf("alias not removed after configure command %v", term.conf.Aliases)
|
||||
}
|
||||
if findCmdName(term.cmds, "blah", noPrefix) != "" {
|
||||
t.Fatalf("new alias found after delete")
|
||||
}
|
||||
|
||||
err = configureCmd(&term, callContext{}, "show-location-expr")
|
||||
if err == nil {
|
||||
t.Fatalf("no error form configureCmd(show-location-expr)")
|
||||
}
|
||||
if !term.conf.ShowLocationExpr {
|
||||
t.Fatalf("ShowLocationExpr not set to true")
|
||||
}
|
||||
|
||||
assertNoErrorConfigureCmd(t, &term, "show-location-expr false")
|
||||
if term.conf.ShowLocationExpr {
|
||||
t.Fatalf("ShowLocationExpr set to true")
|
||||
}
|
||||
|
||||
assertNoErrorConfigureCmd(t, &term, "substitute-path a b")
|
||||
assertNoErrorConfigureCmd(t, &term, "substitute-path c d")
|
||||
assertSubstitutePath(t, term.conf.SubstitutePath, "a", "b", "c", "d")
|
||||
|
||||
buf.Reset()
|
||||
assertNoErrorConfigureCmd(t, &term, "substitute-path")
|
||||
t.Logf("current substitute-path: %q", buf.String())
|
||||
if buf.String() != "\"a\" → \"b\"\n\"c\" → \"d\"\n" {
|
||||
t.Fatalf("wrong substitute-path value")
|
||||
}
|
||||
|
||||
assertNoErrorConfigureCmd(t, &term, "substitute-path -clear c")
|
||||
assertSubstitutePath(t, term.conf.SubstitutePath, "a", "b")
|
||||
|
||||
assertNoErrorConfigureCmd(t, &term, "substitute-path -clear")
|
||||
assertSubstitutePath(t, term.conf.SubstitutePath)
|
||||
|
||||
assertNoErrorConfigureCmd(t, &term, "substitute-path \"\" something")
|
||||
assertSubstitutePath(t, term.conf.SubstitutePath, "", "something")
|
||||
|
||||
assertNoErrorConfigureCmd(t, &term, "substitute-path somethingelse \"\"")
|
||||
assertSubstitutePath(t, term.conf.SubstitutePath, "", "something", "somethingelse", "")
|
||||
|
||||
assertDebugInfoDirs(t, term.conf.DebugInfoDirectories)
|
||||
|
||||
assertNoErrorConfigureCmd(t, &term, "debug-info-directories -add a")
|
||||
assertDebugInfoDirs(t, term.conf.DebugInfoDirectories, "a")
|
||||
assertNoErrorConfigureCmd(t, &term, "debug-info-directories -add b")
|
||||
assertDebugInfoDirs(t, term.conf.DebugInfoDirectories, "a", "b")
|
||||
assertNoErrorConfigureCmd(t, &term, "debug-info-directories -add c")
|
||||
assertDebugInfoDirs(t, term.conf.DebugInfoDirectories, "a", "b", "c")
|
||||
assertNoErrorConfigureCmd(t, &term, "debug-info-directories -rm b")
|
||||
assertDebugInfoDirs(t, term.conf.DebugInfoDirectories, "a", "c")
|
||||
assertNoErrorConfigureCmd(t, &term, "debug-info-directories -clear")
|
||||
assertDebugInfoDirs(t, term.conf.DebugInfoDirectories)
|
||||
}
|
||||
|
||||
func TestIssue1090(t *testing.T) {
|
||||
|
@ -1,9 +1,10 @@
|
||||
package terminal
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
|
||||
"github.com/go-delve/delve/pkg/config"
|
||||
@ -33,7 +34,7 @@ func configureCmd(t *Term, ctx callContext, args string) error {
|
||||
|
||||
func configureList(t *Term) error {
|
||||
w := new(tabwriter.Writer)
|
||||
w.Init(os.Stdout, 0, 8, 1, ' ', 0)
|
||||
w.Init(t.stdout, 0, 8, 1, ' ', 0)
|
||||
config.ConfigureList(w, t.conf, "yaml")
|
||||
return w.Flush()
|
||||
}
|
||||
@ -47,8 +48,11 @@ func configureSet(t *Term, args string) error {
|
||||
rest = v[1]
|
||||
}
|
||||
|
||||
if cfgname == "alias" {
|
||||
switch cfgname {
|
||||
case "alias":
|
||||
return configureSetAlias(t, rest)
|
||||
case "debug-info-directories":
|
||||
return configureSetDebugInfoDirectories(t, rest)
|
||||
}
|
||||
|
||||
field := config.ConfigureFindFieldByName(t.conf, cfgname, "yaml")
|
||||
@ -64,8 +68,22 @@ func configureSet(t *Term, args string) error {
|
||||
}
|
||||
|
||||
func configureSetSubstitutePath(t *Term, rest string) error {
|
||||
if strings.TrimSpace(rest) == "-clear" {
|
||||
t.conf.SubstitutePath = t.conf.SubstitutePath[:0]
|
||||
return nil
|
||||
}
|
||||
argv := config.SplitQuotedFields(rest, '"')
|
||||
if len(argv) == 2 && argv[0] == "-clear" {
|
||||
argv = argv[1:]
|
||||
}
|
||||
switch len(argv) {
|
||||
case 0:
|
||||
w := new(tabwriter.Writer)
|
||||
w.Init(t.stdout, 0, 8, 1, ' ', 0)
|
||||
for i := range t.conf.SubstitutePath {
|
||||
fmt.Fprintf(w, "%q\t→\t%q\n", t.conf.SubstitutePath[i].From, t.conf.SubstitutePath[i].To)
|
||||
}
|
||||
w.Flush()
|
||||
case 1: // delete substitute-path rule
|
||||
for i := range t.conf.SubstitutePath {
|
||||
if t.conf.SubstitutePath[i].From == argv[0] {
|
||||
@ -112,3 +130,46 @@ func configureSetAlias(t *Term, rest string) error {
|
||||
t.cmds.Merge(t.conf.Aliases)
|
||||
return nil
|
||||
}
|
||||
|
||||
func configureSetDebugInfoDirectories(t *Term, rest string) error {
|
||||
v := config.Split2PartsBySpace(rest)
|
||||
|
||||
if t.client != nil {
|
||||
did, err := t.client.GetDebugInfoDirectories()
|
||||
if err == nil {
|
||||
t.conf.DebugInfoDirectories = did
|
||||
}
|
||||
}
|
||||
|
||||
switch v[0] {
|
||||
case "-clear":
|
||||
t.conf.DebugInfoDirectories = t.conf.DebugInfoDirectories[:0]
|
||||
case "-add":
|
||||
if len(v) < 2 {
|
||||
return errors.New("not enough arguments to \"config debug-info-directories\"")
|
||||
}
|
||||
t.conf.DebugInfoDirectories = append(t.conf.DebugInfoDirectories, v[1])
|
||||
case "-rm":
|
||||
if len(v) < 2 {
|
||||
return errors.New("not enough arguments to \"config debug-info-directories\"")
|
||||
}
|
||||
found := false
|
||||
for i := range t.conf.DebugInfoDirectories {
|
||||
if t.conf.DebugInfoDirectories[i] == v[1] {
|
||||
found = true
|
||||
t.conf.DebugInfoDirectories = append(t.conf.DebugInfoDirectories[:i], t.conf.DebugInfoDirectories[i+1:]...)
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return fmt.Errorf("could not find %q in debug-info-directories", v[1])
|
||||
}
|
||||
default:
|
||||
return errors.New("wrong argument to \"config debug-info-directories\"")
|
||||
}
|
||||
|
||||
if t.client != nil {
|
||||
t.client.SetDebugInfoDirectories(t.conf.DebugInfoDirectories)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -427,6 +427,44 @@ func (env *Env) starlarkPredeclare() starlark.StringDict {
|
||||
}
|
||||
return env.interfaceToStarlarkValue(rpcRet), nil
|
||||
})
|
||||
r["debug_info_directories"] = starlark.NewBuiltin("debug_info_directories", func(thread *starlark.Thread, _ *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
|
||||
if err := isCancelled(thread); err != nil {
|
||||
return starlark.None, decorateError(thread, err)
|
||||
}
|
||||
var rpcArgs rpc2.DebugInfoDirectoriesIn
|
||||
var rpcRet rpc2.DebugInfoDirectoriesOut
|
||||
if len(args) > 0 && args[0] != starlark.None {
|
||||
err := unmarshalStarlarkValue(args[0], &rpcArgs.Set, "Set")
|
||||
if err != nil {
|
||||
return starlark.None, decorateError(thread, err)
|
||||
}
|
||||
}
|
||||
if len(args) > 1 && args[1] != starlark.None {
|
||||
err := unmarshalStarlarkValue(args[1], &rpcArgs.List, "List")
|
||||
if err != nil {
|
||||
return starlark.None, decorateError(thread, err)
|
||||
}
|
||||
}
|
||||
for _, kv := range kwargs {
|
||||
var err error
|
||||
switch kv[0].(starlark.String) {
|
||||
case "Set":
|
||||
err = unmarshalStarlarkValue(kv[1], &rpcArgs.Set, "Set")
|
||||
case "List":
|
||||
err = unmarshalStarlarkValue(kv[1], &rpcArgs.List, "List")
|
||||
default:
|
||||
err = fmt.Errorf("unknown argument %q", kv[0])
|
||||
}
|
||||
if err != nil {
|
||||
return starlark.None, decorateError(thread, err)
|
||||
}
|
||||
}
|
||||
err := env.ctx.Client().CallAPI("DebugInfoDirectories", &rpcArgs, &rpcRet)
|
||||
if err != nil {
|
||||
return starlark.None, err
|
||||
}
|
||||
return env.interfaceToStarlarkValue(rpcRet), nil
|
||||
})
|
||||
r["detach"] = starlark.NewBuiltin("detach", func(thread *starlark.Thread, _ *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
|
||||
if err := isCancelled(thread); err != nil {
|
||||
return starlark.None, decorateError(thread, err)
|
||||
|
@ -3,95 +3,9 @@ package terminal
|
||||
import (
|
||||
"errors"
|
||||
"net/rpc"
|
||||
"runtime"
|
||||
"testing"
|
||||
|
||||
"github.com/go-delve/delve/pkg/config"
|
||||
)
|
||||
|
||||
type tRule struct {
|
||||
from string
|
||||
to string
|
||||
}
|
||||
|
||||
type tCase struct {
|
||||
rules []tRule
|
||||
path string
|
||||
res string
|
||||
}
|
||||
|
||||
func platformCases() []tCase {
|
||||
casesUnix := []tCase{
|
||||
// Should not depend on separator at the end of rule path
|
||||
{[]tRule{{"/tmp/path", "/new/path2"}}, "/tmp/path/file.go", "/new/path2/file.go"},
|
||||
{[]tRule{{"/tmp/path/", "/new/path2/"}}, "/tmp/path/file.go", "/new/path2/file.go"},
|
||||
{[]tRule{{"/tmp/path/", "/new/path2"}}, "/tmp/path/file.go", "/new/path2/file.go"},
|
||||
{[]tRule{{"/tmp/path", "/new/path2/"}}, "/tmp/path/file.go", "/new/path2/file.go"},
|
||||
// Should apply to directory prefixes
|
||||
{[]tRule{{"/tmp/path", "/new/path2"}}, "/tmp/path-2/file.go", "/tmp/path-2/file.go"},
|
||||
// Should apply to exact matches
|
||||
{[]tRule{{"/tmp/path/file.go", "/new/path2/file2.go"}}, "/tmp/path/file.go", "/new/path2/file2.go"},
|
||||
// First matched rule should be used
|
||||
{[]tRule{
|
||||
{"/tmp/path1", "/new/path1"},
|
||||
{"/tmp/path2", "/new/path2"},
|
||||
{"/tmp/path2", "/new/path3"}}, "/tmp/path2/file.go", "/new/path2/file.go"},
|
||||
}
|
||||
casesLinux := []tCase{
|
||||
// Should be case-sensitive
|
||||
{[]tRule{{"/tmp/path", "/new/path2"}}, "/TmP/path/file.go", "/TmP/path/file.go"},
|
||||
}
|
||||
casesFreebsd := []tCase{
|
||||
// Should be case-sensitive
|
||||
{[]tRule{{"/tmp/path", "/new/path2"}}, "/TmP/path/file.go", "/TmP/path/file.go"},
|
||||
}
|
||||
casesDarwin := []tCase{
|
||||
// Can be either case-sensitive or case-insensitive depending on
|
||||
// filesystem settings, we always treat it as case-sensitive.
|
||||
{[]tRule{{"/tmp/path", "/new/path2"}}, "/TmP/PaTh/file.go", "/TmP/PaTh/file.go"},
|
||||
}
|
||||
casesWindows := []tCase{
|
||||
// Should not depend on separator at the end of rule path
|
||||
{[]tRule{{`c:\tmp\path`, `d:\new\path2`}}, `c:\tmp\path\file.go`, `d:\new\path2\file.go`},
|
||||
{[]tRule{{`c:\tmp\path\`, `d:\new\path2\`}}, `c:\tmp\path\file.go`, `d:\new\path2\file.go`},
|
||||
{[]tRule{{`c:\tmp\path`, `d:\new\path2\`}}, `c:\tmp\path\file.go`, `d:\new\path2\file.go`},
|
||||
{[]tRule{{`c:\tmp\path\`, `d:\new\path2`}}, `c:\tmp\path\file.go`, `d:\new\path2\file.go`},
|
||||
// Should apply to directory prefixes
|
||||
{[]tRule{{`c:\tmp\path`, `d:\new\path2`}}, `c:\tmp\path-2\file.go`, `c:\tmp\path-2\file.go`},
|
||||
// Should apply to exact matches
|
||||
{[]tRule{{`c:\tmp\path\file.go`, `d:\new\path2\file2.go`}}, `c:\tmp\path\file.go`, `d:\new\path2\file2.go`},
|
||||
// Should be case-insensitive
|
||||
{[]tRule{{`c:\tmp\path`, `d:\new\path2`}}, `C:\TmP\PaTh\file.go`, `d:\new\path2\file.go`},
|
||||
}
|
||||
|
||||
if runtime.GOOS == "windows" {
|
||||
return casesWindows
|
||||
}
|
||||
if runtime.GOOS == "darwin" {
|
||||
return append(casesUnix, casesDarwin...)
|
||||
}
|
||||
if runtime.GOOS == "linux" {
|
||||
return append(casesUnix, casesLinux...)
|
||||
}
|
||||
if runtime.GOOS == "freebsd" {
|
||||
return append(casesUnix, casesFreebsd...)
|
||||
}
|
||||
return casesUnix
|
||||
}
|
||||
|
||||
func TestSubstitutePath(t *testing.T) {
|
||||
for _, c := range platformCases() {
|
||||
var subRules config.SubstitutePathRules
|
||||
for _, r := range c.rules {
|
||||
subRules = append(subRules, config.SubstitutePathRule{From: r.from, To: r.to})
|
||||
}
|
||||
res := New(nil, &config.Config{SubstitutePath: subRules}).substitutePath(c.path)
|
||||
if c.res != res {
|
||||
t.Errorf("terminal.SubstitutePath(%q) => %q, want %q", c.path, res, c.res)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsErrProcessExited(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
|
@ -197,6 +197,12 @@ type Client interface {
|
||||
// If cont is true a continue command will be sent instead.
|
||||
Disconnect(cont bool) error
|
||||
|
||||
// SetDebugInfoDirectories sets directories used to search for debug symbols
|
||||
SetDebugInfoDirectories([]string) error
|
||||
|
||||
// GetDebugInfoDirectories returns the list of directories used to search for debug symbols
|
||||
GetDebugInfoDirectories() ([]string, error)
|
||||
|
||||
// CallAPI allows calling an arbitrary rpc method (used by starlark bindings)
|
||||
CallAPI(method string, args, reply interface{}) error
|
||||
}
|
||||
|
@ -47,20 +47,22 @@ Type "help" followed by the name of a command for more information about it.`
|
||||
|
||||
dlv config -list
|
||||
|
||||
Show all configuration parameters.
|
||||
Show all configuration parameters.
|
||||
|
||||
config -list <parameter>
|
||||
dlv config -list <parameter>
|
||||
|
||||
Show value of a configuration parameter.
|
||||
Show value of a configuration parameter.
|
||||
|
||||
dlv config <parameter> <value>
|
||||
|
||||
Changes the value of a configuration parameter.
|
||||
Changes the value of a configuration parameter.
|
||||
|
||||
dlv config substitutePath <from> <to>
|
||||
dlv config substitutePath <from>
|
||||
dlv config substitutePath -clear
|
||||
|
||||
Adds or removes a path substitution rule.`
|
||||
Adds or removes a path substitution rule. If -clear is used all substitutePath rules are removed.
|
||||
See also Documentation/cli/substitutepath.md.`
|
||||
msgSources = `Print list of source files.
|
||||
|
||||
dlv sources [<regex>]
|
||||
|
@ -3,6 +3,7 @@ package dap
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/go-delve/delve/pkg/config"
|
||||
)
|
||||
@ -44,8 +45,19 @@ func configureSet(sargs *launchAttachArgs, args string) (bool, string, error) {
|
||||
}
|
||||
|
||||
func configureSetSubstitutePath(args *launchAttachArgs, rest string) error {
|
||||
if strings.TrimSpace(rest) == "-clear" {
|
||||
args.substitutePathClientToServer = args.substitutePathClientToServer[:0]
|
||||
args.substitutePathServerToClient = args.substitutePathServerToClient[:0]
|
||||
return nil
|
||||
}
|
||||
argv := config.SplitQuotedFields(rest, '"')
|
||||
if len(argv) == 2 && argv[0] == "-clear" {
|
||||
argv = argv[1:]
|
||||
}
|
||||
switch len(argv) {
|
||||
case 0:
|
||||
// do nothing, let caller show the current list of substitute path rules
|
||||
return nil
|
||||
case 1: // delete substitute-path rule
|
||||
for i := range args.substitutePathClientToServer {
|
||||
if args.substitutePathClientToServer[i][0] == argv[0] {
|
||||
@ -69,7 +81,7 @@ func configureSetSubstitutePath(args *launchAttachArgs, rest string) error {
|
||||
args.substitutePathServerToClient = append(args.substitutePathServerToClient, [2]string{argv[1], argv[0]})
|
||||
|
||||
default:
|
||||
return fmt.Errorf("too many arguments to \"config substitute-path\"")
|
||||
return fmt.Errorf("too many arguments to \"config substitutePath\"")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -298,17 +298,6 @@ func TestConfigureSetSubstitutePath(t *testing.T) {
|
||||
wantErr: false,
|
||||
},
|
||||
// Test invalid input.
|
||||
{
|
||||
name: "error on empty args",
|
||||
args: args{
|
||||
args: &launchAttachArgs{
|
||||
substitutePathClientToServer: [][2]string{},
|
||||
substitutePathServerToClient: [][2]string{},
|
||||
},
|
||||
rest: " \n\r ",
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "error on delete nonexistent rule",
|
||||
args: args{
|
||||
|
@ -4024,6 +4024,9 @@ Type 'dlv help' followed by a command for full documentation.
|
||||
checkEval(t, got, msgConfig, noChildren)
|
||||
|
||||
// Test config.
|
||||
client.EvaluateRequest("dlv config", 1000, "repl")
|
||||
client.ExpectErrorResponse(t)
|
||||
|
||||
client.EvaluateRequest("dlv config -list", 1000, "repl")
|
||||
got = client.ExpectEvaluateResponse(t)
|
||||
checkEval(t, got, formatConfig(50, false, false, "", false, [][2]string{}), noChildren)
|
||||
|
@ -185,6 +185,7 @@ type LaunchAttachCommonConfig struct {
|
||||
// This setting is useful when working in a file system with symbolic links,
|
||||
// running remote debugging, or debugging an executable compiled externally.
|
||||
// The debug adapter will replace the local path with the remote path in all of the calls.
|
||||
// See also Documentation/cli/substitutepath.md.
|
||||
SubstitutePath []SubstitutePath `json:"substitutePath,omitempty"`
|
||||
}
|
||||
|
||||
|
@ -2273,6 +2273,21 @@ func (d *Debugger) FollowExecEnabled() bool {
|
||||
return d.target.FollowExecEnabled()
|
||||
}
|
||||
|
||||
func (d *Debugger) SetDebugInfoDirectories(v []string) {
|
||||
d.recordMutex.Lock()
|
||||
defer d.recordMutex.Unlock()
|
||||
it := proc.ValidTargets{Group: d.target}
|
||||
for it.Next() {
|
||||
it.BinInfo().DebugInfoDirectories = v
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Debugger) DebugInfoDirectories() []string {
|
||||
d.recordMutex.Lock()
|
||||
defer d.recordMutex.Unlock()
|
||||
return d.target.Selected.BinInfo().DebugInfoDirectories
|
||||
}
|
||||
|
||||
func go11DecodeErrorCheck(err error) error {
|
||||
if _, isdecodeerr := err.(dwarf.DecodeError); !isdecodeerr {
|
||||
return err
|
||||
|
@ -554,6 +554,16 @@ func (c *RPCClient) FollowExecEnabled() bool {
|
||||
return out.Enabled
|
||||
}
|
||||
|
||||
func (c *RPCClient) SetDebugInfoDirectories(v []string) error {
|
||||
return c.call("DebugInfoDirectories", DebugInfoDirectoriesIn{Set: true, List: v}, &DebugInfoDirectoriesOut{})
|
||||
}
|
||||
|
||||
func (c *RPCClient) GetDebugInfoDirectories() ([]string, error) {
|
||||
out := &DebugInfoDirectoriesOut{}
|
||||
err := c.call("DebugInfoDirectories", DebugInfoDirectoriesIn{Set: false, List: nil}, out)
|
||||
return out.List, err
|
||||
}
|
||||
|
||||
func (c *RPCClient) call(method string, args, reply interface{}) error {
|
||||
return c.client.Call("RPCServer."+method, args, reply)
|
||||
}
|
||||
|
@ -1079,3 +1079,20 @@ func (s *RPCServer) FollowExecEnabled(arg FollowExecEnabledIn, out *FollowExecEn
|
||||
out.Enabled = s.debugger.FollowExecEnabled()
|
||||
return nil
|
||||
}
|
||||
|
||||
type DebugInfoDirectoriesIn struct {
|
||||
Set bool
|
||||
List []string
|
||||
}
|
||||
|
||||
type DebugInfoDirectoriesOut struct {
|
||||
List []string
|
||||
}
|
||||
|
||||
func (s *RPCServer) DebugInfoDirectories(arg DebugInfoDirectoriesIn, out *DebugInfoDirectoriesOut) error {
|
||||
if arg.Set {
|
||||
s.debugger.SetDebugInfoDirectories(arg.List)
|
||||
}
|
||||
out.List = s.debugger.DebugInfoDirectories()
|
||||
return nil
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user