From 5c379a11a7ce3f064f7285c7dc9cf9c979b106a0 Mon Sep 17 00:00:00 2001 From: Paul Johnston Date: Sat, 9 May 2026 12:20:30 -0600 Subject: [PATCH] Auto-sync root Cargo.toml workspace members and proto_compile_assets deps MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit After GenerateRules has run for every package, rewrite two marker-delimited sections at the repo root: - Cargo.toml between `# gazelle:proto_rust_members start/end` — populated with one entry per package that emitted a proto_rust_library. - BUILD.bazel between `# gazelle:vendor_proto_sources_deps start/end` — populated with the underlying _lib target of each proto_rust_library plus every proto_compiled_sources rule. Both rewrites are no-ops when the markers (or the target file) are absent, so existing repos without the markers see no change. Entries are sorted and deduplicated; the file is only rewritten when content actually changes. --- pkg/language/protobuf/generate.go | 17 ++++ pkg/language/protobuf/lang.go | 15 ++++ pkg/language/protobuf/lifecycle.go | 135 +++++++++++++++++++++++++++++ 3 files changed, 167 insertions(+) diff --git a/pkg/language/protobuf/generate.go b/pkg/language/protobuf/generate.go index 7f60a22f..e3e96381 100644 --- a/pkg/language/protobuf/generate.go +++ b/pkg/language/protobuf/generate.go @@ -102,6 +102,23 @@ func (pl *protobufLang) GenerateRules(args language.GenerateArgs) language.Gener imports[i] = r.PrivateAttr(config.GazelleImportsKey) internalLabel := label.New("", args.Rel, r.Name()) protoc.GlobalRuleIndex().Put(internalLabel, r) + switch r.Kind() { + case "proto_rust_library": + pl.protoRustLibraryPackages = append(pl.protoRustLibraryPackages, args.Rel) + // The proto_rust_library macro's underlying _proto_rust_lib rule + // (named "_lib") is what provides ProtoCompileInfo for the + // wrapper lib.rs + Cargo.toml; that's the label that belongs in + // the root proto_compile_assets aggregator. + pl.vendorAssetLabels = append(pl.vendorAssetLabels, "//"+args.Rel+":"+r.Name()+"_lib") + case "proto_compiled_sources": + pl.vendorAssetLabels = append(pl.vendorAssetLabels, "//"+args.Rel+":"+r.Name()) + } + } + + // Capture the repo root on the first call so DoneGeneratingRules can + // locate the root Cargo.toml without access to *config.Config. + if pl.repoRoot == "" { + pl.repoRoot = args.Config.RepoRoot } // special case if this is the root BUILD file and the user requested to diff --git a/pkg/language/protobuf/lang.go b/pkg/language/protobuf/lang.go index 802fc5c2..eeb9b78f 100644 --- a/pkg/language/protobuf/lang.go +++ b/pkg/language/protobuf/lang.go @@ -40,6 +40,21 @@ type protobufLang struct { starlarkRules arrayFlags // starlarkPlugins stores custom starlark proto plugin names in the form filename%pluginname starlarkPlugins arrayFlags + // protoRustLibraryPackages collects the workspace-relative path of every + // package that emits a proto_rust_library rule. Populated in + // GenerateRules and consumed in DoneGeneratingRules to update the root + // Cargo.toml [workspace] members list. + protoRustLibraryPackages []string + // vendorAssetLabels collects bazel labels of every generated rule that + // provides ProtoCompileInfo and should appear in the root + // `proto_compile_assets` aggregator. Populated in GenerateRules and + // consumed in DoneGeneratingRules to update the deps list of the + // vendoring target between the vendor_proto_sources_deps markers. + vendorAssetLabels []string + // repoRoot is captured from the first GenerateRules call so + // DoneGeneratingRules (which receives no config) can locate the root + // Cargo.toml and BUILD.bazel. + repoRoot string } // Name implements part of the language.Language interface. diff --git a/pkg/language/protobuf/lifecycle.go b/pkg/language/protobuf/lifecycle.go index 2b5f557a..019b79d7 100644 --- a/pkg/language/protobuf/lifecycle.go +++ b/pkg/language/protobuf/lifecycle.go @@ -2,6 +2,12 @@ package protobuf import ( "context" + "fmt" + "log" + "os" + "path/filepath" + "sort" + "strings" ) // Before implements part of the language.LifecycleManager interface. @@ -9,9 +15,138 @@ func (pl *protobufLang) Before(context.Context) { } // DoneGeneratingRules implements part of the language.LifecycleManager interface. +// +// Performs two cross-package syncs that need every GenerateRules call to +// have completed first: +// +// 1. Root Cargo.toml [workspace] members list — the lines between the +// `# gazelle:proto_rust_members start/end` markers are replaced with +// one entry per package that emitted a proto_rust_library. +// +// 2. Root BUILD.bazel proto_compile_assets aggregator deps — the lines +// between the `# gazelle:vendor_proto_sources_deps start/end` markers +// are replaced with one entry per generated proto_compiled_sources rule +// and one entry per proto_rust_library's underlying _lib target. +// +// Both syncs are no-ops when the corresponding markers are absent (or the +// target file does not exist). func (pl *protobufLang) DoneGeneratingRules() { + if pl.repoRoot == "" { + return + } + if err := updateRootCargoMembers(pl.repoRoot, pl.protoRustLibraryPackages); err != nil { + log.Printf("warning: could not update root Cargo.toml proto_rust_members: %v", err) + } + if err := updateRootVendorAssetsDeps(pl.repoRoot, pl.vendorAssetLabels); err != nil { + log.Printf("warning: could not update root BUILD.bazel vendor_proto_sources_deps: %v", err) + } } // AfterResolvingDeps implements part of the language.LifecycleManager interface. func (pl *protobufLang) AfterResolvingDeps(context.Context) { } + +const ( + cargoMembersStartMarker = "# gazelle:proto_rust_members start" + cargoMembersEndMarker = "# gazelle:proto_rust_members end" + vendorAssetsDepsStartMarker = "# gazelle:vendor_proto_sources_deps start" + vendorAssetsDepsEndMarker = "# gazelle:vendor_proto_sources_deps end" +) + +// updateRootCargoMembers rewrites the gazelle:proto_rust_members marker +// section in the root Cargo.toml with a sorted, deduplicated list of +// `"",` entries. No-op if the file is missing or the markers are +// absent. +func updateRootCargoMembers(repoRoot string, packages []string) error { + return rewriteMarkerSection( + filepath.Join(repoRoot, "Cargo.toml"), + cargoMembersStartMarker, + cargoMembersEndMarker, + packages, + "[workspace] members list", + ) +} + +// updateRootVendorAssetsDeps rewrites the gazelle:vendor_proto_sources_deps +// marker section in the root BUILD.bazel with a sorted, deduplicated list +// of `"