Allow debugging with SOONG_DELVE=<listen addr>

Allow running Soong in a headless delve debugger by passing
SOONG_DELVE=<listen addr> in the environment.

Bug: 80165685
Test: SOONG_DELVE=:1234 m nothing
Change-Id: Icfc893c8a8354a9bbc99112d9c83259cb41906d1
This commit is contained in:
Colin Cross 2019-06-19 13:33:24 -07:00
parent 7b97ecd1f5
commit aa812d122c
5 changed files with 81 additions and 1 deletions

View File

@ -337,6 +337,19 @@ build/soong/scripts/setup_go_workspace_for_soong.sh
This will bind mount the Soong source directories into the directory in the layout expected by This will bind mount the Soong source directories into the directory in the layout expected by
the IDE. the IDE.
### Running Soong in a debugger
To run the soong_build process in a debugger, install `dlv` and then start the build with
`SOONG_DELVE=<listen addr>` in the environment.
For examle:
```bash
SOONG_DELVE=:1234 m nothing
```
and then in another terminal:
```
dlv connect :1234
```
## Contact ## Contact
Email android-building@googlegroups.com (external) for any questions, or see Email android-building@googlegroups.com (external) for any questions, or see

View File

@ -16,6 +16,7 @@ package android
import ( import (
"os" "os"
"os/exec"
"strings" "strings"
"android/soong/env" "android/soong/env"
@ -29,8 +30,16 @@ import (
// a manifest regeneration. // a manifest regeneration.
var originalEnv map[string]string var originalEnv map[string]string
var SoongDelveListen string
var SoongDelvePath string
func init() { func init() {
// Delve support needs to read this environment variable very early, before NewConfig has created a way to
// access originalEnv with dependencies. Store the value where soong_build can find it, it will manually
// ensure the dependencies are created.
SoongDelveListen = os.Getenv("SOONG_DELVE")
SoongDelvePath, _ = exec.LookPath("dlv")
originalEnv = make(map[string]string) originalEnv = make(map[string]string)
for _, env := range os.Environ() { for _, env := range os.Environ() {
idx := strings.IndexRune(env, '=') idx := strings.IndexRune(env, '=')
@ -38,6 +47,8 @@ func init() {
originalEnv[env[:idx]] = env[idx+1:] originalEnv[env[:idx]] = env[idx+1:]
} }
} }
// Clear the environment to prevent use of os.Getenv(), which would not provide dependencies on environment
// variable values. The environment is available through ctx.Config().Getenv, ctx.Config().IsEnvTrue, etc.
os.Clearenv() os.Clearenv()
} }

View File

@ -18,7 +18,12 @@ import (
"flag" "flag"
"fmt" "fmt"
"os" "os"
"os/exec"
"path/filepath" "path/filepath"
"strconv"
"strings"
"syscall"
"time"
"github.com/google/blueprint/bootstrap" "github.com/google/blueprint/bootstrap"
@ -50,6 +55,42 @@ func newNameResolver(config android.Config) *android.NameResolver {
} }
func main() { func main() {
if android.SoongDelveListen != "" {
if android.SoongDelvePath == "" {
fmt.Fprintln(os.Stderr, "SOONG_DELVE is set but failed to find dlv")
os.Exit(1)
}
pid := strconv.Itoa(os.Getpid())
cmd := []string{android.SoongDelvePath,
"attach", pid,
"--headless",
"-l", android.SoongDelveListen,
"--api-version=2",
"--accept-multiclient",
"--log",
}
fmt.Println("Starting", strings.Join(cmd, " "))
dlv := exec.Command(cmd[0], cmd[1:]...)
dlv.Stdout = os.Stdout
dlv.Stderr = os.Stderr
dlv.Stdin = nil
// Put dlv into its own process group so we can kill it and the child process it starts.
dlv.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
err := dlv.Start()
if err != nil {
// Print the error starting dlv and continue.
fmt.Println(err)
} else {
// Kill the process group for dlv when soong_build exits.
defer syscall.Kill(-dlv.Process.Pid, syscall.SIGKILL)
// Wait to give dlv a chance to connect and pause the process.
time.Sleep(time.Second)
}
}
flag.Parse() flag.Parse()
// The top-level Blueprints file is passed as the first argument. // The top-level Blueprints file is passed as the first argument.
@ -72,7 +113,17 @@ func main() {
ctx.SetAllowMissingDependencies(configuration.AllowMissingDependencies()) ctx.SetAllowMissingDependencies(configuration.AllowMissingDependencies())
bootstrap.Main(ctx.Context, configuration, configuration.ConfigFileName, configuration.ProductVariablesFileName) extraNinjaDeps := []string{configuration.ConfigFileName, configuration.ProductVariablesFileName}
// Read the SOONG_DELVE again through configuration so that there is a dependency on the environment variable
// and soong_build will rerun when it is set for the first time.
if listen := configuration.Getenv("SOONG_DELVE"); listen != "" {
// Add a non-existent file to the dependencies so that soong_build will rerun when the debugger is
// enabled even if it completed successfully.
extraNinjaDeps = append(extraNinjaDeps, filepath.Join(configuration.BuildDir(), "always_rerun_for_delve"))
}
bootstrap.Main(ctx.Context, configuration, extraNinjaDeps...)
if docFile != "" { if docFile != "" {
if err := writeDocs(ctx, docFile); err != nil { if err := writeDocs(ctx, docFile); err != nil {

View File

@ -81,6 +81,7 @@ var Configuration = map[string]PathConfig{
"bzip2": Allowed, "bzip2": Allowed,
"dd": Allowed, "dd": Allowed,
"diff": Allowed, "diff": Allowed,
"dlv": Allowed,
"egrep": Allowed, "egrep": Allowed,
"expr": Allowed, "expr": Allowed,
"find": Allowed, "find": Allowed,

View File

@ -162,6 +162,10 @@ func (c *Cmd) wrapSandbox() {
c.ctx.Printf("AllowBuildBrokenUsesNetwork: %v", c.Sandbox.AllowBuildBrokenUsesNetwork) c.ctx.Printf("AllowBuildBrokenUsesNetwork: %v", c.Sandbox.AllowBuildBrokenUsesNetwork)
c.ctx.Printf("BuildBrokenUsesNetwork: %v", c.config.BuildBrokenUsesNetwork()) c.ctx.Printf("BuildBrokenUsesNetwork: %v", c.config.BuildBrokenUsesNetwork())
sandboxArgs = append(sandboxArgs, "-N") sandboxArgs = append(sandboxArgs, "-N")
} else if dlv, _ := c.config.Environment().Get("SOONG_DELVE"); dlv != "" {
// The debugger is enabled and soong_build will pause until a remote delve process connects, allow
// network connections.
sandboxArgs = append(sandboxArgs, "-N")
} }
// Stop nsjail from parsing arguments // Stop nsjail from parsing arguments