@@ -2,16 +2,151 @@ package protobuf
22
33import (
44 "context"
5+ "fmt"
6+ "log"
7+ "os"
8+ "path/filepath"
9+ "sort"
10+ "strings"
511)
612
713// Before implements part of the language.LifecycleManager interface.
814func (pl * protobufLang ) Before (context.Context ) {
915}
1016
1117// DoneGeneratingRules implements part of the language.LifecycleManager interface.
18+ //
19+ // Performs two cross-package syncs that need every GenerateRules call to
20+ // have completed first:
21+ //
22+ // 1. Root Cargo.toml [workspace] members list — the lines between the
23+ // `# gazelle:proto_rust_members start/end` markers are replaced with
24+ // one entry per package that emitted a proto_rust_library.
25+ //
26+ // 2. Root BUILD.bazel proto_compile_assets aggregator deps — the lines
27+ // between the `# gazelle:vendor_proto_sources_deps start/end` markers
28+ // are replaced with one entry per generated proto_compiled_sources rule
29+ // and one entry per proto_rust_library's underlying _lib target.
30+ //
31+ // Both syncs are no-ops when the corresponding markers are absent (or the
32+ // target file does not exist).
1233func (pl * protobufLang ) DoneGeneratingRules () {
34+ if pl .repoRoot == "" {
35+ return
36+ }
37+ if err := updateRootCargoMembers (pl .repoRoot , pl .protoRustLibraryPackages ); err != nil {
38+ log .Printf ("warning: could not update root Cargo.toml proto_rust_members: %v" , err )
39+ }
40+ if err := updateRootVendorAssetsDeps (pl .repoRoot , pl .vendorAssetLabels ); err != nil {
41+ log .Printf ("warning: could not update root BUILD.bazel vendor_proto_sources_deps: %v" , err )
42+ }
1343}
1444
1545// AfterResolvingDeps implements part of the language.LifecycleManager interface.
1646func (pl * protobufLang ) AfterResolvingDeps (context.Context ) {
1747}
48+
49+ const (
50+ cargoMembersStartMarker = "# gazelle:proto_rust_members start"
51+ cargoMembersEndMarker = "# gazelle:proto_rust_members end"
52+ vendorAssetsDepsStartMarker = "# gazelle:vendor_proto_sources_deps start"
53+ vendorAssetsDepsEndMarker = "# gazelle:vendor_proto_sources_deps end"
54+ )
55+
56+ // updateRootCargoMembers rewrites the gazelle:proto_rust_members marker
57+ // section in the root Cargo.toml with a sorted, deduplicated list of
58+ // `"<pkg>",` entries. No-op if the file is missing or the markers are
59+ // absent.
60+ func updateRootCargoMembers (repoRoot string , packages []string ) error {
61+ return rewriteMarkerSection (
62+ filepath .Join (repoRoot , "Cargo.toml" ),
63+ cargoMembersStartMarker ,
64+ cargoMembersEndMarker ,
65+ packages ,
66+ "[workspace] members list" ,
67+ )
68+ }
69+
70+ // updateRootVendorAssetsDeps rewrites the gazelle:vendor_proto_sources_deps
71+ // marker section in the root BUILD.bazel with a sorted, deduplicated list
72+ // of `"<label>",` entries. No-op if the file is missing or the markers are
73+ // absent.
74+ func updateRootVendorAssetsDeps (repoRoot string , labels []string ) error {
75+ return rewriteMarkerSection (
76+ filepath .Join (repoRoot , "BUILD.bazel" ),
77+ vendorAssetsDepsStartMarker ,
78+ vendorAssetsDepsEndMarker ,
79+ labels ,
80+ "vendor_proto_sources deps list" ,
81+ )
82+ }
83+
84+ // rewriteMarkerSection replaces every line between startMarker and
85+ // endMarker in path with a sorted, deduplicated quoted-comma list of
86+ // entries. Marker lines themselves are preserved; indentation is taken
87+ // from the start marker. The file is not written if the resulting content
88+ // is identical (avoids spurious mtime bumps). When entries is non-empty
89+ // but markers are absent, a warning is logged once with the supplied
90+ // description so the maintainer knows where to add them.
91+ func rewriteMarkerSection (path , startMarker , endMarker string , entries []string , description string ) error {
92+ src , err := os .ReadFile (path )
93+ if err != nil {
94+ if os .IsNotExist (err ) {
95+ return nil
96+ }
97+ return fmt .Errorf ("read %s: %w" , path , err )
98+ }
99+
100+ seen := make (map [string ]bool , len (entries ))
101+ uniq := make ([]string , 0 , len (entries ))
102+ for _ , e := range entries {
103+ if seen [e ] {
104+ continue
105+ }
106+ seen [e ] = true
107+ uniq = append (uniq , e )
108+ }
109+ sort .Strings (uniq )
110+
111+ lines := strings .Split (string (src ), "\n " )
112+ startIdx , endIdx := - 1 , - 1
113+ var indent string
114+ for i , line := range lines {
115+ trimmed := strings .TrimSpace (line )
116+ if startIdx < 0 && trimmed == startMarker {
117+ startIdx = i
118+ indent = line [:len (line )- len (strings .TrimLeft (line , " \t " ))]
119+ continue
120+ }
121+ if startIdx >= 0 && trimmed == endMarker {
122+ endIdx = i
123+ break
124+ }
125+ }
126+ if startIdx < 0 || endIdx < 0 {
127+ if len (uniq ) > 0 {
128+ log .Printf ("warning: %s has %d entries to enroll in %s but lacks the %s / %s markers — add them to enable auto-update" , path , len (uniq ), description , startMarker , endMarker )
129+ }
130+ return nil
131+ }
132+
133+ newSection := make ([]string , 0 , len (uniq )+ 2 )
134+ newSection = append (newSection , lines [startIdx ])
135+ for _ , e := range uniq {
136+ newSection = append (newSection , fmt .Sprintf ("%s\" %s\" ," , indent , e ))
137+ }
138+ newSection = append (newSection , lines [endIdx ])
139+
140+ out := append ([]string {}, lines [:startIdx ]... )
141+ out = append (out , newSection ... )
142+ out = append (out , lines [endIdx + 1 :]... )
143+
144+ updated := strings .Join (out , "\n " )
145+ if updated == string (src ) {
146+ return nil
147+ }
148+ if err := os .WriteFile (path , []byte (updated ), 0o644 ); err != nil {
149+ return fmt .Errorf ("write %s: %w" , path , err )
150+ }
151+ return nil
152+ }
0 commit comments