11package main
22
33import (
4+ "encoding/json"
45 "errors"
56 "flag"
67 "fmt"
@@ -15,67 +16,63 @@ import (
1516 "github.com/nantian-gw/gateway/internal/protocompat"
1617)
1718
18- const protoPath = "proto/gateway/control/v1/control.proto"
19+ const (
20+ protoModule = "github.com/nantian-gw/proto"
21+ protoRelPath = "gateway/control/v1/control.proto"
22+ )
1923
2024func main () {
2125 var (
22- repoRootFlag = flag .String ("repo-root " , "" , "repository root directory " )
23- baseRefFlag = flag .String ("base-ref " , "" , "git ref to compare against " )
26+ baseVersionFlag = flag .String ("base-version " , "" , "proto module version to compare against (required) " )
27+ repoRootFlag = flag .String ("repo-root " , "" , "gateway repository root directory " )
2428 )
2529 flag .Parse ()
2630
31+ baseVersion := strings .TrimSpace (* baseVersionFlag )
32+ if baseVersion == "" {
33+ fatalf ("--base-version is required (e.g. v0.1.0 or a pseudo-version)" )
34+ }
35+
2736 repoRoot , err := resolveRepoRoot (* repoRootFlag )
2837 if err != nil {
2938 fatalf ("resolve repo root: %v" , err )
3039 }
3140
32- baseRef := strings .TrimSpace (* baseRefFlag )
33- if baseRef == "" {
34- baseRef , err = detectBaseRef (repoRoot )
35- if err != nil {
36- fatalf ("detect base ref: %v" , err )
37- }
38- }
39-
40- tempDir , err := os .MkdirTemp ("" , "aeg-proto-compat-*" )
41+ currentProtoDir , err := moduleDir (repoRoot , protoModule + "@latest" )
4142 if err != nil {
42- fatalf ("create temp dir: %v" , err )
43- }
44- defer os .RemoveAll (tempDir )
45-
46- baseProtoRoot := filepath .Join (tempDir , "base-proto" )
47- baseProtoPath := filepath .Join (baseProtoRoot , "gateway/control/v1/control.proto" )
48- if err := os .MkdirAll (filepath .Dir (baseProtoPath ), 0o755 ); err != nil {
49- fatalf ("prepare temp proto tree: %v" , err )
43+ fatalf ("resolve current proto module: %v" , err )
5044 }
5145
52- baseProto , err := gitShow (repoRoot , fmt . Sprintf ( "%s:%s" , baseRef , protoPath ) )
46+ baseProtoDir , err := moduleDir (repoRoot , protoModule + "@" + baseVersion )
5347 if err != nil {
54- fatalf ("load base proto from %s: %v" , baseRef , err )
55- }
56- if err := os .WriteFile (baseProtoPath , baseProto , 0o644 ); err != nil {
57- fatalf ("write temp base proto: %v" , err )
48+ fatalf ("resolve base proto module (%s): %v" , baseVersion , err )
5849 }
5950
6051 protocInclude , err := findProtocInclude ()
6152 if err != nil {
6253 fatalf ("locate protoc include: %v" , err )
6354 }
6455
56+ tempDir , err := os .MkdirTemp ("" , "proto-compat-*" )
57+ if err != nil {
58+ fatalf ("create temp dir: %v" , err )
59+ }
60+ defer os .RemoveAll (tempDir )
61+
6562 currentDescriptorPath := filepath .Join (tempDir , "current.pb" )
6663 baseDescriptorPath := filepath .Join (tempDir , "base.pb" )
6764
6865 if err := compileDescriptor (
69- filepath . Join ( repoRoot , "proto" ) ,
70- filepath .Join (repoRoot , protoPath ),
66+ currentProtoDir ,
67+ filepath .Join (currentProtoDir , protoRelPath ),
7168 protocInclude ,
7269 currentDescriptorPath ,
7370 ); err != nil {
7471 fatalf ("compile current proto descriptor: %v" , err )
7572 }
7673 if err := compileDescriptor (
77- baseProtoRoot ,
78- filepath .Join (baseProtoRoot , "gateway/control/v1/control.proto" ),
74+ baseProtoDir ,
75+ filepath .Join (baseProtoDir , protoRelPath ),
7976 protocInclude ,
8077 baseDescriptorPath ,
8178 ); err != nil {
@@ -101,102 +98,75 @@ func main() {
10198 }
10299
103100 result := protocompat .CompareFiles (baseFile , currentFile )
104-
105- fmt .Printf ("[proto-compat] base ref: %s\n " , baseRef )
106- fmt .Printf ("[proto-compat] target: %s\n " , protoPath )
107- for _ , warning := range result .Warnings {
108- fmt .Printf ("[proto-compat] warning: %s: %s\n " , warning .Path , warning .Message )
109- }
110101 if ! result .OK () {
111- for _ , finding := range result .Errors {
112- fmt .Printf ("[proto-compat] error: %s: %s\n " , finding .Path , finding .Message )
102+ fmt .Println ("Backward-incompatible changes detected:" )
103+ for _ , f := range result .Errors {
104+ fmt .Printf (" ERROR: %s: %s\n " , f .Path , f .Message )
113105 }
114- os .Exit (1 )
115106 }
116-
117- fmt .Println ("[proto-compat] compatibility check passed" )
118- }
119-
120- func resolveRepoRoot (explicit string ) (string , error ) {
121- if strings .TrimSpace (explicit ) != "" {
122- return filepath .Abs (explicit )
107+ for _ , f := range result .Warnings {
108+ fmt .Printf (" WARNING: %s: %s\n " , f .Path , f .Message )
123109 }
124110
125- current , err := os .Getwd ()
126- if err != nil {
127- return "" , err
128- }
129-
130- for {
131- if _ , statErr := os .Stat (filepath .Join (current , ".git" )); statErr == nil {
132- return current , nil
133- }
134- parent := filepath .Dir (current )
135- if parent == current {
136- return "" , errors .New ("could not find .git directory from current working directory" )
137- }
138- current = parent
111+ if ! result .OK () {
112+ os .Exit (1 )
139113 }
114+ fmt .Println ("Proto is backward compatible." )
140115}
141116
142- func detectBaseRef (repoRoot string ) (string , error ) {
143- if tag , err := gitOutput (repoRoot , "describe" , "--tags" , "--abbrev=0" , "--match" , "v*" ); err == nil {
144- return strings .TrimSpace (tag ), nil
145- }
117+ type moduleInfo struct {
118+ Dir string `json:"Dir"`
119+ }
146120
147- headParent , err := gitOutput (repoRoot , "rev-parse" , "--verify" , "HEAD^" )
121+ func moduleDir (repoRoot , moduleQuery string ) (string , error ) {
122+ download := exec .Command ("go" , "mod" , "download" , "-json" , moduleQuery )
123+ download .Dir = repoRoot
124+ download .Stderr = os .Stderr
125+ downloadOutput , err := download .Output ()
148126 if err != nil {
149- return "" , errors . New ( "no release tag and no parent commit available for proto compatibility check" )
127+ return "" , fmt . Errorf ( "go mod download %s: %w" , moduleQuery , err )
150128 }
151- return strings .TrimSpace (headParent ), nil
152- }
153129
154- func gitShow (repoRoot , object string ) ([]byte , error ) {
155- cmd := exec .Command ("git" , "-C" , repoRoot , "show" , object )
156- output , err := cmd .Output ()
157- if err != nil {
158- return nil , err
130+ var info moduleInfo
131+ if err := json .Unmarshal (downloadOutput , & info ); err != nil {
132+ return "" , fmt .Errorf ("parse go mod download output: %w" , err )
133+ }
134+ if info .Dir == "" {
135+ return "" , fmt .Errorf ("module %s: Dir is empty" , moduleQuery )
159136 }
160- return output , nil
137+ return info . Dir , nil
161138}
162139
163- func gitOutput (repoRoot string , args ... string ) (string , error ) {
164- cmd := exec .Command ("git" , append ([]string {"-C" , repoRoot }, args ... )... )
165- output , err := cmd .Output ()
140+ func resolveRepoRoot (flag string ) (string , error ) {
141+ if flag != "" {
142+ return flag , nil
143+ }
144+ cmd := exec .Command ("git" , "rev-parse" , "--show-toplevel" )
145+ cmd .Stderr = os .Stderr
146+ out , err := cmd .Output ()
166147 if err != nil {
167- return "" , err
148+ return "" , fmt . Errorf ( "find git root: %w" , err )
168149 }
169- return string (output ), nil
150+ return strings . TrimSpace ( string (out ) ), nil
170151}
171152
172153func findProtocInclude () (string , error ) {
173- if include := strings .TrimSpace (os .Getenv ("PROTOC_INCLUDE" )); include != "" {
174- if wellKnownTypeExists (include ) {
175- return include , nil
176- }
177- return "" , fmt .Errorf ("PROTOC_INCLUDE does not contain google/protobuf well-known types: %s" , include )
154+ if env := os .Getenv ("PROTOC_INCLUDE" ); env != "" {
155+ return env , nil
178156 }
179157
180- protocPath , err := exec .LookPath ("protoc" )
181- if err != nil {
182- return "" , errors .New ("protoc not found in PATH" )
183- }
184- protocRoot := filepath .Dir (filepath .Dir (protocPath ))
185-
186- candidates := []string {
187- filepath .Join (protocRoot , "include" ),
158+ searchRoots := []string {
188159 "/usr/local/include" ,
189160 "/usr/include" ,
190- "/opt/homebrew/include" ,
191- "/opt/local/include" ,
161+ filepath .Join (os .Getenv ("HOME" ), ".local/include" ),
192162 }
193- for _ , candidate := range candidates {
194- if wellKnownTypeExists (candidate ) {
195- return candidate , nil
163+
164+ for _ , root := range searchRoots {
165+ if wellKnownTypeExists (root ) {
166+ return root , nil
196167 }
197168 }
198169
199- searchRoots := []string {"/usr/local" , "/usr" , "/opt/homebrew" , "/opt/local" }
200170 for _ , root := range searchRoots {
201171 candidate , err := findWellKnownType (root )
202172 if err != nil {
@@ -277,4 +247,4 @@ func findDescriptorFile(set *descriptorpb.FileDescriptorSet, name string) (*desc
277247func fatalf (format string , args ... any ) {
278248 fmt .Fprintf (os .Stderr , "[proto-compat] " + format + "\n " , args ... )
279249 os .Exit (1 )
280- }
250+ }
0 commit comments