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  }