github.phpd.cn/thought-machine/please@v12.2.0+incompatible/src/parse/asp/builtins.go (about)

     1  package asp
     2  
     3  import (
     4  	"fmt"
     5  	"path"
     6  	"reflect"
     7  	"sort"
     8  	"strconv"
     9  	"strings"
    10  
    11  	"core"
    12  	"fs"
    13  )
    14  
    15  // A few sneaky globals for when we don't have a scope handy
    16  var stringMethods, dictMethods, configMethods map[string]*pyFunc
    17  
    18  const subincludePackageName = "_remote"
    19  
    20  // A nativeFunc is a function that implements a builtin function natively.
    21  type nativeFunc func(*scope, []pyObject) pyObject
    22  
    23  // registerBuiltins sets up the "special" builtins that map to native code.
    24  func registerBuiltins(s *scope) {
    25  	setNativeCode(s, "build_rule", buildRule)
    26  	setNativeCode(s, "subrepo", subrepo)
    27  	setNativeCode(s, "fail", builtinFail)
    28  	setNativeCode(s, "subinclude", subinclude)
    29  	setNativeCode(s, "load", bazelLoad).varargs = true
    30  	setNativeCode(s, "package", pkg).kwargs = true
    31  	setNativeCode(s, "sorted", sorted)
    32  	setNativeCode(s, "isinstance", isinstance)
    33  	setNativeCode(s, "range", pyRange)
    34  	setNativeCode(s, "enumerate", enumerate)
    35  	setNativeCode(s, "zip", zip).varargs = true
    36  	setNativeCode(s, "len", lenFunc)
    37  	setNativeCode(s, "glob", glob)
    38  	setNativeCode(s, "bool", boolType)
    39  	setNativeCode(s, "int", intType)
    40  	setNativeCode(s, "str", strType)
    41  	setNativeCode(s, "join_path", joinPath).varargs = true
    42  	setNativeCode(s, "get_base_path", getBasePath)
    43  	setNativeCode(s, "package_name", packageName)
    44  	setNativeCode(s, "get_labels", getLabels)
    45  	setNativeCode(s, "add_dep", addDep)
    46  	setNativeCode(s, "add_out", addOut)
    47  	setNativeCode(s, "add_licence", addLicence)
    48  	setNativeCode(s, "get_command", getCommand)
    49  	setNativeCode(s, "set_command", setCommand)
    50  	stringMethods = map[string]*pyFunc{
    51  		"join":       setNativeCode(s, "join", strJoin),
    52  		"split":      setNativeCode(s, "split", strSplit),
    53  		"replace":    setNativeCode(s, "replace", strReplace),
    54  		"partition":  setNativeCode(s, "partition", strPartition),
    55  		"rpartition": setNativeCode(s, "rpartition", strRPartition),
    56  		"startswith": setNativeCode(s, "startswith", strStartsWith),
    57  		"endswith":   setNativeCode(s, "endswith", strEndsWith),
    58  		"lstrip":     setNativeCode(s, "lstrip", strLStrip),
    59  		"rstrip":     setNativeCode(s, "rstrip", strRStrip),
    60  		"strip":      setNativeCode(s, "strip", strStrip),
    61  		"find":       setNativeCode(s, "find", strFind),
    62  		"rfind":      setNativeCode(s, "find", strRFind),
    63  		"format":     setNativeCode(s, "format", strFormat),
    64  		"count":      setNativeCode(s, "count", strCount),
    65  		"upper":      setNativeCode(s, "upper", strUpper),
    66  		"lower":      setNativeCode(s, "lower", strLower),
    67  	}
    68  	stringMethods["format"].kwargs = true
    69  	dictMethods = map[string]*pyFunc{
    70  		"get":        setNativeCode(s, "get", dictGet),
    71  		"setdefault": s.Lookup("setdefault").(*pyFunc),
    72  		"keys":       setNativeCode(s, "keys", dictKeys),
    73  		"items":      setNativeCode(s, "items", dictItems),
    74  		"values":     setNativeCode(s, "values", dictValues),
    75  		"copy":       setNativeCode(s, "copy", dictCopy),
    76  	}
    77  	configMethods = map[string]*pyFunc{
    78  		"get":        setNativeCode(s, "config_get", configGet),
    79  		"setdefault": s.Lookup("setdefault").(*pyFunc),
    80  	}
    81  	setLogCode(s, "debug", log.Debug)
    82  	setLogCode(s, "info", log.Info)
    83  	setLogCode(s, "notice", log.Notice)
    84  	setLogCode(s, "warning", log.Warning)
    85  	setLogCode(s, "error", log.Errorf)
    86  	setLogCode(s, "fatal", log.Fatalf)
    87  }
    88  
    89  // registerSubincludePackage sets up the package for remote subincludes.
    90  func registerSubincludePackage(s *scope) {
    91  	var pkg *core.Package
    92  	if pkg = s.state.Graph.Package(subincludePackageName); pkg == nil {
    93  		pkg = core.NewPackage(subincludePackageName)
    94  		s.state.Graph.AddPackage(pkg)
    95  	}
    96  	s.interpreter.subincludeScope = s.NewPackagedScope(pkg)
    97  	// Always counts as being in callback mode (i.e. the package is already parsed and we are adding individual targets later).
    98  	s.interpreter.subincludeScope.Callback = true
    99  	// Another small hack - replace the code for these two with native code, must be done after the
   100  	// declarations which are in misc_rules.
   101  	buildRule := s.Lookup("build_rule").(*pyFunc)
   102  	f := setNativeCode(s, "filegroup", filegroup)
   103  	f.args = buildRule.args
   104  	f.argIndices = buildRule.argIndices
   105  	f.defaults = buildRule.defaults
   106  	f.constants = buildRule.constants
   107  	f.types = buildRule.types
   108  	f = setNativeCode(s, "hash_filegroup", hashFilegroup)
   109  	f.args = buildRule.args
   110  	f.argIndices = buildRule.argIndices
   111  	f.defaults = buildRule.defaults
   112  	f.constants = buildRule.constants
   113  	f.types = buildRule.types
   114  }
   115  
   116  func setNativeCode(s *scope, name string, code nativeFunc) *pyFunc {
   117  	f := s.Lookup(name).(*pyFunc)
   118  	f.nativeCode = code
   119  	f.code = nil // Might as well save a little memory here
   120  	return f
   121  }
   122  
   123  // setLogCode specialises setNativeCode for handling the log functions (of which there are a few)
   124  func setLogCode(s *scope, name string, f func(format string, args ...interface{})) {
   125  	setNativeCode(s, name, func(s *scope, args []pyObject) pyObject {
   126  		if str, ok := args[0].(pyString); ok {
   127  			l := make([]interface{}, len(args))
   128  			for i, arg := range args {
   129  				l[i] = arg
   130  			}
   131  			f("//%s: %s", s.pkgFilename(), fmt.Sprintf(string(str), l[1:]...))
   132  			return None
   133  		}
   134  		f("//%s: %s", s.pkgFilename(), args)
   135  		return None
   136  	}).varargs = true
   137  }
   138  
   139  // buildRule implements the build_rule() builtin function.
   140  // This is the main interface point; every build rule ultimately calls this to add
   141  // new objects to the build graph.
   142  func buildRule(s *scope, args []pyObject) pyObject {
   143  	s.NAssert(s.pkg == nil, "Cannot create new build rules in this context")
   144  	// We need to set various defaults from config here; it is useful to put it on the rule but not often so
   145  	// because most rules pass them through anyway.
   146  	// TODO(peterebden): when we get rid of the old parser, put these defaults on all the build rules and
   147  	//                   get rid of this.
   148  	config := s.Lookup("CONFIG").(*pyConfig)
   149  	args[11] = defaultFromConfig(config, args[11], "DEFAULT_VISIBILITY")
   150  	args[15] = defaultFromConfig(config, args[15], "DEFAULT_TESTONLY")
   151  	args[30] = defaultFromConfig(config, args[30], "DEFAULT_LICENCES")
   152  	args[20] = defaultFromConfig(config, args[20], "BUILD_SANDBOX")
   153  	args[21] = defaultFromConfig(config, args[21], "TEST_SANDBOX")
   154  	target := createTarget(s, args)
   155  	s.Assert(s.pkg.Target(target.Label.Name) == nil, "Duplicate build target in %s: %s", s.pkg.Name, target.Label.Name)
   156  	s.pkg.AddTarget(target)
   157  	populateTarget(s, target, args)
   158  	if s.Callback {
   159  		// We are in a post-build function, so add the target directly to the graph now.
   160  		log.Debug("Adding new target %s directly to graph", target.Label)
   161  		target.AddedPostBuild = true
   162  		s.state.Graph.AddTarget(target)
   163  		s.pkg.MarkTargetModified(target)
   164  	}
   165  	return pyString(":" + target.Label.Name)
   166  }
   167  
   168  // filegroup implements the filegroup() builtin.
   169  func filegroup(s *scope, args []pyObject) pyObject {
   170  	args[1] = filegroupCommand
   171  	return buildRule(s, args)
   172  }
   173  
   174  // hashFilegroup implements the hash_filegroup() builtin.
   175  func hashFilegroup(s *scope, args []pyObject) pyObject {
   176  	args[1] = hashFilegroupCommand
   177  	return buildRule(s, args)
   178  }
   179  
   180  // defaultFromConfig sets a default value from the config if the property isn't set.
   181  func defaultFromConfig(config *pyConfig, arg pyObject, name string) pyObject {
   182  	if arg == nil || arg == None {
   183  		return config.Get(name, arg)
   184  	}
   185  	return arg
   186  }
   187  
   188  // pkg implements the package() builtin function.
   189  func pkg(s *scope, args []pyObject) pyObject {
   190  	s.Assert(s.pkg.NumTargets() == 0, "package() must be called before any build targets are defined")
   191  	c, ok := s.Lookup("CONFIG").(*pyConfig)
   192  	s.Assert(ok, "CONFIG object has been altered")
   193  	for k, v := range s.locals {
   194  		k = strings.ToUpper(k)
   195  		s.Assert(c.Get(k, nil) != nil, "error calling package(): %s is not a known config value", k)
   196  		c.IndexAssign(pyString(k), v)
   197  	}
   198  	return None
   199  }
   200  
   201  // tagName applies the given tag to a target name.
   202  func tagName(name, tag string) string {
   203  	if name[0] != '_' {
   204  		name = "_" + name
   205  	}
   206  	if strings.ContainsRune(name, '#') {
   207  		name = name + "_"
   208  	} else {
   209  		name = name + "#"
   210  	}
   211  	return name + tag
   212  }
   213  
   214  // bazelLoad implements the load() builtin, which is only available for Bazel compatibility.
   215  func bazelLoad(s *scope, args []pyObject) pyObject {
   216  	s.Assert(s.state.Config.Bazel.Compatibility, "load() is only available in Bazel compatibility mode. See `plz help bazel` for more information.")
   217  	// The argument always looks like a build label, but it is not really one (i.e. there is no BUILD file that defines it).
   218  	// We do not support their legacy syntax here (i.e. "/tools/build_rules/build_test" etc).
   219  	l := core.ParseBuildLabel(string(args[0].(pyString)), s.pkg.Name)
   220  	s.SetAll(s.interpreter.Subinclude(path.Join(l.PackageName, l.Name)), false)
   221  	return None
   222  }
   223  
   224  // builtinFail raises an immediate error that can't be intercepted.
   225  func builtinFail(s *scope, args []pyObject) pyObject {
   226  	s.Error(string(args[0].(pyString)))
   227  	return None
   228  }
   229  
   230  func subinclude(s *scope, args []pyObject) pyObject {
   231  	t := subincludeTarget(s, subincludeLabel(s, args))
   232  	for _, out := range t.Outputs() {
   233  		s.SetAll(s.interpreter.Subinclude(path.Join(t.OutDir(), out)), false)
   234  	}
   235  	return None
   236  }
   237  
   238  // subincludeTarget returns the target for a subinclude() call to a label.
   239  // It blocks until the target exists and is built.
   240  func subincludeTarget(s *scope, l core.BuildLabel) *core.BuildTarget {
   241  	if s.pkg == nil {
   242  		// Really we should not get here, but it's hard to prove that's not the case. Make the best of it.
   243  		return s.state.WaitForBuiltTarget(l, l.PackageName)
   244  	}
   245  	t := s.state.WaitForBuiltTarget(l, s.pkg.Name)
   246  	if l.PackageName != subincludePackageName {
   247  		s.pkg.RegisterSubinclude(l)
   248  	}
   249  	return t
   250  }
   251  
   252  // subincludeLabel returns the label for a subinclude() call (which might be indirect
   253  // if the given argument was a URL instead of a build label)
   254  func subincludeLabel(s *scope, args []pyObject) core.BuildLabel {
   255  	target := string(args[0].(pyString))
   256  	s.NAssert(strings.HasPrefix(target, ":"), "Subincludes cannot be from the local package")
   257  	if !strings.HasPrefix(target, "http") {
   258  		return core.ParseBuildLabel(target, "")
   259  	}
   260  	// Check if this target is already registered (this will always happen eventually because
   261  	// we re-parse the same package again).
   262  	name := strings.Replace(path.Base(target), ".", "_", -1)
   263  	label := core.NewBuildLabel(subincludePackageName, name)
   264  	if s.state.Graph.Target(label) != nil {
   265  		return label
   266  	}
   267  	remoteFile, ok := s.interpreter.subincludeScope.Lookup("remote_file").(*pyFunc)
   268  	s.interpreter.subincludeScope.Assert(ok, "remote_file is not callable")
   269  	// Call using the normal entry point, which is a bit of a faff but it sorts out default arguments and so forth
   270  	a := []CallArgument{
   271  		{
   272  			Name:  "name",
   273  			Value: Expression{Val: &ValueExpression{String: `"` + name + `"`}},
   274  		}, {
   275  			Name:  "url",
   276  			Value: Expression{Val: &ValueExpression{String: `"` + target + `"`}},
   277  		},
   278  	}
   279  	if args[1] != nil && args[1] != None {
   280  		a = append(a, CallArgument{
   281  			Name: "hashes",
   282  			Value: Expression{Val: &ValueExpression{List: &List{
   283  				Values: []*Expression{{Val: &ValueExpression{
   284  					String: `"` + string(args[1].(pyString)) + `"`,
   285  				}}},
   286  			}}},
   287  		})
   288  	}
   289  	remoteFile.Call(s.interpreter.subincludeScope, &Call{Arguments: a})
   290  	return label
   291  }
   292  
   293  func lenFunc(s *scope, args []pyObject) pyObject {
   294  	return pyInt(args[0].Len())
   295  }
   296  
   297  func isinstance(s *scope, args []pyObject) pyObject {
   298  	obj := args[0]
   299  	types := args[1]
   300  	if f, ok := types.(*pyFunc); ok && isType(obj, f.name) {
   301  		// Special case for 'str' and so forth that are functions but also types.
   302  		return True
   303  	} else if l, ok := types.(pyList); ok {
   304  		for _, li := range l {
   305  			if lif, ok := li.(*pyFunc); ok && isType(obj, lif.name) {
   306  				return True
   307  			} else if reflect.TypeOf(obj) == reflect.TypeOf(li) {
   308  				return True
   309  			}
   310  		}
   311  	}
   312  	return newPyBool(reflect.TypeOf(obj) == reflect.TypeOf(types))
   313  }
   314  
   315  func isType(obj pyObject, name string) bool {
   316  	switch obj.(type) {
   317  	case pyBool:
   318  		return name == "bool" || name == "int" // N.B. For compatibility with old assert statements
   319  	case pyInt:
   320  		return name == "int"
   321  	case pyString:
   322  		return name == "str"
   323  	case pyList:
   324  		return name == "list"
   325  	case pyDict:
   326  		return name == "dict"
   327  	}
   328  	return false
   329  }
   330  
   331  func strJoin(s *scope, args []pyObject) pyObject {
   332  	self := string(args[0].(pyString))
   333  	seq := asStringList(s, args[1], "seq")
   334  	return pyString(strings.Join(seq, self))
   335  }
   336  
   337  func strSplit(s *scope, args []pyObject) pyObject {
   338  	self := args[0].(pyString)
   339  	on := args[1].(pyString)
   340  	return fromStringList(strings.Split(string(self), string(on)))
   341  }
   342  
   343  func strReplace(s *scope, args []pyObject) pyObject {
   344  	self := args[0].(pyString)
   345  	old := args[1].(pyString)
   346  	new := args[2].(pyString)
   347  	return pyString(strings.Replace(string(self), string(old), string(new), -1))
   348  }
   349  
   350  func strPartition(s *scope, args []pyObject) pyObject {
   351  	self := args[0].(pyString)
   352  	sep := args[1].(pyString)
   353  	if idx := strings.Index(string(self), string(sep)); idx != -1 {
   354  		return pyList{self[:idx], self[idx : idx+1], self[idx+1:]}
   355  	}
   356  	return pyList{self, pyString(""), pyString("")}
   357  }
   358  
   359  func strRPartition(s *scope, args []pyObject) pyObject {
   360  	self := args[0].(pyString)
   361  	sep := args[1].(pyString)
   362  	if idx := strings.LastIndex(string(self), string(sep)); idx != -1 {
   363  		return pyList{self[:idx], self[idx : idx+1], self[idx+1:]}
   364  	}
   365  	return pyList{pyString(""), pyString(""), self}
   366  }
   367  
   368  func strStartsWith(s *scope, args []pyObject) pyObject {
   369  	self := args[0].(pyString)
   370  	x := args[1].(pyString)
   371  	return newPyBool(strings.HasPrefix(string(self), string(x)))
   372  }
   373  
   374  func strEndsWith(s *scope, args []pyObject) pyObject {
   375  	self := args[0].(pyString)
   376  	x := args[1].(pyString)
   377  	return newPyBool(strings.HasSuffix(string(self), string(x)))
   378  }
   379  
   380  func strLStrip(s *scope, args []pyObject) pyObject {
   381  	self := args[0].(pyString)
   382  	cutset := args[1].(pyString)
   383  	return pyString(strings.TrimLeft(string(self), string(cutset)))
   384  }
   385  
   386  func strRStrip(s *scope, args []pyObject) pyObject {
   387  	self := args[0].(pyString)
   388  	cutset := args[1].(pyString)
   389  	return pyString(strings.TrimRight(string(self), string(cutset)))
   390  }
   391  
   392  func strStrip(s *scope, args []pyObject) pyObject {
   393  	self := args[0].(pyString)
   394  	cutset := args[1].(pyString)
   395  	return pyString(strings.Trim(string(self), string(cutset)))
   396  }
   397  
   398  func strFind(s *scope, args []pyObject) pyObject {
   399  	self := args[0].(pyString)
   400  	needle := args[1].(pyString)
   401  	return pyInt(strings.Index(string(self), string(needle)))
   402  }
   403  
   404  func strRFind(s *scope, args []pyObject) pyObject {
   405  	self := args[0].(pyString)
   406  	needle := args[1].(pyString)
   407  	return pyInt(strings.LastIndex(string(self), string(needle)))
   408  }
   409  
   410  func strFormat(s *scope, args []pyObject) pyObject {
   411  	self := string(args[0].(pyString))
   412  	for k, v := range s.locals {
   413  		self = strings.Replace(self, "{"+k+"}", v.String(), -1)
   414  	}
   415  	return pyString(strings.Replace(strings.Replace(self, "{{", "{", -1), "}}", "}", -1))
   416  }
   417  
   418  func strCount(s *scope, args []pyObject) pyObject {
   419  	self := string(args[0].(pyString))
   420  	needle := string(args[1].(pyString))
   421  	return pyInt(strings.Count(self, needle))
   422  }
   423  
   424  func strUpper(s *scope, args []pyObject) pyObject {
   425  	self := string(args[0].(pyString))
   426  	return pyString(strings.ToUpper(self))
   427  }
   428  
   429  func strLower(s *scope, args []pyObject) pyObject {
   430  	self := string(args[0].(pyString))
   431  	return pyString(strings.ToLower(self))
   432  }
   433  
   434  func boolType(s *scope, args []pyObject) pyObject {
   435  	return newPyBool(args[0].IsTruthy())
   436  }
   437  
   438  func intType(s *scope, args []pyObject) pyObject {
   439  	i, err := strconv.Atoi(string(args[0].(pyString)))
   440  	s.Assert(err == nil, "%s", err)
   441  	return pyInt(i)
   442  }
   443  
   444  func strType(s *scope, args []pyObject) pyObject {
   445  	return pyString(args[0].String())
   446  }
   447  
   448  func glob(s *scope, args []pyObject) pyObject {
   449  	include := asStringList(s, args[0], "include")
   450  	exclude := asStringList(s, args[1], "exclude")
   451  	hidden := args[2].IsTruthy()
   452  	exclude = append(exclude, s.state.Config.Parse.BuildFileName...)
   453  	return fromStringList(fs.Glob(s.state.Config.Parse.BuildFileName, s.pkg.SourceRoot(), include, exclude, exclude, hidden))
   454  }
   455  
   456  func asStringList(s *scope, arg pyObject, name string) []string {
   457  	l, ok := arg.(pyList)
   458  	s.Assert(ok, "argument %s must be a list", name)
   459  	sl := make([]string, len(l))
   460  	for i, x := range l {
   461  		sx, ok := x.(pyString)
   462  		s.Assert(ok, "%s must be a list of strings", name)
   463  		sl[i] = string(sx)
   464  	}
   465  	return sl
   466  }
   467  
   468  func fromStringList(l []string) pyList {
   469  	ret := make(pyList, len(l))
   470  	for i, s := range l {
   471  		ret[i] = pyString(s)
   472  	}
   473  	return ret
   474  }
   475  
   476  func configGet(s *scope, args []pyObject) pyObject {
   477  	self := args[0].(*pyConfig)
   478  	return self.Get(string(args[1].(pyString)), args[2])
   479  }
   480  
   481  func dictGet(s *scope, args []pyObject) pyObject {
   482  	self := args[0].(pyDict)
   483  	sk, ok := args[1].(pyString)
   484  	s.Assert(ok, "dict keys must be strings, not %s", args[1].Type())
   485  	if ret, present := self[string(sk)]; present {
   486  		return ret
   487  	}
   488  	return args[2]
   489  }
   490  
   491  func dictKeys(s *scope, args []pyObject) pyObject {
   492  	self := args[0].(pyDict)
   493  	ret := make(pyList, len(self))
   494  	for i, k := range self.Keys() {
   495  		ret[i] = pyString(k)
   496  	}
   497  	return ret
   498  }
   499  
   500  func dictValues(s *scope, args []pyObject) pyObject {
   501  	self := args[0].(pyDict)
   502  	ret := make(pyList, len(self))
   503  	for i, k := range self.Keys() {
   504  		ret[i] = self[k]
   505  	}
   506  	return ret
   507  }
   508  
   509  func dictItems(s *scope, args []pyObject) pyObject {
   510  	self := args[0].(pyDict)
   511  	ret := make(pyList, len(self))
   512  	for i, k := range self.Keys() {
   513  		ret[i] = pyList{pyString(k), self[k]}
   514  	}
   515  	return ret
   516  }
   517  
   518  func dictCopy(s *scope, args []pyObject) pyObject {
   519  	self := args[0].(pyDict)
   520  	ret := make(pyDict, len(self))
   521  	for k, v := range self {
   522  		ret[k] = v
   523  	}
   524  	return ret
   525  }
   526  
   527  func sorted(s *scope, args []pyObject) pyObject {
   528  	l, ok := args[0].(pyList)
   529  	s.Assert(ok, "unsortable type %s", args[0].Type())
   530  	l = l[:]
   531  	sort.Slice(l, func(i, j int) bool { return l[i].Operator(LessThan, l[j]).IsTruthy() })
   532  	return l
   533  }
   534  
   535  func joinPath(s *scope, args []pyObject) pyObject {
   536  	l := make([]string, len(args))
   537  	for i, arg := range args {
   538  		l[i] = string(arg.(pyString))
   539  	}
   540  	return pyString(path.Join(l...))
   541  }
   542  
   543  func getBasePath(s *scope, args []pyObject) pyObject {
   544  	return pyString(s.pkg.Name)
   545  }
   546  
   547  func packageName(s *scope, args []pyObject) pyObject {
   548  	if s.pkg.Subrepo != nil {
   549  		return pyString(s.pkg.Subrepo.MakeRelativeName(s.pkg.Name))
   550  	}
   551  	return pyString(s.pkg.Name)
   552  }
   553  
   554  func pyRange(s *scope, args []pyObject) pyObject {
   555  	start := args[0].(pyInt)
   556  	stop, isInt := args[1].(pyInt)
   557  	step := args[2].(pyInt)
   558  	if !isInt {
   559  		// Stop not passed so we start at 0 and start is the stop.
   560  		stop = start
   561  		start = 0
   562  	}
   563  	ret := make(pyList, 0, stop-start)
   564  	for i := start; i < stop; i += step {
   565  		ret = append(ret, i)
   566  	}
   567  	return ret
   568  }
   569  
   570  func enumerate(s *scope, args []pyObject) pyObject {
   571  	l, ok := args[0].(pyList)
   572  	s.Assert(ok, "Argument to enumerate must be a list, not %s", args[0].Type())
   573  	ret := make(pyList, len(l))
   574  	for i, li := range l {
   575  		ret[i] = pyList{pyInt(i), li}
   576  	}
   577  	return ret
   578  }
   579  
   580  func zip(s *scope, args []pyObject) pyObject {
   581  	lastLen := 0
   582  	for i, seq := range args {
   583  		si, ok := seq.(pyList)
   584  		s.Assert(ok, "Arguments to zip must be lists, not %s", si.Type())
   585  		// This isn't a restriction in Python but I can't be bothered handling all the stuff that real zip does.
   586  		s.Assert(i == 0 || lastLen == len(si), "All arguments to zip must have the same length")
   587  		lastLen = len(si)
   588  	}
   589  	ret := make(pyList, lastLen)
   590  	for i := range ret {
   591  		r := make(pyList, len(args))
   592  		for j, li := range args {
   593  			r[j] = li.(pyList)[i]
   594  		}
   595  		ret[i] = r
   596  	}
   597  	return ret
   598  }
   599  
   600  // getLabels returns the set of labels for a build target and its transitive dependencies.
   601  // The labels are filtered by the given prefix, which is stripped from the returned labels.
   602  // Two formats are supported here: either passing just the name of a target in the current
   603  // package, or a build label referring specifically to one.
   604  func getLabels(s *scope, args []pyObject) pyObject {
   605  	name := string(args[0].(pyString))
   606  	prefix := string(args[1].(pyString))
   607  	if core.LooksLikeABuildLabel(name) {
   608  		label := core.ParseBuildLabel(name, s.pkg.Name)
   609  		return getLabelsInternal(s.state.Graph.TargetOrDie(label), prefix, core.Built)
   610  	}
   611  	target := getTargetPost(s, name)
   612  	return getLabelsInternal(target, prefix, core.Building)
   613  }
   614  
   615  func getLabelsInternal(target *core.BuildTarget, prefix string, minState core.BuildTargetState) pyObject {
   616  	if target.State() < minState {
   617  		log.Fatalf("get_labels called on a target that is not yet built: %s", target.Label)
   618  	}
   619  	labels := map[string]bool{}
   620  	done := map[*core.BuildTarget]bool{}
   621  	var getLabels func(*core.BuildTarget)
   622  	getLabels = func(t *core.BuildTarget) {
   623  		for _, label := range t.Labels {
   624  			if strings.HasPrefix(label, prefix) {
   625  				labels[strings.TrimSpace(strings.TrimPrefix(label, prefix))] = true
   626  			}
   627  		}
   628  		done[t] = true
   629  		if !t.OutputIsComplete || t == target {
   630  			for _, dep := range t.Dependencies() {
   631  				if !done[dep] {
   632  					getLabels(dep)
   633  				}
   634  			}
   635  		}
   636  	}
   637  	getLabels(target)
   638  	ret := make([]string, len(labels))
   639  	i := 0
   640  	for label := range labels {
   641  		ret[i] = label
   642  		i++
   643  	}
   644  	sort.Strings(ret)
   645  	return fromStringList(ret)
   646  }
   647  
   648  // getTargetPost is called by various functions to get a target from the current package.
   649  // Panics if the target is not in the current package or has already been built.
   650  func getTargetPost(s *scope, name string) *core.BuildTarget {
   651  	target := s.pkg.Target(name)
   652  	s.Assert(target != nil, "Unknown build target %s in %s", name, s.pkg.Name)
   653  	// It'd be cheating to try to modify targets that're already built.
   654  	// Prohibit this because it'd likely end up with nasty race conditions.
   655  	s.Assert(target.State() < core.Built, "Attempted to modify target %s, but it's already built", target.Label)
   656  	return target
   657  }
   658  
   659  // addDep adds a dependency to a target.
   660  func addDep(s *scope, args []pyObject) pyObject {
   661  	s.Assert(s.Callback, "can only be called from a pre- or post-build callback")
   662  	target := getTargetPost(s, string(args[0].(pyString)))
   663  	dep := core.ParseBuildLabel(string(args[1].(pyString)), s.pkg.Name)
   664  	exported := args[2].IsTruthy()
   665  	target.AddMaybeExportedDependency(dep, exported, false)
   666  	// Note that here we're in a post-build function so we must call this explicitly
   667  	// (in other callbacks it's handled after the package parses all at once).
   668  	s.state.Graph.AddDependency(target.Label, dep)
   669  	s.pkg.MarkTargetModified(target)
   670  	return None
   671  }
   672  
   673  // addOut adds an output to a target.
   674  func addOut(s *scope, args []pyObject) pyObject {
   675  	target := getTargetPost(s, string(args[0].(pyString)))
   676  	name := string(args[1].(pyString))
   677  	out := string(args[2].(pyString))
   678  	if out == "" {
   679  		target.AddOutput(name)
   680  		s.pkg.MustRegisterOutput(name, target)
   681  	} else {
   682  		target.AddNamedOutput(name, out)
   683  		s.pkg.MustRegisterOutput(out, target)
   684  	}
   685  	return None
   686  }
   687  
   688  // addLicence adds a licence to a target.
   689  func addLicence(s *scope, args []pyObject) pyObject {
   690  	target := getTargetPost(s, string(args[0].(pyString)))
   691  	target.AddLicence(string(args[1].(pyString)))
   692  	return None
   693  }
   694  
   695  // getCommand gets the command of a target, optionally for a configuration.
   696  func getCommand(s *scope, args []pyObject) pyObject {
   697  	target := getTargetPost(s, string(args[0].(pyString)))
   698  	return pyString(target.GetCommandConfig(string(args[1].(pyString))))
   699  }
   700  
   701  // setCommand sets the command of a target, optionally for a configuration.
   702  func setCommand(s *scope, args []pyObject) pyObject {
   703  	target := getTargetPost(s, string(args[0].(pyString)))
   704  	config := string(args[1].(pyString))
   705  	command := string(args[2].(pyString))
   706  	if command == "" {
   707  		target.Command = config
   708  	} else {
   709  		target.AddCommand(config, command)
   710  	}
   711  	return None
   712  }
   713  
   714  // selectFunc implements the select() builtin.
   715  func selectFunc(s *scope, args []pyObject) pyObject {
   716  	d, _ := asDict(args[0])
   717  	var def pyObject
   718  	pkgName := ""
   719  	if s.pkg != nil {
   720  		pkgName = s.pkg.Name
   721  	}
   722  	// TODO(peterebden): this is an arbitrary match that drops Bazel's order-of-matching rules. Fix.
   723  	for k, v := range d {
   724  		if k == "//conditions:default" || k == "default" {
   725  			def = v
   726  		} else if selectTarget(s, core.ParseBuildLabel(k, pkgName)).HasLabel("config:on") {
   727  			return v
   728  		}
   729  	}
   730  	s.NAssert(def == nil, "None of the select() conditions matched")
   731  	return def
   732  }
   733  
   734  // selectTarget returns the target to be used for a select() call.
   735  // It panics appropriately if the target isn't built yet.
   736  func selectTarget(s *scope, l core.BuildLabel) *core.BuildTarget {
   737  	if s.pkg != nil && l.PackageName == s.pkg.Name {
   738  		t := s.pkg.Target(l.Name)
   739  		s.NAssert(t == nil, "Target %s in select() call has not been defined yet", l.Name)
   740  		return t
   741  	}
   742  	return subincludeTarget(s, l)
   743  }
   744  
   745  // subrepo implements the subrepo() builtin that adds a new repository.
   746  func subrepo(s *scope, args []pyObject) pyObject {
   747  	root := func(def string) string {
   748  		if args[2] != None {
   749  			return string(args[2].(pyString))
   750  		}
   751  		return def
   752  	}
   753  
   754  	name := string(args[0].(pyString))
   755  	dep := string(args[1].(pyString))
   756  	if dep == "" {
   757  		// This is deliberately different to facilitate binding subrepos within the same VCS repo.
   758  		s.state.Graph.AddSubrepo(&core.Subrepo{Name: name, Root: root(name)})
   759  		return None
   760  	}
   761  	// N.B. The target must be already registered on this package.
   762  	t := s.pkg.TargetOrDie(core.ParseBuildLabel(dep, s.pkg.Name).Name)
   763  	s.state.Graph.AddSubrepo(&core.Subrepo{
   764  		Name:   name,
   765  		Root:   root(path.Join(t.OutDir(), name)),
   766  		Target: t,
   767  	})
   768  	log.Debug("Registered subrepo %s", name)
   769  	return None
   770  }