2019-02-10 13:04:11 -05:00
package main
import (
"fmt"
2021-03-18 06:32:40 -04:00
"go/build"
2021-03-23 23:49:32 -04:00
"io"
2021-03-18 06:32:40 -04:00
"io/ioutil"
2021-03-23 23:49:32 -04:00
"net/http"
2019-02-10 13:04:11 -05:00
"os"
"os/exec"
"path/filepath"
2021-03-23 23:49:32 -04:00
"regexp"
2019-02-10 13:04:11 -05:00
"runtime"
2021-03-23 23:49:32 -04:00
"strconv"
2019-02-10 13:04:11 -05:00
"strings"
)
2021-03-18 06:32:40 -04:00
// envFile returns the name of the Go environment configuration file.
// Copy from https://github.com/golang/go/blob/c4f2a9788a7be04daf931ac54382fbe2cb754938/src/cmd/go/internal/cfg/cfg.go#L150-L166
func envFile ( ) ( string , error ) {
if file := os . Getenv ( "GOENV" ) ; file != "" {
if file == "off" {
return "" , fmt . Errorf ( "GOENV=off" )
}
return file , nil
}
dir , err := os . UserConfigDir ( )
if err != nil {
return "" , err
}
if dir == "" {
return "" , fmt . Errorf ( "missing user-config dir" )
}
return filepath . Join ( dir , "go" , "env" ) , nil
}
// GetRuntimeEnv returns the value of runtime environment variable,
// that is set by running following command: `go env -w key=value`.
func GetRuntimeEnv ( key string ) ( string , error ) {
file , err := envFile ( )
if err != nil {
return "" , err
}
if file == "" {
return "" , fmt . Errorf ( "missing runtime env file" )
}
var data [ ] byte
var runtimeEnv string
data , readErr := ioutil . ReadFile ( file )
if readErr != nil {
return "" , readErr
}
envStrings := strings . Split ( string ( data ) , "\n" )
for _ , envItem := range envStrings {
envItem = strings . TrimSuffix ( envItem , "\r" )
envKeyValue := strings . Split ( envItem , "=" )
if strings . EqualFold ( strings . TrimSpace ( envKeyValue [ 0 ] ) , key ) {
runtimeEnv = strings . TrimSpace ( envKeyValue [ 1 ] )
}
}
return runtimeEnv , nil
}
// GetGOBIN returns GOBIN environment variable as a string. It will NOT be empty.
func GetGOBIN ( ) string {
// The one set by user explicitly by `export GOBIN=/path` or `env GOBIN=/path command`
GOBIN := os . Getenv ( "GOBIN" )
if GOBIN == "" {
var err error
// The one set by user by running `go env -w GOBIN=/path`
GOBIN , err = GetRuntimeEnv ( "GOBIN" )
if err != nil {
// The default one that Golang uses
return filepath . Join ( build . Default . GOPATH , "bin" )
}
if GOBIN == "" {
return filepath . Join ( build . Default . GOPATH , "bin" )
}
return GOBIN
}
return GOBIN
}
2021-03-23 23:49:32 -04:00
func getProjectProtocVersion ( url string ) ( string , error ) {
resp , err := http . Get ( url )
if err != nil {
return "" , fmt . Errorf ( "can not get the version of protobuf used in V2Ray project" )
}
defer resp . Body . Close ( )
body , err := io . ReadAll ( resp . Body )
if err != nil {
return "" , fmt . Errorf ( "can not read from body" )
}
versionRegexp := regexp . MustCompile ( ` \/\/\s*protoc\s*v(\d+\.\d+\.\d+) ` )
matched := versionRegexp . FindStringSubmatch ( string ( body ) )
return matched [ 1 ] , nil
}
func getInstalledProtocVersion ( protocPath string ) ( string , error ) {
cmd := exec . Command ( protocPath , "--version" )
cmd . Env = append ( cmd . Env , os . Environ ( ) ... )
output , cmdErr := cmd . CombinedOutput ( )
if cmdErr != nil {
return "" , cmdErr
}
versionRegexp := regexp . MustCompile ( ` protoc\s*(\d+\.\d+\.\d+) ` )
matched := versionRegexp . FindStringSubmatch ( string ( output ) )
return matched [ 1 ] , nil
}
func parseVersion ( s string , width int ) int64 {
strList := strings . Split ( s , "." )
format := fmt . Sprintf ( "%%s%%0%ds" , width )
v := ""
for _ , value := range strList {
v = fmt . Sprintf ( format , v , value )
}
var result int64
var err error
if result , err = strconv . ParseInt ( v , 10 , 64 ) ; err != nil {
return 0
}
return result
}
func needToUpdate ( targetedVersion , installedVersion string ) bool {
vt := parseVersion ( targetedVersion , 4 )
vi := parseVersion ( installedVersion , 4 )
return vt > vi
}
2019-02-10 13:04:11 -05:00
func main ( ) {
2021-03-23 23:49:32 -04:00
targetedVersion , err := getProjectProtocVersion ( "https://raw.githubusercontent.com/v2fly/v2ray-core/HEAD/config.pb.go" )
if err != nil {
fmt . Println ( err )
os . Exit ( 1 )
}
2020-08-27 04:37:59 -04:00
pwd , wdErr := os . Getwd ( )
if wdErr != nil {
fmt . Println ( "Can not get current working directory." )
os . Exit ( 1 )
}
2019-02-10 13:04:11 -05:00
2021-03-18 06:32:40 -04:00
GOBIN := GetGOBIN ( )
2020-10-28 21:27:05 -04:00
binPath := os . Getenv ( "PATH" )
2021-03-18 06:32:40 -04:00
pathSlice := [ ] string { pwd , GOBIN , binPath }
2020-10-28 21:27:05 -04:00
binPath = strings . Join ( pathSlice , string ( os . PathListSeparator ) )
os . Setenv ( "PATH" , binPath )
2021-03-18 06:32:40 -04:00
suffix := ""
2020-10-19 10:39:51 -04:00
if runtime . GOOS == "windows" {
2021-03-18 06:32:40 -04:00
suffix = ".exe"
2020-10-19 10:39:51 -04:00
}
2021-03-18 06:32:40 -04:00
protoc := "protoc" + suffix
2020-10-19 11:20:19 -04:00
2020-10-20 22:30:16 -04:00
if path , err := exec . LookPath ( protoc ) ; err != nil {
2020-10-28 21:27:05 -04:00
fmt . Println ( "Make sure that you have `" + protoc + "` in your system path or current path. To download it, please visit https://github.com/protocolbuffers/protobuf/releases" )
2020-10-19 11:20:19 -04:00
os . Exit ( 1 )
2020-10-20 22:30:16 -04:00
} else {
protoc = path
2020-10-19 11:20:19 -04:00
}
2020-10-19 10:39:51 -04:00
2021-03-23 23:49:32 -04:00
installedVersion , err := getInstalledProtocVersion ( protoc )
if err != nil {
fmt . Println ( err )
os . Exit ( 1 )
}
if needToUpdate ( targetedVersion , installedVersion ) {
fmt . Printf ( `
You are using an old protobuf version . Please update to v % s or later .
Download it from https : //github.com/protocolbuffers/protobuf/releases
* Protobuf version used in V2Ray project : v % s
* Protobuf version you have installed : v % s
` , targetedVersion , targetedVersion , installedVersion )
os . Exit ( 1 )
}
2020-08-27 04:37:59 -04:00
protoFilesMap := make ( map [ string ] [ ] string )
walkErr := filepath . Walk ( "./" , func ( path string , info os . FileInfo , err error ) error {
2019-02-10 13:04:11 -05:00
if err != nil {
fmt . Println ( err )
return err
}
if info . IsDir ( ) {
return nil
}
dir := filepath . Dir ( path )
filename := filepath . Base ( path )
if strings . HasSuffix ( filename , ".proto" ) {
2020-08-27 04:37:59 -04:00
protoFilesMap [ dir ] = append ( protoFilesMap [ dir ] , path )
2019-02-10 13:04:11 -05:00
}
return nil
} )
2020-08-27 04:37:59 -04:00
if walkErr != nil {
fmt . Println ( walkErr )
os . Exit ( 1 )
}
2019-02-10 13:04:11 -05:00
2020-08-27 04:37:59 -04:00
for _ , files := range protoFilesMap {
for _ , relProtoFile := range files {
2021-03-18 06:32:40 -04:00
args := [ ] string {
"--go_out" , pwd ,
"--go_opt" , "paths=source_relative" ,
"--go-grpc_out" , pwd ,
"--go-grpc_opt" , "paths=source_relative" ,
"--plugin" , "protoc-gen-go=" + filepath . Join ( GOBIN , "protoc-gen-go" + suffix ) ,
"--plugin" , "protoc-gen-go-grpc=" + filepath . Join ( GOBIN , "protoc-gen-go-grpc" + suffix ) ,
2020-08-24 08:10:26 -04:00
}
2020-08-27 04:37:59 -04:00
args = append ( args , relProtoFile )
2020-08-24 08:10:26 -04:00
cmd := exec . Command ( protoc , args ... )
cmd . Env = append ( cmd . Env , os . Environ ( ) ... )
2020-08-27 04:37:59 -04:00
output , cmdErr := cmd . CombinedOutput ( )
2020-08-24 08:10:26 -04:00
if len ( output ) > 0 {
fmt . Println ( string ( output ) )
}
2020-08-27 04:37:59 -04:00
if cmdErr != nil {
fmt . Println ( cmdErr )
os . Exit ( 1 )
}
}
}
2019-02-10 13:04:11 -05:00
}