github.com/imannamdari/v2ray-core/v5@v5.0.5/infra/vprotogen/main.go (about) 1 package main 2 3 import ( 4 "bufio" 5 "bytes" 6 "fmt" 7 "go/build" 8 "io" 9 "io/ioutil" 10 "net/http" 11 "os" 12 "os/exec" 13 "path/filepath" 14 "regexp" 15 "runtime" 16 "strconv" 17 "strings" 18 ) 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 := ioutil.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 V2Ray 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 pwd, err := os.Getwd() 151 if err != nil { 152 fmt.Println("Can not get current working directory.") 153 os.Exit(1) 154 } 155 156 GOBIN := GetGOBIN() 157 binPath := os.Getenv("PATH") 158 pathSlice := []string{pwd, GOBIN, binPath} 159 binPath = strings.Join(pathSlice, string(os.PathListSeparator)) 160 os.Setenv("PATH", binPath) 161 162 suffix := "" 163 if runtime.GOOS == "windows" { 164 suffix = ".exe" 165 } 166 167 targetedVersion, err := getProjectProtocVersion("https://raw.githubusercontent.com/v2fly/v2ray-core/HEAD/config.pb.go") 168 if err != nil { 169 fmt.Println(err) 170 os.Exit(1) 171 } 172 173 protoc, err := whichProtoc(suffix, targetedVersion) 174 if err != nil { 175 fmt.Println(err) 176 os.Exit(1) 177 } 178 179 if linkPath, err := os.Readlink(protoc); err == nil { 180 protoc = linkPath 181 } 182 183 installedVersion, err := getInstalledProtocVersion(protoc) 184 if err != nil { 185 fmt.Println(err) 186 os.Exit(1) 187 } 188 189 if needToUpdate(targetedVersion, installedVersion) { 190 fmt.Printf(` 191 You are using an old protobuf version, please update to v%s or later. 192 Download it from https://github.com/protocolbuffers/protobuf/releases 193 194 * Protobuf version used in V2Ray project: v%s 195 * Protobuf version you have installed: v%s 196 197 `, targetedVersion, targetedVersion, installedVersion) 198 os.Exit(1) 199 } 200 201 protoFilesMap := make(map[string][]string) 202 walkErr := filepath.Walk("./", func(path string, info os.FileInfo, err error) error { 203 if err != nil { 204 fmt.Println(err) 205 return err 206 } 207 208 if info.IsDir() { 209 return nil 210 } 211 212 dir := filepath.Dir(path) 213 filename := filepath.Base(path) 214 if strings.HasSuffix(filename, ".proto") && 215 filename != "typed_message.proto" && 216 filename != "descriptor.proto" { 217 protoFilesMap[dir] = append(protoFilesMap[dir], path) 218 } 219 220 return nil 221 }) 222 if walkErr != nil { 223 fmt.Println(walkErr) 224 os.Exit(1) 225 } 226 227 for _, files := range protoFilesMap { 228 for _, relProtoFile := range files { 229 args := []string{ 230 "-I", fmt.Sprintf("%v/../include", filepath.Dir(protoc)), 231 "-I", ".", 232 "--go_out", pwd, 233 "--go_opt", "paths=source_relative", 234 "--go-grpc_out", pwd, 235 "--go-grpc_opt", "paths=source_relative", 236 "--plugin", "protoc-gen-go=" + filepath.Join(GOBIN, "protoc-gen-go"+suffix), 237 "--plugin", "protoc-gen-go-grpc=" + filepath.Join(GOBIN, "protoc-gen-go-grpc"+suffix), 238 } 239 args = append(args, relProtoFile) 240 cmd := exec.Command(protoc, args...) 241 cmd.Env = append(cmd.Env, os.Environ()...) 242 output, cmdErr := cmd.CombinedOutput() 243 if len(output) > 0 { 244 fmt.Println(string(output)) 245 } 246 if cmdErr != nil { 247 fmt.Println(cmdErr) 248 os.Exit(1) 249 } 250 } 251 } 252 253 normalizeWalkErr := filepath.Walk("./", func(path string, info os.FileInfo, err error) error { 254 if err != nil { 255 fmt.Println(err) 256 return err 257 } 258 259 if info.IsDir() { 260 return nil 261 } 262 263 filename := filepath.Base(path) 264 if strings.HasSuffix(filename, ".pb.go") && 265 path != "config.pb.go" { 266 if err := NormalizeGeneratedProtoFile(path); err != nil { 267 fmt.Println(err) 268 os.Exit(1) 269 } 270 } 271 272 return nil 273 }) 274 if normalizeWalkErr != nil { 275 fmt.Println(normalizeWalkErr) 276 os.Exit(1) 277 } 278 } 279 280 func NormalizeGeneratedProtoFile(path string) error { 281 fd, err := os.OpenFile(path, os.O_RDWR, 0o644) 282 if err != nil { 283 return err 284 } 285 286 _, err = fd.Seek(0, os.SEEK_SET) 287 if err != nil { 288 return err 289 } 290 out := bytes.NewBuffer(nil) 291 scanner := bufio.NewScanner(fd) 292 valid := false 293 for scanner.Scan() { 294 if !valid && !strings.HasPrefix(scanner.Text(), "package ") { 295 continue 296 } 297 valid = true 298 out.Write(scanner.Bytes()) 299 out.Write([]byte("\n")) 300 } 301 _, err = fd.Seek(0, os.SEEK_SET) 302 if err != nil { 303 return err 304 } 305 err = fd.Truncate(0) 306 if err != nil { 307 return err 308 } 309 _, err = io.Copy(fd, bytes.NewReader(out.Bytes())) 310 if err != nil { 311 return err 312 } 313 return nil 314 }