// 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 == ", 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 == ", func(_ *State, suffix string) (bool, error) { if suffix == runtime.GOARCH { return true, nil } if _, ok := syslist.KnownArch[suffix]; !ok { return false, fmt.Errorf("unrecognized GOOS %q", suffix) } return false, nil }) conds["compiler"] = PrefixCondition( "runtime.Compiler == ", 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 } } }