common.go 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  1. // Package common contains common utilities that are shared among other packages.
  2. // See each sub-package for detail.
  3. package common
  4. import (
  5. "fmt"
  6. "go/build"
  7. "os"
  8. "path/filepath"
  9. "reflect"
  10. "strings"
  11. "github.com/xtls/xray-core/common/errors"
  12. )
  13. // ErrNoClue is for the situation that existing information is not enough to make a decision. For example, Router may return this error when there is no suitable route.
  14. var ErrNoClue = errors.New("not enough information for making a decision")
  15. // Must panics if err is not nil.
  16. func Must(err error) {
  17. if err != nil {
  18. panic(err)
  19. }
  20. }
  21. // Must2 panics if the second parameter is not nil, otherwise returns the first parameter.
  22. // This is useful when function returned "sth, err" and avoid many "if err != nil"
  23. // Internal usage only, if user input can cause err, it must be handled
  24. func Must2[T any](v T, err error) T {
  25. Must(err)
  26. return v
  27. }
  28. // Error2 returns the err from the 2nd parameter.
  29. func Error2(v interface{}, err error) error {
  30. return err
  31. }
  32. // envFile returns the name of the Go environment configuration file.
  33. // Copy from https://github.com/golang/go/blob/c4f2a9788a7be04daf931ac54382fbe2cb754938/src/cmd/go/internal/cfg/cfg.go#L150-L166
  34. func envFile() (string, error) {
  35. if file := os.Getenv("GOENV"); file != "" {
  36. if file == "off" {
  37. return "", errors.New("GOENV=off")
  38. }
  39. return file, nil
  40. }
  41. dir, err := os.UserConfigDir()
  42. if err != nil {
  43. return "", err
  44. }
  45. if dir == "" {
  46. return "", errors.New("missing user-config dir")
  47. }
  48. return filepath.Join(dir, "go", "env"), nil
  49. }
  50. // GetRuntimeEnv returns the value of runtime environment variable,
  51. // that is set by running following command: `go env -w key=value`.
  52. func GetRuntimeEnv(key string) (string, error) {
  53. file, err := envFile()
  54. if err != nil {
  55. return "", err
  56. }
  57. if file == "" {
  58. return "", errors.New("missing runtime env file")
  59. }
  60. var data []byte
  61. var runtimeEnv string
  62. data, readErr := os.ReadFile(file)
  63. if readErr != nil {
  64. return "", readErr
  65. }
  66. envStrings := strings.Split(string(data), "\n")
  67. for _, envItem := range envStrings {
  68. envItem = strings.TrimSuffix(envItem, "\r")
  69. envKeyValue := strings.Split(envItem, "=")
  70. if strings.EqualFold(strings.TrimSpace(envKeyValue[0]), key) {
  71. runtimeEnv = strings.TrimSpace(envKeyValue[1])
  72. }
  73. }
  74. return runtimeEnv, nil
  75. }
  76. // GetGOBIN returns GOBIN environment variable as a string. It will NOT be empty.
  77. func GetGOBIN() string {
  78. // The one set by user explicitly by `export GOBIN=/path` or `env GOBIN=/path command`
  79. GOBIN := os.Getenv("GOBIN")
  80. if GOBIN == "" {
  81. var err error
  82. // The one set by user by running `go env -w GOBIN=/path`
  83. GOBIN, err = GetRuntimeEnv("GOBIN")
  84. if err != nil {
  85. // The default one that Golang uses
  86. return filepath.Join(build.Default.GOPATH, "bin")
  87. }
  88. if GOBIN == "" {
  89. return filepath.Join(build.Default.GOPATH, "bin")
  90. }
  91. return GOBIN
  92. }
  93. return GOBIN
  94. }
  95. // GetGOPATH returns GOPATH environment variable as a string. It will NOT be empty.
  96. func GetGOPATH() string {
  97. // The one set by user explicitly by `export GOPATH=/path` or `env GOPATH=/path command`
  98. GOPATH := os.Getenv("GOPATH")
  99. if GOPATH == "" {
  100. var err error
  101. // The one set by user by running `go env -w GOPATH=/path`
  102. GOPATH, err = GetRuntimeEnv("GOPATH")
  103. if err != nil {
  104. // The default one that Golang uses
  105. return build.Default.GOPATH
  106. }
  107. if GOPATH == "" {
  108. return build.Default.GOPATH
  109. }
  110. return GOPATH
  111. }
  112. return GOPATH
  113. }
  114. // GetModuleName returns the value of module in `go.mod` file.
  115. func GetModuleName(pathToProjectRoot string) (string, error) {
  116. var moduleName string
  117. loopPath := pathToProjectRoot
  118. for {
  119. if idx := strings.LastIndex(loopPath, string(filepath.Separator)); idx >= 0 {
  120. gomodPath := filepath.Join(loopPath, "go.mod")
  121. gomodBytes, err := os.ReadFile(gomodPath)
  122. if err != nil {
  123. loopPath = loopPath[:idx]
  124. continue
  125. }
  126. gomodContent := string(gomodBytes)
  127. moduleIdx := strings.Index(gomodContent, "module ")
  128. newLineIdx := strings.Index(gomodContent, "\n")
  129. if moduleIdx >= 0 {
  130. if newLineIdx >= 0 {
  131. moduleName = strings.TrimSpace(gomodContent[moduleIdx+6 : newLineIdx])
  132. moduleName = strings.TrimSuffix(moduleName, "\r")
  133. } else {
  134. moduleName = strings.TrimSpace(gomodContent[moduleIdx+6:])
  135. }
  136. return moduleName, nil
  137. }
  138. return "", fmt.Errorf("can not get module path in `%s`", gomodPath)
  139. }
  140. break
  141. }
  142. return moduleName, fmt.Errorf("no `go.mod` file in every parent directory of `%s`", pathToProjectRoot)
  143. }
  144. // CloseIfExists call obj.Close() if obj is not nil.
  145. func CloseIfExists(obj any) error {
  146. if obj != nil {
  147. v := reflect.ValueOf(obj)
  148. if !v.IsNil() {
  149. return Close(obj)
  150. }
  151. }
  152. return nil
  153. }