From aa812d122cb1a6419df00aa876bd0a98b7b85340 Mon Sep 17 00:00:00 2001 From: Colin Cross Date: Wed, 19 Jun 2019 13:33:24 -0700 Subject: [PATCH] Allow debugging with SOONG_DELVE= Allow running Soong in a headless delve debugger by passing SOONG_DELVE= in the environment. Bug: 80165685 Test: SOONG_DELVE=:1234 m nothing Change-Id: Icfc893c8a8354a9bbc99112d9c83259cb41906d1 --- README.md | 13 ++++++++++ android/env.go | 11 ++++++++ cmd/soong_build/main.go | 53 ++++++++++++++++++++++++++++++++++++++- ui/build/paths/config.go | 1 + ui/build/sandbox_linux.go | 4 +++ 5 files changed, 81 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index ebbe8bc50..531ef4c6a 100644 --- a/README.md +++ b/README.md @@ -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 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=` in the environment. +For examle: +```bash +SOONG_DELVE=:1234 m nothing +``` +and then in another terminal: +``` +dlv connect :1234 +``` + ## Contact Email android-building@googlegroups.com (external) for any questions, or see diff --git a/android/env.go b/android/env.go index 469dfffed..d9f2db2f3 100644 --- a/android/env.go +++ b/android/env.go @@ -16,6 +16,7 @@ package android import ( "os" + "os/exec" "strings" "android/soong/env" @@ -29,8 +30,16 @@ import ( // a manifest regeneration. var originalEnv map[string]string +var SoongDelveListen string +var SoongDelvePath string 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) for _, env := range os.Environ() { idx := strings.IndexRune(env, '=') @@ -38,6 +47,8 @@ func init() { 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() } diff --git a/cmd/soong_build/main.go b/cmd/soong_build/main.go index 41c7d46ce..30381e088 100644 --- a/cmd/soong_build/main.go +++ b/cmd/soong_build/main.go @@ -18,7 +18,12 @@ import ( "flag" "fmt" "os" + "os/exec" "path/filepath" + "strconv" + "strings" + "syscall" + "time" "github.com/google/blueprint/bootstrap" @@ -50,6 +55,42 @@ func newNameResolver(config android.Config) *android.NameResolver { } 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() // The top-level Blueprints file is passed as the first argument. @@ -72,7 +113,17 @@ func main() { 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 err := writeDocs(ctx, docFile); err != nil { diff --git a/ui/build/paths/config.go b/ui/build/paths/config.go index e2c504338..a4be2ac47 100644 --- a/ui/build/paths/config.go +++ b/ui/build/paths/config.go @@ -81,6 +81,7 @@ var Configuration = map[string]PathConfig{ "bzip2": Allowed, "dd": Allowed, "diff": Allowed, + "dlv": Allowed, "egrep": Allowed, "expr": Allowed, "find": Allowed, diff --git a/ui/build/sandbox_linux.go b/ui/build/sandbox_linux.go index b94db7448..11ff6677c 100644 --- a/ui/build/sandbox_linux.go +++ b/ui/build/sandbox_linux.go @@ -162,6 +162,10 @@ func (c *Cmd) wrapSandbox() { c.ctx.Printf("AllowBuildBrokenUsesNetwork: %v", c.Sandbox.AllowBuildBrokenUsesNetwork) c.ctx.Printf("BuildBrokenUsesNetwork: %v", c.config.BuildBrokenUsesNetwork()) 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