github.com/stackb/rules_proto@v0.0.0-20240221195024-5428336c51f1/pkg/rule/rules_python/py_library.go (about) 1 package rules_python 2 3 import ( 4 "path/filepath" 5 "strings" 6 7 "github.com/bazelbuild/bazel-gazelle/config" 8 "github.com/bazelbuild/bazel-gazelle/label" 9 "github.com/bazelbuild/bazel-gazelle/resolve" 10 "github.com/bazelbuild/bazel-gazelle/rule" 11 12 "github.com/stackb/rules_proto/pkg/protoc" 13 ) 14 15 var pyLibraryKindInfo = rule.KindInfo{ 16 MergeableAttrs: map[string]bool{ 17 "srcs": true, 18 "deps": true, 19 "visibility": true, 20 "imports": true, 21 }, 22 NonEmptyAttrs: map[string]bool{ 23 "srcs": true, 24 }, 25 ResolveAttrs: map[string]bool{ 26 "deps": true, 27 }, 28 } 29 30 // PyLibrary implements RuleProvider for 'py_library'-derived rules. 31 type PyLibrary struct { 32 KindName string 33 RuleNameSuffix string 34 Outputs []string 35 Config *protoc.ProtocConfiguration 36 RuleConfig *protoc.LanguageRuleConfig 37 Resolver protoc.DepsResolver 38 } 39 40 // Kind implements part of the ruleProvider interface. 41 func (s *PyLibrary) Kind() string { 42 return s.KindName 43 } 44 45 // Name implements part of the ruleProvider interface. 46 func (s *PyLibrary) Name() string { 47 return s.Config.Library.BaseName() + s.RuleNameSuffix 48 } 49 50 // Srcs computes the srcs list for the rule. 51 func (s *PyLibrary) Srcs() []string { 52 srcs := make([]string, 0) 53 for _, output := range s.Outputs { 54 if strings.HasSuffix(output, ".py") { 55 srcs = append(srcs, protoc.StripRel(s.Config.Rel, output)) 56 } 57 } 58 return srcs 59 } 60 61 // Deps computes the deps list for the rule. 62 func (s *PyLibrary) Deps() []string { 63 return s.RuleConfig.GetDeps() 64 } 65 66 // Visibility provides visibility labels. 67 func (s *PyLibrary) Visibility() []string { 68 return s.RuleConfig.GetVisibility() 69 } 70 71 // ImportsAttr provides the py_library.imports attribute values. 72 func (s *PyLibrary) ImportsAttr() (imps []string) { 73 // if we have a strip_import_prefix on the proto_library, the python search 74 // path should include the directory N parents above the current package, 75 // where N is the number of segments needed to ascend to the prefix from 76 // the dir for the current rule. 77 if s.Config.Library.StripImportPrefix() == "" { 78 return 79 } 80 prefix := s.Config.Library.StripImportPrefix() 81 if !strings.HasPrefix(prefix, "/") { 82 return // deal with relative-imports at another time 83 } 84 85 prefix = strings.TrimPrefix(prefix, "/") 86 rel, err := filepath.Rel(prefix, s.Config.Rel) 87 if err != nil { 88 return // the prefix doesn't prefix the current path, shouldn't happen 89 } 90 91 parts := strings.Split(rel, "/") 92 for i := 0; i < len(parts); i++ { 93 parts[i] = ".." 94 } 95 imp := strings.Join(parts, "/") 96 imps = append(imps, imp) 97 return 98 } 99 100 // Rule implements part of the ruleProvider interface. 101 func (s *PyLibrary) Rule(otherGen ...*rule.Rule) *rule.Rule { 102 newRule := rule.NewRule(s.Kind(), s.Name()) 103 104 newRule.SetAttr("srcs", s.Srcs()) 105 106 deps := s.Deps() 107 if len(deps) > 0 { 108 newRule.SetAttr("deps", deps) 109 } 110 111 imports := s.ImportsAttr() 112 if len(imports) > 0 { 113 newRule.SetAttr("imports", imports) 114 } 115 116 visibility := s.Visibility() 117 if len(visibility) > 0 { 118 newRule.SetAttr("visibility", visibility) 119 } 120 121 return newRule 122 } 123 124 func pyFilenameToImport(s string) string { 125 if strings.HasSuffix(s, ".py") { 126 return strings.ReplaceAll(s[:len(s)-3], "/", ".") 127 } 128 return s 129 } 130 131 // Imports implements part of the RuleProvider interface. 132 func (s *PyLibrary) Imports(c *config.Config, r *rule.Rule, file *rule.File) []resolve.ImportSpec { 133 if lib, ok := r.PrivateAttr(protoc.ProtoLibraryKey).(protoc.ProtoLibrary); ok { 134 specs := protoc.ProtoLibraryImportSpecsForKind(r.Kind(), lib) 135 specs = maybeStripImportPrefix(specs, lib.StripImportPrefix()) 136 from := label.New("", file.Pkg, r.Name()) 137 for _, o := range s.Outputs { 138 pyImp := pyFilenameToImport(o) 139 protoc.GlobalResolver().Provide("py", "py", pyImp, from) 140 specs = append(specs, resolve.ImportSpec{Lang: "py", Imp: pyImp}) 141 } 142 143 return specs 144 } 145 return nil 146 } 147 148 // Resolve implements part of the RuleProvider interface. 149 func (s *PyLibrary) Resolve(c *config.Config, ix *resolve.RuleIndex, r *rule.Rule, imports []string, from label.Label) { 150 s.Resolver(c, ix, r, imports, from) 151 } 152 153 func maybeStripImportPrefix(specs []resolve.ImportSpec, stripImportPrefix string) []resolve.ImportSpec { 154 if stripImportPrefix == "" { 155 return specs 156 } 157 158 prefix := strings.TrimPrefix(stripImportPrefix, "/") 159 for i, spec := range specs { 160 spec.Imp = strings.TrimPrefix(spec.Imp, prefix) 161 spec.Imp = strings.TrimPrefix(spec.Imp, "/") // should never be absolute 162 specs[i] = spec 163 } 164 165 return specs 166 }