From e072a6490336ccb6ca0deeb6c065afa9339b678f Mon Sep 17 00:00:00 2001 From: Hyang-Ah Hana Kim Date: Mon, 18 Sep 2023 11:59:58 -0400 Subject: [PATCH] pkg/terminal: add 'packages' command (#3499) This command lists the packages included in the debugee. The implementation utilizes "ListPackagesBuildInfo" RPC. In order to support server-side filtering like `sources` and other commands, expanded the ListPackagesBuildInfo RPC to take an optional filter field. --- Documentation/cli/README.md | 9 ++++++++ Documentation/cli/starlark.md | 2 +- pkg/terminal/command.go | 17 ++++++++++++++ pkg/terminal/command_test.go | 28 +++++++++++++++++++++++ pkg/terminal/starbind/starlark_mapping.go | 10 +++++++- service/client.go | 2 ++ service/rpc2/client.go | 6 +++++ service/rpc2/server.go | 17 ++++++++++++-- 8 files changed, 87 insertions(+), 4 deletions(-) diff --git a/Documentation/cli/README.md b/Documentation/cli/README.md index 2754e69551..a288dd1e3e 100644 --- a/Documentation/cli/README.md +++ b/Documentation/cli/README.md @@ -89,6 +89,7 @@ Command | Description [help](#help) | Prints the help message. [libraries](#libraries) | List loaded dynamic libraries [list](#list) | Show source code. +[packages](#packages) | Print list of packages. [source](#source) | Executes a file containing a list of delve commands [sources](#sources) | Print list of source files. [target](#target) | Manages child process debugging. @@ -539,6 +540,14 @@ The command 'on cond ' is equivalent to 'cond ] + +If regex is specified only the packages matching it will be returned. + + ## print Evaluate an expression. diff --git a/Documentation/cli/starlark.md b/Documentation/cli/starlark.md index da0b3d3a26..7546961d2f 100644 --- a/Documentation/cli/starlark.md +++ b/Documentation/cli/starlark.md @@ -54,7 +54,7 @@ functions(Filter) | Equivalent to API call [ListFunctions](https://godoc.org/git goroutines(Start, Count, Filters, GoroutineGroupingOptions, EvalScope) | Equivalent to API call [ListGoroutines](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.ListGoroutines) local_vars(Scope, Cfg) | Equivalent to API call [ListLocalVars](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.ListLocalVars) package_vars(Filter, Cfg) | Equivalent to API call [ListPackageVars](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.ListPackageVars) -packages_build_info(IncludeFiles) | Equivalent to API call [ListPackagesBuildInfo](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.ListPackagesBuildInfo) +packages_build_info(IncludeFiles, Filter) | Equivalent to API call [ListPackagesBuildInfo](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.ListPackagesBuildInfo) registers(ThreadID, IncludeFp, Scope) | Equivalent to API call [ListRegisters](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.ListRegisters) sources(Filter) | Equivalent to API call [ListSources](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.ListSources) targets() | Equivalent to API call [ListTargets](https://godoc.org/github.com/go-delve/delve/service/rpc2#RPCServer.ListTargets) diff --git a/pkg/terminal/command.go b/pkg/terminal/command.go index 886eb27a36..abe6a86765 100644 --- a/pkg/terminal/command.go +++ b/pkg/terminal/command.go @@ -356,6 +356,11 @@ If regex is specified only the functions matching it will be returned.`}, types [] If regex is specified only the types matching it will be returned.`}, + {aliases: []string{"packages"}, cmdFn: packages, helpMsg: `Print list of packages. + + packages [] + +If regex is specified only the packages matching it will be returned.`}, {aliases: []string{"args"}, allowedPrefixes: onPrefix | deferredPrefix, group: dataCmds, cmdFn: args, helpMsg: `Print function arguments. [goroutine ] [frame ] args [-v] [] @@ -2224,6 +2229,18 @@ func sources(t *Term, ctx callContext, args string) error { return t.printSortedStrings(t.client.ListSources(args)) } +func packages(t *Term, ctx callContext, args string) error { + info, err := t.client.ListPackagesBuildInfo(args, false) + if err != nil { + return err + } + pkgs := make([]string, 0, len(info)) + for _, i := range info { + pkgs = append(pkgs, i.ImportPath) + } + return t.printSortedStrings(pkgs, nil) +} + func funcs(t *Term, ctx callContext, args string) error { return t.printSortedStrings(t.client.ListFunctions(args)) } diff --git a/pkg/terminal/command_test.go b/pkg/terminal/command_test.go index 403c9e8da9..15764fb300 100644 --- a/pkg/terminal/command_test.go +++ b/pkg/terminal/command_test.go @@ -1445,3 +1445,31 @@ func TestRestartBreakpoints(t *testing.T) { } }) } + +func TestListPackages(t *testing.T) { + test.AllowRecording(t) + withTestTerminal("goroutinestackprog", t, func(term *FakeTerminal) { + out := term.MustExec("packages") + t.Logf("> packages\n%s", out) + seen := map[string]bool{} + for _, p := range strings.Split(strings.TrimSpace(out), "\n") { + seen[p] = true + } + if !seen["main"] || !seen["runtime"] { + t.Error("output omits 'main' and 'runtime'") + } + + out = term.MustExec("packages runtime") + t.Logf("> packages runtime\n%s", out) + + for _, p := range strings.Split(strings.TrimSpace(out), "\n") { + if !strings.Contains(p, "runtime") { + t.Errorf("output includes unexpected %q", p) + } + seen[p] = true + } + if !seen["runtime"] { + t.Error("output omits 'runtime'") + } + }) +} diff --git a/pkg/terminal/starbind/starlark_mapping.go b/pkg/terminal/starbind/starlark_mapping.go index 967e1bf94c..35f1f0751f 100644 --- a/pkg/terminal/starbind/starlark_mapping.go +++ b/pkg/terminal/starbind/starlark_mapping.go @@ -1275,11 +1275,19 @@ func (env *Env) starlarkPredeclare() (starlark.StringDict, map[string]string) { return starlark.None, decorateError(thread, err) } } + if len(args) > 1 && args[1] != starlark.None { + err := unmarshalStarlarkValue(args[1], &rpcArgs.Filter, "Filter") + if err != nil { + return starlark.None, decorateError(thread, err) + } + } for _, kv := range kwargs { var err error switch kv[0].(starlark.String) { case "IncludeFiles": err = unmarshalStarlarkValue(kv[1], &rpcArgs.IncludeFiles, "IncludeFiles") + case "Filter": + err = unmarshalStarlarkValue(kv[1], &rpcArgs.Filter, "Filter") default: err = fmt.Errorf("unknown argument %q", kv[0]) } @@ -1293,7 +1301,7 @@ func (env *Env) starlarkPredeclare() (starlark.StringDict, map[string]string) { } return env.interfaceToStarlarkValue(rpcRet), nil }) - doc["packages_build_info"] = "builtin packages_build_info(IncludeFiles)\n\npackages_build_info returns the list of packages used by the program along with\nthe directory where each package was compiled and optionally the list of\nfiles constituting the package.\nNote that the directory path is a best guess and may be wrong is a tool\nother than cmd/go is used to perform the build." + doc["packages_build_info"] = "builtin packages_build_info(IncludeFiles, Filter)\n\npackages_build_info returns the list of packages used by the program along with\nthe directory where each package was compiled and optionally the list of\nfiles constituting the package.\nNote that the directory path is a best guess and may be wrong is a tool\nother than cmd/go is used to perform the build." r["registers"] = starlark.NewBuiltin("registers", 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) diff --git a/service/client.go b/service/client.go index 6e42224f0e..a76d0a1d2e 100644 --- a/service/client.go +++ b/service/client.go @@ -108,6 +108,8 @@ type Client interface { ListFunctions(filter string) ([]string, error) // ListTypes lists all types in the process matching filter. ListTypes(filter string) ([]string, error) + // ListPackagesBuildInfo lists all packages in the process matching filter. + ListPackagesBuildInfo(filter string, includeFiles bool) ([]api.PackageBuildInfo, error) // ListLocalVariables lists all local variables in scope. ListLocalVariables(scope api.EvalScope, cfg api.LoadConfig) ([]api.Variable, error) // ListFunctionArgs lists all arguments to the current function. diff --git a/service/rpc2/client.go b/service/rpc2/client.go index 22f88382f7..085ab04389 100644 --- a/service/rpc2/client.go +++ b/service/rpc2/client.go @@ -357,6 +357,12 @@ func (c *RPCClient) ListPackageVariables(filter string, cfg api.LoadConfig) ([]a return out.Variables, err } +func (c *RPCClient) ListPackagesBuildInfo(filter string, includeFiles bool) ([]api.PackageBuildInfo, error) { + var out ListPackagesBuildInfoOut + err := c.call("ListPackagesBuildInfo", ListPackagesBuildInfoIn{Filter: filter, IncludeFiles: includeFiles}, &out) + return out.List, err +} + func (c *RPCClient) ListLocalVariables(scope api.EvalScope, cfg api.LoadConfig) ([]api.Variable, error) { var out ListLocalVarsOut err := c.call("ListLocalVars", ListLocalVarsIn{scope, cfg}, &out) diff --git a/service/rpc2/server.go b/service/rpc2/server.go index ab18d23202..dbc39b48ec 100644 --- a/service/rpc2/server.go +++ b/service/rpc2/server.go @@ -3,6 +3,7 @@ package rpc2 import ( "errors" "fmt" + "regexp" "sort" "time" @@ -912,12 +913,13 @@ func (s *RPCServer) ListDynamicLibraries(in ListDynamicLibrariesIn, out *ListDyn return nil } -// ListPackagesBuildInfoIn holds the arguments of ListPackages. +// ListPackagesBuildInfoIn holds the arguments of ListPackagesBuildInfo. type ListPackagesBuildInfoIn struct { IncludeFiles bool + Filter string // if not empty, returns only packages matching the regexp. } -// ListPackagesBuildInfoOut holds the return values of ListPackages. +// ListPackagesBuildInfoOut holds the return values of ListPackagesBuildInfo. type ListPackagesBuildInfoOut struct { List []api.PackageBuildInfo } @@ -928,9 +930,20 @@ type ListPackagesBuildInfoOut struct { // Note that the directory path is a best guess and may be wrong is a tool // other than cmd/go is used to perform the build. func (s *RPCServer) ListPackagesBuildInfo(in ListPackagesBuildInfoIn, out *ListPackagesBuildInfoOut) error { + var pattern *regexp.Regexp + if in.Filter != "" { + p, err := regexp.Compile(in.Filter) + if err != nil { + return fmt.Errorf("invalid Filter pattern: %v", err) + } + pattern = p + } pkgs := s.debugger.ListPackagesBuildInfo(in.IncludeFiles) out.List = make([]api.PackageBuildInfo, 0, len(pkgs)) for _, pkg := range pkgs { + if pattern != nil && !pattern.MatchString(pkg.ImportPath) { + continue + } var files []string if len(pkg.Files) > 0 {