1// Copyright 2022 The Go Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style 3// license that can be found in the LICENSE file. 4 5package script 6 7import ( 8 "cmd/go/internal/imports" 9 "fmt" 10 "os" 11 "runtime" 12 "sync" 13) 14 15// DefaultConds returns a set of broadly useful script conditions. 16// 17// Run the 'help' command within a script engine to view a list of the available 18// conditions. 19func DefaultConds() map[string]Cond { 20 conds := make(map[string]Cond) 21 22 conds["GOOS"] = PrefixCondition( 23 "runtime.GOOS == <suffix>", 24 func(_ *State, suffix string) (bool, error) { 25 if suffix == runtime.GOOS { 26 return true, nil 27 } 28 if _, ok := imports.KnownOS[suffix]; !ok { 29 return false, fmt.Errorf("unrecognized GOOS %q", suffix) 30 } 31 return false, nil 32 }) 33 34 conds["GOARCH"] = PrefixCondition( 35 "runtime.GOARCH == <suffix>", 36 func(_ *State, suffix string) (bool, error) { 37 if suffix == runtime.GOARCH { 38 return true, nil 39 } 40 if _, ok := imports.KnownArch[suffix]; !ok { 41 return false, fmt.Errorf("unrecognized GOOS %q", suffix) 42 } 43 return false, nil 44 }) 45 46 conds["compiler"] = PrefixCondition( 47 "runtime.Compiler == <suffix>", 48 func(_ *State, suffix string) (bool, error) { 49 if suffix == runtime.Compiler { 50 return true, nil 51 } 52 switch suffix { 53 case "gc", "gccgo": 54 return false, nil 55 default: 56 return false, fmt.Errorf("unrecognized compiler %q", suffix) 57 } 58 }) 59 60 conds["root"] = BoolCondition("os.Geteuid() == 0", os.Geteuid() == 0) 61 62 return conds 63} 64 65// Condition returns a Cond with the given summary and evaluation function. 66func Condition(summary string, eval func(*State) (bool, error)) Cond { 67 return &funcCond{eval: eval, usage: CondUsage{Summary: summary}} 68} 69 70type funcCond struct { 71 eval func(*State) (bool, error) 72 usage CondUsage 73} 74 75func (c *funcCond) Usage() *CondUsage { return &c.usage } 76 77func (c *funcCond) Eval(s *State, suffix string) (bool, error) { 78 if suffix != "" { 79 return false, ErrUsage 80 } 81 return c.eval(s) 82} 83 84// PrefixCondition returns a Cond with the given summary and evaluation function. 85func PrefixCondition(summary string, eval func(*State, string) (bool, error)) Cond { 86 return &prefixCond{eval: eval, usage: CondUsage{Summary: summary, Prefix: true}} 87} 88 89type prefixCond struct { 90 eval func(*State, string) (bool, error) 91 usage CondUsage 92} 93 94func (c *prefixCond) Usage() *CondUsage { return &c.usage } 95 96func (c *prefixCond) Eval(s *State, suffix string) (bool, error) { 97 return c.eval(s, suffix) 98} 99 100// BoolCondition returns a Cond with the given truth value and summary. 101// The Cond rejects the use of condition suffixes. 102func BoolCondition(summary string, v bool) Cond { 103 return &boolCond{v: v, usage: CondUsage{Summary: summary}} 104} 105 106type boolCond struct { 107 v bool 108 usage CondUsage 109} 110 111func (b *boolCond) Usage() *CondUsage { return &b.usage } 112 113func (b *boolCond) Eval(s *State, suffix string) (bool, error) { 114 if suffix != "" { 115 return false, ErrUsage 116 } 117 return b.v, nil 118} 119 120// OnceCondition returns a Cond that calls eval the first time the condition is 121// evaluated. Future calls reuse the same result. 122// 123// The eval function is not passed a *State because the condition is cached 124// across all execution states and must not vary by state. 125func OnceCondition(summary string, eval func() (bool, error)) Cond { 126 return &onceCond{eval: eval, usage: CondUsage{Summary: summary}} 127} 128 129type onceCond struct { 130 once sync.Once 131 v bool 132 err error 133 eval func() (bool, error) 134 usage CondUsage 135} 136 137func (l *onceCond) Usage() *CondUsage { return &l.usage } 138 139func (l *onceCond) Eval(s *State, suffix string) (bool, error) { 140 if suffix != "" { 141 return false, ErrUsage 142 } 143 l.once.Do(func() { l.v, l.err = l.eval() }) 144 return l.v, l.err 145} 146 147// CachedCondition is like Condition but only calls eval the first time the 148// condition is evaluated for a given suffix. 149// Future calls with the same suffix reuse the earlier result. 150// 151// The eval function is not passed a *State because the condition is cached 152// across all execution states and must not vary by state. 153func CachedCondition(summary string, eval func(string) (bool, error)) Cond { 154 return &cachedCond{eval: eval, usage: CondUsage{Summary: summary, Prefix: true}} 155} 156 157type cachedCond struct { 158 m sync.Map 159 eval func(string) (bool, error) 160 usage CondUsage 161} 162 163func (c *cachedCond) Usage() *CondUsage { return &c.usage } 164 165func (c *cachedCond) Eval(_ *State, suffix string) (bool, error) { 166 for { 167 var ready chan struct{} 168 169 v, loaded := c.m.Load(suffix) 170 if !loaded { 171 ready = make(chan struct{}) 172 v, loaded = c.m.LoadOrStore(suffix, (<-chan struct{})(ready)) 173 174 if !loaded { 175 inPanic := true 176 defer func() { 177 if inPanic { 178 c.m.Delete(suffix) 179 } 180 close(ready) 181 }() 182 183 b, err := c.eval(suffix) 184 inPanic = false 185 186 if err == nil { 187 c.m.Store(suffix, b) 188 return b, nil 189 } else { 190 c.m.Store(suffix, err) 191 return false, err 192 } 193 } 194 } 195 196 switch v := v.(type) { 197 case bool: 198 return v, nil 199 case error: 200 return false, v 201 case <-chan struct{}: 202 <-v 203 } 204 } 205} 206