github.com/stackb/rules_proto@v0.0.0-20240221195024-5428336c51f1/pkg/protoc/starlark_util.go (about)

     1  package protoc
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"io/ioutil"
     7  	"os"
     8  	"path/filepath"
     9  	"strings"
    10  
    11  	"go.starlark.net/starlark"
    12  	"go.starlark.net/starlarkstruct"
    13  )
    14  
    15  type errorReporter func(format string, args ...interface{}) error
    16  
    17  type goStarlarkFunction func(thread *starlark.Thread, _ *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error)
    18  
    19  // Symbol is the type of a Starlark constructor symbol.  It prints more
    20  // favorably than a starlark.String.
    21  type Symbol string
    22  
    23  func (s Symbol) String() string             { return string(s) }
    24  func (s Symbol) GoString() string           { return string(s) }
    25  func (s Symbol) Type() string               { return "symbol" }
    26  func (s Symbol) Freeze()                    {} // immutable
    27  func (s Symbol) Truth() starlark.Bool       { return len(s) > 0 }
    28  func (s Symbol) Hash() (uint32, error)      { return starlark.String(s).Hash() }
    29  func (s Symbol) Len() int                   { return len(s) } // bytes
    30  func (s Symbol) Index(i int) starlark.Value { return s[i : i+1] }
    31  
    32  func newStringBoolDict(in map[string]bool) *starlark.Dict {
    33  	out := &starlark.Dict{}
    34  	for k, v := range in {
    35  		out.SetKey(starlark.String(k), starlark.Bool(v))
    36  	}
    37  	return out
    38  }
    39  
    40  func newStringStringDict(in map[string]string) *starlark.Dict {
    41  	out := &starlark.Dict{}
    42  	for k, v := range in {
    43  		out.SetKey(starlark.String(k), starlark.String(v))
    44  	}
    45  	return out
    46  }
    47  
    48  func newStringList(in []string) *starlark.List {
    49  	values := make([]starlark.Value, len(in))
    50  	for i, v := range in {
    51  		values[i] = starlark.String(v)
    52  	}
    53  	return starlark.NewList(values)
    54  }
    55  
    56  func newStringListDict(in map[string]map[string]bool) *starlark.Dict {
    57  	out := &starlark.Dict{}
    58  	return out
    59  }
    60  
    61  func newPredeclared(plugins, rules map[string]*starlarkstruct.Struct) starlark.StringDict {
    62  	protoc := &starlarkstruct.Module{
    63  		Name: "protoc",
    64  		Members: starlark.StringDict{
    65  			"Plugin":              starlark.NewBuiltin("Plugin", newStarlarkPluginFunction(plugins)),
    66  			"Rule":                starlark.NewBuiltin("Rule", newStarlarkLanguageRuleFunction(rules)),
    67  			"PluginConfiguration": starlark.NewBuiltin("PluginConfiguration", newStarlarkPluginConfiguration()),
    68  		},
    69  	}
    70  
    71  	gazelle := &starlarkstruct.Module{
    72  		Name: "gazelle",
    73  		Members: starlark.StringDict{
    74  			"Rule":     starlark.NewBuiltin("Rule", newGazelleRuleFunction()),
    75  			"LoadInfo": starlark.NewBuiltin("LoadInfo", newGazelleLoadInfoFunction()),
    76  			"KindInfo": starlark.NewBuiltin("KindInfo", newGazelleKindInfoFunction()),
    77  		},
    78  	}
    79  
    80  	return starlark.StringDict{
    81  		protoc.Name:  protoc,
    82  		gazelle.Name: gazelle,
    83  		"struct":     starlark.NewBuiltin("struct", starlarkstruct.Make),
    84  	}
    85  }
    86  
    87  func resolveStarlarkFilename(workDir, filename string) (string, error) {
    88  	if filename == "" {
    89  		return "", fmt.Errorf("filename is empty")
    90  	}
    91  	if _, err := os.Stat(filepath.Join(workDir, filename)); !errors.Is(err, os.ErrNotExist) {
    92  		return filepath.Join(workDir, filename), nil
    93  	}
    94  
    95  	dirname := workDir
    96  	// looking for a file named 'DO_NOT_BUILD_HERE' in a parent directory.  The
    97  	// contents of this file names the sourceRoot directory.
    98  	var sourceRoot string
    99  	for dirname != "." {
   100  		sourceRootFile := filepath.Join(dirname, "DO_NOT_BUILD_HERE")
   101  		if _, err := os.Stat(sourceRootFile); errors.Is(err, os.ErrNotExist) {
   102  			dirname = filepath.Dir(dirname)
   103  		} else {
   104  			data, err := ioutil.ReadFile(sourceRootFile)
   105  			if err != nil {
   106  				return "", fmt.Errorf("failed to read DO_NOT_BUILD_HERE file: %w", err)
   107  			}
   108  			sourceRoot = strings.TrimSpace(string(data))
   109  			break
   110  		}
   111  	}
   112  	if sourceRoot == "" {
   113  		return "", fmt.Errorf("failed to find sourceRoot")
   114  	}
   115  	return filepath.Join(sourceRoot, filename), nil
   116  }
   117  
   118  func loadStarlarkProgram(filename string, src interface{}, predeclared starlark.StringDict, reporter func(msg string), errorReporter func(err error)) (*starlark.StringDict, *starlark.Thread, error) {
   119  	newErrorf := func(msg string, args ...interface{}) error {
   120  		err := fmt.Errorf(filename+": "+msg, args...)
   121  		errorReporter(err)
   122  		return err
   123  	}
   124  
   125  	_, program, err := starlark.SourceProgram(filename, src, predeclared.Has)
   126  	if err != nil {
   127  		return nil, nil, newErrorf("source program error: %v", err)
   128  	}
   129  
   130  	thread := new(starlark.Thread)
   131  	thread.Print = func(thread *starlark.Thread, msg string) {
   132  		reporter(msg)
   133  	}
   134  	globals, err := program.Init(thread, predeclared)
   135  	if err != nil {
   136  		return nil, nil, newErrorf("eval: %w", err)
   137  	}
   138  
   139  	return &globals, thread, nil
   140  }
   141  
   142  func newGazelleRuleFunction() goStarlarkFunction {
   143  	return func(thread *starlark.Thread, _ *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
   144  		var name, kind string
   145  		attrs := new(starlark.Dict)
   146  
   147  		if err := starlark.UnpackArgs("Rule", args, kwargs,
   148  			"name", &name,
   149  			"kind", &kind,
   150  			"attrs?", &attrs,
   151  		); err != nil {
   152  			return nil, err
   153  		}
   154  
   155  		value := starlarkstruct.FromStringDict(
   156  			Symbol("Rule"),
   157  			starlark.StringDict{
   158  				"name":  starlark.String(name),
   159  				"kind":  starlark.String(kind),
   160  				"attrs": attrs,
   161  			},
   162  		)
   163  
   164  		return value, nil
   165  	}
   166  }
   167  
   168  func newGazelleLoadInfoFunction() goStarlarkFunction {
   169  	return func(thread *starlark.Thread, _ *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
   170  		var name string
   171  		symbols := new(starlark.List)
   172  		after := new(starlark.List)
   173  
   174  		if err := starlark.UnpackArgs("LoadInfo", args, kwargs,
   175  			"name", &name,
   176  			"symbols", &symbols,
   177  			"after?", &after,
   178  		); err != nil {
   179  			return nil, err
   180  		}
   181  
   182  		value := starlarkstruct.FromStringDict(
   183  			Symbol("LoadInfo"),
   184  			starlark.StringDict{
   185  				"name":    starlark.String(name),
   186  				"symbols": symbols,
   187  				"after":   after,
   188  			},
   189  		)
   190  
   191  		return value, nil
   192  	}
   193  }
   194  
   195  func newGazelleKindInfoFunction() goStarlarkFunction {
   196  	return func(thread *starlark.Thread, _ *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
   197  		var matchAny bool
   198  		matchAttrs := new(starlark.List)
   199  		nonEmptyAttrs := new(starlark.Dict)
   200  		substituteAttrs := new(starlark.Dict)
   201  		mergeableAttrs := new(starlark.Dict)
   202  		resolveAttrs := new(starlark.Dict)
   203  
   204  		if err := starlark.UnpackArgs("KindInfo", args, kwargs,
   205  			"match_any?", &matchAny,
   206  			"match_attrs?", &matchAttrs,
   207  			"non_empty_attrs?", &nonEmptyAttrs,
   208  			"substitute_attrs?", &substituteAttrs,
   209  			"mergeable_attrs?", &mergeableAttrs,
   210  			"resolve_attrs?", &resolveAttrs,
   211  		); err != nil {
   212  			return nil, err
   213  		}
   214  
   215  		value := starlarkstruct.FromStringDict(
   216  			Symbol("KindInfo"),
   217  			starlark.StringDict{
   218  				"match_any":        starlark.Bool(matchAny),
   219  				"match_attrs":      matchAttrs,
   220  				"non_empty_attrs":  nonEmptyAttrs,
   221  				"substitute_attrs": substituteAttrs,
   222  				"mergeable_attrs":  mergeableAttrs,
   223  				"resolve_attrs":    resolveAttrs,
   224  			},
   225  		)
   226  
   227  		return value, nil
   228  	}
   229  }
   230  
   231  func structAttrStringSlice(in *starlarkstruct.Struct, name string, errorReporter errorReporter) []string {
   232  	value, err := in.Attr(name)
   233  	if err != nil {
   234  		errorReporter("getting struct attr %s: %w", err)
   235  		return nil
   236  	}
   237  	list, ok := value.(*starlark.List)
   238  	if !ok {
   239  		errorReporter("%s is not a list", name)
   240  		return nil
   241  	}
   242  	return stringSlice(list, errorReporter)
   243  }
   244  
   245  func stringSlice(list *starlark.List, errorReporter errorReporter) (out []string) {
   246  	for i := 0; i < list.Len(); i++ {
   247  		value := list.Index(i)
   248  		switch value := (value).(type) {
   249  		case starlark.String:
   250  			out = append(out, value.GoString())
   251  		default:
   252  			errorReporter("list[%d]: expected string, got %s", i, value.Type())
   253  		}
   254  	}
   255  	return
   256  }
   257  
   258  func structAttrBool(in *starlarkstruct.Struct, name string, errorReporter errorReporter) (out bool) {
   259  	value, err := in.Attr(name)
   260  	if err != nil {
   261  		errorReporter("getting struct attr %s: %w", err)
   262  		return
   263  	}
   264  	if value == nil {
   265  		return false
   266  	}
   267  	switch t := value.(type) {
   268  	case starlark.Bool:
   269  		out = bool(t.Truth())
   270  	default:
   271  		errorReporter("attr %q: want bool, got %T", name, value)
   272  	}
   273  	return
   274  }
   275  
   276  func structAttrString(in *starlarkstruct.Struct, name string, errorReporter errorReporter) string {
   277  	value, err := in.Attr(name)
   278  	if err != nil {
   279  		errorReporter("getting struct attr %s: %w", err)
   280  		return ""
   281  	}
   282  	switch value := value.(type) {
   283  	case starlark.String:
   284  		return value.GoString()
   285  	default:
   286  		errorReporter("%s is not a string (%T)", name, value)
   287  		return ""
   288  	}
   289  }
   290  
   291  func structAttrMapStringBool(in *starlarkstruct.Struct, name string, errorReporter errorReporter) (out map[string]bool) {
   292  	value, err := in.Attr(name)
   293  	if err != nil {
   294  		if _, ok := err.(starlark.NoSuchAttrError); ok {
   295  			return
   296  		}
   297  		errorReporter("%v", err)
   298  		return
   299  	}
   300  	if value == nil {
   301  		return
   302  	}
   303  	dict, ok := value.(*starlark.Dict)
   304  	if !ok {
   305  		errorReporter("%v.%s: value must have type starlark.Dict (got %T)", in.Constructor(), name, value)
   306  		return
   307  	}
   308  	out = make(map[string]bool, dict.Len())
   309  	for _, key := range dict.Keys() {
   310  		k, ok := key.(starlark.String)
   311  		if !ok {
   312  			errorReporter("%v.%s: dict keys must have type string (got %T)", in.Constructor(), name, key)
   313  			return
   314  		}
   315  		if value, ok, err := dict.Get(key); ok && err == nil {
   316  			b, ok := value.(starlark.Bool)
   317  			if !ok {
   318  				errorReporter("%v.%s: dict value for %q must have type bool (got %T)", in.Constructor(), name, k.GoString(), value)
   319  				return
   320  			}
   321  			out[k.GoString()] = bool(b.Truth())
   322  		}
   323  	}
   324  	return
   325  }