Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
5c379a1
Auto-sync root Cargo.toml workspace members and proto_compile_assets …
pcj May 9, 2026
5d8f9d9
Merge remote-tracking branch 'origin/master' into feat/rust
pcj May 31, 2026
7932580
proto_rust_library: flat-crate convention — drop pkg attr, _rs suffix…
pcj Jun 8, 2026
b655336
gencopy: print a ProTip with the .update target when a check fails
pcj Jun 9, 2026
68cf4a6
fix: enumerate all protos
pcj Jun 15, 2026
9d81be5
fix: extern path resolution for WKT
pcj Jun 16, 2026
1935ee5
fix(rust): service-only libs, plugin order, output_mappings for path-…
pcj Jun 17, 2026
bf48a4e
checkpoint: per-file progress
pcj Jun 22, 2026
ab7c443
per-file-imports checkpoint
pcj Jun 22, 2026
e4fb3f2
checkpoint: refactor without per_file
pcj Jun 22, 2026
7eb04a4
checkpoint: all rlibs compile
pcj Jun 23, 2026
c6bf973
apply buildifier.fix
pcj Jun 23, 2026
d2f3e70
Merge remote-tracking branch 'origin/master' into feat/rust
pcj Aug 26, 2026
cffd057
fix(rust): deduplicate extern paths and apply rewrites
pcj Aug 28, 2026
b779219
fix(rust): exclude per-file crates from Cargo workspace
pcj Aug 28, 2026
a32faf8
fix(rust): enroll per-file Cargo manifests
pcj Aug 28, 2026
2b9d3be
fix(rust): enroll per-file Cargo deps transitively
pcj Aug 28, 2026
3e5b99b
fix(rust): manage per-file Cargo exclusions
pcj Aug 28, 2026
b5078bb
fix(rust): expose per-file Cargo path dependencies
pcj Aug 28, 2026
4aa32a1
fix(rust): document Cargo path patches
pcj Aug 28, 2026
a0e1c84
Revert "fix(rust): document Cargo path patches"
pcj Aug 28, 2026
eb6f0e7
Revert "fix(rust): expose per-file Cargo path dependencies"
pcj Aug 28, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 22 additions & 2 deletions cmd/gencopy/gencopy.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"os"
"path/filepath"
"strconv"
"strings"

"github.com/google/go-cmp/cmp"
)
Expand Down Expand Up @@ -105,7 +106,7 @@ func readFileAsString(filename string) (string, error) {
return string(bytes), nil
}

