Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle replacements properly for runDev() #69

Merged
merged 8 commits into from
Sep 1, 2021
Merged
113 changes: 76 additions & 37 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,11 @@
package xcaddycmd

import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"log"
"os"
"os/exec"
Expand Down Expand Up @@ -165,53 +168,24 @@ func getCaddyOutputFile() string {
func runDev(ctx context.Context, args []string) error {
binOutput := getCaddyOutputFile()

// get current/main module name
cmd := exec.Command("go", "list", "-m")
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()
if err != nil {
return fmt.Errorf("exec %v: %v: %s", cmd.Args, err, string(out))
}
moduleDir := strings.TrimSpace(string(out))

// get current/main module name and the root directory of the main module
//
// 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
// 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()
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] == "" {
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])
}

replacements = append(replacements, xcaddy.NewReplace(parts[0], parts[1]))
currentModule, moduleDir, replacements, err := parseGoListJson(out)
if err != nil {
return fmt.Errorf("json parse error: %v", err)
}

// reconcile remaining path segments; for example if a module foo/a
Expand Down Expand Up @@ -277,6 +251,71 @@ func runDev(ctx context.Context, args []string) error {
return cmd.Wait()
}

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{}
simnalamburt marked this conversation as resolved.
Show resolved Hide resolved
if err = decoder.Decode(&mod); err == io.EOF {
err = nil
break
} else if err != nil {
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
}

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 {
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))
}
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 {
return path.Join(currentModule, filepath.ToSlash(strings.TrimPrefix(cwd, moduleDir)))
}
Expand Down
108 changes: 108 additions & 0 deletions cmd/main_unix_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
//go:build !windows
// +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)
}
}
112 changes: 112 additions & 0 deletions cmd/main_windows_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
//go:build windows
// +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)
}
}