From e1191cec038b84618d9f080cc6ea0ec9a8117cbe Mon Sep 17 00:00:00 2001 From: xhd2015 Date: Sat, 1 Jun 2024 19:51:42 +0800 Subject: [PATCH] add options --options-from-file and cmd/debug-compile --- .gitignore | 4 +- cmd/debug-compile/main.go | 166 ++++++++++++ cmd/xgo/exec_tool/debug.go | 67 +++-- cmd/xgo/exec_tool/env.go | 8 + cmd/xgo/exec_tool/main.go | 72 +++++- cmd/xgo/main.go | 88 ++++++- cmd/xgo/option.go | 31 ++- cmd/xgo/runtime_gen/core/version.go | 4 +- cmd/xgo/test-explorer/debug.go | 25 +- cmd/xgo/version.go | 4 +- patch/ctxt/ctx.go | 30 +++ patch/ctxt/env.go | 19 ++ patch/ctxt/log.go | 19 ++ patch/ctxt/match.go | 39 ++- patch/func_name/name.go | 24 ++ patch/info/decl_info.go | 118 +++++++++ patch/match/match.go | 122 +++++++++ patch/match/pattern.go | 217 ++++++++++++++++ patch/syntax/rewrite.go | 2 + patch/syntax/syntax.go | 381 +++++++++++++--------------- patch/syntax/vars.go | 39 ++- patch/trap.go | 14 +- runtime/core/version.go | 4 +- support/debug/debug.go | 52 ++++ support/goinfo/list.go | 22 ++ 25 files changed, 1283 insertions(+), 288 deletions(-) create mode 100644 cmd/debug-compile/main.go create mode 100644 patch/ctxt/log.go create mode 100644 patch/info/decl_info.go create mode 100644 patch/match/match.go create mode 100644 patch/match/pattern.go create mode 100644 support/debug/debug.go create mode 100644 support/goinfo/list.go diff --git a/.gitignore b/.gitignore index b1e42d17..9e2fee3e 100644 --- a/.gitignore +++ b/.gitignore @@ -29,4 +29,6 @@ /covers cover*.out -/tmp \ No newline at end of file +/tmp + +debug-compile.json \ No newline at end of file diff --git a/cmd/debug-compile/main.go b/cmd/debug-compile/main.go new file mode 100644 index 00000000..4ab527fd --- /dev/null +++ b/cmd/debug-compile/main.go @@ -0,0 +1,166 @@ +package main + +import ( + "encoding/json" + "errors" + "fmt" + "io/ioutil" + "os" + "strings" + "time" + + "github.com/xhd2015/xgo/cmd/xgo/exec_tool" + "github.com/xhd2015/xgo/support/cmd" + "github.com/xhd2015/xgo/support/debug" + "github.com/xhd2015/xgo/support/netutil" +) + +// usage: +// go run -tags dev ./cmd/xgo --debug-compile ./src --> will generate a file called debug-compile.json +// go run -tags dev ./cmd/xgo build --build-compile --> will build the compiler with -gcflags and print it's path +// go run ./cmd/debug-compile --debug-with-dlv --> will read debug-compile.json, and start a debug server listen on localhost:2345 + +func main() { + args := os.Args[1:] + err := run(args) + if err != nil { + fmt.Fprintf(os.Stderr, "%v\n", err) + os.Exit(1) + } +} + +func run(args []string) error { + var compilerBinary string + n := len(args) + var projectDir string + + data, readErr := ioutil.ReadFile("debug-compile.json") + if readErr != nil { + if !errors.Is(readErr, os.ErrNotExist) { + return readErr + } + } + var debugCompiler *exec_tool.DebugCompile + if len(data) > 0 { + err := json.Unmarshal(data, &debugCompiler) + if err != nil { + return fmt.Errorf("parse debug-compile.json: %w", err) + } + } + + var extraFlags []string + var extraEnvs []string + var debugWithDlv bool + for i := 0; i < n; i++ { + arg := args[i] + if arg == "--project-dir" { + projectDir = args[i+1] + i++ + continue + } + if arg == "--env" { + extraEnvs = append(extraEnvs, args[i+1]) + i++ + continue + } + if arg == "--compiler" { + compilerBinary = args[i+1] + i++ + continue + } + if arg == "-cpuprofile" { + extraFlags = append(extraFlags, arg, args[i+1]) + i++ + continue + } + if arg == "-blockprofile" { + extraFlags = append(extraFlags, arg, args[i+1]) + i++ + continue + } + if arg == "-memprofile" || arg == "-memprofilerate" { + extraFlags = append(extraFlags, arg, args[i+1]) + i++ + continue + } + if arg == "-N" || arg == "-l" { + extraFlags = append(extraFlags, arg) + continue + } + if arg == "-c" { + extraFlags = append(extraFlags, arg, args[i+1]) + continue + } + if arg == "--debug-with-dlv" { + debugWithDlv = true + continue + } + } + if compilerBinary == "" { + compilerBinary = debugCompiler.Compiler + } + + runArgs := append(debugCompiler.Flags, extraFlags...) + runArgs = append(runArgs, debugCompiler.Files...) + + runEnvs := append(debugCompiler.Env, extraEnvs...) + + if debugWithDlv { + return dlvExecListen(projectDir, runEnvs, compilerBinary, runArgs) + } + + start := time.Now() + defer func() { + fmt.Printf("cost: %v\n", time.Since(start)) + }() + return cmd.Dir(projectDir).Env(runEnvs).Run(compilerBinary, runArgs...) +} + +// /Users/xhd2015/.xgo/go-instrument-dev/go1.21.7_Us_xh_in_go_096be049/compile + +func dlvExecListen(dir string, env []string, compilerBinary string, args []string) error { + var vscodeExtra []string + n := len(env) + for i := n - 1; i >= 0; i-- { + e := env[i] + if !strings.HasPrefix(e, "GOROOT=") { + continue + } + goroot := strings.TrimPrefix(e, "GOROOT=") + if goroot != "" { + vscodeExtra = append(vscodeExtra, + fmt.Sprintf(" NOTE: VSCode will map source files to workspace's goroot,"), + fmt.Sprintf(" To fix this, update .vscode/settings.json, set go.goroot to:"), + fmt.Sprintf(" %s", goroot), + fmt.Sprintf(" And set a breakpoint at:"), + fmt.Sprintf(" %s/src/cmd/compile/main.go", goroot), + ) + } + break + } + return netutil.ServePort(2345, true, 500*time.Millisecond, func(port int) { + prompt := debug.FormatDlvPromptOptions(port, &debug.FormatDlvOptions{ + VscodeExtra: vscodeExtra, + }) + fmt.Println(prompt) + }, func(port int) error { + // dlv exec --api-version=2 --listen=localhost:2345 --accept-multiclient --headless ./debug.bin + dlvArgs := []string{ + "--api-version=2", + fmt.Sprintf("--listen=localhost:%d", port), + "--check-go-version=false", + "--log=true", + // "--accept-multiclient", // exits when client exits + "--headless", "exec", + compilerBinary, + "--", + } + // dlvArgs = append(dlvArgs, compilerBin) + dlvArgs = append(dlvArgs, args...) + err := cmd.Dir(dir).Env(env).Run("dlv", dlvArgs...) + if err != nil { + return fmt.Errorf("dlv: %w", err) + } + return nil + }) +} diff --git a/cmd/xgo/exec_tool/debug.go b/cmd/xgo/exec_tool/debug.go index 9e1f7aba..ed6cf5ac 100644 --- a/cmd/xgo/exec_tool/debug.go +++ b/cmd/xgo/exec_tool/debug.go @@ -7,23 +7,54 @@ import ( "path/filepath" ) -func getDebugEnv(xgoCompilerEnableEnv string) map[string]string { - return map[string]string{ - "COMPILER_ALLOW_IR_REWRITE": "true", - "COMPILER_ALLOW_SYNTAX_REWRITE": "true", - "COMPILER_DEBUG_IR_REWRITE_FUNC": os.Getenv("COMPILER_DEBUG_IR_REWRITE_FUNC"), - "COMPILER_DEBUG_IR_DUMP_FUNCS": os.Getenv("COMPILER_DEBUG_IR_DUMP_FUNCS"), - XGO_DEBUG_DUMP_IR: os.Getenv(XGO_DEBUG_DUMP_IR), - XGO_DEBUG_DUMP_IR_FILE: os.Getenv(XGO_DEBUG_DUMP_IR_FILE), - XGO_DEBUG_DUMP_AST: os.Getenv(XGO_DEBUG_DUMP_AST), - XGO_DEBUG_DUMP_AST_FILE: os.Getenv(XGO_DEBUG_DUMP_AST_FILE), - "GOCACHE": os.Getenv("GOCACHE"), - XGO_MAIN_MODULE: os.Getenv(XGO_MAIN_MODULE), - XGO_COMPILE_PKG_DATA_DIR: os.Getenv(XGO_COMPILE_PKG_DATA_DIR), - XGO_STD_LIB_TRAP_DEFAULT_ALLOW: os.Getenv(XGO_STD_LIB_TRAP_DEFAULT_ALLOW), - "GOROOT": "../..", - "PATH": "../../bin:${env:PATH}", - "XGO_COMPILER_ENABLE": xgoCompilerEnableEnv, +type DebugCompile struct { + Package string `json:"package"` + Env []string `json:"env"` + Compiler string `json:"compiler"` + Flags []string `json:"flags"` + Files []string `json:"files"` +} + +func getDebugEnvMapping(xgoCompilerEnableEnv string) map[string]string { + envs := getDebugEnvList(xgoCompilerEnableEnv) + mapping := make(map[string]string, len(envs)) + for _, env := range envs { + mapping[env[0]] = env[1] + } + return mapping +} + +func getDebugEnv(xgoCompilerEnableEnv string) []string { + envs := getDebugEnvList(xgoCompilerEnableEnv) + joints := make([]string, 0, len(envs)) + for _, env := range envs { + joints = append(joints, env[0]+"="+env[1]) + } + return joints +} + +func getDebugEnvList(xgoCompilerEnableEnv string) [][2]string { + return [][2]string{ + {"XGO_COMPILER_ENABLE", xgoCompilerEnableEnv}, + {"COMPILER_ALLOW_IR_REWRITE", "true"}, + {"COMPILER_ALLOW_SYNTAX_REWRITE", "true"}, + {"COMPILER_DEBUG_IR_REWRITE_FUNC", os.Getenv("COMPILER_DEBUG_IR_REWRITE_FUNC")}, + {"COMPILER_DEBUG_IR_DUMP_FUNCS", os.Getenv("COMPILER_DEBUG_IR_DUMP_FUNCS")}, + {XGO_DEBUG_DUMP_IR, os.Getenv(XGO_DEBUG_DUMP_IR)}, + {XGO_DEBUG_DUMP_IR_FILE, os.Getenv(XGO_DEBUG_DUMP_IR_FILE)}, + {XGO_DEBUG_DUMP_AST, os.Getenv(XGO_DEBUG_DUMP_AST)}, + {XGO_DEBUG_DUMP_AST_FILE, os.Getenv(XGO_DEBUG_DUMP_AST_FILE)}, + {XGO_MAIN_MODULE, os.Getenv(XGO_MAIN_MODULE)}, + {XGO_COMPILE_PKG_DATA_DIR, os.Getenv(XGO_COMPILE_PKG_DATA_DIR)}, + {XGO_STD_LIB_TRAP_DEFAULT_ALLOW, os.Getenv(XGO_STD_LIB_TRAP_DEFAULT_ALLOW)}, + {XGO_DEBUG_COMPILE_PKG, os.Getenv(XGO_DEBUG_COMPILE_PKG)}, + {XGO_DEBUG_COMPILE_LOG_FILE, os.Getenv(XGO_DEBUG_COMPILE_LOG_FILE)}, + {XGO_COMPILER_OPTIONS_FILE, os.Getenv(XGO_COMPILER_OPTIONS_FILE)}, + {XGO_SRC_WD, os.Getenv(XGO_SRC_WD)}, + {"GOROOT", os.Getenv("GOROOT")}, + {"GOPATH", os.Getenv("GOPATH")}, + {"PATH", os.Getenv("PATH")}, + {"GOCACHE", os.Getenv("GOCACHE")}, } } @@ -35,7 +66,7 @@ func getVscodeDebugCmd(cmd string, xgoCompilerEnableEnv string, args []string) * Mode: "exec", Program: cmd, Args: args, - Env: getDebugEnv(xgoCompilerEnableEnv), + Env: getDebugEnvMapping(xgoCompilerEnableEnv), } } diff --git a/cmd/xgo/exec_tool/env.go b/cmd/xgo/exec_tool/env.go index a74eeacf..ce9ef0a2 100644 --- a/cmd/xgo/exec_tool/env.go +++ b/cmd/xgo/exec_tool/env.go @@ -17,3 +17,11 @@ const XGO_MAIN_MODULE = "XGO_MAIN_MODULE" const XGO_COMPILE_PKG_DATA_DIR = "XGO_COMPILE_PKG_DATA_DIR" const XGO_STD_LIB_TRAP_DEFAULT_ALLOW = "XGO_STD_LIB_TRAP_DEFAULT_ALLOW" + +const XGO_DEBUG_COMPILE_PKG = "XGO_DEBUG_COMPILE_PKG" +const XGO_DEBUG_COMPILE_LOG_FILE = "XGO_DEBUG_COMPILE_LOG_FILE" + +const XGO_COMPILER_OPTIONS_FILE = "XGO_COMPILER_OPTIONS_FILE" + +// xgo's origial working dir +const XGO_SRC_WD = "XGO_SRC_WD" diff --git a/cmd/xgo/exec_tool/main.go b/cmd/xgo/exec_tool/main.go index 0e097413..85a99125 100644 --- a/cmd/xgo/exec_tool/main.go +++ b/cmd/xgo/exec_tool/main.go @@ -4,10 +4,12 @@ import ( "encoding/json" "fmt" "io" + "io/ioutil" "os" "os/exec" "path/filepath" "runtime" + "strconv" "strings" "time" @@ -92,6 +94,9 @@ func handleCompile(cmd string, opts *options, args []string) error { } } + debugCompilePkg := os.Getenv(XGO_DEBUG_COMPILE_PKG) + debugCompileLogFile := os.Getenv(XGO_DEBUG_COMPILE_LOG_FILE) + xgoCompilerEnableEnv, ok := os.LookupEnv(XGO_COMPILER_ENABLE) if !ok { if opts.enable { @@ -125,7 +130,7 @@ func handleCompile(cmd string, opts *options, args []string) error { compilerBin, "--", } - envs := getDebugEnv(xgoCompilerEnableEnv) + envs := getDebugEnvMapping(xgoCompilerEnableEnv) // dlvArgs = append(dlvArgs, compilerBin) dlvArgs = append(dlvArgs, args...) var strPrint []string @@ -195,6 +200,31 @@ func handleCompile(cmd string, opts *options, args []string) error { } } } + if debugCompilePkg != "" && debugCompilePkg == pkgPath { + envs := getDebugEnv(xgoCompilerEnableEnv) + flags, files := splitArgs(args) + str := formatDebugCompile(envs, compilerBin, flags, files) + + debugOptions := &DebugCompile{ + Package: pkgPath, + Env: envs, + Compiler: compilerBin, + Flags: flags, + Files: files, + } + err := ioutil.WriteFile(debugCompileLogFile, []byte(str), 0755) + if err != nil { + return err + } + srcWD := os.Getenv(XGO_SRC_WD) + err = marshalNoEscape(filepath.Join(srcWD, "debug-compile.json"), debugOptions) + if err != nil { + return err + } + for { + time.Sleep(1024 * time.Minute) + } + } // COMPILER_ALLOW_SYNTAX_REWRITE=${COMPILER_ALLOW_SYNTAX_REWRITE:-true} COMPILER_ALLOW_IR_REWRITE=${COMPILER_ALLOW_IR_REWRITE:-true} "$shdir/compile" ${@:2} runCommandExitFilter(compilerBin, args, func(cmd *exec.Cmd) { cmd.Env = append(cmd.Env, @@ -207,6 +237,46 @@ func handleCompile(cmd string, opts *options, args []string) error { return nil } +func marshalNoEscape(file string, data interface{}) error { + f, err := os.OpenFile(file, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0755) + if err != nil { + return err + } + defer f.Close() + enc := json.NewEncoder(f) + enc.SetEscapeHTML(false) + enc.SetIndent("", " ") + return enc.Encode(data) +} + +func splitArgs(args []string) (flags []string, files []string) { + n := len(args) + i := n - 1 + for ; i >= 0; i-- { + if !strings.HasSuffix(args[i], ".go") { + break + } + } + return args[:i+1], args[i+1:] +} +func formatDebugCompile(env []string, bin string, flags []string, files []string) string { + var b strings.Builder + + b.WriteString(fmt.Sprintf("var env = []string{%s}\n", formatGoList(env))) + b.WriteString(fmt.Sprintf("var flags = []string{%s}\n", formatGoList(flags))) + b.WriteString(fmt.Sprintf("var files = []string{%s}\n", formatGoList(files))) + b.WriteString(fmt.Sprintf("var compiler = %q\n", bin)) + + return b.String() +} + +func formatGoList(list []string) string { + qlist := make([]string, 0, len(list)) + for _, e := range list { + qlist = append(qlist, strconv.Quote(e)) + } + return strings.Join(qlist, ", ") +} func hasFlag(args []string, flag string) bool { flagEq := flag + "=" for _, arg := range args { diff --git a/cmd/xgo/main.go b/cmd/xgo/main.go index d34e990b..51aa7901 100644 --- a/cmd/xgo/main.go +++ b/cmd/xgo/main.go @@ -1,8 +1,11 @@ package main import ( + "crypto/md5" + "encoding/hex" "errors" "fmt" + "io/ioutil" "os" "os/exec" "path/filepath" @@ -127,11 +130,15 @@ func handleBuild(cmd string, args []string) error { flagC := opts.flagC logCompile := opts.logCompile logDebugOption := opts.logDebug + debugCompile := opts.debugCompile optXgoSrc := opts.xgoSrc noBuildOutput := opts.noBuildOutput noInstrument := opts.noInstrument resetInstrument := opts.resetInstrument noSetup := opts.noSetup + + optionsFromFile := opts.optionsFromFile + debugWithDlv := opts.debugWithDlv xgoHome := opts.xgoHome syncXgoOnly := opts.syncXgoOnly @@ -245,6 +252,17 @@ func handleBuild(cmd string, args []string) error { if len(gcflags) > 0 { buildCacheSuffix += "-gcflags" } + if optionsFromFile != "" { + data, err := ioutil.ReadFile(optionsFromFile) + if err != nil { + return err + } + if len(data) > 0 { + h := md5.New() + h.Write(data) + buildCacheSuffix += "-" + hex.EncodeToString(h.Sum(nil)) + } + } buildCacheDir := filepath.Join(instrumentDir, "build-cache"+buildCacheSuffix) revisionFile := filepath.Join(instrumentDir, "xgo-revision.txt") fullSyncRecord := filepath.Join(instrumentDir, "full-sync-record.txt") @@ -299,7 +317,7 @@ func handleBuild(cmd string, args []string) error { return err } } - resetOrRevisionChanged := resetInstrument || revisionChanged + resetOrRevisionChanged := resetInstrument || revisionChanged || buildCompiler if isDevelopment || resetOrRevisionChanged { logDebug("sync goroot %s -> %s", goroot, instrumentGoroot) err = syncGoroot(goroot, instrumentGoroot, fullSyncRecord) @@ -352,13 +370,18 @@ func handleBuild(cmd string, args []string) error { close(setupDone) if buildCompiler { + fmt.Printf("%s\n", compilerBin) return nil } + logDebug("trap stdlib: %v", trapStdlib) + // before invoking exec_tool, tail follow its log if logCompile { go tailLog(compileLog) } + var debugCompilePkg string + var debugCompileLogFile string execCmdEnv := os.Environ() var execCmd *exec.Cmd @@ -377,6 +400,23 @@ func handleBuild(cmd string, args []string) error { return err } } + if debugCompile { + // find the main package we are compile + pkgs, err := goinfo.ListPackages(projectDir, mod, remainArgs) + if err != nil { + return err + } + if len(pkgs) == 0 { + return fmt.Errorf("--debug-compile: no packages") + } + if len(pkgs) > 1 { + return fmt.Errorf("--debug-compile: need 1 package, found: %d", len(pkgs)) + } + debugCompilePkg = pkgs[0] + debugCompileLogFile = filepath.Join(tmpDir, "debug-compile.log") + go tailLog(debugCompileLogFile) + logDebug("debug compile package: %s", debugCompilePkg) + } if stackTrace == "on" && overlay == "" { // check if xgo/runtime ready impResult, impRuntimeErr := importRuntimeDep(cmdTest, instrumentGoroot, instrumentGo, goVersion, modfile, realXgoSrc, projectDir, subPaths, mainModule, mod, remainArgs) @@ -482,6 +522,10 @@ func handleBuild(cmd string, args []string) error { } execCmd.Env = append(execCmd.Env, "XGO_DEBUG_VSCODE="+xgoDebugVscode) + // debug compile package + execCmd.Env = append(execCmd.Env, exec_tool.XGO_DEBUG_COMPILE_PKG+"="+debugCompilePkg) + execCmd.Env = append(execCmd.Env, exec_tool.XGO_DEBUG_COMPILE_LOG_FILE+"="+debugCompileLogFile) + // stack trace if stackTrace != "" { execCmd.Env = append(execCmd.Env, "XGO_STACK_TRACE="+stackTrace) @@ -493,6 +537,22 @@ func handleBuild(cmd string, args []string) error { trapStdlibEnv = "true" } execCmd.Env = append(execCmd.Env, "XGO_STD_LIB_TRAP_DEFAULT_ALLOW="+trapStdlibEnv) + + // compiler options (make abs) + var absOptionsFromFile string + if optionsFromFile != "" { + absOptionsFromFile, err = filepath.Abs(optionsFromFile) + if err != nil { + return err + } + } + execCmd.Env = append(execCmd.Env, exec_tool.XGO_COMPILER_OPTIONS_FILE+"="+absOptionsFromFile) + + wd, err := os.Getwd() + if err != nil { + return err + } + execCmd.Env = append(execCmd.Env, exec_tool.XGO_SRC_WD+"="+wd) } logDebug("command env: %v", execCmd.Env) execCmd.Stdout = os.Stdout @@ -509,22 +569,16 @@ func handleBuild(cmd string, args []string) error { // if dump IR is not nil, output to stdout if tmpIRFile != "" { - err := copyToStdout(tmpIRFile) + // ast file not exists + err := checkedCopyToStdout(tmpIRFile, "--dump-ir not effective, use -a to trigger recompile or check if you package path matches") if err != nil { - // ir file not exists - if errors.Is(err, os.ErrNotExist) { - return fmt.Errorf("--dump-ir not effective, use -a to trigger recompile or check if you package path matches") - } return err } } if tmpASTFile != "" { - err := copyToStdout(tmpASTFile) + // ast file not exists + err := checkedCopyToStdout(tmpASTFile, "--dump-ast not effective, use -a to trigger recompile or check if you package path matches") if err != nil { - // ir file not exists - if errors.Is(err, os.ErrNotExist) { - return fmt.Errorf("--dump-ast not effective, use -a to trigger recompile or check if you package path matches") - } return err } } @@ -540,6 +594,18 @@ func handleBuild(cmd string, args []string) error { return nil } +func checkedCopyToStdout(file string, msg string) error { + err := copyToStdout(file) + if err != nil { + // ir file not exists + if errors.Is(err, os.ErrNotExist) { + return errors.New(msg) + } + return err + } + return nil +} + func checkGoVersion(goroot string, noInstrument bool) (*goinfo.GoVersion, error) { // check if we are using expected go version goVersionStr, err := goinfo.GetGoVersionOutput(filepath.Join(goroot, "bin", "go")) diff --git a/cmd/xgo/option.go b/cmd/xgo/option.go index d60cf0cc..f09a4164 100644 --- a/cmd/xgo/option.go +++ b/cmd/xgo/option.go @@ -23,13 +23,16 @@ type options struct { logCompile bool - logDebug *string + logDebug *string + debugCompile bool noBuildOutput bool noInstrument bool resetInstrument bool noSetup bool + optionsFromFile string + // dev only debugWithDlv bool xgoHome string @@ -79,6 +82,8 @@ func parseOptions(cmd string, args []string) (*options, error) { var resetInstrument bool var noSetup bool + var optionsFromFile string + var debugWithDlv bool var xgoHome string @@ -94,6 +99,7 @@ func parseOptions(cmd string, args []string) (*options, error) { var logCompile bool var logDebug *string + var debugCompile bool var noBuildOutput bool @@ -140,6 +146,10 @@ func parseOptions(cmd string, args []string) (*options, error) { Flags: []string{"--with-goroot"}, Value: &withGoroot, }, + { + Flags: []string{"--options-from-file"}, + Value: &optionsFromFile, + }, { Flags: []string{"--dump-ir"}, Value: &dumpIR, @@ -179,6 +189,13 @@ func parseOptions(cmd string, args []string) (*options, error) { logDebug = &v }, }, + { + Flags: []string{"--debug-compile"}, + Single: true, + Set: func(v string) { + debugCompile = true + }, + }, } if isDevelopment { @@ -346,15 +363,19 @@ func parseOptions(cmd string, args []string) (*options, error) { dumpIR: dumpIR, dumpAST: dumpAST, - logCompile: logCompile, - logDebug: logDebug, + logCompile: logCompile, + logDebug: logDebug, + debugCompile: debugCompile, noBuildOutput: noBuildOutput, noInstrument: noInstrument, resetInstrument: resetInstrument, noSetup: noSetup, - debugWithDlv: debugWithDlv, - xgoHome: xgoHome, + + optionsFromFile: optionsFromFile, + + debugWithDlv: debugWithDlv, + xgoHome: xgoHome, syncXgoOnly: syncXgoOnly, setupDev: setupDev, diff --git a/cmd/xgo/runtime_gen/core/version.go b/cmd/xgo/runtime_gen/core/version.go index e2c0cdfc..fdcf5ab6 100755 --- a/cmd/xgo/runtime_gen/core/version.go +++ b/cmd/xgo/runtime_gen/core/version.go @@ -7,8 +7,8 @@ import ( ) const VERSION = "1.0.38" -const REVISION = "808e011ba0498b1aaff762095543c880301dc26b+1" -const NUMBER = 249 +const REVISION = "f8e03d3647811f7d4244cbb68369f9e3401e53b9+1" +const NUMBER = 250 // these fields will be filled by compiler const XGO_VERSION = "" diff --git a/cmd/xgo/test-explorer/debug.go b/cmd/xgo/test-explorer/debug.go index 0e13e624..7fcfdee4 100644 --- a/cmd/xgo/test-explorer/debug.go +++ b/cmd/xgo/test-explorer/debug.go @@ -8,8 +8,8 @@ import ( "time" "github.com/xhd2015/xgo/support/cmd" + debug_util "github.com/xhd2015/xgo/support/debug" "github.com/xhd2015/xgo/support/netutil" - "github.com/xhd2015/xgo/support/strutil" ) type DebugRequest struct { @@ -74,13 +74,7 @@ func debug(ctx *RunContext) error { return err } err = netutil.ServePort(2345, true, 500*time.Millisecond, func(port int) { - // user need to set breakpoint explicitly - fmt.Fprintf(stderr, "dlv listen on localhost:%d\n", port) - fmt.Fprintf(stderr, "Debug with IDEs:\n") - fmt.Fprintf(stderr, " > VSCode: add the following config to .vscode/launch.json configurations:") - fmt.Fprintf(stderr, "\n%s\n", strutil.IndentLines(formatVscodeConfig(port), " ")) - fmt.Fprintf(stderr, " > GoLand: click Add Configuration > Go Remote > localhost:%d\n", port) - fmt.Fprintf(stderr, " > Terminal: dlv connect localhost:%d\n", port) + fmt.Fprintln(stderr, debug_util.FormatDlvPrompt(port)) }, func(port int) error { // dlv exec --api-version=2 --listen=localhost:2345 --accept-multiclient --headless ./debug.bin return cmd.Dir(filepath.Dir(file)).Debug().Stderr(stderr).Stdout(stdout). @@ -104,18 +98,3 @@ func debug(ctx *RunContext) error { } return nil } - -func formatVscodeConfig(port int) string { - return fmt.Sprintf(`{ - "configurations": [ - { - "name": "Debug dlv localhost:%d", - "type": "go", - "request": "attach", - "mode": "remote", - "port": %d, - "host": "127.0.0.1" - } - } -}`, port, port) -} diff --git a/cmd/xgo/version.go b/cmd/xgo/version.go index ab61b634..617add1e 100644 --- a/cmd/xgo/version.go +++ b/cmd/xgo/version.go @@ -3,8 +3,8 @@ package main import "fmt" const VERSION = "1.0.38" -const REVISION = "808e011ba0498b1aaff762095543c880301dc26b+1" -const NUMBER = 249 +const REVISION = "f8e03d3647811f7d4244cbb68369f9e3401e53b9+1" +const NUMBER = 250 func getRevision() string { revSuffix := "" diff --git a/patch/ctxt/ctx.go b/patch/ctxt/ctx.go index 1cc1db39..56c92c0d 100644 --- a/patch/ctxt/ctx.go +++ b/patch/ctxt/ctx.go @@ -12,6 +12,10 @@ const XgoRuntimeTracePkg = XgoModule + "/runtime/trace" const XgoLinkTrapVarForGenerated = "__xgo_link_trap_var_for_generated" +func InitAfterLoad() { + isMainModule = IsSameModule(GetPkgPath(), XgoMainModule) +} + func SkipPackageTrap() bool { pkgPath := GetPkgPath() if pkgPath == "" { @@ -103,3 +107,29 @@ func cutPkgPrefix(s string, pkg string) (suffix string, ok bool) { } return s[n+1:], true } + +var isMainModule bool + +func IsMainModule() bool { + return isMainModule +} + +func IsPkgMainModule(pkg string) bool { + return IsSameModule(pkg, XgoMainModule) +} + +func IsSameModule(pkgPath string, modulePath string) bool { + if modulePath == "" { + return false + } + if !strings.HasPrefix(pkgPath, modulePath) { + return false + } + if len(pkgPath) == len(modulePath) { + return true + } + if pkgPath[len(modulePath)] == '/' { + return true + } + return false +} diff --git a/patch/ctxt/env.go b/patch/ctxt/env.go index b69d5ec5..ad9b271b 100644 --- a/patch/ctxt/env.go +++ b/patch/ctxt/env.go @@ -5,5 +5,24 @@ import "os" var XgoMainModule = os.Getenv("XGO_MAIN_MODULE") var XgoCompilePkgDataDir = os.Getenv("XGO_COMPILE_PKG_DATA_DIR") +var XGO_COMPILER_ENABLE = os.Getenv("XGO_COMPILER_ENABLE") == "true" + +// default true +var XGO_COMPILER_ENABLE_SYNTAX = os.Getenv("XGO_COMPILER_ENABLE_SYNTAX") != "false" + +var XGO_COMPILER_SYNTAX_SKIP_INJECT_XGO_FLAGS = os.Getenv("XGO_COMPILER_SYNTAX_SKIP_INJECT_XGO_FLAGS") == "true" + +var XGO_COMPILER_SYNTAX_SKIP_FILL_FUNC_NAMES = os.Getenv("XGO_COMPILER_SYNTAX_SKIP_FILL_FUNC_NAMES") == "true" + +var XGO_COMPILER_SYNTAX_SKIP_ALL_TRAP = os.Getenv("XGO_COMPILER_SYNTAX_SKIP_ALL_TRAP") == "true" + +var XGO_COMPILER_SYNTAX_SKIP_VAR_TRAP = os.Getenv("XGO_COMPILER_SYNTAX_SKIP_VAR_TRAP") == "true" + +var XGO_COMPILER_SYNTAX_SKIP_GEN_CODE = os.Getenv("XGO_COMPILER_SYNTAX_SKIP_GEN_CODE") == "true" + +var XGO_COMPILER_LOG_COST = os.Getenv("XGO_COMPILER_LOG_COST") == "true" + +var XGO_COMPILER_OPTIONS_FILE = os.Getenv("XGO_COMPILER_OPTIONS_FILE") + // enabled via: --trap-stdlib var XgoStdTrapDefaultAllow = os.Getenv("XGO_STD_LIB_TRAP_DEFAULT_ALLOW") == "true" diff --git a/patch/ctxt/log.go b/patch/ctxt/log.go new file mode 100644 index 00000000..c2b22677 --- /dev/null +++ b/patch/ctxt/log.go @@ -0,0 +1,19 @@ +package ctxt + +import ( + "fmt" + "os" + "time" +) + +var dummy = func() {} + +func LogSpan(msg string) func() { + if !XGO_COMPILER_LOG_COST { + return dummy + } + start := time.Now() + return func() { + fmt.Fprintf(os.Stderr, "%s: %v\n", msg, time.Since(start)) + } +} diff --git a/patch/ctxt/match.go b/patch/ctxt/match.go index 0be932a1..69184efa 100644 --- a/patch/ctxt/match.go +++ b/patch/ctxt/match.go @@ -1,6 +1,43 @@ package ctxt -import "strings" +import ( + "cmd/compile/internal/xgo_rewrite_internal/patch/info" + "cmd/compile/internal/xgo_rewrite_internal/patch/match" + "encoding/json" + "fmt" + "io/ioutil" + "strings" +) + +type Options struct { + FilterRules []match.Rule `json:"filter_rules"` +} + +var opts Options + +func init() { + if XGO_COMPILER_OPTIONS_FILE == "" { + return + } + data, err := ioutil.ReadFile(XGO_COMPILER_OPTIONS_FILE) + if err != nil { + panic(fmt.Errorf("read xgo compiler options: %w", err)) + } + if len(data) == 0 { + return + } + err = json.Unmarshal(data, &opts) + if err != nil { + panic(fmt.Errorf("parse xgo compiler options: %w", err)) + } + for _, rule := range opts.FilterRules { + rule.Parse() + } +} + +func GetAction(fn *info.DeclInfo) string { + return match.MatchRules(opts.FilterRules, GetPkgPath(), isMainModule, fn) +} func MatchAnyPattern(pkgPath string, pkgName string, funcName string, patterns []string) bool { for _, pattern := range patterns { diff --git a/patch/func_name/name.go b/patch/func_name/name.go index 1a0e700d..a13d6913 100644 --- a/patch/func_name/name.go +++ b/patch/func_name/name.go @@ -1,6 +1,7 @@ package func_name import ( + "cmd/compile/internal/syntax" "fmt" ) @@ -13,3 +14,26 @@ func FormatFuncRefName(recvTypeName string, recvPtr bool, funcName string) strin } return recvTypeName + "." + funcName } + +func FormatFuncRefNameSyntax(pos syntax.Pos, recvTypeName string, recvPtr bool, funcName string) syntax.Expr { + if recvTypeName == "" { + return syntax.NewName(pos, funcName) + } + + var x syntax.Expr + if recvPtr { + // return fmt.Sprintf("(*%s).%s", recvTypeName, funcName) + x = &syntax.ParenExpr{ + X: &syntax.Operation{ + Op: syntax.Mul, + X: syntax.NewName(pos, recvTypeName), + }, + } + } else { + x = syntax.NewName(pos, recvTypeName) + } + return &syntax.SelectorExpr{ + X: x, + Sel: syntax.NewName(pos, funcName), + } +} diff --git a/patch/info/decl_info.go b/patch/info/decl_info.go new file mode 100644 index 00000000..9eb03525 --- /dev/null +++ b/patch/info/decl_info.go @@ -0,0 +1,118 @@ +package info + +import ( + "cmd/compile/internal/syntax" + xgo_func_name "cmd/compile/internal/xgo_rewrite_internal/patch/func_name" + "fmt" +) + +type DeclKind int + +const ( + Kind_Func DeclKind = 0 + Kind_Var DeclKind = 1 + Kind_VarPtr DeclKind = 2 + Kind_Const DeclKind = 3 + + // TODO + // Kind_Interface VarKind = 4 +) + +func (c DeclKind) IsFunc() bool { + return c == Kind_Func +} + +func (c DeclKind) IsVarOrConst() bool { + return c == Kind_Var || c == Kind_VarPtr || c == Kind_Const +} +func (c DeclKind) String() string { + switch c { + case Kind_Func: + return "func" + case Kind_Var: + return "var" + case Kind_VarPtr: + return "var_ptr" + case Kind_Const: + return "const" + default: + return fmt.Sprintf("decl_%d", int(c)) + } +} + +type DeclInfo struct { + FuncDecl *syntax.FuncDecl + VarDecl *syntax.VarDecl + ConstDecl *syntax.ConstDecl + + // is this var decl follow a const __xgo_trap_xxx = 1? + FollowingTrapConst bool + + Kind DeclKind + Name string + RecvTypeName string + RecvPtr bool + Generic bool + Closure bool + Stdlib bool + + // this is an interface type declare + // only the RecvTypeName is valid + Interface bool + + // arg names + RecvName string + ArgNames []string + ResNames []string + FirstArgCtx bool + LastResError bool + + FileSyntax *syntax.File + FileIndex int + + // this is file name after applied -trimpath + File string + Line int +} + +func (c *DeclInfo) RefName() string { + if c.Interface { + return "nil" + } + // if c.Generic, then the ref name is for generic + if !c.Kind.IsFunc() { + return c.Name + } + return xgo_func_name.FormatFuncRefName(c.RecvTypeName, c.RecvPtr, c.Name) +} + +func (c *DeclInfo) RefNameSyntax(pos syntax.Pos) syntax.Expr { + if c.Interface { + return syntax.NewName(pos, "nil") + } + // if c.Generic, then the ref name is for generic + if !c.Kind.IsFunc() { + return syntax.NewName(pos, c.Name) + } + return xgo_func_name.FormatFuncRefNameSyntax(pos, c.RecvTypeName, c.RecvPtr, c.Name) +} + +func (c *DeclInfo) GenericName() string { + if !c.Generic { + return "" + } + return c.RefName() +} + +func (c *DeclInfo) IdentityName() string { + if c.Interface { + return c.RecvTypeName + } + if !c.Kind.IsFunc() { + if c.Kind == Kind_VarPtr { + return "*" + c.Name + } + return c.Name + } + return xgo_func_name.FormatFuncRefName(c.RecvTypeName, c.RecvPtr, c.Name) +} diff --git a/patch/match/match.go b/patch/match/match.go new file mode 100644 index 00000000..8cf3138e --- /dev/null +++ b/patch/match/match.go @@ -0,0 +1,122 @@ +package match + +import ( + "cmd/compile/internal/base" + "cmd/compile/internal/types" + "cmd/compile/internal/xgo_rewrite_internal/patch/info" + "strings" +) + +type Rule struct { + Any bool `json:"any"` + Kind *string `json:"kind"` + Pkg *string `json:"pkg"` + Name *string `json:"name"` + Stdlib *bool `json:"stdlib"` + MainModule *bool `json:"main_module"` + Generic *bool `json:"generic"` + Exported *bool `json:"exported"` + Action string `json:"action"` // include,exclude or empty + + kinds []string + pkgs Patterns + names Patterns +} + +func (c *Rule) Parse() { + c.kinds = toList(c.Kind) + c.pkgs = CompilePatterns(toList(c.Pkg)) + c.names = CompilePatterns(toList(c.Name)) +} + +func toList(s *string) []string { + if s == nil { + return nil + } + list := strings.Split(*s, ",") + i := 0 + n := len(list) + for j := 0; j < n; j++ { + e := list[j] + e = strings.TrimSpace(e) + if e != "" { + list[i] = e + i++ + } + } + return list[:i] +} + +func MatchRules(rules []Rule, pkgPath string, isMainModule bool, funcDecl *info.DeclInfo) string { + for _, rule := range rules { + if Match(&rule, pkgPath, isMainModule, funcDecl) { + return rule.Action + } + } + return "" +} + +func Match(rule *Rule, pkgPath string, isMainModule bool, funcDecl *info.DeclInfo) bool { + if rule == nil { + return false + } + if rule.Any { + return true + } + var hasAnyCondition bool + if len(rule.kinds) > 0 { + hasAnyCondition = true + if !listContains(rule.kinds, funcDecl.Kind.String()) { + return false + } + } + if len(rule.pkgs) > 0 { + hasAnyCondition = true + if !rule.pkgs.MatchAny(pkgPath) { + return false + } + } + if len(rule.names) > 0 { + hasAnyCondition = true + if !rule.names.MatchAny(funcDecl.IdentityName()) { + return false + } + } + if rule.MainModule != nil { + hasAnyCondition = true + if *rule.MainModule != isMainModule { + return false + } + } + if rule.Stdlib != nil { + hasAnyCondition = true + if *rule.Stdlib != base.Flag.Std { + return false + } + } + if rule.Generic != nil && funcDecl.Kind.IsFunc() { + hasAnyCondition = true + if *rule.Generic != funcDecl.Generic { + return false + } + } + if rule.Exported != nil { + hasAnyCondition = true + if *rule.Exported != types.IsExported(funcDecl.Name) { + return false + } + } + if hasAnyCondition { + return true + } + return false +} + +func listContains(list []string, e string) bool { + for _, x := range list { + if x == e { + return true + } + } + return false +} diff --git a/patch/match/pattern.go b/patch/match/pattern.go new file mode 100644 index 00000000..778d3b50 --- /dev/null +++ b/patch/match/pattern.go @@ -0,0 +1,217 @@ +package match + +import ( + "fmt" + "strings" +) + +type Pattern struct { + exprs []expr +} +type Patterns []*Pattern + +func CompilePatterns(patterns []string) Patterns { + list := make([]*Pattern, 0, len(patterns)) + for _, p := range patterns { + ptn := CompilePattern(p) + list = append(list, ptn) + } + return list +} + +func CompilePattern(s string) *Pattern { + segments := splitPath(s) + exprs := make([]expr, 0, len(segments)) + for _, seg := range segments { + expr := compileExpr(seg) + exprs = append(exprs, expr) + } + return &Pattern{exprs: exprs} +} + +type kind int + +const ( + kind_plain_str = iota + kind_star // * +) + +type element struct { + kind kind + runes []rune +} + +type elements []element + +func (c *Pattern) MatchPrefix(path string) bool { + return matchSegsPrefix(c.exprs, splitPath(path)) +} + +// match exact +func (c *Pattern) Match(path string) bool { + return matchSegsFull(c.exprs, splitPath(path)) +} + +func (c *Pattern) matchPrefixPaths(paths []string) bool { + return matchSegsPrefix(c.exprs, paths) +} + +func (c Patterns) MatchAnyPrefix(path string) bool { + paths := splitPath(path) + return matchAnyPatterns(c, paths) +} + +func (c Patterns) MatchAny(path string) bool { + paths := splitPath(path) + for _, pattern := range c { + if matchSegsFull(pattern.exprs, paths) { + return true + } + } + return false +} + +func (c Patterns) matchAnyPrefixPaths(paths []string) bool { + return matchAnyPatterns(c, paths) +} +func matchAnyPatterns(patterns []*Pattern, paths []string) bool { + for _, pattern := range patterns { + if pattern.matchPrefixPaths(paths) { + return true + } + } + return false +} + +type expr struct { + doubleStar bool + elements elements +} + +func compileExpr(s string) expr { + if s == "" { + return expr{} + } + if s == "**" { + return expr{doubleStar: true} + } + runes := []rune(s) + + elems := make(elements, 0) + + lastIdx := 0 + for i, ch := range runes { + if ch != '*' { + continue + } + if i > lastIdx { + elems = append(elems, element{kind: kind_plain_str, runes: runes[lastIdx:i]}) + } + lastIdx = i + 1 + if i > 0 && runes[i-1] == '*' { + continue + } + elems = append(elems, element{kind: kind_star}) + } + if lastIdx < len(runes) { + elems = append(elems, element{kind: kind_plain_str, runes: runes[lastIdx:]}) + } + return expr{elements: elems} +} + +func splitPath(path string) []string { + segments := strings.Split(path, "/") + filtered := make([]string, 0, len(segments)) + for _, seg := range segments { + if seg == "" { + continue + } + filtered = append(filtered, seg) + } + return filtered +} + +func matchSegsPrefix(exprs []expr, segments []string) bool { + return doMatch(exprs, segments, true) +} + +func matchSegsFull(exprs []expr, segments []string) bool { + return doMatch(exprs, segments, false) +} + +// f(L,j,segs,i) = if L[j] double star: f(L,j,segs,i+1) or f(L,j+1,segs,i); else if L[j] matches segs[i],f(L,j+1,segs,i+1) +// if j>=L.length: if segments empty +func doMatch(exprs []expr, segments []string, prefix bool) bool { + if len(exprs) == 0 { + if prefix { + return true + } + return len(segments) == 0 + } + expr := exprs[0] + if expr.doubleStar { + if len(segments) > 0 && doMatch(exprs, segments[1:], prefix) { + return true + } + return doMatch(exprs[1:], segments, prefix) + } + if len(segments) == 0 { + return expr.matchEmpty() + } + + if !expr.matchNoDoubleStar(segments[0], prefix) { + return false + } + return doMatch(exprs[1:], segments[1:], prefix) +} + +func (c expr) matchNoDoubleStar(name string, prefix bool) bool { + return c.matchRunesFrom(0, prefix, []rune(name)) +} + +// f(L, i, runes,j ) -> if L[i]==="*", f(L,i+1, runes,j) or f(L,i,runes,j+1) +func (c expr) matchRunesFrom(i int, prefix bool, runes []rune) bool { + if i >= len(c.elements) { + return len(runes) == 0 + } + part := c.elements[i] + switch part.kind { + case kind_star: + if c.matchRunesFrom(i+1, prefix, runes) { + return true + } + if len(runes) > 0 { + return c.matchRunesFrom(i, prefix, runes[1:]) + } + return false + case kind_plain_str: + n := len(part.runes) + if !prefix { + if n != len(runes) { + return false + } + } else { + if n > len(runes) { + return false + } + } + for j := 0; j < n; j++ { + if runes[j] != part.runes[j] { + return false + } + } + return true + default: + panic(fmt.Errorf("unknown expr kind: %v", part.kind)) + } +} + +func (c expr) matchEmpty() bool { + // all are just stars + for _, part := range c.elements { + if part.kind != kind_star { + return false + } + } + return true +} diff --git a/patch/syntax/rewrite.go b/patch/syntax/rewrite.go index 5eccf3c9..b8601f08 100644 --- a/patch/syntax/rewrite.go +++ b/patch/syntax/rewrite.go @@ -3,6 +3,7 @@ package syntax import ( "cmd/compile/internal/base" "cmd/compile/internal/syntax" + xgo_ctxt "cmd/compile/internal/xgo_rewrite_internal/patch/ctxt" "fmt" "os" @@ -96,6 +97,7 @@ func replaceIdent(root syntax.Node, match string, to string) { } func rewriteFuncsSource(funcDecls []*DeclInfo, pkgPath string) { + defer xgo_ctxt.LogSpan("rewriteFuncsSource")() for _, fn := range funcDecls { if !fn.Kind.IsFunc() { continue diff --git a/patch/syntax/syntax.go b/patch/syntax/syntax.go index f7c2e5ba..86c72cb1 100644 --- a/patch/syntax/syntax.go +++ b/patch/syntax/syntax.go @@ -3,6 +3,7 @@ package syntax import ( "cmd/compile/internal/base" "cmd/compile/internal/syntax" + "cmd/compile/internal/xgo_rewrite_internal/patch/info" "fmt" "io" "os" @@ -11,9 +12,16 @@ import ( "strings" xgo_ctxt "cmd/compile/internal/xgo_rewrite_internal/patch/ctxt" - xgo_func_name "cmd/compile/internal/xgo_rewrite_internal/patch/func_name" ) +type DeclInfo = info.DeclInfo +type DeclKind = info.DeclKind + +const Kind_Func = info.Kind_Func +const Kind_Var = info.Kind_Var +const Kind_VarPtr = info.Kind_VarPtr +const Kind_Const = info.Kind_Const + const XGO_TOOLCHAIN_VERSION = "XGO_TOOLCHAIN_VERSION" const XGO_TOOLCHAIN_REVISION = "XGO_TOOLCHAIN_REVISION" const XGO_TOOLCHAIN_VERSION_NUMBER = "XGO_TOOLCHAIN_VERSION_NUMBER" @@ -29,7 +37,7 @@ const straceFlagConstName = "__xgo_injected_StraceFlag" const trapStdlibFlagConstName = "__xgo_injected_StdlibTrapDefaultAllow" // this link function is considered safe as we do not allow user -// to define such one,there will no abuse +// to define such one,there will be no abuse const XgoLinkGeneratedRegisterFunc = "__xgo_link_generated_register_func" const XgoRegisterFuncs = "__xgo_register_funcs" const XgoLocalFuncStub = "__xgo_local_func_stub" @@ -44,9 +52,18 @@ func init() { } func AfterFilesParsed(fileList []*syntax.File, addFile func(name string, r io.Reader) *syntax.File) { + xgo_ctxt.InitAfterLoad() + defer xgo_ctxt.LogSpan("AfterFilesParsed")() + if !xgo_ctxt.XGO_COMPILER_ENABLE_SYNTAX { + return + } debugSyntax(fileList) - injectXgoFlags(fileList) - fillFuncArgResNames(fileList) + if !xgo_ctxt.XGO_COMPILER_SYNTAX_SKIP_INJECT_XGO_FLAGS { + injectXgoFlags(fileList) + } + if !xgo_ctxt.XGO_COMPILER_SYNTAX_SKIP_FILL_FUNC_NAMES { + fillFuncArgResNames(fileList) + } registerAndTrapFuncs(fileList, addFile) } @@ -99,12 +116,12 @@ func debugPkgSyntax(files []*syntax.File) { // } } -func GetSyntaxDeclMapping() map[string]map[LineCol]*DeclInfo { +func GetSyntaxDeclMapping() map[string]map[LineCol]*info.DeclInfo { return getSyntaxDeclMapping() } var allFiles []*syntax.File -var allDecls []*DeclInfo +var allDecls []*info.DeclInfo func ClearFiles() { allFiles = nil @@ -186,6 +203,7 @@ func ClearSyntaxDeclMapping() { } func registerAndTrapFuncs(fileList []*syntax.File, addFile func(name string, r io.Reader) *syntax.File) { + defer xgo_ctxt.LogSpan("registerAndTrapFuncs")() allFiles = fileList pkgPath := xgo_ctxt.GetPkgPath() @@ -198,7 +216,7 @@ func registerAndTrapFuncs(fileList []*syntax.File, addFile func(name string, r i needTimeRewrite = true } - skipTrap := xgo_ctxt.SkipPackageTrap() + skipTrap := xgo_ctxt.XGO_COMPILER_SYNTAX_SKIP_ALL_TRAP || xgo_ctxt.SkipPackageTrap() // debugPkgSyntax(fileList) // if true { @@ -213,8 +231,8 @@ func registerAndTrapFuncs(fileList []*syntax.File, addFile func(name string, r i // complexity, and runtime can be compiled or cached, we cannot locate // where its _pkg_.a is. - varTrap := allowVarTrap() - var funcDelcs []*DeclInfo + varTrap := !xgo_ctxt.XGO_COMPILER_SYNTAX_SKIP_VAR_TRAP && allowVarTrap() + var funcDelcs []*info.DeclInfo if needTimePatch || needTimeRewrite || !skipTrap { funcDelcs = getFuncDecls(fileList, varTrap) } @@ -225,6 +243,7 @@ func registerAndTrapFuncs(fileList []*syntax.File, addFile func(name string, r i if needTimeRewrite { rewriteTimePatch(funcDelcs) } + if skipTrap { return } @@ -255,7 +274,12 @@ func registerAndTrapFuncs(fileList []*syntax.File, addFile func(name string, r i rewriteFuncsSource(funcDelcs, pkgPath) if varTrap { - trapVariables(pkgPath, fileList, funcDelcs) + // write package data + err := writePkgData(pkgPath, funcDelcs) + if err != nil { + base.Fatalf("write pkg data: %v", err) + } + trapVariables(fileList, funcDelcs) // debug // fmt.Fprintf(os.Stderr, "ast:") // syntax.Fdump(os.Stderr, fileList[0]) @@ -278,35 +302,33 @@ func registerAndTrapFuncs(fileList []*syntax.File, addFile func(name string, r i // see https://github.com/golang/go/issues/33437 // see also: https://github.com/golang/go/issues/57832 The input code is just too big for the compiler to handle. // here we split the files per 1000 functions - batchFuncDecls := splitBatch(funcDelcs, 1000) - for i, funcDecls := range batchFuncDecls { - if len(funcDecls) == 0 { - continue - } + if !xgo_ctxt.XGO_COMPILER_SYNTAX_SKIP_GEN_CODE { + batchFuncDecls := splitBatch(funcDelcs, 1000) + logBatchGen := xgo_ctxt.LogSpan("batch gen") + for i, funcDecls := range batchFuncDecls { + if len(funcDecls) == 0 { + continue + } + // NOTE: here the trick is to use init across multiple files, + // in go, init can appear more than once even in single file + fileNameBase := "__xgo_autogen_register_func_info" + if len(batchFuncDecls) > 1 { + // special when there are multiple files + fileNameBase += fmt.Sprintf("_%d", i) + } + initFile := addFile(fileNameBase+".go", strings.NewReader(generateRegFileCode(pkgName, "init", ""))) - // use XgoLinkRegFunc for general purepose - body := generateFuncRegBody(funcDecls, XgoLinkGeneratedRegisterFunc, XgoLocalFuncStub) + initFn := initFile.DeclList[0].(*syntax.FuncDecl) + pos := initFn.Pos() + // use XgoLinkRegFunc for general purepose + body := generateFuncRegBody(pos, funcDecls, XgoLinkGeneratedRegisterFunc, XgoLocalFuncStub) - // NOTE: here the trick is to use init across multiple files, - // in go, init can appear more than once even in single file - fileCode := generateRegFileCode(pkgName, "init", body) - fileNameBase := "__xgo_autogen_register_func_info" - if len(batchFuncDecls) > 1 { - // special when there are multiple files - fileNameBase += fmt.Sprintf("_%d", i) - } - if false { - // debug - dir := filepath.Join("/tmp/xgo_debug_gen", pkgPath) - os.MkdirAll(dir, 0755) - os.WriteFile(filepath.Join(dir, fileNameBase+".go"), []byte(fileCode), 0755) - } - addFile(fileNameBase+".go", strings.NewReader(fileCode)) - if false && pkgPath == "main" { - // debug - os.WriteFile("/tmp/debug.go", []byte(fileCode), 0755) - panic("debug") + for _, stmt := range body { + fillPos(pos, stmt) + } + initFn.Body.List = append(initFn.Body.List, body...) } + logBatchGen() } } @@ -422,10 +444,14 @@ func splitBatch(funcDecls []*DeclInfo, batch int) [][]*DeclInfo { if batch <= 0 { panic("invalid batch") } + n := len(funcDecls) + if n <= batch { + // fast path + return [][]*DeclInfo{funcDecls} + } var res [][]*DeclInfo var cur []*DeclInfo - n := len(funcDecls) for i := 0; i < n; i++ { cur = append(cur, funcDecls[i]) if len(cur) >= batch { @@ -444,91 +470,6 @@ type FileDecl struct { File *syntax.File Funcs []*DeclInfo } -type DeclKind int - -const ( - Kind_Func DeclKind = 0 - Kind_Var DeclKind = 1 - Kind_VarPtr DeclKind = 2 - Kind_Const DeclKind = 3 - - // TODO - // Kind_Interface VarKind = 4 -) - -func (c DeclKind) IsFunc() bool { - return c == Kind_Func -} - -func (c DeclKind) IsVarOrConst() bool { - return c == Kind_Var || c == Kind_VarPtr || c == Kind_Const -} - -type DeclInfo struct { - FuncDecl *syntax.FuncDecl - VarDecl *syntax.VarDecl - ConstDecl *syntax.ConstDecl - - // is this var decl follow a const __xgo_trap_xxx = 1? - FollowingTrapConst bool - - Kind DeclKind - Name string - RecvTypeName string - RecvPtr bool - Generic bool - Closure bool - Stdlib bool - - // this is an interface type declare - // only the RecvTypeName is valid - Interface bool - - // arg names - RecvName string - ArgNames []string - ResNames []string - FirstArgCtx bool - LastResError bool - - FileSyntax *syntax.File - FileIndex int - - // this is file name after applied -trimpath - File string - Line int -} - -func (c *DeclInfo) RefName() string { - if c.Interface { - return "nil" - } - // if c.Generic, then the ref name is for generic - if !c.Kind.IsFunc() { - return c.Name - } - return xgo_func_name.FormatFuncRefName(c.RecvTypeName, c.RecvPtr, c.Name) -} - -func (c *DeclInfo) GenericName() string { - if !c.Generic { - return "" - } - return c.RefName() -} - -func (c *DeclInfo) IdentityName() string { - if c.Interface { - return c.RecvTypeName - } - if !c.Kind.IsFunc() { - if c.Kind == Kind_VarPtr { - return "*" + c.Name - } - return c.Name - } - return xgo_func_name.FormatFuncRefName(c.RecvTypeName, c.RecvPtr, c.Name) -} func fillMissingArgNames(fn *syntax.FuncDecl) { if fn.Recv != nil { @@ -556,7 +497,7 @@ func fillName(field *syntax.Field, namePrefix string) { var AbsFilename func(name string) string var TrimFilename func(b *syntax.PosBase) string -func getFuncDecls(files []*syntax.File, varTrap bool) []*DeclInfo { +func getFuncDecls(files []*syntax.File, varTrap bool) []*info.DeclInfo { // fileInfos := make([]*FileDecl, 0, len(files)) var declFuncs []*DeclInfo for i, f := range files { @@ -609,11 +550,11 @@ func getFuncDecls(files []*syntax.File, varTrap bool) []*DeclInfo { func isTrapped(declFuncs []*DeclInfo, i int) bool { fn := declFuncs[i] - if fn.Kind != Kind_Var { + if fn.Kind != info.Kind_Var { return false } last := declFuncs[i-1] - if last.Kind != Kind_Const { + if last.Kind != info.Kind_Const { return false } const xgoTrapPrefix = "__xgo_trap_" @@ -627,16 +568,25 @@ func isTrapped(declFuncs []*DeclInfo, i int) bool { return true } -func filterFuncDecls(funcDecls []*DeclInfo, pkgPath string) []*DeclInfo { - filtered := make([]*DeclInfo, 0, len(funcDecls)) - for _, fn := range funcDecls { - // disable part of stdlibs - if !xgo_ctxt.AllowPkgFuncTrap(pkgPath, base.Flag.Std, fn.IdentityName()) { - continue +func filterFuncDecls(funcDecls []*info.DeclInfo, pkgPath string) []*info.DeclInfo { + n := len(funcDecls) + i := 0 + for j := 0; j < n; j++ { + fn := funcDecls[j] + + action := xgo_ctxt.GetAction(fn) + if action == "" { + // disable part of stdlibs + if !xgo_ctxt.AllowPkgFuncTrap(pkgPath, base.Flag.Std, fn.IdentityName()) { + action = "exclude" + } + } + if action == "" || action == "include" { + funcDecls[i] = fn + i++ } - filtered = append(filtered, fn) } - return filtered + return funcDecls[:i] } func extractFuncDecls(fileIndex int, f *syntax.File, file string, decl syntax.Decl, varTrap bool) []*DeclInfo { @@ -800,105 +750,119 @@ func genStructType(fields []*StructDef) string { return fmt.Sprintf("struct{\n%s\n}\n", strings.Join(concats, "\n")) } -func generateFuncRegBody(funcDecls []*DeclInfo, xgoRegFunc string, xgoLocalFuncStub string) string { +// return a list of statements +// +// fileA := "..." +// fileB := "..." +// __xgo_link_generated_register_func(__xgo_local_func_stub{ +// __xgo_local_pkg_name, +// 0, // kind +// ... +// }) +func generateFuncRegBody(pos syntax.Pos, funcDecls []*DeclInfo, xgoRegFunc string, xgoLocalFuncStub string) []syntax.Stmt { fileDeclaredMapping := make(map[int]bool) - var fileDefs []string - stmts := make([]string, 0, len(funcDecls)) + var fileDefs []syntax.Stmt + stmts := make([]syntax.Stmt, 0, len(funcDecls)) + if xgo_ctxt.XGO_COMPILER_LOG_COST { + fmt.Fprintf(os.Stderr, "funcDecls: %d\n", len(funcDecls)) + } + + logBodyGen := xgo_ctxt.LogSpan("funcDecl body gen") for _, funcDecl := range funcDecls { if funcDecl.Name == "_" { // there are function with name "_" continue } - var fnRefName string = "nil" - var varRefName string = "nil" + var fnRefName syntax.Expr + var varRefName syntax.Expr if funcDecl.Kind.IsFunc() { if !funcDecl.Generic { - fnRefName = funcDecl.RefName() + fnRefName = funcDecl.RefNameSyntax(pos) } - } else if funcDecl.Kind == Kind_Var { - varRefName = "&" + funcDecl.RefName() - } else if funcDecl.Kind == Kind_Const { - varRefName = funcDecl.RefName() + } else if funcDecl.Kind == info.Kind_Var { + varRefName = &syntax.Operation{ + Op: syntax.And, + X: funcDecl.RefNameSyntax(pos), + } + } else if funcDecl.Kind == info.Kind_Const { + varRefName = funcDecl.RefNameSyntax(pos) } fileIdx := funcDecl.FileIndex fileRef := getFileRef(fileIdx) + if fnRefName == nil { + fnRefName = syntax.NewName(pos, "nil") + } + if varRefName == nil { + varRefName = syntax.NewName(pos, "nil") + } + // check expected__xgo_stub_def and __xgo_local_func_stub for correctness var _ = expected__xgo_stub_def - regKind := func(kind DeclKind, identityName string) { - fieldList := []string{ - XgoLocalPkgName, // PkgPath - strconv.FormatInt(int64(kind), 10), // Kind - fnRefName, // Fn - varRefName, // Var - "0", // PC, filled later - strconv.FormatBool(funcDecl.Interface), // Interface - strconv.FormatBool(funcDecl.Generic), // Generic - strconv.FormatBool(funcDecl.Closure), // Closure - strconv.FormatBool(funcDecl.Stdlib), // Stdlib - strconv.Quote(funcDecl.RecvTypeName), // RecvTypeName - strconv.FormatBool(funcDecl.RecvPtr), // RecvPtr - strconv.Quote(funcDecl.Name), // Name - strconv.Quote(identityName), // IdentityName - strconv.Quote(funcDecl.RecvName), // RecvName - quoteNamesExpr(funcDecl.ArgNames), // ArgNames - quoteNamesExpr(funcDecl.ResNames), // ResNames - strconv.FormatBool(funcDecl.FirstArgCtx), // FirstArgCtx - strconv.FormatBool(funcDecl.LastResError), // LastResErr - fileRef, /* declFunc.FileRef */ // File - strconv.FormatInt(int64(funcDecl.Line), 10), // Line + regKind := func(kind info.DeclKind, identityName string) { + fieldList := [...]syntax.Expr{ + syntax.NewName(pos, XgoLocalPkgName), // PkgPath + newIntLit(int(kind)), // Kind + fnRefName, // Fn + varRefName, // Var + newIntLit(0), // PC, filled later + newBool(pos, funcDecl.Interface), // Interface + newBool(pos, funcDecl.Generic), // Generic + newBool(pos, funcDecl.Closure), // Closure + newBool(pos, funcDecl.Stdlib), // Stdlib + newStringLit(funcDecl.RecvTypeName), // RecvTypeName + newBool(pos, funcDecl.RecvPtr), // RecvPtr + newStringLit(funcDecl.Name), // Name + newStringLit(identityName), // IdentityName + newStringLit(funcDecl.RecvName), // RecvName + quoteNamesExprSyntax(pos, funcDecl.ArgNames), // ArgNames + quoteNamesExprSyntax(pos, funcDecl.ResNames), // ResNames + newBool(pos, funcDecl.FirstArgCtx), // FirstArgCtx + newBool(pos, funcDecl.LastResError), // LastResErr + syntax.NewName(pos, fileRef), /* declFunc.FileRef */ // File + newIntLit(funcDecl.Line), // Line } - fields := strings.Join(fieldList, ",") - stmts = append(stmts, fmt.Sprintf("%s(%s{%s})", xgoRegFunc, xgoLocalFuncStub, fields)) + // fields := strings.Join(fieldList, ",") + // stmts = append(stmts, fmt.Sprintf("%s(%s{%s})", xgoRegFunc, xgoLocalFuncStub, fields)) + stmts = append(stmts, &syntax.ExprStmt{ + X: &syntax.CallExpr{ + Fun: syntax.NewName(pos, xgoRegFunc), + ArgList: []syntax.Expr{ + &syntax.CompositeLit{ + Type: syntax.NewName(pos, xgoLocalFuncStub), + ElemList: fieldList[:], + Rbrace: pos, + }, + }, + }, + }) } identityName := funcDecl.IdentityName() regKind(funcDecl.Kind, identityName) - if funcDecl.Kind == Kind_Var { - regKind(Kind_VarPtr, "*"+identityName) + if funcDecl.Kind == info.Kind_Var { + regKind(info.Kind_VarPtr, "*"+identityName) } // add files if !fileDeclaredMapping[fileIdx] { fileDeclaredMapping[fileIdx] = true fileValue := funcDecl.File - fileDefs = append(fileDefs, fmt.Sprintf("%s := %q", fileRef, fileValue)) + fileDefs = append(fileDefs, &syntax.AssignStmt{ + Op: syntax.Def, + Lhs: syntax.NewName(pos, fileRef), + Rhs: newStringLit(fileValue), + }) } } + logBodyGen() if len(stmts) == 0 { - return "" - } - allStmts := make([]string, 0, 2+len(fileDefs)+len(stmts)) - if false { - // debug - allStmts = append(allStmts, `__xgo_reg_func_old:=__xgo_reg_func; __xgo_reg_func = func(info interface{}){ - fmt.Print("reg:"+`+XgoLocalPkgName+`+"\n") - v := reflect.ValueOf(info) - if v.Kind() != reflect.Struct { - panic("non struct:"+`+XgoLocalPkgName+`) - } - __xgo_reg_func_old(info) - }`) + return nil } - // debug, do not include file paths + allStmts := make([]syntax.Stmt, 0, 2+len(fileDefs)+len(stmts)) allStmts = append(allStmts, fileDefs...) - if false { - // debug - pkgPath := xgo_ctxt.GetPkgPath() - if strings.HasSuffix(pkgPath, "dao/impl") { - if true { - code := strings.Join(append(allStmts, stmts...), "\n") - os.WriteFile("/tmp/test.go", []byte(code), 0755) - panic("debug") - } - - if len(stmts) > 100 { - stmts = stmts[:100] - } - } - } allStmts = append(allStmts, stmts...) - return strings.Join(allStmts, "\n") + return allStmts } func generateRegFileCode(pkgName string, fnName string, body string) string { autoGenStmts := []string{ @@ -956,6 +920,23 @@ func quoteNamesExpr(names []string) string { return "[]string{" + strings.Join(qNames, ",") + "}" } +func quoteNamesExprSyntax(pos syntax.Pos, names []string) syntax.Expr { + if len(names) == 0 { + return syntax.NewName(pos, "nil") + } + qNames := make([]syntax.Expr, 0, len(names)) + for _, name := range names { + qNames = append(qNames, newStringLit(name)) + } + return &syntax.CompositeLit{ + Type: &syntax.SliceType{ + Elem: syntax.NewName(pos, "string"), + }, + ElemList: qNames, + Rbrace: pos, + } +} + func isName(expr syntax.Expr, name string) bool { nameExp, ok := expr.(*syntax.Name) if !ok { diff --git a/patch/syntax/vars.go b/patch/syntax/vars.go index b4fe83f3..0c1c74e9 100644 --- a/patch/syntax/vars.go +++ b/patch/syntax/vars.go @@ -4,6 +4,7 @@ import ( "cmd/compile/internal/base" "cmd/compile/internal/syntax" xgo_ctxt "cmd/compile/internal/xgo_rewrite_internal/patch/ctxt" + "cmd/compile/internal/xgo_rewrite_internal/patch/info" "cmd/compile/internal/xgo_rewrite_internal/patch/pkgdata" "fmt" "os" @@ -12,24 +13,15 @@ import ( ) func allowVarTrap() bool { - pkgPath := xgo_ctxt.GetPkgPath() - return allowPkgVarTrap(pkgPath) -} - -func allowPkgVarTrap(pkgPath string) bool { // prevent all std variables if base.Flag.Std { return false } - mainModule := xgo_ctxt.XgoMainModule - if mainModule == "" { - return false - } + return allowPkgVarTrap(xgo_ctxt.GetPkgPath()) +} - if strings.HasPrefix(pkgPath, mainModule) && (len(pkgPath) == len(mainModule) || pkgPath[len(mainModule)] == '/') { - return true - } - return false +func allowPkgVarTrap(pkgPath string) bool { + return xgo_ctxt.IsPkgMainModule(pkgPath) } func collectVarDecls(declKind DeclKind, names []*syntax.Name, typ syntax.Expr) []*DeclInfo { @@ -47,13 +39,12 @@ func collectVarDecls(declKind DeclKind, names []*syntax.Name, typ syntax.Expr) [ return decls } -func trapVariables(pkgPath string, fileList []*syntax.File, funcDelcs []*DeclInfo) { - names := make(map[string]*DeclInfo, len(funcDelcs)) +func writePkgData(pkgPath string, funcDelcs []*DeclInfo) error { + defer xgo_ctxt.LogSpan("writePkgData")() varNames := make(map[string]*pkgdata.VarInfo) constNames := make(map[string]*pkgdata.ConstInfo) for _, funcDecl := range funcDelcs { identityName := funcDecl.IdentityName() - names[identityName] = funcDecl if funcDecl.Kind == Kind_Var || funcDecl.Kind == Kind_VarPtr { varNames[identityName] = &pkgdata.VarInfo{ Trap: funcDecl.FollowingTrapConst, @@ -69,12 +60,18 @@ func trapVariables(pkgPath string, fileList []*syntax.File, funcDelcs []*DeclInf constNames[identityName] = constInfo } } - err := pkgdata.WritePkgData(pkgPath, &pkgdata.PackageData{ + return pkgdata.WritePkgData(pkgPath, &pkgdata.PackageData{ Consts: constNames, Vars: varNames, }) - if err != nil { - base.Fatalf("write pkg data: %v", err) +} + +func trapVariables(fileList []*syntax.File, funcDelcs []*DeclInfo) { + defer xgo_ctxt.LogSpan("trapVariables")() + names := make(map[string]*DeclInfo, len(funcDelcs)) + for _, funcDecl := range funcDelcs { + identityName := funcDecl.IdentityName() + names[identityName] = funcDecl } // iterate each file, find variable reference, for _, file := range fileList { @@ -636,12 +633,12 @@ func (c *BlockContext) trapValueNode(node *syntax.Name, globaleNames map[string] var rhsAssign *syntax.AssignStmt var isCallArg bool var untypedConstType string - if decl.Kind == Kind_Var || decl.Kind == Kind_VarPtr { + if decl.Kind == info.Kind_Var || decl.Kind == info.Kind_VarPtr { if !decl.FollowingTrapConst && !c.isVarOKToTrap(node) { return node } // good to go - } else if decl.Kind == Kind_Const { + } else if decl.Kind == info.Kind_Const { // untyped const(most cases) should only be used in // several cases because runtime type is unknown if decl.ConstDecl.Type == nil { diff --git a/patch/trap.go b/patch/trap.go index 279b5f25..19b2817a 100644 --- a/patch/trap.go +++ b/patch/trap.go @@ -11,6 +11,7 @@ import ( "cmd/compile/internal/types" xgo_ctxt "cmd/compile/internal/xgo_rewrite_internal/patch/ctxt" + xgo_info "cmd/compile/internal/xgo_rewrite_internal/patch/info" xgo_record "cmd/compile/internal/xgo_rewrite_internal/patch/record" xgo_syntax "cmd/compile/internal/xgo_rewrite_internal/patch/syntax" ) @@ -216,6 +217,18 @@ func InsertTrapForFunc(fn *ir.Func, forGeneric bool) bool { return false } + if isClosure { + action := xgo_ctxt.GetAction(&xgo_info.DeclInfo{ + Kind: xgo_info.Kind_Func, + Name: identityName, + Closure: true, + }) + if action != "" && action != "include" { + // filter + return false + } + } + pkgPath := xgo_ctxt.GetPkgPath() trap := typecheck.LookupRuntime("__xgo_trap") fnPos := fn.Pos() @@ -293,7 +306,6 @@ func CanInsertTrapOrLink(fn *ir.Func) (string, bool) { return "", false } return linkName, false - // ir.Dump("after:", fn) } // disable all stdlib IR rewrite if base.Flag.Std { diff --git a/runtime/core/version.go b/runtime/core/version.go index e2c0cdfc..fdcf5ab6 100644 --- a/runtime/core/version.go +++ b/runtime/core/version.go @@ -7,8 +7,8 @@ import ( ) const VERSION = "1.0.38" -const REVISION = "808e011ba0498b1aaff762095543c880301dc26b+1" -const NUMBER = 249 +const REVISION = "f8e03d3647811f7d4244cbb68369f9e3401e53b9+1" +const NUMBER = 250 // these fields will be filled by compiler const XGO_VERSION = "" diff --git a/support/debug/debug.go b/support/debug/debug.go new file mode 100644 index 00000000..3face955 --- /dev/null +++ b/support/debug/debug.go @@ -0,0 +1,52 @@ +package debug + +import ( + "fmt" + "strings" + + "github.com/xhd2015/xgo/support/strutil" +) + +func FormatDlvPrompt(port int) string { + return FormatDlvPromptOptions(port, nil) +} + +type FormatDlvOptions struct { + VscodeExtra []string +} + +func FormatDlvPromptOptions(port int, opts *FormatDlvOptions) string { + // user need to set breakpoint explicitly + msgs := []string{ + fmt.Sprintf("dlv listen on localhost:%d", port), + fmt.Sprintf("Debug with IDEs:"), + fmt.Sprintf(" > VSCode: add the following config to .vscode/launch.json configurations:"), + fmt.Sprintf("%s", strutil.IndentLines(FormatVscodeRemoteConfig(port), " ")), + } + if opts != nil { + msgs = append(msgs, opts.VscodeExtra...) + } + msgs = append(msgs, []string{ + fmt.Sprintf(" > GoLand: click Add Configuration > Go Remote > localhost:%d", port), + fmt.Sprintf(" > Terminal: dlv connect localhost:%d", port), + }...) + + return strings.Join(msgs, "\n") +} + +func FormatVscodeRemoteConfig(port int) string { + return fmt.Sprintf(`{ + "configurations": [ + { + "name": "Debug dlv localhost:%d", + "type": "go", + "debugAdapter": "dlv-dap", + "request": "attach", + "mode": "remote", + "port": %d, + "host": "127.0.0.1", + "cwd":"./" + } + } +}`, port, port) +} diff --git a/support/goinfo/list.go b/support/goinfo/list.go new file mode 100644 index 00000000..b0d6f7f9 --- /dev/null +++ b/support/goinfo/list.go @@ -0,0 +1,22 @@ +package goinfo + +import ( + "strings" + + "github.com/xhd2015/xgo/support/cmd" +) + +// go list ./pkg +func ListPackages(dir string, mod string, args []string) ([]string, error) { + flags := []string{"list"} + if mod != "" { + flags = append(flags, "-mod="+mod) + } + flags = append(flags, args...) + output, err := cmd.Dir(dir).Output("go", flags...) + if err != nil { + return nil, err + } + lines := strings.Split(output, "\n") + return lines, nil +}