Skip to content

Commit d07619f

Browse files
committed
[wip] Begin implementation of module-level py_proto_library import support
1 parent 004be45 commit d07619f

File tree

26 files changed

+188
-4
lines changed

26 files changed

+188
-4
lines changed

gazelle/python/generate.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ const (
4242
pyTestEntrypointTargetname = "__test__"
4343
conftestFilename = "conftest.py"
4444
conftestTargetname = "conftest"
45+
protoKey = "_proto_target"
46+
protoRelKey = "_proto_rel"
4547
)
4648

4749
var (
@@ -572,11 +574,16 @@ func ensureNoCollision(file *rule.File, targetName, kind string) error {
572574
func generateProtoLibraries(args language.GenerateArgs, cfg *pythonconfig.Config, pythonProjectRoot string, visibility []string, res *language.GenerateResult) {
573575
// First, enumerate all the proto_library in this package.
574576
var protoRuleNames []string
577+
protoRules := map[string]*rule.Rule{}
578+
protoRel := map[string]string{}
579+
575580
for _, r := range args.OtherGen {
576581
if r.Kind() != "proto_library" {
577582
continue
578583
}
579584
protoRuleNames = append(protoRuleNames, r.Name())
585+
protoRules[r.Name()] = r
586+
protoRel[r.Name()] = args.Rel
580587
}
581588
sort.Strings(protoRuleNames)
582589

@@ -609,6 +616,8 @@ func generateProtoLibraries(args language.GenerateArgs, cfg *pythonconfig.Config
609616
addVisibility(visibility).
610617
addResolvedDependency(":" + protoRuleName).
611618
generateImportsAttribute().build()
619+
pyProtoLibrary.SetPrivateAttr(protoKey, protoRuleName)
620+
pyProtoLibrary.SetPrivateAttr(protoRelKey, protoRel[protoRuleName])
612621

613622
res.Gen = append(res.Gen, pyProtoLibrary)
614623
res.Imports = append(res.Imports, pyProtoLibrary.PrivateAttr(config.GazelleImportsKey))

gazelle/python/resolve.go

Lines changed: 58 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,18 @@ func (*Resolver) Name() string { return languageName }
5757
func (py *Resolver) Imports(c *config.Config, r *rule.Rule, f *rule.File) []resolve.ImportSpec {
5858
cfgs := c.Exts[languageName].(pythonconfig.Configs)
5959
cfg := cfgs[f.Pkg]
60+
6061
srcs := r.AttrStrings("srcs")
62+
if srcs != nil {
63+
return importsSrcLibrary(cfg, srcs, f)
64+
} else if isProtoLibrary(r) {
65+
return importsProtoLibrary(cfg, r, f)
66+
}
67+
68+
return nil
69+
}
70+
71+
func importsSrcLibrary(cfg *pythonconfig.Config, srcs []string, f *rule.File) []resolve.ImportSpec {
6172
provides := make([]resolve.ImportSpec, 0, len(srcs)+1)
6273
for _, src := range srcs {
6374
ext := filepath.Ext(src)
@@ -114,6 +125,42 @@ func importSpecFromSrc(pythonProjectRoot, bzlPkg, src string) resolve.ImportSpec
114125
}
115126
}
116127

128+
func isProtoLibrary(r *rule.Rule) bool {
129+
return r.Kind() == pyProtoLibraryKind
130+
}
131+
132+
func importsProtoLibrary(cfg *pythonconfig.Config, r *rule.Rule, f *rule.File) []resolve.ImportSpec {
133+
specs := []resolve.ImportSpec{}
134+
135+
// First, determine the root module and emit an import for that,
136+
// i.e. for //foo:foo_py_pb2, we'd get foo.foo_pb2
137+
protoRuleAttr := r.PrivateAttr(protoKey)
138+
protoRelAttr := r.PrivateAttr(protoRelKey)
139+
if protoRuleAttr == nil || protoRelAttr == nil {
140+
return specs
141+
}
142+
143+
protoRule := protoRuleAttr.(string)
144+
generatedPbFileName := strings.TrimSuffix(protoRule, "_proto") + "_pb2.py"
145+
protoRel := protoRelAttr.(string)
146+
147+
specs = append(specs, importSpecFromSrc(cfg.PythonProjectRoot(), protoRel, generatedPbFileName))
148+
149+
// TODO: use parsed proto FileInfo to enumerate importable constants, like messages,
150+
// and emit ImportSpec for them
151+
// protoPkg := r.PrivateAttr(proto.PackageKey).(proto.Package)
152+
// for _, protoFileInfo := range protoPkg.Files {
153+
// for _, svc := range protoFileInfo.Services {
154+
// specs = append(specs, resolve.ImportSpec{
155+
// Lang: languageName,
156+
// Imp: fmt.Sprintf("%s.%s", rootPath, svc),
157+
// })
158+
// }
159+
// Repeat for Messages, Enums
160+
161+
return specs
162+
}
163+
117164
// Embeds returns a list of labels of rules that the given rule embeds. If
118165
// a rule is embedded by another importable rule of the same language, only
119166
// the embedding rule will be indexed. The embedding rule will inherit
@@ -210,9 +257,9 @@ func (py *Resolver) Resolve(
210257
baseParts = pkgParts[:len(pkgParts)-(relativeDepth-1)]
211258
}
212259
// Build absolute module path
213-
absParts := append([]string{}, baseParts...) // base path
214-
absParts = append(absParts, fromParts...) // subpath from 'from'
215-
absParts = append(absParts, imported) // actual imported symbol
260+
absParts := append([]string{}, baseParts...) // base path
261+
absParts = append(absParts, fromParts...) // subpath from 'from'
262+
absParts = append(absParts, imported) // actual imported symbol
216263

217264
moduleName = strings.Join(absParts, ".")
218265
}
@@ -282,6 +329,9 @@ func (py *Resolver) Resolve(
282329
// Check if the imported module is part of the standard library.
283330
if isStdModule(Module{Name: moduleName}) {
284331
continue MODULES_LOOP
332+
} else if r.Kind() == pyProtoLibraryKind {
333+
// For py_proto_library, fall back to guessing the label based on the proto_library rule name.
334+
matches = py.resolveProtoFallback(cfg)
285335
} else if cfg.ValidateImportStatements() {
286336
err := fmt.Errorf(
287337
"%[1]q, line %[2]d: %[3]q is an invalid dependency: possible solutions:\n"+
@@ -372,6 +422,11 @@ func (py *Resolver) Resolve(
372422
}
373423
}
374424

425+
func (*Resolver) resolveProtoFallback(c *pythonconfig.Config) []resolve.FindResult {
426+
// TODO
427+
return []resolve.FindResult{}
428+
}
429+
375430
// addResolvedDeps adds the pre-resolved dependencies from the rule's private attributes
376431
// to the provided deps set.
377432
func addResolvedDeps(

gazelle/python/testdata/directive_python_proto_naming_convention/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,4 @@ correctly:
66
1. Has no effect on pre-existing `py_proto_library` when `gazelle:python_generate_proto` is disabled.
77
2. Uses the default value when proto generation is on and `python_proto_naming_convention` is not set.
88
3. Uses the provided naming convention when proto generation is on and `python_proto_naming_convention` is set.
9-
4. With a pre-existing `py_proto_library` not following a given naming convention, keeps it intact and does not rename it.
9+
4. With a pre-existing `py_proto_library` not following a given naming convention, keeps it intact and does not rename it.

gazelle/python/testdata/resolves_proto_imports/BUILD.in

Whitespace-only changes.

gazelle/python/testdata/resolves_proto_imports/BUILD.out

Whitespace-only changes.
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Resolves proto imports
2+
3+
This test asserts that Gazelle can resolve imports from `py_proto_library` targets:
4+
5+
1. Generates a dependency in the default case.
6+
2. Uses `gazelle:resolve` to generate dependencies.
7+
3. Uses `python_proto_naming_convention` to generate dependencies.
8+
9+
[gh-1703]: https://github.com/bazel-contrib/rules_python/issues/1703

gazelle/python/testdata/resolves_proto_imports/WORKSPACE

Whitespace-only changes.

gazelle/python/testdata/resolves_proto_imports/test.yaml

Whitespace-only changes.

gazelle/python/testdata/resolves_proto_imports/test1_generates_dependency/BUILD.in

Whitespace-only changes.
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
load("@rules_python//python:defs.bzl", "py_library")
2+
3+
py_library(
4+
name = "test1_generates_dependency",
5+
srcs = ["bar.py"],
6+
visibility = ["//:__subpackages__"],
7+
deps = ["//test1_generates_dependency/foo:test1_generates_dependency_foo_py_pb2"],
8+
)

0 commit comments

Comments
 (0)