| // Copyright 2022 The Go Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style | |
| // license that can be found in the LICENSE file. | |
| package script | |
| import ( | |
| "fmt" | |
| "internal/syslist" | |
| "os" | |
| "runtime" | |
| "sync" | |
| ) | |
| // DefaultConds returns a set of broadly useful script conditions. | |
| // | |
| // Run the 'help' command within a script engine to view a list of the available | |
| // conditions. | |
| func DefaultConds() map[string]Cond { | |
| conds := make(map[string]Cond) | |
| conds["GOOS"] = PrefixCondition( | |
| "runtime.GOOS == <suffix>", | |
| func(_ *State, suffix string) (bool, error) { | |
| if suffix == runtime.GOOS { | |
| return true, nil | |
| } | |
| if _, ok := syslist.KnownOS[suffix]; !ok { | |
| return false, fmt.Errorf("unrecognized GOOS %q", suffix) | |
| } | |
| return false, nil | |
| }) | |
| conds["GOARCH"] = PrefixCondition( | |
| "runtime.GOARCH == <suffix>", | |
| func(_ *State, suffix string) (bool, error) { | |
| if suffix == runtime.GOARCH { | |
| return true, nil | |
| } | |
| if _, ok := syslist.KnownArch[suffix]; !ok { | |
| return false, fmt.Errorf("unrecognized GOARCH %q", suffix) | |
| } | |
| return false, nil | |
| }) | |
| conds["compiler"] = PrefixCondition( | |
| "runtime.Compiler == <suffix>", | |
| func(_ *State, suffix string) (bool, error) { | |
| if suffix == runtime.Compiler { | |
| return true, nil | |
| } | |
| switch suffix { | |
| case "gc", "gccgo": | |
| return false, nil | |
| default: | |
| return false, fmt.Errorf("unrecognized compiler %q", suffix) | |
| } | |
| }) | |
| conds["root"] = BoolCondition("os.Geteuid() == 0", os.Geteuid() == 0) | |
| return conds | |
| } | |
| // Condition returns a Cond with the given summary and evaluation function. | |
| func Condition(summary string, eval func(*State) (bool, error)) Cond { | |
| return &funcCond{eval: eval, usage: CondUsage{Summary: summary}} | |
| } | |
| type funcCond struct { | |
| eval func(*State) (bool, error) | |
| usage CondUsage | |
| } | |
| func (c *funcCond) Usage() *CondUsage { return &c.usage } | |
| func (c *funcCond) Eval(s *State, suffix string) (bool, error) { | |
| if suffix != "" { | |
| return false, ErrUsage | |
| } | |
| return c.eval(s) | |
| } | |
| // PrefixCondition returns a Cond with the given summary and evaluation function. | |
| func PrefixCondition(summary string, eval func(*State, string) (bool, error)) Cond { | |
| return &prefixCond{eval: eval, usage: CondUsage{Summary: summary, Prefix: true}} | |
| } | |
| type prefixCond struct { | |
| eval func(*State, string) (bool, error) | |
| usage CondUsage | |
| } | |
| func (c *prefixCond) Usage() *CondUsage { return &c.usage } | |
| func (c *prefixCond) Eval(s *State, suffix string) (bool, error) { | |
| return c.eval(s, suffix) | |
| } | |
| // BoolCondition returns a Cond with the given truth value and summary. | |
| // The Cond rejects the use of condition suffixes. | |
| func BoolCondition(summary string, v bool) Cond { | |
| return &boolCond{v: v, usage: CondUsage{Summary: summary}} | |
| } | |
| type boolCond struct { | |
| v bool | |
| usage CondUsage | |
| } | |
| func (b *boolCond) Usage() *CondUsage { return &b.usage } | |
| func (b *boolCond) Eval(s *State, suffix string) (bool, error) { | |
| if suffix != "" { | |
| return false, ErrUsage | |
| } | |
| return b.v, nil | |
| } | |
| // OnceCondition returns a Cond that calls eval the first time the condition is | |
| // evaluated. Future calls reuse the same result. | |
| // | |
| // The eval function is not passed a *State because the condition is cached | |
| // across all execution states and must not vary by state. | |
| func OnceCondition(summary string, eval func() (bool, error)) Cond { | |
| return &onceCond{ | |
| eval: sync.OnceValues(eval), | |
| usage: CondUsage{Summary: summary}, | |
| } | |
| } | |
| type onceCond struct { | |
| eval func() (bool, error) | |
| usage CondUsage | |
| } | |
| func (l *onceCond) Usage() *CondUsage { return &l.usage } | |
| func (l *onceCond) Eval(s *State, suffix string) (bool, error) { | |
| if suffix != "" { | |
| return false, ErrUsage | |
| } | |
| return l.eval() | |
| } | |
| // CachedCondition is like Condition but only calls eval the first time the | |
| // condition is evaluated for a given suffix. | |
| // Future calls with the same suffix reuse the earlier result. | |
| // | |
| // The eval function is not passed a *State because the condition is cached | |
| // across all execution states and must not vary by state. | |
| func CachedCondition(summary string, eval func(string) (bool, error)) Cond { | |
| return &cachedCond{eval: eval, usage: CondUsage{Summary: summary, Prefix: true}} | |
| } | |
| type cachedCond struct { | |
| m sync.Map | |
| eval func(string) (bool, error) | |
| usage CondUsage | |
| } | |
| func (c *cachedCond) Usage() *CondUsage { return &c.usage } | |
| func (c *cachedCond) Eval(_ *State, suffix string) (bool, error) { | |
| for { | |
| var ready chan struct{} | |
| v, loaded := c.m.Load(suffix) | |
| if !loaded { | |
| ready = make(chan struct{}) | |
| v, loaded = c.m.LoadOrStore(suffix, (<-chan struct{})(ready)) | |
| if !loaded { | |
| inPanic := true | |
| defer func() { | |
| if inPanic { | |
| c.m.Delete(suffix) | |
| } | |
| close(ready) | |
| }() | |
| b, err := c.eval(suffix) | |
| inPanic = false | |
| if err == nil { | |
| c.m.Store(suffix, b) | |
| return b, nil | |
| } else { | |
| c.m.Store(suffix, err) | |
| return false, err | |
| } | |
| } | |
| } | |
| switch v := v.(type) { | |
| case bool: | |
| return v, nil | |
| case error: | |
| return false, v | |
| case <-chan struct{}: | |
| <-v | |
| } | |
| } | |
| } |