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