func check(_ *Config, pkg *PackageConfig, pairs []*SrcDst) error {
func check(cfg *Config, pkg *PackageConfig, pairs []*SrcDst) error {
for _, pair := range pairs {
expected, err := readFileAsString(pair.Src)
if err != nil {
Expand All @@ -117,7 +118,10 @@ func check(_ *Config, pkg *PackageConfig, pairs []*SrcDst) error {
}

if diff := cmp.Diff(expected, actual); diff != "" {
return fmt.Errorf("gencopy mismatch %q vs. %q (-want +got):\n%s", pair.Src, pair.Dst, diff)
return fmt.Errorf(
"gencopy mismatch %q vs. %q (-want +got):\n%s\n%s",
pair.Src, pair.Dst, diff, regenerateProTip(pkg.TargetLabel, cfg.UpdateTargetLabelName),
)
}
}

Expand All @@ -129,6 +133,22 @@ func check(_ *Config, pkg *PackageConfig, pairs []*SrcDst) error {
return nil
}

// regenerateProTip returns a friendly hint pointing the developer at the
// `.update` target that regenerates the checked-in copies. targetLabel is the
// proto_compile rule's label (e.g. "//proto:foo_proto_compile" or
// "@@repo//proto:foo_proto_compile"); updateName is the .update target's
// rule name (e.g. "foo_proto_compile.update").
func regenerateProTip(targetLabel, updateName string) string {
if updateName == "" {
return ""
}
updateLabel := updateName
if idx := strings.LastIndex(targetLabel, ":"); idx >= 0 {
updateLabel = targetLabel[:idx+1] + updateName
}
return fmt.Sprintf("\nProTip: to regenerate, run:\n bazel run %s\n", updateLabel)
}

func update(cfg *Config, pkg *PackageConfig, pairs []*SrcDst) error {
for _, pair := range pairs {
pair.Dst += cfg.Extension
Expand Down
36 changes: 36 additions & 0 deletions cmd/gencopy/gencopy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,42 @@ func TestRunPkg(t *testing.T) {
}
}

func TestRegenerateProTip(t *testing.T) {
for name, tc := range map[string]struct {
targetLabel string
updateName string
want string
}{
"empty update name": {
targetLabel: "//proto:foo_proto_compile",
updateName: "",
want: "",
},
"local target": {
targetLabel: "//proto:foo_proto_compile",
updateName: "foo_proto_compile.update",
want: "\nProTip: to regenerate, run:\n bazel run //proto:foo_proto_compile.update\n",
},
"external repo target": {
targetLabel: "@@some_repo//pkg:foo_proto_compile",
updateName: "foo_proto_compile.update",
want: "\nProTip: to regenerate, run:\n bazel run @@some_repo//pkg:foo_proto_compile.update\n",
},
"target with no colon": {
targetLabel: "raw_label_no_colon",
updateName: "foo_proto_compile.update",
want: "\nProTip: to regenerate, run:\n bazel run foo_proto_compile.update\n",
},
} {
t.Run(name, func(t *testing.T) {
got := regenerateProTip(tc.targetLabel, tc.updateName)
if got != tc.want {
t.Errorf("regenerateProTip: got %q, want %q", got, tc.want)
}
})
}
}

// listFiles - convenience debugging function to log the files under a given dir
func listFiles(t *testing.T, dir string) error {
return filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
Expand Down
2 changes: 1 addition & 1 deletion example/thing/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ proto_library(
name = "thing_proto",
srcs = ["thing.proto"],
visibility = ["//visibility:public"],
deps = ["@protobufapis//google/protobuf:timestamp_proto"],
deps = ["@com_google_protobuf//:timestamp_proto"],
)

proto_cc_library(
Expand Down
2 changes: 2 additions & 0 deletions pkg/language/protobuf/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ go_library(
go_test(
name = "protobuf_test",
srcs = [
"config_filemode_test.go",
"generate_test.go",
"override_test.go",
],
Expand All @@ -37,6 +38,7 @@ go_test(
"@bazel_gazelle//config",
"@bazel_gazelle//label",
"@bazel_gazelle//language",
"@bazel_gazelle//language/proto",
"@bazel_gazelle//resolve",
"@bazel_gazelle//rule",
"@bazel_gazelle//testtools",
Expand Down
8 changes: 8 additions & 0 deletions pkg/language/protobuf/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,10 +117,18 @@ func (pl *protobufLang) Configure(c *config.Config, rel string, f *rule.File) {

// getOrCreatePackageConfig either inserts a new config into the map under the
// language name or replaces it with a clone.
//
// When cloning from the parent directory's config we must overwrite the
// embedded *config.Config pointer to the current directory's config. Without
// this, lookups that walk `cfg.Config.Exts[...]` (e.g.
// `IsProtoFileMode` reading the standard proto language's per-directory mode)
// resolve against the parent's `Exts` and miss directives like
// `# gazelle:proto file` that were applied to the child only.
func (pl *protobufLang) getOrCreatePackageConfig(config *config.Config) *protoc.PackageConfig {
var cfg *protoc.PackageConfig
if existingExt, ok := config.Exts[pl.name]; ok {
cfg = existingExt.(*protoc.PackageConfig).Clone()
cfg.Config = config
} else {
cfg = protoc.NewPackageConfig(config)
}
Expand Down
37 changes: 37 additions & 0 deletions pkg/language/protobuf/config_filemode_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package protobuf

import (
"testing"

"github.com/bazelbuild/bazel-gazelle/config"
gproto "github.com/bazelbuild/bazel-gazelle/language/proto"
)

// Regression: getOrCreatePackageConfig must point the cloned PackageConfig's
// embedded *config.Config at the CURRENT directory's config, not the parent's.
// Otherwise IsProtoFileMode reads the parent's proto-language config and
// misses a child directory that opted into `# gazelle:proto file`.
func TestGetOrCreatePackageConfig_RebindsConfig(t *testing.T) {
pl := &protobufLang{name: "protobuf"}

// Parent: default mode.
parent := &config.Config{Exts: map[string]interface{}{}}
parent.Exts["proto"] = &gproto.ProtoConfig{Mode: gproto.DefaultMode}
pl.getOrCreatePackageConfig(parent)

// Child: clone via gazelle's Config.Clone, then proto-lang flips mode to FileMode
// for this dir only.
child := parent.Clone()
childProto := &gproto.ProtoConfig{Mode: gproto.FileMode}
child.Exts["proto"] = childProto

cfg := pl.getOrCreatePackageConfig(child)
if cfg.Config != child {
t.Fatalf("cloned PackageConfig.Config not rebound to child: got %p, want %p", cfg.Config, child)
}

// IsProtoFileMode-equivalent check.
if gproto.GetProtoConfig(cfg.Config).Mode != gproto.FileMode {
t.Errorf("expected FileMode via cfg.Config, got %v", gproto.GetProtoConfig(cfg.Config).Mode)
}
}
18 changes: 17 additions & 1 deletion pkg/language/protobuf/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package protobuf
import (
"log"
"path"
"strings"

"github.com/bazelbuild/bazel-gazelle/config"
"github.com/bazelbuild/bazel-gazelle/label"
Expand Down Expand Up @@ -104,7 +105,14 @@ func (pl *protobufLang) GenerateRules(args language.GenerateArgs) language.Gener
protoc.GlobalRuleIndex().Put(internalLabel, r)
switch r.Kind() {
case "proto_rust_library":
pl.protoRustLibraryPackages = append(pl.protoRustLibraryPackages, args.Rel)
if protoRustLibraryIsExplicitWorkspaceMember(r.Name()) {
pl.protoRustLibraryPackages = append(pl.protoRustLibraryPackages, args.Rel)
} else {
pl.protoRustPerFilePackageDirs = append(
pl.protoRustPerFilePackageDirs,
path.Join(args.Rel, "_rust"),
)
}
// The proto_rust_library macro's underlying _proto_rust_lib rule
// (named "<name>_lib") is what provides ProtoCompileInfo for the
// wrapper lib.rs + Cargo.toml; that's the label that belongs in
Expand Down Expand Up @@ -136,6 +144,14 @@ func (pl *protobufLang) GenerateRules(args language.GenerateArgs) language.Gener
}
}

// protoRustLibraryIsExplicitWorkspaceMember reports whether the generated
// crate belongs in the root Cargo.toml marker section. Per-file crates are
// path dependencies of these package-level roots, so Cargo enrolls them in
// the workspace transitively after their manifests are vendored.
func protoRustLibraryIsExplicitWorkspaceMember(name string) bool {
return !strings.Contains(name, "__")
}

func matchingFiles(files map[string]*protoc.File, srcs []label.Label) []*protoc.File {
matching := make([]*protoc.File, 0)
for _, src := range srcs {
Expand Down
22 changes: 22 additions & 0 deletions pkg/language/protobuf/generate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,28 @@ import "google/protobuf/any.proto";
}
}

func TestProtoRustLibraryIsExplicitWorkspaceMember(t *testing.T) {
for name, tc := range map[string]struct {
ruleName string
want bool
}{
"package-level crate": {
ruleName: "example_proto_rs",
want: true,
},
"per-file crate": {
ruleName: "example_proto__message_proto_rs",
want: false,
},
} {
t.Run(name, func(t *testing.T) {
if got := protoRustLibraryIsExplicitWorkspaceMember(tc.ruleName); got != tc.want {
t.Errorf("protoRustLibraryIsExplicitWorkspaceMember(%q) = %t, want %t", tc.ruleName, got, tc.want)
}
})
}
}

type testGenerateRulesState struct {
t *testing.T
tmpdir string
Expand Down
10 changes: 8 additions & 2 deletions pkg/language/protobuf/lang.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,17 @@ 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
// protoRustLibraryPackages collects the workspace-relative path of each
// package-level proto_rust_library. Per-file crates remain standalone path
// dependencies under excluded _rust directories. Populated in
// GenerateRules and consumed in DoneGeneratingRules to update the root
// Cargo.toml [workspace] members list.
protoRustLibraryPackages []string
// protoRustPerFilePackageDirs collects the workspace-relative _rust
// directories that contain standalone per-file crates. These directories
// are excluded from the root Cargo workspace so each generated manifest can
// be used directly or as a path dependency.
protoRustPerFilePackageDirs []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
Expand Down
36 changes: 29 additions & 7 deletions pkg/language/protobuf/lifecycle.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,23 @@ 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
// Performs three 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.
// one entry per package-level 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
// 3. Root Cargo.toml [workspace] exclude list — the lines between the
// `# gazelle:proto_rust_excludes start/end` markers are replaced with
// one entry per Bazel package containing standalone per-file crates.
//
// All syncs are no-ops when the corresponding markers are absent (or the
// target file does not exist).
func (pl *protobufLang) DoneGeneratingRules() {
if pl.repoRoot == "" {
Expand All @@ -37,6 +41,9 @@ func (pl *protobufLang) DoneGeneratingRules() {
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 := updateRootCargoExcludes(pl.repoRoot, pl.protoRustPerFilePackageDirs); err != nil {
log.Printf("warning: could not update root Cargo.toml proto_rust_excludes: %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)
}
Expand All @@ -47,10 +54,12 @@ 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"
cargoMembersStartMarker = "# gazelle:proto_rust_members start"
cargoMembersEndMarker = "# gazelle:proto_rust_members end"
cargoExcludesStartMarker = "# gazelle:proto_rust_excludes start"
cargoExcludesEndMarker = "# gazelle:proto_rust_excludes end"
vendorAssetsDepsStartMarker = "# gazelle:vendor_proto_sources_deps start"
vendorAssetsDepsEndMarker = "# gazelle:vendor_proto_sources_deps end"
)

// updateRootCargoMembers rewrites the gazelle:proto_rust_members marker
Expand All @@ -67,6 +76,19 @@ func updateRootCargoMembers(repoRoot string, packages []string) error {
)
}

// updateRootCargoExcludes rewrites the gazelle:proto_rust_excludes marker
// section in the root Cargo.toml with directories containing standalone
// per-file crates.
func updateRootCargoExcludes(repoRoot string, packages []string) error {
return rewriteMarkerSection(
filepath.Join(repoRoot, "Cargo.toml"),
cargoExcludesStartMarker,
cargoExcludesEndMarker,
packages,
"[workspace] exclude list",
)
}

// updateRootVendorAssetsDeps rewrites the gazelle:vendor_proto_sources_deps
// marker section in the root BUILD.bazel with a sorted, deduplicated list
// of `"<label>",` entries. No-op if the file is missing or the markers are
Expand Down
6 changes: 5 additions & 1 deletion pkg/plugin/neoeinstein/prost/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,21 @@ go_library(
"//pkg/protoc",
"@bazel_gazelle//label",
"@bazel_gazelle//rule",
"@com_github_emicklei_proto//:proto",
],
)

go_test(
name = "prost_test",
srcs = [
"extern_paths_test.go",
"needs_cwkt_test.go",
"nested_type_path_test.go",
"protoc-gen-prost_test.go",
"register_nested_test.go",
],
embed = [":prost"],
deps = [
":prost",
"//pkg/plugintest",
"//pkg/protoc",
"@bazel_gazelle//label",
Expand Down
Loading
Loading