autofix.go 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193
  1. package iris
  2. import (
  3. "archive/zip"
  4. "bytes"
  5. stdContext "context"
  6. "fmt"
  7. "io"
  8. "io/ioutil"
  9. "os"
  10. "os/exec"
  11. "path/filepath"
  12. "strings"
  13. "time"
  14. "github.com/kataras/golog"
  15. )
  16. const defaultModuleName = "app"
  17. // simple function does not uses AST, it simply replaces import paths,
  18. // creates a go.mod file if not exists and then run the `go mod tidy`
  19. // command to remove old dependencies and install the new ones.
  20. // It does NOT replaces breaking changes.
  21. // The developer SHOULD visit the changelog(HISTORY.md) in order to learn
  22. // everything about the new features and any breaking changes that comes with it.
  23. func tryFix() error {
  24. wdir, err := filepath.Abs(".") // should return the current directory (on both go run & executable).
  25. if err != nil {
  26. return fmt.Errorf("can not resolve current working directory: %w", err)
  27. }
  28. // First of all, backup the current project,
  29. // so any changes can be reverted by the end developer.
  30. backupDest := wdir + "_irisbckp.zip"
  31. golog.Infof("Backup <%s> to <%s>", wdir, backupDest)
  32. err = zipDir(wdir, backupDest)
  33. if err != nil {
  34. return fmt.Errorf("backup dir: %w", err)
  35. }
  36. // go module.
  37. goModFile := filepath.Join(wdir, "go.mod")
  38. if !fileExists(goModFile) {
  39. golog.Warnf("Project is not a go module. Executing <go.mod init app>")
  40. f, err := os.Create(goModFile)
  41. if err != nil {
  42. return fmt.Errorf("go.mod: %w", os.ErrNotExist)
  43. }
  44. fmt.Fprintf(f, "module %s\ngo 1.15\n", defaultModuleName)
  45. f.Close()
  46. }
  47. // contnets replacements.
  48. golog.Infof("Updating...") // note: we will not replace GOPATH project paths.
  49. err = replaceDirContents(wdir, map[string]string{
  50. `"github.com/kataras/iris`: `"github.com/kataras/iris/v12`,
  51. // Note: we could use
  52. // regexp's FindAllSubmatch, take the dir part and replace
  53. // any HandleDir and e.t.c, but we are not going to do this.
  54. // Look the comment of the tryFix() function.
  55. })
  56. if err != nil {
  57. return fmt.Errorf("replace import paths: %w", err)
  58. }
  59. commands := []string{
  60. // "go clean --modcache",
  61. "go env -w GOPROXY=https://goproxy.cn,https://gocenter.io,https://goproxy.io,direct",
  62. "go mod tidy",
  63. }
  64. for _, c := range commands {
  65. if err = runCmd(wdir, c); err != nil {
  66. // print out the command, especially
  67. // with go env -w the user should know it.
  68. // We use that because many of our users are living in China,
  69. // which the default goproxy is blocked).
  70. golog.Infof("$ %s", c)
  71. return fmt.Errorf("command <%s>: %w", c, err)
  72. }
  73. }
  74. return nil
  75. }
  76. func fileExists(path string) bool {
  77. stat, err := os.Stat(path)
  78. if err != nil {
  79. return os.IsExist(err)
  80. }
  81. return !stat.IsDir() && stat.Mode().IsRegular()
  82. }
  83. func runCmd(wdir, c string) error {
  84. ctx, cancel := stdContext.WithTimeout(stdContext.Background(), 2*time.Minute)
  85. defer cancel()
  86. parts := strings.Split(c, " ")
  87. name, args := parts[0], parts[1:]
  88. cmd := exec.CommandContext(ctx, name, args...)
  89. // cmd.Path = wdir
  90. cmd.Stdout = os.Stdout
  91. cmd.Stderr = os.Stderr
  92. return cmd.Run()
  93. }
  94. // zipDir zips a directory, recursively.
  95. // It accepts a source directory and a destination zip file.
  96. func zipDir(src, dest string) error {
  97. folderName := filepath.Base(src)
  98. file, err := os.Create(dest)
  99. if err != nil {
  100. return err
  101. }
  102. defer file.Close()
  103. w := zip.NewWriter(file)
  104. defer w.Close()
  105. walkFunc := func(path string, info os.FileInfo, err error) error {
  106. if err != nil {
  107. return err
  108. }
  109. if info.IsDir() {
  110. return nil
  111. }
  112. file, err := os.Open(path)
  113. if err != nil {
  114. return err
  115. }
  116. defer file.Close()
  117. relPath := filepath.Join(folderName, strings.TrimPrefix(path, src))
  118. f, err := w.Create(relPath)
  119. if err != nil {
  120. return err
  121. }
  122. _, err = io.Copy(f, file)
  123. return err
  124. }
  125. return filepath.Walk(src, walkFunc)
  126. }
  127. func replaceDirContents(target string, replacements map[string]string) error {
  128. walkFunc := func(path string, info os.FileInfo, err error) error {
  129. if err != nil {
  130. return err
  131. }
  132. if info.IsDir() || !info.Mode().IsRegular() {
  133. return nil
  134. }
  135. file, err := os.OpenFile(path, os.O_RDWR, 0666)
  136. if err != nil {
  137. return err
  138. }
  139. defer file.Close()
  140. contents, ioErr := ioutil.ReadAll(file)
  141. if ioErr != nil {
  142. return ioErr
  143. }
  144. replaced := false
  145. for oldContent, newContent := range replacements {
  146. newContents := bytes.ReplaceAll(contents, []byte(oldContent), []byte(newContent))
  147. if len(newContents) > 0 {
  148. replaced = true
  149. contents = newContents[0:]
  150. }
  151. }
  152. if replaced {
  153. file.Truncate(0)
  154. file.Seek(0, 0)
  155. _, err = file.Write(contents)
  156. return err
  157. }
  158. return nil
  159. }
  160. return filepath.Walk(target, walkFunc)
  161. }