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