From 6df3668f534b57c7a87450251efad733e3211e44 Mon Sep 17 00:00:00 2001 From: Hyeon Kim Date: Mon, 30 Aug 2021 01:36:28 +0900 Subject: [PATCH 1/8] Parse JSON output of 'go list' It's more robust in this way --- cmd/main.go | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/cmd/main.go b/cmd/main.go index f8e9772..d55e3e6 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -16,6 +16,7 @@ package xcaddycmd import ( "context" + "encoding/json" "fmt" "log" "os" @@ -165,23 +166,20 @@ func getCaddyOutputFile() string { func runDev(ctx context.Context, args []string) error { binOutput := getCaddyOutputFile() - // get current/main module name - cmd := exec.Command("go", "list", "-m") + // get current/main module name and the root directory of the main module + cmd := exec.Command("go", "list", "-m", "-json") cmd.Stderr = os.Stderr out, err := cmd.Output() if err != nil { return fmt.Errorf("exec %v: %v: %s", cmd.Args, err, string(out)) } - currentModule := strings.TrimSpace(string(out)) - - // get the root directory of the main module - cmd = exec.Command("go", "list", "-m", "-f={{.Dir}}") - cmd.Stderr = os.Stderr - out, err = cmd.Output() + var mainModule map[string]interface{} + err = json.Unmarshal(out, &mainModule) if err != nil { - return fmt.Errorf("exec %v: %v: %s", cmd.Args, err, string(out)) + return fmt.Errorf("json parse error: %v", err) } - moduleDir := strings.TrimSpace(string(out)) + currentModule := mainModule["Path"].(string) + moduleDir := mainModule["Dir"].(string) // make sure the module being developed is replaced // so that the local copy is used From 4ecef2c5802d4c5a915e7ba7900317bde90687c9 Mon Sep 17 00:00:00 2001 From: Hyeon Kim Date: Mon, 30 Aug 2021 02:15:44 +0900 Subject: [PATCH 2/8] Handle all possible replacements properly for runDev() 1. Handle all possible replacements properly for runDev() Replacement targets are not always paths. Reference: https://pkg.go.dev/cmd/go/internal/list#pkg-variables 2. Parse replacement info from 'go list -m -json all' It's more robust in this way --- cmd/main.go | 40 +++++++++++++++++++++++++++++++--------- 1 file changed, 31 insertions(+), 9 deletions(-) diff --git a/cmd/main.go b/cmd/main.go index d55e3e6..6728a02 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -15,9 +15,11 @@ package xcaddycmd import ( + "bytes" "context" "encoding/json" "fmt" + "io" "log" "os" "os/exec" @@ -191,25 +193,45 @@ func runDev(ctx context.Context, args []string) error { // and since this tool is a carry-through for the user's actual // go.mod, we need to transfer their replace directives through // to the one we're making - cmd = exec.Command("go", "list", "-m", "-f={{if .Replace}}{{.Path}}=>{{.Replace}}{{end}}", "all") + cmd = exec.Command("go", "list", "-m", "-json", "all") cmd.Stderr = os.Stderr out, err = cmd.Output() if err != nil { return fmt.Errorf("exec %v: %v: %s", cmd.Args, err, string(out)) } - for _, line := range strings.Split(string(out), "\n") { - parts := strings.Split(line, "=>") - if len(parts) != 2 || parts[0] == "" || parts[1] == "" { + decoder := json.NewDecoder(bytes.NewReader(out)) + for { + var mod map[string]interface{} + if err := decoder.Decode(&mod); err == io.EOF { + break + } else if err != nil { + return fmt.Errorf("json parse error: %v", err) + } + rep, ok := mod["Replace"].(map[string]interface{}) + if !ok { continue } - // adjust relative replacements in original module since our temporary module is in a different directory - if !filepath.IsAbs(parts[1]) { - parts[1] = filepath.Join(moduleDir, parts[1]) - log.Printf("[INFO] Resolved relative replacement %s to %s", line, parts[1]) + srcPath := mod["Path"].(string) + srcVersion := mod["Version"].(string) + src := srcPath + "@" + srcVersion + + // 1. Target is module, version is required in this case + // 2A. Target is absolute path + // 2B. Target is relative path, proper handling is required in this case + dstPath := rep["Path"].(string) + dstVersion, isTargetModule := rep["Version"].(string) + var dst string + if isTargetModule { + dst = dstPath + "@" + dstVersion + } else if filepath.IsAbs(dstPath) { + dst = dstPath + } else { + dst = filepath.Join(moduleDir, dstPath) + log.Printf("[INFO] Resolved relative replacement %s to %s", dstPath, dst) } - replacements = append(replacements, xcaddy.NewReplace(parts[0], parts[1])) + replacements = append(replacements, xcaddy.NewReplace(src, dst)) } // reconcile remaining path segments; for example if a module foo/a From 05fa6e3e241cf7e945509912a2b4cb1c94aea072 Mon Sep 17 00:00:00 2001 From: Hyeon Kim Date: Tue, 31 Aug 2021 04:38:48 +0900 Subject: [PATCH 3/8] Test the 'go list -m -json all' parsing logic --- cmd/main.go | 84 +++++++++++++++++++++++++++--------------------- cmd/main_test.go | 80 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 127 insertions(+), 37 deletions(-) diff --git a/cmd/main.go b/cmd/main.go index 6728a02..c262611 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -185,10 +185,7 @@ func runDev(ctx context.Context, args []string) error { // make sure the module being developed is replaced // so that the local copy is used - replacements := []xcaddy.Replace{ - xcaddy.NewReplace(currentModule, moduleDir), - } - + // // replace directives only apply to the top-level/main go.mod, // and since this tool is a carry-through for the user's actual // go.mod, we need to transfer their replace directives through @@ -199,39 +196,9 @@ func runDev(ctx context.Context, args []string) error { if err != nil { return fmt.Errorf("exec %v: %v: %s", cmd.Args, err, string(out)) } - decoder := json.NewDecoder(bytes.NewReader(out)) - for { - var mod map[string]interface{} - if err := decoder.Decode(&mod); err == io.EOF { - break - } else if err != nil { - return fmt.Errorf("json parse error: %v", err) - } - rep, ok := mod["Replace"].(map[string]interface{}) - if !ok { - continue - } - - srcPath := mod["Path"].(string) - srcVersion := mod["Version"].(string) - src := srcPath + "@" + srcVersion - - // 1. Target is module, version is required in this case - // 2A. Target is absolute path - // 2B. Target is relative path, proper handling is required in this case - dstPath := rep["Path"].(string) - dstVersion, isTargetModule := rep["Version"].(string) - var dst string - if isTargetModule { - dst = dstPath + "@" + dstVersion - } else if filepath.IsAbs(dstPath) { - dst = dstPath - } else { - dst = filepath.Join(moduleDir, dstPath) - log.Printf("[INFO] Resolved relative replacement %s to %s", dstPath, dst) - } - - replacements = append(replacements, xcaddy.NewReplace(src, dst)) + replacements, err := parseGoListJson(currentModule, moduleDir, out) + if err != nil { + return fmt.Errorf("json parse error: %v", err) } // reconcile remaining path segments; for example if a module foo/a @@ -297,6 +264,49 @@ func runDev(ctx context.Context, args []string) error { return cmd.Wait() } +func parseGoListJson(currentModule, moduleDir string, out []byte) ([]xcaddy.Replace, error) { + replacements := []xcaddy.Replace{ + xcaddy.NewReplace(currentModule, moduleDir), + } + + decoder := json.NewDecoder(bytes.NewReader(out)) + for { + var mod map[string]interface{} + if err := decoder.Decode(&mod); err == io.EOF { + break + } else if err != nil { + return nil, err + } + rep, ok := mod["Replace"].(map[string]interface{}) + if !ok { + continue + } + + srcPath := mod["Path"].(string) + srcVersion := mod["Version"].(string) + src := srcPath + "@" + srcVersion + + // 1. Target is module, version is required in this case + // 2A. Target is absolute path + // 2B. Target is relative path, proper handling is required in this case + dstPath := rep["Path"].(string) + dstVersion, isTargetModule := rep["Version"].(string) + var dst string + if isTargetModule { + dst = dstPath + "@" + dstVersion + } else if filepath.IsAbs(dstPath) { + dst = dstPath + } else { + dst = filepath.Join(moduleDir, dstPath) + log.Printf("[INFO] Resolved relative replacement %s to %s", dstPath, dst) + } + + replacements = append(replacements, xcaddy.NewReplace(src, dst)) + } + + return replacements, nil +} + func normalizeImportPath(currentModule, cwd, moduleDir string) string { return path.Join(currentModule, filepath.ToSlash(strings.TrimPrefix(cwd, moduleDir))) } diff --git a/cmd/main_test.go b/cmd/main_test.go index b911be2..626f216 100644 --- a/cmd/main_test.go +++ b/cmd/main_test.go @@ -1,10 +1,90 @@ package xcaddycmd import ( + "reflect" "runtime" "testing" + + "github.com/caddyserver/xcaddy" ) +func TestParseGoListJson(t *testing.T) { + replacements, err := parseGoListJson("github.com/simnalamburt/module", "/home/work/module", []byte(` +{ + "Path": "github.com/simnalamburt/module", + "Main": true, + "Dir": "/home/work/module", + "GoMod": "/home/work/module/go.mod", + "GoVersion": "1.17" +} +{ + "Path": "replacetest1", + "Version": "v1.2.3", + "Replace": { + "Path": "golang.org/x/example", + "Version": "v0.0.0-20210811190340-787a929d5a0d", + "Time": "2021-08-11T19:03:40Z", + "GoMod": "/home/simnalamburt/.go/pkg/mod/cache/download/golang.org/x/example/@v/v0.0.0-20210811190340-787a929d5a0d.mod", + "GoVersion": "1.15" + }, + "GoMod": "/home/simnalamburt/.go/pkg/mod/cache/download/golang.org/x/example/@v/v0.0.0-20210811190340-787a929d5a0d.mod", + "GoVersion": "1.15" +} +{ + "Path": "replacetest2", + "Version": "v0.0.1", + "Replace": { + "Path": "golang.org/x/example", + "Version": "v0.0.0-20210407023211-09c3a5e06b5d", + "Time": "2021-04-07T02:32:11Z", + "GoMod": "/home/simnalamburt/.go/pkg/mod/cache/download/golang.org/x/example/@v/v0.0.0-20210407023211-09c3a5e06b5d.mod", + "GoVersion": "1.15" + }, + "GoMod": "/home/simnalamburt/.go/pkg/mod/cache/download/golang.org/x/example/@v/v0.0.0-20210407023211-09c3a5e06b5d.mod", + "GoVersion": "1.15" +} +{ + "Path": "replacetest3", + "Version": "v1.2.3", + "Replace": { + "Path": "./fork1", + "Dir": "/home/work/module/fork1", + "GoMod": "/home/work/module/fork1/go.mod", + "GoVersion": "1.17" + }, + "Dir": "/home/work/module/fork1", + "GoMod": "/home/work/module/fork1/go.mod", + "GoVersion": "1.17" +} +{ + "Path": "replacetest4", + "Version": "v0.0.1", + "Replace": { + "Path": "/srv/fork2", + "Dir": "/home/work/module/fork2", + "GoMod": "/home/work/module/fork2/go.mod", + "GoVersion": "1.17" + }, + "Dir": "/home/work/module/fork2", + "GoMod": "/home/work/module/fork2/go.mod", + "GoVersion": "1.17" +} +`)) + if err != nil { + t.Errorf("Error occured during JSON parsing") + } + expected := []xcaddy.Replace{ + xcaddy.NewReplace("github.com/simnalamburt/module", "/home/work/module"), + xcaddy.NewReplace("replacetest1@v1.2.3", "golang.org/x/example@v0.0.0-20210811190340-787a929d5a0d"), + xcaddy.NewReplace("replacetest2@v0.0.1", "golang.org/x/example@v0.0.0-20210407023211-09c3a5e06b5d"), + xcaddy.NewReplace("replacetest3@v1.2.3", "/home/work/module/fork1"), + xcaddy.NewReplace("replacetest4@v0.0.1", "/srv/fork2"), + } + if !reflect.DeepEqual(replacements, expected) { + t.Errorf("Expected replacements '%v' but got '%v'", expected, replacements) + } +} + func TestSplitWith(t *testing.T) { for i, tc := range []struct { input string From 6d2101701477b58d07f48f8d015cfcce45faaad6 Mon Sep 17 00:00:00 2001 From: Hyeon Kim Date: Tue, 31 Aug 2021 05:33:12 +0900 Subject: [PATCH 4/8] Call 'go list' command only once `go list -m -json all` contains all informations we need so we don't need extra `go list -m -json` call. --- cmd/main.go | 63 +++++++++++++++++++++++++++--------------------- cmd/main_test.go | 38 ++++++++++++++++++++++------- 2 files changed, 65 insertions(+), 36 deletions(-) diff --git a/cmd/main.go b/cmd/main.go index c262611..f9dbdd1 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -169,20 +169,7 @@ func runDev(ctx context.Context, args []string) error { binOutput := getCaddyOutputFile() // get current/main module name and the root directory of the main module - cmd := exec.Command("go", "list", "-m", "-json") - cmd.Stderr = os.Stderr - out, err := cmd.Output() - if err != nil { - return fmt.Errorf("exec %v: %v: %s", cmd.Args, err, string(out)) - } - var mainModule map[string]interface{} - err = json.Unmarshal(out, &mainModule) - if err != nil { - return fmt.Errorf("json parse error: %v", err) - } - currentModule := mainModule["Path"].(string) - moduleDir := mainModule["Dir"].(string) - + // // make sure the module being developed is replaced // so that the local copy is used // @@ -190,13 +177,13 @@ func runDev(ctx context.Context, args []string) error { // and since this tool is a carry-through for the user's actual // go.mod, we need to transfer their replace directives through // to the one we're making - cmd = exec.Command("go", "list", "-m", "-json", "all") + cmd := exec.Command("go", "list", "-m", "-json", "all") cmd.Stderr = os.Stderr - out, err = cmd.Output() + out, err := cmd.Output() if err != nil { return fmt.Errorf("exec %v: %v: %s", cmd.Args, err, string(out)) } - replacements, err := parseGoListJson(currentModule, moduleDir, out) + currentModule, moduleDir, replacements, err := parseGoListJson(out) if err != nil { return fmt.Errorf("json parse error: %v", err) } @@ -264,19 +251,30 @@ func runDev(ctx context.Context, args []string) error { return cmd.Wait() } -func parseGoListJson(currentModule, moduleDir string, out []byte) ([]xcaddy.Replace, error) { - replacements := []xcaddy.Replace{ - xcaddy.NewReplace(currentModule, moduleDir), - } +func parseGoListJson(out []byte) (currentModule, moduleDir string, replacements []xcaddy.Replace, err error) { + var unjoinedReplaces []int decoder := json.NewDecoder(bytes.NewReader(out)) for { var mod map[string]interface{} - if err := decoder.Decode(&mod); err == io.EOF { + if err = decoder.Decode(&mod); err == io.EOF { + err = nil break } else if err != nil { - return nil, err + return + } + + is_main, ok := mod["Main"].(bool) + if ok && is_main { + // Current module is main module, retrieve the main module name and + // root directory path of the main module + currentModule = mod["Path"].(string) + moduleDir = mod["Dir"].(string) + replacements = append(replacements, xcaddy.NewReplace(currentModule, moduleDir)) + continue } + + // Skip if current module is not replacement rep, ok := mod["Replace"].(map[string]interface{}) if !ok { continue @@ -297,14 +295,25 @@ func parseGoListJson(currentModule, moduleDir string, out []byte) ([]xcaddy.Repl } else if filepath.IsAbs(dstPath) { dst = dstPath } else { - dst = filepath.Join(moduleDir, dstPath) - log.Printf("[INFO] Resolved relative replacement %s to %s", dstPath, dst) + if moduleDir != "" { + dst = filepath.Join(moduleDir, dstPath) + log.Printf("[INFO] Resolved relative replacement %s to %s", dstPath, dst) + } else { + // moduleDir is not parsed yet, defer to later + dst = dstPath + unjoinedReplaces = append(unjoinedReplaces, len(replacements)) + } } replacements = append(replacements, xcaddy.NewReplace(src, dst)) } - - return replacements, nil + for _, idx := range unjoinedReplaces { + unresolved := string(replacements[idx].New) + resolved := filepath.Join(moduleDir, unresolved) + log.Printf("[INFO] Resolved relative replacement %s to %s", unresolved, resolved) + replacements[idx].New = xcaddy.ReplacementPath(resolved) + } + return } func normalizeImportPath(currentModule, cwd, moduleDir string) string { diff --git a/cmd/main_test.go b/cmd/main_test.go index 626f216..37ee422 100644 --- a/cmd/main_test.go +++ b/cmd/main_test.go @@ -9,14 +9,7 @@ import ( ) func TestParseGoListJson(t *testing.T) { - replacements, err := parseGoListJson("github.com/simnalamburt/module", "/home/work/module", []byte(` -{ - "Path": "github.com/simnalamburt/module", - "Main": true, - "Dir": "/home/work/module", - "GoMod": "/home/work/module/go.mod", - "GoVersion": "1.17" -} + currentModule, moduleDir, replacements, err := parseGoListJson([]byte(` { "Path": "replacetest1", "Version": "v1.2.3", @@ -56,6 +49,13 @@ func TestParseGoListJson(t *testing.T) { "GoMod": "/home/work/module/fork1/go.mod", "GoVersion": "1.17" } +{ + "Path": "github.com/simnalamburt/module", + "Main": true, + "Dir": "/home/work/module", + "GoMod": "/home/work/module/go.mod", + "GoVersion": "1.17" +} { "Path": "replacetest4", "Version": "v0.0.1", @@ -69,16 +69,36 @@ func TestParseGoListJson(t *testing.T) { "GoMod": "/home/work/module/fork2/go.mod", "GoVersion": "1.17" } +{ + "Path": "replacetest5", + "Version": "v1.2.3", + "Replace": { + "Path": "./fork3", + "Dir": "/home/work/module/fork3", + "GoMod": "/home/work/module/fork3/go.mod", + "GoVersion": "1.17" + }, + "Dir": "/home/work/module/fork3", + "GoMod": "/home/work/module/fork3/go.mod", + "GoVersion": "1.17" +} `)) if err != nil { t.Errorf("Error occured during JSON parsing") } + if currentModule != "github.com/simnalamburt/module" { + t.Errorf("Unexpected module name") + } + if moduleDir != "/home/work/module" { + t.Errorf("Unexpected module path") + } expected := []xcaddy.Replace{ - xcaddy.NewReplace("github.com/simnalamburt/module", "/home/work/module"), xcaddy.NewReplace("replacetest1@v1.2.3", "golang.org/x/example@v0.0.0-20210811190340-787a929d5a0d"), xcaddy.NewReplace("replacetest2@v0.0.1", "golang.org/x/example@v0.0.0-20210407023211-09c3a5e06b5d"), xcaddy.NewReplace("replacetest3@v1.2.3", "/home/work/module/fork1"), + xcaddy.NewReplace("github.com/simnalamburt/module", "/home/work/module"), xcaddy.NewReplace("replacetest4@v0.0.1", "/srv/fork2"), + xcaddy.NewReplace("replacetest5@v1.2.3", "/home/work/module/fork3"), } if !reflect.DeepEqual(replacements, expected) { t.Errorf("Expected replacements '%v' but got '%v'", expected, replacements) From acf8319b09959e59a2624c392eb486a4faecbcdd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hyeon=20Kim=20=28=EA=B9=80=EC=A7=80=ED=98=84=29?= Date: Wed, 1 Sep 2021 05:49:54 +0900 Subject: [PATCH 5/8] main_windows_test.go: Make tests for Windows --- cmd/main_test.go | 100 ----------------------------------- cmd/main_unix_test.go | 106 +++++++++++++++++++++++++++++++++++++ cmd/main_windows_test.go | 110 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 216 insertions(+), 100 deletions(-) create mode 100644 cmd/main_unix_test.go create mode 100644 cmd/main_windows_test.go diff --git a/cmd/main_test.go b/cmd/main_test.go index 37ee422..b911be2 100644 --- a/cmd/main_test.go +++ b/cmd/main_test.go @@ -1,110 +1,10 @@ package xcaddycmd import ( - "reflect" "runtime" "testing" - - "github.com/caddyserver/xcaddy" ) -func TestParseGoListJson(t *testing.T) { - currentModule, moduleDir, replacements, err := parseGoListJson([]byte(` -{ - "Path": "replacetest1", - "Version": "v1.2.3", - "Replace": { - "Path": "golang.org/x/example", - "Version": "v0.0.0-20210811190340-787a929d5a0d", - "Time": "2021-08-11T19:03:40Z", - "GoMod": "/home/simnalamburt/.go/pkg/mod/cache/download/golang.org/x/example/@v/v0.0.0-20210811190340-787a929d5a0d.mod", - "GoVersion": "1.15" - }, - "GoMod": "/home/simnalamburt/.go/pkg/mod/cache/download/golang.org/x/example/@v/v0.0.0-20210811190340-787a929d5a0d.mod", - "GoVersion": "1.15" -} -{ - "Path": "replacetest2", - "Version": "v0.0.1", - "Replace": { - "Path": "golang.org/x/example", - "Version": "v0.0.0-20210407023211-09c3a5e06b5d", - "Time": "2021-04-07T02:32:11Z", - "GoMod": "/home/simnalamburt/.go/pkg/mod/cache/download/golang.org/x/example/@v/v0.0.0-20210407023211-09c3a5e06b5d.mod", - "GoVersion": "1.15" - }, - "GoMod": "/home/simnalamburt/.go/pkg/mod/cache/download/golang.org/x/example/@v/v0.0.0-20210407023211-09c3a5e06b5d.mod", - "GoVersion": "1.15" -} -{ - "Path": "replacetest3", - "Version": "v1.2.3", - "Replace": { - "Path": "./fork1", - "Dir": "/home/work/module/fork1", - "GoMod": "/home/work/module/fork1/go.mod", - "GoVersion": "1.17" - }, - "Dir": "/home/work/module/fork1", - "GoMod": "/home/work/module/fork1/go.mod", - "GoVersion": "1.17" -} -{ - "Path": "github.com/simnalamburt/module", - "Main": true, - "Dir": "/home/work/module", - "GoMod": "/home/work/module/go.mod", - "GoVersion": "1.17" -} -{ - "Path": "replacetest4", - "Version": "v0.0.1", - "Replace": { - "Path": "/srv/fork2", - "Dir": "/home/work/module/fork2", - "GoMod": "/home/work/module/fork2/go.mod", - "GoVersion": "1.17" - }, - "Dir": "/home/work/module/fork2", - "GoMod": "/home/work/module/fork2/go.mod", - "GoVersion": "1.17" -} -{ - "Path": "replacetest5", - "Version": "v1.2.3", - "Replace": { - "Path": "./fork3", - "Dir": "/home/work/module/fork3", - "GoMod": "/home/work/module/fork3/go.mod", - "GoVersion": "1.17" - }, - "Dir": "/home/work/module/fork3", - "GoMod": "/home/work/module/fork3/go.mod", - "GoVersion": "1.17" -} -`)) - if err != nil { - t.Errorf("Error occured during JSON parsing") - } - if currentModule != "github.com/simnalamburt/module" { - t.Errorf("Unexpected module name") - } - if moduleDir != "/home/work/module" { - t.Errorf("Unexpected module path") - } - expected := []xcaddy.Replace{ - xcaddy.NewReplace("replacetest1@v1.2.3", "golang.org/x/example@v0.0.0-20210811190340-787a929d5a0d"), - xcaddy.NewReplace("replacetest2@v0.0.1", "golang.org/x/example@v0.0.0-20210407023211-09c3a5e06b5d"), - xcaddy.NewReplace("replacetest3@v1.2.3", "/home/work/module/fork1"), - xcaddy.NewReplace("github.com/simnalamburt/module", "/home/work/module"), - xcaddy.NewReplace("replacetest4@v0.0.1", "/srv/fork2"), - xcaddy.NewReplace("replacetest5@v1.2.3", "/home/work/module/fork3"), - } - if !reflect.DeepEqual(replacements, expected) { - t.Errorf("Expected replacements '%v' but got '%v'", expected, replacements) - } -} - func TestSplitWith(t *testing.T) { for i, tc := range []struct { input string diff --git a/cmd/main_unix_test.go b/cmd/main_unix_test.go new file mode 100644 index 0000000..c05646e --- /dev/null +++ b/cmd/main_unix_test.go @@ -0,0 +1,106 @@ +//go:build !windows +package xcaddycmd + +import ( + "reflect" + "testing" + + "github.com/caddyserver/xcaddy" +) + +func TestParseGoListJson(t *testing.T) { + currentModule, moduleDir, replacements, err := parseGoListJson([]byte(` +{ + "Path": "replacetest1", + "Version": "v1.2.3", + "Replace": { + "Path": "golang.org/x/example", + "Version": "v0.0.0-20210811190340-787a929d5a0d", + "Time": "2021-08-11T19:03:40Z", + "GoMod": "/home/simnalamburt/.go/pkg/mod/cache/download/golang.org/x/example/@v/v0.0.0-20210811190340-787a929d5a0d.mod", + "GoVersion": "1.15" + }, + "GoMod": "/home/simnalamburt/.go/pkg/mod/cache/download/golang.org/x/example/@v/v0.0.0-20210811190340-787a929d5a0d.mod", + "GoVersion": "1.15" +} +{ + "Path": "replacetest2", + "Version": "v0.0.1", + "Replace": { + "Path": "golang.org/x/example", + "Version": "v0.0.0-20210407023211-09c3a5e06b5d", + "Time": "2021-04-07T02:32:11Z", + "GoMod": "/home/simnalamburt/.go/pkg/mod/cache/download/golang.org/x/example/@v/v0.0.0-20210407023211-09c3a5e06b5d.mod", + "GoVersion": "1.15" + }, + "GoMod": "/home/simnalamburt/.go/pkg/mod/cache/download/golang.org/x/example/@v/v0.0.0-20210407023211-09c3a5e06b5d.mod", + "GoVersion": "1.15" +} +{ + "Path": "replacetest3", + "Version": "v1.2.3", + "Replace": { + "Path": "./fork1", + "Dir": "/home/work/module/fork1", + "GoMod": "/home/work/module/fork1/go.mod", + "GoVersion": "1.17" + }, + "Dir": "/home/work/module/fork1", + "GoMod": "/home/work/module/fork1/go.mod", + "GoVersion": "1.17" +} +{ + "Path": "github.com/simnalamburt/module", + "Main": true, + "Dir": "/home/work/module", + "GoMod": "/home/work/module/go.mod", + "GoVersion": "1.17" +} +{ + "Path": "replacetest4", + "Version": "v0.0.1", + "Replace": { + "Path": "/srv/fork2", + "Dir": "/home/work/module/fork2", + "GoMod": "/home/work/module/fork2/go.mod", + "GoVersion": "1.17" + }, + "Dir": "/home/work/module/fork2", + "GoMod": "/home/work/module/fork2/go.mod", + "GoVersion": "1.17" +} +{ + "Path": "replacetest5", + "Version": "v1.2.3", + "Replace": { + "Path": "./fork3", + "Dir": "/home/work/module/fork3", + "GoMod": "/home/work/module/fork3/go.mod", + "GoVersion": "1.17" + }, + "Dir": "/home/work/module/fork3", + "GoMod": "/home/work/module/fork3/go.mod", + "GoVersion": "1.17" +} +`)) + if err != nil { + t.Errorf("Error occured during JSON parsing") + } + if currentModule != "github.com/simnalamburt/module" { + t.Errorf("Unexpected module name") + } + if moduleDir != "/home/work/module" { + t.Errorf("Unexpected module path") + } + expected := []xcaddy.Replace{ + xcaddy.NewReplace("replacetest1@v1.2.3", "golang.org/x/example@v0.0.0-20210811190340-787a929d5a0d"), + xcaddy.NewReplace("replacetest2@v0.0.1", "golang.org/x/example@v0.0.0-20210407023211-09c3a5e06b5d"), + xcaddy.NewReplace("replacetest3@v1.2.3", "/home/work/module/fork1"), + xcaddy.NewReplace("github.com/simnalamburt/module", "/home/work/module"), + xcaddy.NewReplace("replacetest4@v0.0.1", "/srv/fork2"), + xcaddy.NewReplace("replacetest5@v1.2.3", "/home/work/module/fork3"), + } + if !reflect.DeepEqual(replacements, expected) { + t.Errorf("Expected replacements '%v' but got '%v'", expected, replacements) + } +} diff --git a/cmd/main_windows_test.go b/cmd/main_windows_test.go new file mode 100644 index 0000000..57af0f5 --- /dev/null +++ b/cmd/main_windows_test.go @@ -0,0 +1,110 @@ +//go:build windows +package xcaddycmd + +import ( + "reflect" + "testing" + + "github.com/caddyserver/xcaddy" +) + +func TestParseGoListJson(t *testing.T) { + currentModule, moduleDir, replacements, err := parseGoListJson([]byte(` +{ + "Path": "replacetest1", + "Version": "v1.2.3", + "Replace": { + "Path": "golang.org/x/example", + "Version": "v0.0.0-20210811190340-787a929d5a0d", + "Time": "2021-08-11T19:03:40Z", + "Dir": "C:\\Users\\simna\\go\\pkg\\mod\\golang.org\\x\\example@v0.0.0-20210811190340-787a929d5a0d", + "GoMod": "C:\\Users\\simna\\go\\pkg\\mod\\cache\\download\\golang.org\\x\\example\\@v\\v0.0.0-20210811190340-787a929d5a0d.mod", + "GoVersion": "1.15" + }, + "Dir": "C:\\Users\\simna\\go\\pkg\\mod\\golang.org\\x\\example@v0.0.0-20210811190340-787a929d5a0d", + "GoMod": "C:\\Users\\simna\\go\\pkg\\mod\\cache\\download\\golang.org\\x\\example\\@v\\v0.0.0-20210811190340-787a929d5a0d.mod", + "GoVersion": "1.15" +} +{ + "Path": "replacetest2", + "Version": "v0.0.1", + "Replace": { + "Path": "golang.org/x/example", + "Version": "v0.0.0-20210407023211-09c3a5e06b5d", + "Time": "2021-04-07T02:32:11Z", + "Dir": "C:\\Users\\simna\\go\\pkg\\mod\\golang.org\\x\\example@v0.0.0-20210407023211-09c3a5e06b5d", + "GoMod": "C:\\Users\\simna\\go\\pkg\\mod\\cache\\download\\golang.org\\x\\example\\@v\\v0.0.0-20210407023211-09c3a5e06b5d.mod", + "GoVersion": "1.15" + }, + "Dir": "C:\\Users\\simna\\go\\pkg\\mod\\golang.org\\x\\example@v0.0.0-20210407023211-09c3a5e06b5d", + "GoMod": "C:\\Users\\simna\\go\\pkg\\mod\\cache\\download\\golang.org\\x\\example\\@v\\v0.0.0-20210407023211-09c3a5e06b5d.mod", + "GoVersion": "1.15" +} +{ + "Path": "replacetest3", + "Version": "v1.2.3", + "Replace": { + "Path": "./fork1", + "Dir": "C:\\Users\\work\\module\\fork1", + "GoMod": "C:\\Users\\work\\module\\fork1\\go.mod", + "GoVersion": "1.17" + }, + "Dir": "C:\\Users\\work\\module\\fork1", + "GoMod": "C:\\Users\\work\\module\\fork1\\go.mod", + "GoVersion": "1.17" +} +{ + "Path": "github.com/simnalamburt/module", + "Main": true, + "Dir": "C:\\Users\\work\\module", + "GoMod": "C:\\Users\\work\\module\\go.mod", + "GoVersion": "1.17" +} +{ + "Path": "replacetest4", + "Version": "v0.0.1", + "Replace": { + "Path": "C:\\go\\fork2", + "Dir": "C:\\Users\\work\\module\\fork2", + "GoMod": "C:\\Users\\work\\module\\fork2\\go.mod", + "GoVersion": "1.17" + }, + "Dir": "C:\\Users\\work\\module\\fork2", + "GoMod": "C:\\Users\\work\\module\\fork2\\go.mod", + "GoVersion": "1.17" +} +{ + "Path": "replacetest5", + "Version": "v1.2.3", + "Replace": { + "Path": "./fork3", + "Dir": "C:\\Users\\work\\module\\fork3", + "GoMod": "C:\\Users\\work\\module\\fork1\\go.mod", + "GoVersion": "1.17" + }, + "Dir": "C:\\Users\\work\\module\\fork3", + "GoMod": "C:\\Users\\work\\module\\fork3\\go.mod", + "GoVersion": "1.17" +} +`)) + if err != nil { + t.Errorf("Error occured during JSON parsing") + } + if currentModule != "github.com/simnalamburt/module" { + t.Errorf("Unexpected module name") + } + if moduleDir != "C:\\Users\\work\\module" { + t.Errorf("Unexpected module path") + } + expected := []xcaddy.Replace{ + xcaddy.NewReplace("replacetest1@v1.2.3", "golang.org/x/example@v0.0.0-20210811190340-787a929d5a0d"), + xcaddy.NewReplace("replacetest2@v0.0.1", "golang.org/x/example@v0.0.0-20210407023211-09c3a5e06b5d"), + xcaddy.NewReplace("replacetest3@v1.2.3", "C:\\Users\\work\\module\\fork1"), + xcaddy.NewReplace("github.com/simnalamburt/module", "C:\\Users\\work\\module"), + xcaddy.NewReplace("replacetest4@v0.0.1", "C:\\go\\fork2"), + xcaddy.NewReplace("replacetest5@v1.2.3", "C:\\Users\\work\\module\\fork3"), + } + if !reflect.DeepEqual(replacements, expected) { + t.Errorf("Expected replacements '%v' but got '%v'", expected, replacements) + } +} From e0ed2ec5773822c490d23351cd8d73d8f57429fb Mon Sep 17 00:00:00 2001 From: Hyeon Kim Date: Wed, 1 Sep 2021 06:50:20 +0900 Subject: [PATCH 6/8] Support Go 1.16 --- cmd/main_unix_test.go | 2 ++ cmd/main_windows_test.go | 2 ++ 2 files changed, 4 insertions(+) diff --git a/cmd/main_unix_test.go b/cmd/main_unix_test.go index c05646e..e226425 100644 --- a/cmd/main_unix_test.go +++ b/cmd/main_unix_test.go @@ -1,4 +1,6 @@ //go:build !windows +// +build !windows + package xcaddycmd import ( diff --git a/cmd/main_windows_test.go b/cmd/main_windows_test.go index 57af0f5..3d65e58 100644 --- a/cmd/main_windows_test.go +++ b/cmd/main_windows_test.go @@ -1,4 +1,6 @@ //go:build windows +// +build windows + package xcaddycmd import ( From d46c2668d54cf825b1ad2f5276176afee244d449 Mon Sep 17 00:00:00 2001 From: Hyeon Kim Date: Wed, 1 Sep 2021 17:51:29 +0900 Subject: [PATCH 7/8] Use struct instead of map[string]interface{} --- cmd/main.go | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/cmd/main.go b/cmd/main.go index f9dbdd1..c106565 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -256,7 +256,15 @@ func parseGoListJson(out []byte) (currentModule, moduleDir string, replacements decoder := json.NewDecoder(bytes.NewReader(out)) for { - var mod map[string]interface{} + type Module struct { + Path string // module path + Version string // module version + Replace *Module // replaced by this module + Main bool // is this the main module? + Dir string // directory holding files for this module, if any + } + + var mod Module if err = decoder.Decode(&mod); err == io.EOF { err = nil break @@ -264,33 +272,29 @@ func parseGoListJson(out []byte) (currentModule, moduleDir string, replacements return } - is_main, ok := mod["Main"].(bool) - if ok && is_main { + if mod.Main { // Current module is main module, retrieve the main module name and // root directory path of the main module - currentModule = mod["Path"].(string) - moduleDir = mod["Dir"].(string) + currentModule = mod.Path + moduleDir = mod.Dir replacements = append(replacements, xcaddy.NewReplace(currentModule, moduleDir)) continue } // Skip if current module is not replacement - rep, ok := mod["Replace"].(map[string]interface{}) - if !ok { + if mod.Replace == nil { continue } - srcPath := mod["Path"].(string) - srcVersion := mod["Version"].(string) - src := srcPath + "@" + srcVersion + src := mod.Path + "@" + mod.Version // 1. Target is module, version is required in this case // 2A. Target is absolute path // 2B. Target is relative path, proper handling is required in this case - dstPath := rep["Path"].(string) - dstVersion, isTargetModule := rep["Version"].(string) + dstPath := mod.Replace.Path + dstVersion := mod.Replace.Version var dst string - if isTargetModule { + if dstVersion != "" { dst = dstPath + "@" + dstVersion } else if filepath.IsAbs(dstPath) { dst = dstPath From 12b50e756fe8fff47db37f5ea9af23b605e75374 Mon Sep 17 00:00:00 2001 From: Mohammed Al Sahaf Date: Wed, 1 Sep 2021 21:25:03 +0300 Subject: [PATCH 8/8] extract and unexport the `module` struct --- cmd/main.go | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/cmd/main.go b/cmd/main.go index c106565..b35e278 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -251,20 +251,20 @@ func runDev(ctx context.Context, args []string) error { return cmd.Wait() } +type module struct { + Path string // module path + Version string // module version + Replace *module // replaced by this module + Main bool // is this the main module? + Dir string // directory holding files for this module, if any +} + func parseGoListJson(out []byte) (currentModule, moduleDir string, replacements []xcaddy.Replace, err error) { var unjoinedReplaces []int decoder := json.NewDecoder(bytes.NewReader(out)) for { - type Module struct { - Path string // module path - Version string // module version - Replace *Module // replaced by this module - Main bool // is this the main module? - Dir string // directory holding files for this module, if any - } - - var mod Module + var mod module if err = decoder.Decode(&mod); err == io.EOF { err = nil break