Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
119 changes: 85 additions & 34 deletions pkg/plugin/neoeinstein/prost/extern_paths.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,28 +108,39 @@ func ResolveExternPathOptionsForReferences(cfg *protoc.PluginConfiguration, r *r
// package. Self-extern overrides are NOT included — see
// ResolveExternPathOptionsForReferences for the variant that adds them.
func ResolveTransitiveExternPaths(r *rule.Rule, from label.Label) []string {
lib := r.PrivateAttr(protoc.ProtoLibraryKey)
if lib == nil {
libraries := mergedLibraries(r)
if len(libraries) == 0 {
return nil
}
library := lib.(protoc.ProtoLibrary)
libRule := library.Rule()

if cached, ok := libRule.PrivateAttr(TransitiveExternPathsKey).([]string); ok {
// Cache off the first library's underlying rule — merge can occur but the
// post-merge PrivateAttrs travel with the first library's rule for back-
// compat with the existing cache placement.
cacheRule := libraries[0].Rule()
if cached, ok := cacheRule.PrivateAttr(TransitiveExternPathsKey).([]string); ok {
return cached
}

resolver := protoc.GlobalResolver()

ownFiles := make(map[string]bool)
for _, src := range library.Srcs() {
ownFiles[path.Join(from.Pkg, src)] = true
for _, library := range libraries {
for _, src := range library.Srcs() {
ownFiles[path.Join(from.Pkg, src)] = true
}
}
// Also treat any proto file whose registered prost_extern crate matches
// one of our own as own. This is a belt-and-suspenders guard for cases
// where ownFiles can't identify a merged-in file by path alone (the
// caller's `from.Pkg` is the rule's bazel package, but a merged proto_-
// library may sit in a different bazel package).
ownCrates := ownCrateNames(libraries, resolver, from)

seen := make(map[string]bool)
stack := list.New()
for _, src := range library.Srcs() {
stack.PushBack(path.Join(from.Pkg, src))
for _, library := range libraries {
for _, src := range library.Srcs() {
stack.PushBack(path.Join(from.Pkg, src))
}
}

externPathsByPackage := make(map[string]string)
Expand All @@ -154,11 +165,6 @@ func ResolveTransitiveExternPaths(r *rule.Rule, from label.Label) []string {
continue
}

// Skip well-known types — prost ships these built-in.
if strings.HasPrefix(protofile, "google/protobuf/") {
continue
}

results := resolver.Resolve("proto", "prost_extern", protofile)
if len(results) == 0 {
continue
Expand All @@ -170,13 +176,20 @@ func ResolveTransitiveExternPaths(r *rule.Rule, from label.Label) []string {
if protoPackage == "" {
continue
}
// Skip files whose crate is one of ours — they're part of the
// merged library set, just couldn't be matched by file path.
if ownCrates[crateName] {
continue
}
if _, exists := externPathsByPackage[protoPackage]; exists {
continue
}

// extern_path=.{proto_package}=::{crate_name}::{rust_module_path}
rustModulePath := strings.ReplaceAll(protoPackage, ".", "::")
externPathsByPackage[protoPackage] = "extern_path=." + protoPackage + "=::" + crateName + "::" + rustModulePath
// extern_path=.{proto_package}=::{crate_name}
// The crate exposes all generated types at its root (see the
// proto_rust_library Starlark macro), so no nested module path is
// appended after the crate name.
externPathsByPackage[protoPackage] = "extern_path=." + protoPackage + "=::" + crateName
}

result := make([]string, 0, len(externPathsByPackage))
Expand All @@ -185,7 +198,7 @@ func ResolveTransitiveExternPaths(r *rule.Rule, from label.Label) []string {
}
sort.Strings(result)

libRule.SetPrivateAttr(TransitiveExternPathsKey, result)
cacheRule.SetPrivateAttr(TransitiveExternPathsKey, result)
return result
}

Expand All @@ -205,31 +218,68 @@ func mergeExternPathOptions(cfg *protoc.PluginConfiguration, externPaths []strin

// ownProtoPackages returns the set of proto packages the library itself
// contributes, computed from prost_extern resolver entries for each own
// proto file. Cached on the library rule.
// proto file across all merged proto_libraries. Cached on the rule.
func ownProtoPackages(r *rule.Rule, from label.Label) map[string]bool {
lib := r.PrivateAttr(protoc.ProtoLibraryKey)
if lib == nil {
libraries := mergedLibraries(r)
if len(libraries) == 0 {
return nil
}
library := lib.(protoc.ProtoLibrary)
libRule := library.Rule()

if cached, ok := libRule.PrivateAttr(OwnProtoPackagesKey).(map[string]bool); ok {
cacheRule := libraries[0].Rule()
if cached, ok := cacheRule.PrivateAttr(OwnProtoPackagesKey).(map[string]bool); ok {
return cached
}

resolver := protoc.GlobalResolver()
out := make(map[string]bool)
for _, src := range library.Srcs() {
ownFile := path.Join(from.Pkg, src)
for _, ext := range resolver.Resolve("proto", "prost_extern", ownFile) {
if ext.Label.Pkg != "" {
out[ext.Label.Pkg] = true
for _, library := range libraries {
for _, src := range library.Srcs() {
ownFile := path.Join(from.Pkg, src)
for _, ext := range resolver.Resolve("proto", "prost_extern", ownFile) {
if ext.Label.Pkg != "" {
out[ext.Label.Pkg] = true
}
}
}
}

libRule.SetPrivateAttr(OwnProtoPackagesKey, out)
cacheRule.SetPrivateAttr(OwnProtoPackagesKey, out)
return out
}

// mergedLibraries returns the full set of proto_libraries backing a proto_-
// compile / proto_compiled_sources rule. Prefers MergedProtoLibrariesKey
// (set by proto_compile.Rule for every merge — see protoc.MergedProtoLibrariesKey)
// and falls back to wrapping the single ProtoLibraryKey for callers that
// haven't migrated (e.g. proto_rust_library, hand-constructed test rules).
// Returns nil when the rule carries neither.
func mergedLibraries(r *rule.Rule) []protoc.ProtoLibrary {
if libs, ok := r.PrivateAttr(protoc.MergedProtoLibrariesKey).([]protoc.ProtoLibrary); ok && len(libs) > 0 {
return libs
}
if lib, ok := r.PrivateAttr(protoc.ProtoLibraryKey).(protoc.ProtoLibrary); ok && lib != nil {
return []protoc.ProtoLibrary{lib}
}
return nil
}

// ownCrateNames returns the set of rust crate names registered (via
// prost_extern) for files belonging to any of the library's merged proto_-
// libraries. Used to recognise own-merged files in the dep walk even when
// their on-disk path doesn't share a prefix with from.Pkg (e.g. a
// proto_compiled_sources at //a:foo that merges in //b:bar_proto would
// otherwise see b/bar.proto as external).
func ownCrateNames(libraries []protoc.ProtoLibrary, resolver protoc.ImportResolver, from label.Label) map[string]bool {
out := make(map[string]bool)
for _, library := range libraries {
for _, src := range library.Srcs() {
ownFile := path.Join(from.Pkg, src)
for _, ext := range resolver.Resolve("proto", "prost_extern", ownFile) {
if ext.Label.Name != "" {
out[ext.Label.Name] = true
}
}
}
}
return out
}

Expand All @@ -247,8 +297,9 @@ func selfExternPathsForOverride(ownPackages map[string]bool, parents []string) [
if !hasParentInImports(ownPkg, parentPkgs) {
continue
}
rustModulePath := strings.ReplaceAll(ownPkg, ".", "::")
out = append(out, "extern_path=."+ownPkg+"=crate::"+rustModulePath)
// All own types live at the crate root (flat convention), so the
// self-extern override maps the proto sub-package to bare `crate`.
out = append(out, "extern_path=."+ownPkg+"=crate")
}
return out
}
Expand Down
Loading
Loading