Make it possible to debug soong_ui.

This works by setting the SOONG_UI_DELVE= environment variable to the
port on which soong_ui should accept a Delve connection on.

This is achieved by reusing the Delve execution logic between soong_ui
and soong_build.

Test: Manual.
Change-Id: Id2c1d4b6faac1a4a3918c91030ce2239f7daf54f
This commit is contained in:
Lukacs T. Berki 2021-03-02 10:09:41 +01:00
parent 6d3e726887
commit 7d613bfe2c
6 changed files with 69 additions and 63 deletions

View file

@ -15,11 +15,6 @@
package android package android
import ( import (
"fmt"
"os"
"strings"
"syscall"
"android/soong/shared" "android/soong/shared"
) )
@ -31,9 +26,6 @@ import (
// a manifest regeneration. // a manifest regeneration.
var originalEnv map[string]string var originalEnv map[string]string
var soongDelveListen string
var soongDelvePath string
var isDebugging bool
func InitEnvironment(envFile string) { func InitEnvironment(envFile string) {
var err error var err error
@ -41,51 +33,6 @@ func InitEnvironment(envFile string) {
if err != nil { if err != nil {
panic(err) panic(err)
} }
soongDelveListen = originalEnv["SOONG_DELVE"]
soongDelvePath = originalEnv["SOONG_DELVE_PATH"]
}
// Returns whether the current process is running under Delve due to
// ReexecWithDelveMaybe().
func IsDebugging() bool {
return isDebugging
}
func ReexecWithDelveMaybe() {
isDebugging = os.Getenv("SOONG_DELVE_REEXECUTED") == "true"
if isDebugging || soongDelveListen == "" {
return
}
if soongDelvePath == "" {
fmt.Fprintln(os.Stderr, "SOONG_DELVE is set but failed to find dlv")
os.Exit(1)
}
soongDelveEnv := []string{}
for _, env := range os.Environ() {
idx := strings.IndexRune(env, '=')
if idx != -1 {
soongDelveEnv = append(soongDelveEnv, env)
}
}
soongDelveEnv = append(soongDelveEnv, "SOONG_DELVE_REEXECUTED=true")
dlvArgv := []string{
soongDelvePath,
"--listen=:" + soongDelveListen,
"--headless=true",
"--api-version=2",
"exec",
os.Args[0],
"--",
}
dlvArgv = append(dlvArgv, os.Args[1:]...)
os.Chdir(absSrcDir)
syscall.Exec(soongDelvePath, dlvArgv, soongDelveEnv)
fmt.Fprintln(os.Stderr, "exec() failed while trying to reexec with Delve")
os.Exit(1)
} }
func EnvSingleton() Singleton { func EnvSingleton() Singleton {

View file

@ -66,7 +66,9 @@ func (c *docsSingleton) GenerateBuildActions(ctx SingletonContext) {
soongDocs := ctx.Rule(pctx, "soongDocs", soongDocs := ctx.Rule(pctx, "soongDocs",
blueprint.RuleParams{ blueprint.RuleParams{
Command: fmt.Sprintf("rm -f ${outDir}/* && %s --soong_docs %s %s", Command: fmt.Sprintf("rm -f ${outDir}/* && %s --soong_docs %s %s",
primaryBuilder.String(), docsFile.String(), strings.Join(os.Args[1:], " ")), primaryBuilder.String(),
docsFile.String(),
"\""+strings.Join(os.Args[1:], "\" \"")+"\""),
CommandDeps: []string{primaryBuilder.String()}, CommandDeps: []string{primaryBuilder.String()},
Description: fmt.Sprintf("%s docs $out", primaryBuilder.Base()), Description: fmt.Sprintf("%s docs $out", primaryBuilder.Base()),
}, },

View file

@ -33,11 +33,15 @@ var (
outDir string outDir string
docFile string docFile string
bazelQueryViewDir string bazelQueryViewDir string
delveListen string
delvePath string
) )
func init() { func init() {
flag.StringVar(&topDir, "top", "", "Top directory of the Android source tree") flag.StringVar(&topDir, "top", "", "Top directory of the Android source tree")
flag.StringVar(&outDir, "out", "", "Soong output directory (usually $TOP/out/soong)") flag.StringVar(&outDir, "out", "", "Soong output directory (usually $TOP/out/soong)")
flag.StringVar(&delveListen, "delve_listen", "", "Delve port to listen on for debugging")
flag.StringVar(&delvePath, "delve_path", "", "Path to Delve. Only used if --delve_listen is set")
flag.StringVar(&docFile, "soong_docs", "", "build documentation file to output") flag.StringVar(&docFile, "soong_docs", "", "build documentation file to output")
flag.StringVar(&bazelQueryViewDir, "bazel_queryview_dir", "", "path to the bazel queryview directory") flag.StringVar(&bazelQueryViewDir, "bazel_queryview_dir", "", "path to the bazel queryview directory")
} }
@ -87,9 +91,9 @@ func newConfig(srcDir string) android.Config {
func main() { func main() {
flag.Parse() flag.Parse()
shared.ReexecWithDelveMaybe(delveListen, delvePath)
android.InitSandbox(topDir) android.InitSandbox(topDir)
android.InitEnvironment(shared.JoinPath(topDir, outDir, "soong.environment.available")) android.InitEnvironment(shared.JoinPath(topDir, outDir, "soong.environment.available"))
android.ReexecWithDelveMaybe()
// The top-level Blueprints file is passed as the first argument. // The top-level Blueprints file is passed as the first argument.
srcDir := filepath.Dir(flag.Arg(0)) srcDir := filepath.Dir(flag.Arg(0))
@ -101,9 +105,7 @@ func main() {
// user sets SOONG_DELVE the first time. // user sets SOONG_DELVE the first time.
configuration.Getenv("SOONG_DELVE") configuration.Getenv("SOONG_DELVE")
configuration.Getenv("SOONG_DELVE_PATH") configuration.Getenv("SOONG_DELVE_PATH")
// Read the SOONG_DELVE again through configuration so that there is a dependency on the environment variable if shared.IsDebugging() {
// 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 // Add a non-existent file to the dependencies so that soong_build will rerun when the debugger is
// enabled even if it completed successfully. // enabled even if it completed successfully.
extraNinjaDeps = append(extraNinjaDeps, filepath.Join(configuration.BuildDir(), "always_rerun_for_delve")) extraNinjaDeps = append(extraNinjaDeps, filepath.Join(configuration.BuildDir(), "always_rerun_for_delve"))

View file

@ -25,6 +25,7 @@ import (
"strings" "strings"
"time" "time"
"android/soong/shared"
"android/soong/ui/build" "android/soong/ui/build"
"android/soong/ui/logger" "android/soong/ui/logger"
"android/soong/ui/metrics" "android/soong/ui/metrics"
@ -118,6 +119,8 @@ func inList(s string, list []string) bool {
// Command is the type of soong_ui execution. Only one type of // Command is the type of soong_ui execution. Only one type of
// execution is specified. The args are specific to the command. // execution is specified. The args are specific to the command.
func main() { func main() {
shared.ReexecWithDelveMaybe(os.Getenv("SOONG_UI_DELVE"), shared.ResolveDelveBinary())
buildStarted := time.Now() buildStarted := time.Now()
c, args, err := getCommand(os.Args) c, args, err := getCommand(os.Args)

View file

@ -1,8 +1,15 @@
package shared package shared
import ( import (
"fmt"
"os" "os"
"os/exec" "os/exec"
"strings"
"syscall"
)
var (
isDebugging bool
) )
// Finds the Delve binary to use. Either uses the SOONG_DELVE_PATH environment // Finds the Delve binary to use. Either uses the SOONG_DELVE_PATH environment
@ -15,3 +22,48 @@ func ResolveDelveBinary() string {
return result return result
} }
// Returns whether the current process is running under Delve due to
// ReexecWithDelveMaybe().
func IsDebugging() bool {
return isDebugging
}
// Re-executes the binary in question under the control of Delve when
// delveListen is not the empty string. delvePath gives the path to the Delve.
func ReexecWithDelveMaybe(delveListen, delvePath string) {
isDebugging = os.Getenv("SOONG_DELVE_REEXECUTED") == "true"
if isDebugging || delveListen == "" {
return
}
if delvePath == "" {
fmt.Fprintln(os.Stderr, "Delve debugging requested but failed to find dlv")
os.Exit(1)
}
soongDelveEnv := []string{}
for _, env := range os.Environ() {
idx := strings.IndexRune(env, '=')
if idx != -1 {
soongDelveEnv = append(soongDelveEnv, env)
}
}
soongDelveEnv = append(soongDelveEnv, "SOONG_DELVE_REEXECUTED=true")
dlvArgv := []string{
delvePath,
"--listen=:" + delveListen,
"--headless=true",
"--api-version=2",
"exec",
os.Args[0],
"--",
}
dlvArgv = append(dlvArgv, os.Args[1:]...)
syscall.Exec(delvePath, dlvArgv, soongDelveEnv)
fmt.Fprintln(os.Stderr, "exec() failed while trying to reexec with Delve")
os.Exit(1)
}

View file

@ -105,11 +105,6 @@ func runSoong(ctx Context, config Config) {
soongBuildEnv.Set("BAZEL_WORKSPACE", absPath(ctx, ".")) soongBuildEnv.Set("BAZEL_WORKSPACE", absPath(ctx, "."))
soongBuildEnv.Set("BAZEL_METRICS_DIR", config.BazelMetricsDir()) soongBuildEnv.Set("BAZEL_METRICS_DIR", config.BazelMetricsDir())
if os.Getenv("SOONG_DELVE") != "" {
// SOONG_DELVE is already in cmd.Environment
soongBuildEnv.Set("SOONG_DELVE_PATH", shared.ResolveDelveBinary())
}
err := writeEnvironmentFile(ctx, envFile, soongBuildEnv.AsMap()) err := writeEnvironmentFile(ctx, envFile, soongBuildEnv.AsMap())
if err != nil { if err != nil {
ctx.Fatalf("failed to write environment file %s: %s", envFile, err) ctx.Fatalf("failed to write environment file %s: %s", envFile, err)
@ -176,6 +171,11 @@ func runSoong(ctx Context, config Config) {
"-f", filepath.Join(config.SoongOutDir(), file)) "-f", filepath.Join(config.SoongOutDir(), file))
cmd.Environment.Set("SOONG_OUTDIR", config.SoongOutDir()) cmd.Environment.Set("SOONG_OUTDIR", config.SoongOutDir())
if os.Getenv("SOONG_DELVE") != "" {
// SOONG_DELVE is already in cmd.Environment
cmd.Environment.Set("SOONG_DELVE_PATH", shared.ResolveDelveBinary())
}
cmd.Sandbox = soongSandbox cmd.Sandbox = soongSandbox
cmd.RunAndStreamOrFatal() cmd.RunAndStreamOrFatal()
} }