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