diff --git a/go.mod b/go.mod index 9bb82c7c..68455559 100644 --- a/go.mod +++ b/go.mod @@ -29,7 +29,7 @@ require ( go.opentelemetry.io/otel/sdk v1.1.0 go.opentelemetry.io/otel/trace v1.1.0 go.uber.org/automaxprocs v1.4.0 - golang.org/x/net v0.0.0-20210928044308-7d9f5e0b762b // indirect + golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f // indirect golang.org/x/sys v0.0.0-20211106132015-ebca88c72f68 // indirect golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac google.golang.org/genproto v0.0.0-20210928142010-c7af6a1a74c9 // indirect diff --git a/go.sum b/go.sum index 9ad65f48..e6bd20bc 100644 --- a/go.sum +++ b/go.sum @@ -488,8 +488,8 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/net v0.0.0-20210917221730-978cfadd31cf/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20210928044308-7d9f5e0b762b h1:eB48h3HiRycXNy8E0Gf5e0hv7YT6Kt14L/D73G1fuwo= -golang.org/x/net v0.0.0-20210928044308-7d9f5e0b762b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f h1:OfiFi4JbukWwe3lzw+xunroH1mnC1e2Gy5cxNJApiSY= +golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= diff --git a/tools/goctl/goctl.go b/tools/goctl/goctl.go index 9abba714..7997c2db 100644 --- a/tools/goctl/goctl.go +++ b/tools/goctl/goctl.go @@ -23,6 +23,7 @@ import ( "github.com/tal-tech/go-zero/tools/goctl/internal/errorx" "github.com/tal-tech/go-zero/tools/goctl/internal/version" "github.com/tal-tech/go-zero/tools/goctl/kube" + "github.com/tal-tech/go-zero/tools/goctl/migrate" "github.com/tal-tech/go-zero/tools/goctl/model/mongo" model "github.com/tal-tech/go-zero/tools/goctl/model/sql/command" "github.com/tal-tech/go-zero/tools/goctl/plugin" @@ -45,6 +46,22 @@ var commands = []cli.Command{ Usage: "upgrade goctl to latest version", Action: upgrade.Upgrade, }, + { + Name: "migrate", + Usage: "migrate from tal-tech to zeromicro", + Description: "migrate is a transition command to help users migrate their projects from tal-tech to zeromicro version", + Action: migrate.Migrate, + Flags: []cli.Flag{ + cli.BoolFlag{ + Name: "verbose, v", + Usage: "verbose enables extra logging", + }, + cli.StringFlag{ + Name: "version", + Usage: "the target release version of github.com/zeromicro/go-zero to refactor", + }, + }, + }, { Name: "api", Usage: "generate api related files", diff --git a/tools/goctl/migrate/migrate.go b/tools/goctl/migrate/migrate.go new file mode 100644 index 00000000..9894a312 --- /dev/null +++ b/tools/goctl/migrate/migrate.go @@ -0,0 +1,113 @@ +package migrate + +import ( + "bytes" + "fmt" + "go/ast" + "go/format" + "go/parser" + "go/token" + "io/fs" + "io/ioutil" + "os" + "path/filepath" + "strings" + "time" + + "github.com/tal-tech/go-zero/tools/goctl/util/console" + "github.com/tal-tech/go-zero/tools/goctl/util/ctx" + "github.com/urfave/cli" +) + +const zeromicroVersion = "1.3.0" + +var fset = token.NewFileSet() + +func Migrate(c *cli.Context) error { + verbose := c.Bool("verbose") + version := c.String("version") + if len(version) == 0 { + version = zeromicroVersion + } + err := editMod(version, verbose) + if err != nil { + return err + } + + err = rewriteImport(verbose) + if err != nil { + return err + } + + err = tidy(verbose) + if err != nil { + return err + } + + console.Success("[OK] refactor finish, execute %q on project root to check status.", "go test -race ./...") + return nil +} + +func rewriteImport(verbose bool) error { + if verbose { + console.Info("preparing to rewrite import ...") + time.Sleep(200 * time.Millisecond) + } + + wd, err := os.Getwd() + if err != nil { + return err + } + project, err := ctx.Prepare(wd) + if err != nil { + return err + } + root := project.Dir + fsys := os.DirFS(root) + return fs.WalkDir(fsys, ".", func(path string, d fs.DirEntry, err error) error { + if !d.IsDir() { + return nil + } + if verbose { + console.Info("walking to %q", path) + } + pkgs, err := parser.ParseDir(fset, path, func(info fs.FileInfo) bool { + return strings.HasSuffix(info.Name(), ".go") + }, parser.ParseComments) + if err != nil { + return err + } + + return rewriteFile(pkgs, verbose) + }) +} + +func rewriteFile(pkgs map[string]*ast.Package, verbose bool) error { + for _, pkg := range pkgs { + for filename, file := range pkg.Files { + for _, imp := range file.Imports { + if !strings.Contains(imp.Path.Value, deprecatedGoZeroMod) { + continue + } + newPath := strings.ReplaceAll(imp.Path.Value, deprecatedGoZeroMod, goZeroMod) + imp.EndPos = imp.End() + imp.Path.Value = newPath + } + + var w = bytes.NewBuffer(nil) + err := format.Node(w, fset, file) + if err != nil { + return fmt.Errorf("[rewriteImport] format file %s error: %+v", filename, err) + } + + err = ioutil.WriteFile(filename, w.Bytes(), os.ModePerm) + if err != nil { + return fmt.Errorf("[rewriteImport] write file %s error: %+v", filename, err) + } + if verbose { + console.Success("[OK] rewriting %q ... ", filepath.Base(filename)) + } + } + } + return nil +} diff --git a/tools/goctl/migrate/mod.go b/tools/goctl/migrate/mod.go new file mode 100644 index 00000000..2102179d --- /dev/null +++ b/tools/goctl/migrate/mod.go @@ -0,0 +1,95 @@ +package migrate + +import ( + "errors" + "fmt" + "os" + "time" + + "github.com/tal-tech/go-zero/core/stringx" + "github.com/tal-tech/go-zero/tools/goctl/rpc/execx" + "github.com/tal-tech/go-zero/tools/goctl/util/console" + "github.com/tal-tech/go-zero/tools/goctl/util/ctx" +) + +const deprecatedGoZeroMod = "github.com/tal-tech/go-zero" +const goZeroMod = "github.com/zeromicro/go-zero" + +var errInvalidGoMod = errors.New("it's only working for go module") + +func editMod(version string, verbose bool) error { + wd, err := os.Getwd() + if err != nil { + return err + } + + isGoMod, _ := ctx.IsGoMod(wd) + if !isGoMod { + return nil + } + + latest, err := getLatest(goZeroMod, verbose) + if err != nil { + return err + } + + if !stringx.Contains(latest, version) { + return fmt.Errorf("release version %q is not found", version) + } + mod := fmt.Sprintf("%s@%s", goZeroMod, version) + err = removeRequire(deprecatedGoZeroMod, verbose) + if err != nil { + return err + } + return addRequire(mod, verbose) +} + +func addRequire(mod string, verbose bool) error { + if verbose { + console.Info("adding require %s ...", mod) + time.Sleep(200 * time.Millisecond) + } + wd, err := os.Getwd() + if err != nil { + return err + } + + isGoMod, _ := ctx.IsGoMod(wd) + if !isGoMod { + return errInvalidGoMod + } + + _, err = execx.Run("go mod edit -require "+mod, wd) + return err +} + +func removeRequire(mod string, verbose bool) error { + if verbose { + console.Info("remove require %s ...", mod) + time.Sleep(200 * time.Millisecond) + } + wd, err := os.Getwd() + if err != nil { + return err + } + _, err = execx.Run("go mod edit -droprequire "+mod, wd) + return err +} + +func tidy(verbose bool) error { + if verbose { + console.Info("go mod tidy ...") + time.Sleep(200 * time.Millisecond) + } + wd, err := os.Getwd() + if err != nil { + return err + } + isGoMod, _ := ctx.IsGoMod(wd) + if !isGoMod { + return nil + } + + _, err = execx.Run("go mod tidy", wd) + return err +} diff --git a/tools/goctl/migrate/proxy.go b/tools/goctl/migrate/proxy.go new file mode 100644 index 00000000..68bffe7d --- /dev/null +++ b/tools/goctl/migrate/proxy.go @@ -0,0 +1,42 @@ +package migrate + +import ( + "net/url" + "os" + "strings" + + "github.com/tal-tech/go-zero/core/stringx" + "github.com/tal-tech/go-zero/tools/goctl/rpc/execx" +) + +var defaultProxy = "https://goproxy.cn" +var defaultProxies = []string{defaultProxy} + +func goProxy() []string { + wd, err := os.Getwd() + if err != nil { + return defaultProxies + } + + proxy, err := execx.Run("go env GOPROXY", wd) + if err != nil { + return defaultProxies + } + list := strings.FieldsFunc(proxy, func(r rune) bool { + return r == '|' || r == ',' + }) + var ret []string + for _, item := range list { + if len(item) == 0 { + continue + } + _, err = url.Parse(item) + if err == nil && !stringx.Contains(ret, item) { + ret = append(ret, item) + } + } + if !stringx.Contains(ret, defaultProxy) { + ret = append(ret, defaultProxy) + } + return ret +} diff --git a/tools/goctl/migrate/version.go b/tools/goctl/migrate/version.go new file mode 100644 index 00000000..8033a7d2 --- /dev/null +++ b/tools/goctl/migrate/version.go @@ -0,0 +1,47 @@ +package migrate + +import ( + "fmt" + "io/ioutil" + "net/http" + "strings" + "time" + + "github.com/tal-tech/go-zero/tools/goctl/util/console" +) + +var client = http.Client{ + Timeout: 5 * time.Second, +} + +func getLatest(repo string, verbose bool) ([]string, error) { + proxies := goProxy() + for _, proxy := range proxies { + if verbose { + console.Info("use go proxy %q", proxy) + } + log := func(err error) { + console.Warning("get latest versions failed from proxy %q, error: %+v", proxy, err) + } + resp, err := client.Get(fmt.Sprintf("%s/%s/@v/list", proxy, repo)) + if err != nil { + log(err) + continue + } + + if resp.StatusCode != http.StatusOK { + log(fmt.Errorf("%s", resp.Status)) + continue + } + defer resp.Body.Close() + data, err := ioutil.ReadAll(resp.Body) + if err != nil { + log(err) + continue + } + versionStr := string(data) + versions := strings.Fields(versionStr) + return versions, nil + } + return []string{}, nil +}