github.com/sagernet/sing-box@v1.9.0-rc.20/cmd/internal/protogen/main.go (about) 1 package main 2 3 import ( 4 "bufio" 5 "bytes" 6 "fmt" 7 "go/build" 8 "io" 9 "os" 10 "os/exec" 11 "path/filepath" 12 "runtime" 13 "strings" 14 ) 15 16 // envFile returns the name of the Go environment configuration file. 17 // Copy from https://github.com/golang/go/blob/c4f2a9788a7be04daf931ac54382fbe2cb754938/src/cmd/go/internal/cfg/cfg.go#L150-L166 18 func envFile() (string, error) { 19 if file := os.Getenv("GOENV"); file != "" { 20 if file == "off" { 21 return "", fmt.Errorf("GOENV=off") 22 } 23 return file, nil 24 } 25 dir, err := os.UserConfigDir() 26 if err != nil { 27 return "", err 28 } 29 if dir == "" { 30 return "", fmt.Errorf("missing user-config dir") 31 } 32 return filepath.Join(dir, "go", "env"), nil 33 } 34 35 // GetRuntimeEnv returns the value of runtime environment variable, 36 // that is set by running following command: `go env -w key=value`. 37 func GetRuntimeEnv(key string) (string, error) { 38 file, err := envFile() 39 if err != nil { 40 return "", err 41 } 42 if file == "" { 43 return "", fmt.Errorf("missing runtime env file") 44 } 45 var data []byte 46 var runtimeEnv string 47 data, readErr := os.ReadFile(file) 48 if readErr != nil { 49 return "", readErr 50 } 51 envStrings := strings.Split(string(data), "\n") 52 for _, envItem := range envStrings { 53 envItem = strings.TrimSuffix(envItem, "\r") 54 envKeyValue := strings.Split(envItem, "=") 55 if strings.EqualFold(strings.TrimSpace(envKeyValue[0]), key) { 56 runtimeEnv = strings.TrimSpace(envKeyValue[1]) 57 } 58 } 59 return runtimeEnv, nil 60 } 61 62 // GetGOBIN returns GOBIN environment variable as a string. It will NOT be empty. 63 func GetGOBIN() string { 64 // The one set by user explicitly by `export GOBIN=/path` or `env GOBIN=/path command` 65 GOBIN := os.Getenv("GOBIN") 66 if GOBIN == "" { 67 var err error 68 // The one set by user by running `go env -w GOBIN=/path` 69 GOBIN, err = GetRuntimeEnv("GOBIN") 70 if err != nil { 71 // The default one that Golang uses 72 return filepath.Join(build.Default.GOPATH, "bin") 73 } 74 if GOBIN == "" { 75 return filepath.Join(build.Default.GOPATH, "bin") 76 } 77 return GOBIN 78 } 79 return GOBIN 80 } 81 82 func main() { 83 pwd, err := os.Getwd() 84 if err != nil { 85 fmt.Println("Can not get current working directory.") 86 os.Exit(1) 87 } 88 89 GOBIN := GetGOBIN() 90 binPath := os.Getenv("PATH") 91 pathSlice := []string{pwd, GOBIN, binPath} 92 binPath = strings.Join(pathSlice, string(os.PathListSeparator)) 93 os.Setenv("PATH", binPath) 94 95 suffix := "" 96 if runtime.GOOS == "windows" { 97 suffix = ".exe" 98 } 99 100 protoc := "protoc" 101 102 if linkPath, err := os.Readlink(protoc); err == nil { 103 protoc = linkPath 104 } 105 106 protoFilesMap := make(map[string][]string) 107 walkErr := filepath.Walk("./", func(path string, info os.FileInfo, err error) error { 108 if err != nil { 109 fmt.Println(err) 110 return err 111 } 112 113 if info.IsDir() { 114 return nil 115 } 116 117 dir := filepath.Dir(path) 118 filename := filepath.Base(path) 119 if strings.HasSuffix(filename, ".proto") && 120 filename != "typed_message.proto" && 121 filename != "descriptor.proto" { 122 protoFilesMap[dir] = append(protoFilesMap[dir], path) 123 } 124 125 return nil 126 }) 127 if walkErr != nil { 128 fmt.Println(walkErr) 129 os.Exit(1) 130 } 131 132 for _, files := range protoFilesMap { 133 for _, relProtoFile := range files { 134 args := []string{ 135 "-I", ".", 136 "--go_out", pwd, 137 "--go_opt", "paths=source_relative", 138 "--go-grpc_out", pwd, 139 "--go-grpc_opt", "paths=source_relative", 140 "--plugin", "protoc-gen-go=" + filepath.Join(GOBIN, "protoc-gen-go"+suffix), 141 "--plugin", "protoc-gen-go-grpc=" + filepath.Join(GOBIN, "protoc-gen-go-grpc"+suffix), 142 } 143 args = append(args, relProtoFile) 144 cmd := exec.Command(protoc, args...) 145 cmd.Env = append(cmd.Env, os.Environ()...) 146 output, cmdErr := cmd.CombinedOutput() 147 if len(output) > 0 { 148 fmt.Println(string(output)) 149 } 150 if cmdErr != nil { 151 fmt.Println(cmdErr) 152 os.Exit(1) 153 } 154 } 155 } 156 157 normalizeWalkErr := filepath.Walk("./", func(path string, info os.FileInfo, err error) error { 158 if err != nil { 159 fmt.Println(err) 160 return err 161 } 162 163 if info.IsDir() { 164 return nil 165 } 166 167 filename := filepath.Base(path) 168 if strings.HasSuffix(filename, ".pb.go") && 169 path != "config.pb.go" { 170 if err := NormalizeGeneratedProtoFile(path); err != nil { 171 fmt.Println(err) 172 os.Exit(1) 173 } 174 } 175 176 return nil 177 }) 178 if normalizeWalkErr != nil { 179 fmt.Println(normalizeWalkErr) 180 os.Exit(1) 181 } 182 } 183 184 func NormalizeGeneratedProtoFile(path string) error { 185 fd, err := os.OpenFile(path, os.O_RDWR, 0o644) 186 if err != nil { 187 return err 188 } 189 190 _, err = fd.Seek(0, io.SeekStart) 191 if err != nil { 192 return err 193 } 194 out := bytes.NewBuffer(nil) 195 scanner := bufio.NewScanner(fd) 196 valid := false 197 for scanner.Scan() { 198 if !valid && !strings.HasPrefix(scanner.Text(), "package ") { 199 continue 200 } 201 valid = true 202 out.Write(scanner.Bytes()) 203 out.Write([]byte("\n")) 204 } 205 _, err = fd.Seek(0, io.SeekStart) 206 if err != nil { 207 return err 208 } 209 err = fd.Truncate(0) 210 if err != nil { 211 return err 212 } 213 _, err = io.Copy(fd, bytes.NewReader(out.Bytes())) 214 if err != nil { 215 return err 216 } 217 return nil 218 }