github.com/xraypb/xray-core@v1.6.6/infra/vprotogen/main.go (about) 1 package main 2 3 import ( 4 "flag" 5 "fmt" 6 "go/build" 7 "io" 8 "net/http" 9 "os" 10 "os/exec" 11 "path/filepath" 12 "regexp" 13 "runtime" 14 "strconv" 15 "strings" 16 ) 17 18 var directory = flag.String("pwd", "", "Working directory of Xray vprotogen.") 19 20 // envFile returns the name of the Go environment configuration file. 21 // Copy from https://github.com/golang/go/blob/c4f2a9788a7be04daf931ac54382fbe2cb754938/src/cmd/go/internal/cfg/cfg.go#L150-L166 22 func envFile() (string, error) { 23 if file := os.Getenv("GOENV"); file != "" { 24 if file == "off" { 25 return "", fmt.Errorf("GOENV=off") 26 } 27 return file, nil 28 } 29 dir, err := os.UserConfigDir() 30 if err != nil { 31 return "", err 32 } 33 if dir == "" { 34 return "", fmt.Errorf("missing user-config dir") 35 } 36 return filepath.Join(dir, "go", "env"), nil 37 } 38 39 // GetRuntimeEnv returns the value of runtime environment variable, 40 // that is set by running following command: `go env -w key=value`. 41 func GetRuntimeEnv(key string) (string, error) { 42 file, err := envFile() 43 if err != nil { 44 return "", err 45 } 46 if file == "" { 47 return "", fmt.Errorf("missing runtime env file") 48 } 49 var data []byte 50 var runtimeEnv string 51 data, readErr := os.ReadFile(file) 52 if readErr != nil { 53 return "", readErr 54 } 55 envStrings := strings.Split(string(data), "\n") 56 for _, envItem := range envStrings { 57 envItem = strings.TrimSuffix(envItem, "\r") 58 envKeyValue := strings.Split(envItem, "=") 59 if strings.EqualFold(strings.TrimSpace(envKeyValue[0]), key) { 60 runtimeEnv = strings.TrimSpace(envKeyValue[1]) 61 } 62 } 63 return runtimeEnv, nil 64 } 65 66 // GetGOBIN returns GOBIN environment variable as a string. It will NOT be empty. 67 func GetGOBIN() string { 68 // The one set by user explicitly by `export GOBIN=/path` or `env GOBIN=/path command` 69 GOBIN := os.Getenv("GOBIN") 70 if GOBIN == "" { 71 var err error 72 // The one set by user by running `go env -w GOBIN=/path` 73 GOBIN, err = GetRuntimeEnv("GOBIN") 74 if err != nil { 75 // The default one that Golang uses 76 return filepath.Join(build.Default.GOPATH, "bin") 77 } 78 if GOBIN == "" { 79 return filepath.Join(build.Default.GOPATH, "bin") 80 } 81 return GOBIN 82 } 83 return GOBIN 84 } 85 86 func whichProtoc(suffix, targetedVersion string) (string, error) { 87 protoc := "protoc" + suffix 88 89 path, err := exec.LookPath(protoc) 90 if err != nil { 91 errStr := fmt.Sprintf(` 92 Command "%s" not found. 93 Make sure that %s is in your system path or current path. 94 Download %s v%s or later from https://github.com/protocolbuffers/protobuf/releases 95 `, protoc, protoc, protoc, targetedVersion) 96 return "", fmt.Errorf(errStr) 97 } 98 return path, nil 99 } 100 101 func getProjectProtocVersion(url string) (string, error) { 102 resp, err := http.Get(url) 103 if err != nil { 104 return "", fmt.Errorf("can not get the version of protobuf used in xray project") 105 } 106 defer resp.Body.Close() 107 body, err := io.ReadAll(resp.Body) 108 if err != nil { 109 return "", fmt.Errorf("can not read from body") 110 } 111 versionRegexp := regexp.MustCompile(`\/\/\s*protoc\s*v(\d+\.\d+\.\d+)`) 112 matched := versionRegexp.FindStringSubmatch(string(body)) 113 return matched[1], nil 114 } 115 116 func getInstalledProtocVersion(protocPath string) (string, error) { 117 cmd := exec.Command(protocPath, "--version") 118 cmd.Env = append(cmd.Env, os.Environ()...) 119 output, cmdErr := cmd.CombinedOutput() 120 if cmdErr != nil { 121 return "", cmdErr 122 } 123 versionRegexp := regexp.MustCompile(`protoc\s*(\d+\.\d+\.\d+)`) 124 matched := versionRegexp.FindStringSubmatch(string(output)) 125 return matched[1], nil 126 } 127 128 func parseVersion(s string, width int) int64 { 129 strList := strings.Split(s, ".") 130 format := fmt.Sprintf("%%s%%0%ds", width) 131 v := "" 132 for _, value := range strList { 133 v = fmt.Sprintf(format, v, value) 134 } 135 var result int64 136 var err error 137 if result, err = strconv.ParseInt(v, 10, 64); err != nil { 138 return 0 139 } 140 return result 141 } 142 143 func needToUpdate(targetedVersion, installedVersion string) bool { 144 vt := parseVersion(targetedVersion, 4) 145 vi := parseVersion(installedVersion, 4) 146 return vt > vi 147 } 148 149 func main() { 150 flag.Usage = func() { 151 fmt.Fprintf(flag.CommandLine.Output(), "Usage of vprotogen:\n") 152 flag.PrintDefaults() 153 } 154 flag.Parse() 155 156 if !filepath.IsAbs(*directory) { 157 pwd, wdErr := os.Getwd() 158 if wdErr != nil { 159 fmt.Println("Can not get current working directory.") 160 os.Exit(1) 161 } 162 *directory = filepath.Join(pwd, *directory) 163 } 164 165 pwd := *directory 166 GOBIN := GetGOBIN() 167 binPath := os.Getenv("PATH") 168 pathSlice := []string{pwd, GOBIN, binPath} 169 binPath = strings.Join(pathSlice, string(os.PathListSeparator)) 170 os.Setenv("PATH", binPath) 171 172 suffix := "" 173 if runtime.GOOS == "windows" { 174 suffix = ".exe" 175 } 176 177 targetedVersion, err := getProjectProtocVersion("https://raw.githubusercontent.com/xtls/xray-core/HEAD/core/config.pb.go") 178 if err != nil { 179 fmt.Println(err) 180 os.Exit(1) 181 } 182 183 protoc, err := whichProtoc(suffix, targetedVersion) 184 if err != nil { 185 fmt.Println(err) 186 os.Exit(1) 187 } 188 189 installedVersion, err := getInstalledProtocVersion(protoc) 190 if err != nil { 191 fmt.Println(err) 192 os.Exit(1) 193 } 194 195 if needToUpdate(targetedVersion, installedVersion) { 196 fmt.Printf(` 197 You are using an old protobuf version, please update to v%s or later. 198 Download it from https://github.com/protocolbuffers/protobuf/releases 199 200 * Protobuf version used in xray project: v%s 201 * Protobuf version you have installed: v%s 202 203 `, targetedVersion, targetedVersion, installedVersion) 204 os.Exit(1) 205 } 206 207 protoFilesMap := make(map[string][]string) 208 walkErr := filepath.Walk(pwd, func(path string, info os.FileInfo, err error) error { 209 if err != nil { 210 fmt.Println(err) 211 return err 212 } 213 214 if info.IsDir() { 215 return nil 216 } 217 218 dir := filepath.Dir(path) 219 filename := filepath.Base(path) 220 if strings.HasSuffix(filename, ".proto") { 221 path = path[len(pwd)+1:] 222 protoFilesMap[dir] = append(protoFilesMap[dir], path) 223 } 224 225 return nil 226 }) 227 if walkErr != nil { 228 fmt.Println(walkErr) 229 os.Exit(1) 230 } 231 232 for _, files := range protoFilesMap { 233 for _, relProtoFile := range files { 234 args := []string{ 235 "--go_out", pwd, 236 "--go_opt", "paths=source_relative", 237 "--go-grpc_out", pwd, 238 "--go-grpc_opt", "paths=source_relative", 239 "--plugin", "protoc-gen-go=" + filepath.Join(GOBIN, "protoc-gen-go"+suffix), 240 "--plugin", "protoc-gen-go-grpc=" + filepath.Join(GOBIN, "protoc-gen-go-grpc"+suffix), 241 } 242 args = append(args, relProtoFile) 243 cmd := exec.Command(protoc, args...) 244 cmd.Env = append(cmd.Env, os.Environ()...) 245 cmd.Dir = pwd 246 output, cmdErr := cmd.CombinedOutput() 247 if len(output) > 0 { 248 fmt.Println(string(output)) 249 } 250 if cmdErr != nil { 251 fmt.Println(cmdErr) 252 os.Exit(1) 253 } 254 } 255 } 256 }