diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index d5a0a320..6fdc7701 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -30,7 +30,7 @@ jobs: - name: Binary Install continue-on-error: false - run: curl -fsSL https://github.com/xhd2015/xgo/raw/master/install.sh | env INSTALL_TO_BIN=true bash -x + run: curl -fsSL https://github.com/xhd2015/xgo/raw/master/script/install/install.sh | env INSTALL_TO_BIN=true bash -x - name: Check Binary Install continue-on-error: false diff --git a/README.md b/README.md index 33b71603..c0088722 100644 --- a/README.md +++ b/README.md @@ -348,7 +348,7 @@ func TestPatchFunc(t *testing.T) { NOTE: `Mock` and `Patch` supports top-level variables and consts, see [runtime/mock/MOCK_VAR_CONST.md](runtime/mock/MOCK_VAR_CONST.md). ## Trace -> Trace might be the most powerful tool provided by xgo, this blog have a more thorough example: https://blog.xhd2015.xyz/posts/xgo-trace_a-powerful-visualization-tool-in-go +> Trace might be the most powerful tool provided by xgo, this blog has a more thorough example: https://blog.xhd2015.xyz/posts/xgo-trace_a-powerful-visualization-tool-in-go It is painful when debugging with a deep call stack. diff --git a/cmd/xgo/help.go b/cmd/xgo/help.go index bba8178e..c363241b 100644 --- a/cmd/xgo/help.go +++ b/cmd/xgo/help.go @@ -24,8 +24,9 @@ Examples: xgo build -o main -gcflags="all=-N -l" ./ build current module with debug flags xgo run ./ run current module xgo test ./... test all test cases of current module + xgo test -run TestSomething --strace ./ test and collect stack trace + xgo tool trace TestSomething.json view collected stack trace xgo exec go version print instrumented go version - xgo tool trace TestSomething.json view test trace See https://github.com/xhd2015/xgo for documentation. diff --git a/cmd/xgo/runtime_gen/core/version.go b/cmd/xgo/runtime_gen/core/version.go index 06c7f429..214ae6dd 100755 --- a/cmd/xgo/runtime_gen/core/version.go +++ b/cmd/xgo/runtime_gen/core/version.go @@ -6,9 +6,9 @@ import ( "os" ) -const VERSION = "1.0.27" -const REVISION = "6ab4a77013b73241451df46df0614dfaceb37a52+1" -const NUMBER = 201 +const VERSION = "1.0.28" +const REVISION = "2eaf6cf94cd17d2888d6708cb76d194b334c8957+1" +const NUMBER = 202 // these fields will be filled by compiler const XGO_VERSION = "" diff --git a/cmd/xgo/trace.go b/cmd/xgo/trace.go index 01f440c4..3e275d59 100644 --- a/cmd/xgo/trace.go +++ b/cmd/xgo/trace.go @@ -262,21 +262,20 @@ func loadDependency(goroot string, goBinary string, goVersion *goinfo.GoVersion, goModReplace := make(map[string]string) if vendorDir != "" { var hasReplace bool - editArgs := []string{"mod", "edit"} // read vendor/modules.txt, vendorInfo, err := goinfo.ParseVendor(vendorDir) if err != nil { return nil, err } + replacedModules := make([]string, 0, len(vendorInfo.VendorList)) // get all modules, convert all deps to replace for _, mod := range vendorInfo.VendorList { - modInfo, ok := vendorInfo.VendorMeta[mod] - if !ok { - continue - } + // modInfo is completely optional + modInfo := vendorInfo.VendorMeta[mod] modPath := mod.Path - if modInfo.Replacement.Path != "" { + if modInfo.Replacement.Path != "" && + !isLocalReplace(modInfo.Replacement.Path) { // !isLocalReplace, see https://github.com/xhd2015/xgo/issues/87#issuecomment-2074722912 modPath = modInfo.Replacement.Path } vendorModPath := filepath.Join(vendorDir, modPath) @@ -284,7 +283,22 @@ func loadDependency(goroot string, goBinary string, goVersion *goinfo.GoVersion, replaceModFile := filepath.Join(tmpProjectDir, vendorModFile) // replace goMod => vendor=> - editArgs = append(editArgs, fmt.Sprintf("-replace=%s=%s", modPath, vendorModPath)) + // NOTE: if replace without require, go will automatically add + // version v0.0.0-00010101000000-000000000000 + // https://stackoverflow.com/questions/58012771/go-mod-fails-to-find-version-v0-0-0-00010101000000-000000000000-of-a-dependency + // + // to suppress this message, always add require + version := mod.Version + if version == "" { + version = "v0.0.0-00010101000000-000000000000" + } + requireCmd := fmt.Sprintf("-require=%s@%s", modPath, version) + + // logDebug("replace %s -> %s", modPath, vendorModPath) + replacedModules = append(replacedModules, + requireCmd, + fmt.Sprintf("-replace=%s=%s", modPath, vendorModPath), + ) hasReplace = true // create placeholder go.mod for each module modGoVersion := modInfo.GoVersion @@ -297,6 +311,7 @@ func loadDependency(goroot string, goBinary string, goVersion *goinfo.GoVersion, } goModReplace[vendorModFile] = replaceModFile } + logDebug("num replaced modules: %d", len(replacedModules)/2) if hasReplace { if false { @@ -313,13 +328,19 @@ func loadDependency(goroot string, goBinary string, goVersion *goinfo.GoVersion, // force use -mod=mod mod = "mod" // force use mod after replaced vendor modfile = tmpGoMod - editArgs = append(editArgs, tmpGoMod) - err = cmd.Env([]string{ - "GOROOT=" + goroot, - }).Run(goBinary, editArgs...) - if err != nil { - return nil, err + for _, replaceModuleGroup := range splitArgsBatch(replacedModules, 100) { + editArgs := make([]string, 0, 3+len(replaceModuleGroup)) + editArgs = append(editArgs, "mod", "edit") + editArgs = append(editArgs, replaceModuleGroup...) + editArgs = append(editArgs, tmpGoMod) + err = cmd.Env([]string{ + "GOROOT=" + goroot, + }).Run(goBinary, editArgs...) + if err != nil { + return nil, err + } } + } } @@ -344,6 +365,52 @@ func loadDependency(goroot string, goBinary string, goVersion *goinfo.GoVersion, }, nil } +// system may have limit on single go command +func splitArgsBatch(args []string, limit int) [][]string { + if len(args) <= limit { + return [][]string{args} + } + n := len(args) / limit + remain := len(args) - n*limit + bcap := n + if remain > 0 { + bcap++ + } + groups := make([][]string, bcap) + off := 0 + for i := 0; i < n; i++ { + group := make([]string, limit) + for j := 0; j < limit; j++ { + group[j] = args[off] + off++ + } + groups[i] = group + } + if remain > 0 { + group := make([]string, remain) + for i := 0; i < remain; i++ { + group[i] = args[off] + off++ + } + groups[bcap-1] = group + } + return groups +} + +func isLocalReplace(modPath string) bool { + // ./ or ../ + if modPath == "." || modPath == ".." { + return true + } + if strings.HasPrefix(modPath, "./") { + return true + } + if strings.HasPrefix(modPath, "../") { + return true + } + return false +} + func createOverlayFile(tmpProjectDir string, replace map[string]string) (string, error) { overlay := Overlay{Replace: replace} overlayData, err := json.Marshal(overlay) diff --git a/cmd/xgo/version.go b/cmd/xgo/version.go index 31baf20b..8fa85853 100644 --- a/cmd/xgo/version.go +++ b/cmd/xgo/version.go @@ -2,9 +2,9 @@ package main import "fmt" -const VERSION = "1.0.27" -const REVISION = "6ab4a77013b73241451df46df0614dfaceb37a52+1" -const NUMBER = 201 +const VERSION = "1.0.28" +const REVISION = "2eaf6cf94cd17d2888d6708cb76d194b334c8957+1" +const NUMBER = 202 func getRevision() string { revSuffix := "" diff --git a/runtime/core/version.go b/runtime/core/version.go index 06c7f429..214ae6dd 100644 --- a/runtime/core/version.go +++ b/runtime/core/version.go @@ -6,9 +6,9 @@ import ( "os" ) -const VERSION = "1.0.27" -const REVISION = "6ab4a77013b73241451df46df0614dfaceb37a52+1" -const NUMBER = 201 +const VERSION = "1.0.28" +const REVISION = "2eaf6cf94cd17d2888d6708cb76d194b334c8957+1" +const NUMBER = 202 // these fields will be filled by compiler const XGO_VERSION = "" diff --git a/runtime/test/trace_without_dep_vendor/hook_test.go b/runtime/test/trace_without_dep_vendor/hook_test.go index a1ff56a1..6202edc7 100644 --- a/runtime/test/trace_without_dep_vendor/hook_test.go +++ b/runtime/test/trace_without_dep_vendor/hook_test.go @@ -1,7 +1,7 @@ //go:build ignore // +build ignore -package trace_without_dep +package trace_without_dep_vendor import ( "os" diff --git a/runtime/test/trace_without_dep_vendor/trace_test.go b/runtime/test/trace_without_dep_vendor/trace_test.go index 2d84387e..a8658c3a 100644 --- a/runtime/test/trace_without_dep_vendor/trace_test.go +++ b/runtime/test/trace_without_dep_vendor/trace_test.go @@ -1,4 +1,4 @@ -package trace_without_dep +package trace_without_dep_vendor import ( "testing" diff --git a/runtime/test/trace_without_dep_vendor_replace/go.mod b/runtime/test/trace_without_dep_vendor_replace/go.mod new file mode 100644 index 00000000..a83f01b9 --- /dev/null +++ b/runtime/test/trace_without_dep_vendor_replace/go.mod @@ -0,0 +1,7 @@ +module github.com/xhd2015/xgo/runtime/test/trace_without_dep_vendor_replace + +go 1.14 + +require github.com/xhd2015/xgo/runtime/test/trace_without_dep_vendor_replace/lib v1.0.0 + +replace github.com/xhd2015/xgo/runtime/test/trace_without_dep_vendor_replace/lib => ./lib diff --git a/runtime/test/trace_without_dep_vendor_replace/hook_test.go b/runtime/test/trace_without_dep_vendor_replace/hook_test.go new file mode 100644 index 00000000..6211ea4a --- /dev/null +++ b/runtime/test/trace_without_dep_vendor_replace/hook_test.go @@ -0,0 +1,25 @@ +//go:build ignore +// +build ignore + +package trace_without_dep_vendor_replace + +import ( + "os" + "testing" +) + +const traceFile = "TestGreet.json" + +func TestPreCheck(t *testing.T) { + err := os.RemoveAll(traceFile) + if err != nil { + t.Fatal(err) + } +} + +func TestPostCheck(t *testing.T) { + _, err := os.Stat(traceFile) + if err != nil { + t.Fatalf("expect %s, actual: %v", traceFile, err) + } +} diff --git a/runtime/test/trace_without_dep_vendor_replace/lib/go.mod b/runtime/test/trace_without_dep_vendor_replace/lib/go.mod new file mode 100644 index 00000000..6181c63d --- /dev/null +++ b/runtime/test/trace_without_dep_vendor_replace/lib/go.mod @@ -0,0 +1,3 @@ +module github.com/xhd2015/xgo/runtime/test/trace_without_dep_vendor_replace/lib2 + +go 1.14 diff --git a/runtime/test/trace_without_dep_vendor_replace/lib/lib.go b/runtime/test/trace_without_dep_vendor_replace/lib/lib.go new file mode 100644 index 00000000..0005d66e --- /dev/null +++ b/runtime/test/trace_without_dep_vendor_replace/lib/lib.go @@ -0,0 +1,5 @@ +package lib + +func Greet(s string) string { + return "hello " + s +} diff --git a/runtime/test/trace_without_dep_vendor_replace/trace_test.go b/runtime/test/trace_without_dep_vendor_replace/trace_test.go new file mode 100644 index 00000000..667c8f18 --- /dev/null +++ b/runtime/test/trace_without_dep_vendor_replace/trace_test.go @@ -0,0 +1,14 @@ +package trace_without_dep_vendor_replace + +import ( + "testing" + + "github.com/xhd2015/xgo/runtime/test/trace_without_dep_vendor_replace/lib" +) + +func TestGreet(t *testing.T) { + result := lib.Greet("world") + if result != "hello world" { + t.Fatalf("result: %s", result) + } +} diff --git a/runtime/test/trace_without_dep_vendor_replace/vendor/github.com/xhd2015/xgo/runtime/test/trace_without_dep_vendor_replace/lib/go.mod b/runtime/test/trace_without_dep_vendor_replace/vendor/github.com/xhd2015/xgo/runtime/test/trace_without_dep_vendor_replace/lib/go.mod new file mode 100644 index 00000000..6181c63d --- /dev/null +++ b/runtime/test/trace_without_dep_vendor_replace/vendor/github.com/xhd2015/xgo/runtime/test/trace_without_dep_vendor_replace/lib/go.mod @@ -0,0 +1,3 @@ +module github.com/xhd2015/xgo/runtime/test/trace_without_dep_vendor_replace/lib2 + +go 1.14 diff --git a/runtime/test/trace_without_dep_vendor_replace/vendor/github.com/xhd2015/xgo/runtime/test/trace_without_dep_vendor_replace/lib/lib.go b/runtime/test/trace_without_dep_vendor_replace/vendor/github.com/xhd2015/xgo/runtime/test/trace_without_dep_vendor_replace/lib/lib.go new file mode 100644 index 00000000..0005d66e --- /dev/null +++ b/runtime/test/trace_without_dep_vendor_replace/vendor/github.com/xhd2015/xgo/runtime/test/trace_without_dep_vendor_replace/lib/lib.go @@ -0,0 +1,5 @@ +package lib + +func Greet(s string) string { + return "hello " + s +} diff --git a/runtime/test/trace_without_dep_vendor_replace/vendor/modules.txt b/runtime/test/trace_without_dep_vendor_replace/vendor/modules.txt new file mode 100644 index 00000000..fe945f17 --- /dev/null +++ b/runtime/test/trace_without_dep_vendor_replace/vendor/modules.txt @@ -0,0 +1,4 @@ +# github.com/xhd2015/xgo/runtime/test/trace_without_dep_vendor_replace/lib v1.0.0 => ./lib +## explicit +github.com/xhd2015/xgo/runtime/test/trace_without_dep_vendor_replace/lib +# github.com/xhd2015/xgo/runtime/test/trace_without_dep_vendor_replace/lib => ./lib diff --git a/script/install/install.sh b/script/install/install.sh index 1a630620..ecdea4a3 100644 --- a/script/install/install.sh +++ b/script/install/install.sh @@ -74,6 +74,10 @@ curl --fail --location --progress-bar --output "${tmp_dir}/${file}" "$uri" || er ) if [[ "$INSTALL_TO_BIN" == "true" ]];then + # install fails if target already exists + if [[ -f /usr/local/bin/xgo ]];then + mv /usr/local/bin/{xgo,xgo_backup} + fi install "$bin_dir/xgo" /usr/local/bin else if [[ -f ~/.bash_profile ]];then diff --git a/script/run-test/main.go b/script/run-test/main.go index 293df90c..55c4de33 100644 --- a/script/run-test/main.go +++ b/script/run-test/main.go @@ -79,6 +79,12 @@ var extraSubTests = []*TestCase{ dir: "runtime/test/trace_without_dep_vendor", flags: []string{"--strace"}, }, + { + // see https://github.com/xhd2015/xgo/issues/87 + name: "trace_without_dep_vendor_replace", + dir: "runtime/test/trace_without_dep_vendor_replace", + flags: []string{"--strace"}, + }, { name: "trap_with_overlay", dir: "runtime/test/trap_with_overlay", diff --git a/support/goinfo/vendor_parse_test.go b/support/goinfo/vendor_parse_test.go index f977aa0e..4113140a 100644 --- a/support/goinfo/vendor_parse_test.go +++ b/support/goinfo/vendor_parse_test.go @@ -1,6 +1,10 @@ package goinfo -import "testing" +import ( + "os" + "strings" + "testing" +) func TestParseVendorInfo(t *testing.T) { // NOTE: in vendor, replacing module have no @@ -29,3 +33,33 @@ git.some/x1/y1/mark } t.Logf("%v", info) } + +func TestDebugParseCustomVendor(t *testing.T) { + t.Skipf("debug only") + + debugPkg := "test" + file := os.Getenv("TEST_DEBUG_FILE") + + vendorInfo, err := ParseVendor(file) + if err != nil { + t.Fatal(err) + } + + for _, mod := range vendorInfo.VendorList { + if strings.HasPrefix(debugPkg, mod.Path) { + t.Logf("VendorList found mod: %s %s", mod.Path, mod.Version) + } + } + for _, mod := range vendorInfo.VendorReplaced { + if strings.HasPrefix(debugPkg, mod.Path) { + t.Logf("VendorReplaced found mod: %s %s", mod.Path, mod.Version) + } + } + for mod, meta := range vendorInfo.VendorMeta { + if strings.HasPrefix(debugPkg, mod.Path) { + t.Logf("VendorMeta found mod: %s %s, meta: %+v", mod.Path, mod.Version, meta) + } + } + modVersion := vendorInfo.VendorVersion[debugPkg] + t.Logf("VendorVersion: %s", modVersion) +}