mirror of
https://github.com/XTLS/Xray-core.git
synced 2026-10-27 21:46:58 +00:00
225 lines
5.5 KiB
Go
225 lines
5.5 KiB
Go
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"flag"
|
|
"fmt"
|
|
"io/fs"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/bufbuild/protocompile"
|
|
"google.golang.org/protobuf/proto"
|
|
"google.golang.org/protobuf/reflect/protodesc"
|
|
"google.golang.org/protobuf/reflect/protoreflect"
|
|
"google.golang.org/protobuf/types/descriptorpb"
|
|
"google.golang.org/protobuf/types/pluginpb"
|
|
)
|
|
|
|
var (
|
|
directory = flag.String("pwd", "", "Working directory of Xray vprotogen.")
|
|
mode = flag.String("mode", "generate", "Execution mode. 'generate' (default) compiles and saves changes. 'check' reports out-of-date files and exits with non-zero.")
|
|
)
|
|
|
|
// topoSort sorts file descriptors so each dependency appears before files importing it.
|
|
func topoSort(files []protoreflect.FileDescriptor) []*descriptorpb.FileDescriptorProto {
|
|
fileMap := make(map[string]protoreflect.FileDescriptor)
|
|
for _, f := range files {
|
|
fileMap[f.Path()] = f
|
|
}
|
|
|
|
visited := make(map[string]bool)
|
|
var sorted []*descriptorpb.FileDescriptorProto
|
|
|
|
var visit func(fd protoreflect.FileDescriptor)
|
|
visit = func(fd protoreflect.FileDescriptor) {
|
|
if visited[fd.Path()] {
|
|
return
|
|
}
|
|
visited[fd.Path()] = true
|
|
|
|
imports := fd.Imports()
|
|
for i := 0; i < imports.Len(); i++ {
|
|
if dep, ok := fileMap[imports.Get(i).Path()]; ok {
|
|
visit(dep)
|
|
}
|
|
}
|
|
sorted = append(sorted, protodesc.ToFileDescriptorProto(fd))
|
|
}
|
|
|
|
for _, f := range files {
|
|
visit(f)
|
|
}
|
|
return sorted
|
|
}
|
|
|
|
func runPlugin(pluginName, pwd string, req *pluginpb.CodeGeneratorRequest) (*pluginpb.CodeGeneratorResponse, error) {
|
|
reqBytes, err := proto.Marshal(req)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
cmd := exec.Command("go", "tool", pluginName)
|
|
cmd.Dir = pwd
|
|
cmd.Stdin = bytes.NewReader(reqBytes)
|
|
cmd.Env = os.Environ()
|
|
var stdout, stderr bytes.Buffer
|
|
cmd.Stdout = &stdout
|
|
cmd.Stderr = &stderr
|
|
|
|
if err := cmd.Run(); err != nil {
|
|
return nil, fmt.Errorf("%v: %s", err, stderr.String())
|
|
}
|
|
|
|
var resp pluginpb.CodeGeneratorResponse
|
|
if err := proto.Unmarshal(stdout.Bytes(), &resp); err != nil {
|
|
return nil, fmt.Errorf("failed to unmarshal response: %w", err)
|
|
}
|
|
if resp.GetError() != "" {
|
|
return nil, fmt.Errorf("%s", resp.GetError())
|
|
}
|
|
return &resp, nil
|
|
}
|
|
|
|
func main() {
|
|
flag.Usage = func() {
|
|
fmt.Fprintf(flag.CommandLine.Output(), "Usage of vprotogen:\n")
|
|
flag.PrintDefaults()
|
|
}
|
|
flag.Parse()
|
|
|
|
if !filepath.IsAbs(*directory) {
|
|
pwd, err := os.Getwd()
|
|
if err != nil {
|
|
fmt.Println("Can not get current working directory.")
|
|
os.Exit(1)
|
|
}
|
|
*directory = filepath.Join(pwd, *directory)
|
|
}
|
|
pwd := *directory
|
|
|
|
isCheck := false
|
|
switch *mode {
|
|
case "generate", "":
|
|
isCheck = false
|
|
case "check":
|
|
isCheck = true
|
|
default:
|
|
fmt.Fprintf(os.Stderr, "Unrecognized 'mode': %s. Use 'generate' or 'check'.\n", *mode)
|
|
os.Exit(1)
|
|
}
|
|
|
|
if isCheck {
|
|
fmt.Println("Checking for out-of-date Protobuf generated files...")
|
|
}
|
|
|
|
var protoFiles []string
|
|
err := filepath.WalkDir(pwd, func(path string, d fs.DirEntry, err error) error {
|
|
if err != nil || d.IsDir() {
|
|
return err
|
|
}
|
|
if strings.HasSuffix(d.Name(), ".proto") {
|
|
rel, _ := filepath.Rel(pwd, path)
|
|
protoFiles = append(protoFiles, filepath.ToSlash(rel))
|
|
}
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
fmt.Println("Walk error:", err)
|
|
os.Exit(1)
|
|
}
|
|
if len(protoFiles) == 0 {
|
|
return
|
|
}
|
|
|
|
compiler := protocompile.Compiler{
|
|
Resolver: protocompile.WithStandardImports(&protocompile.SourceResolver{
|
|
ImportPaths: []string{pwd},
|
|
}),
|
|
SourceInfoMode: protocompile.SourceInfoStandard,
|
|
}
|
|
|
|
linkedFiles, err := compiler.Compile(context.Background(), protoFiles...)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "Protobuf compilation failed:\n%v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
var fdList []protoreflect.FileDescriptor
|
|
for _, f := range linkedFiles {
|
|
fdList = append(fdList, f)
|
|
}
|
|
|
|
req := &pluginpb.CodeGeneratorRequest{
|
|
FileToGenerate: protoFiles,
|
|
Parameter: proto.String("paths=source_relative"),
|
|
ProtoFile: topoSort(fdList),
|
|
// TODO: replace with unknown in the future
|
|
CompilerVersion: &pluginpb.Version{
|
|
Major: proto.Int32(6),
|
|
Minor: proto.Int32(33),
|
|
Patch: proto.Int32(5),
|
|
},
|
|
}
|
|
|
|
var diffCount int
|
|
for _, plugin := range []string{"protoc-gen-go", "protoc-gen-go-grpc"} {
|
|
resp, err := runPlugin(plugin, pwd, req)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "%s failed: %v\n", plugin, err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
for _, f := range resp.File {
|
|
targetPath := filepath.Join(pwd, filepath.FromSlash(f.GetName()))
|
|
content := []byte(f.GetContent())
|
|
|
|
hasDiff := false
|
|
existing, err := os.ReadFile(targetPath)
|
|
if err != nil {
|
|
hasDiff = true
|
|
} else {
|
|
oldNorm := bytes.ReplaceAll(existing, []byte("\r\n"), []byte("\n"))
|
|
newNorm := bytes.ReplaceAll(content, []byte("\r\n"), []byte("\n"))
|
|
if !bytes.Equal(oldNorm, newNorm) {
|
|
hasDiff = true
|
|
}
|
|
}
|
|
|
|
if hasDiff {
|
|
diffCount++
|
|
fmt.Println(targetPath)
|
|
|
|
if isCheck {
|
|
continue
|
|
}
|
|
|
|
if err := os.MkdirAll(filepath.Dir(targetPath), 0o755); err != nil {
|
|
fmt.Fprintf(os.Stderr, "Error creating directory: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
mode := os.FileMode(0o644)
|
|
if stat, err := os.Stat(targetPath); err == nil {
|
|
mode = stat.Mode().Perm()
|
|
}
|
|
if err := os.WriteFile(targetPath, content, mode); err != nil {
|
|
fmt.Fprintf(os.Stderr, "Error writing %s: %v\n", targetPath, err)
|
|
os.Exit(1)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if isCheck {
|
|
if diffCount > 0 {
|
|
fmt.Println("Protobuf generated file problem(s) found.")
|
|
fmt.Println("Please run 'go run ./infra/vprotogen' to regenerate them.")
|
|
os.Exit(1)
|
|
}
|
|
fmt.Println("All Protobuf generated file check has been passed.")
|
|
}
|
|
}
|