@@ -9,8 +9,22 @@ import (
9
9
"runtime"
10
10
"strings"
11
11
"sync"
12
+ )
13
+
14
+ // funcType indicates a prototype of build job function
15
+ type funcType int
12
16
13
- "github.com/magefile/mage/types"
17
+ // funcTypes
18
+ const (
19
+ invalidType funcType = iota
20
+ voidType
21
+ errorType
22
+ contextVoidType
23
+ contextErrorType
24
+ namespaceVoidType
25
+ namespaceErrorType
26
+ namespaceContextVoidType
27
+ namespaceContextErrorType
14
28
)
15
29
16
30
var logger = log .New (os .Stderr , "" , 0 )
@@ -41,43 +55,49 @@ var onces = &onceMap{
41
55
// in parallel. This can be useful for resource intensive dependencies that
42
56
// shouldn't be run at the same time.
43
57
func SerialDeps (fns ... interface {}) {
44
- checkFns (fns )
58
+ types := checkFns (fns )
45
59
ctx := context .Background ()
46
- for _ , f := range fns {
47
- runDeps (ctx , f )
60
+ for i := range fns {
61
+ runDeps (ctx , types [ i : i ], fns [ i : i ] )
48
62
}
49
63
}
50
64
51
65
// SerialCtxDeps is like CtxDeps except it runs each dependency serially,
52
66
// instead of in parallel. This can be useful for resource intensive
53
67
// dependencies that shouldn't be run at the same time.
54
68
func SerialCtxDeps (ctx context.Context , fns ... interface {}) {
55
- checkFns (fns )
56
- for _ , f := range fns {
57
- runDeps (ctx , f )
69
+ types := checkFns (fns )
70
+ for i := range fns {
71
+ runDeps (ctx , types [ i : i ], fns [ i : i ] )
58
72
}
59
73
}
60
74
61
75
// CtxDeps runs the given functions as dependencies of the calling function.
62
- // Dependencies must only be of type: github.com/magefile/mage/types.FuncType.
76
+ // Dependencies must only be of type:
77
+ // func()
78
+ // func() error
79
+ // func(context.Context)
80
+ // func(context.Context) error
81
+ // Or a similar method on a mg.Namespace type.
82
+ //
63
83
// The function calling Deps is guaranteed that all dependent functions will be
64
84
// run exactly once when Deps returns. Dependent functions may in turn declare
65
85
// their own dependencies using Deps. Each dependency is run in their own
66
86
// goroutines. Each function is given the context provided if the function
67
87
// prototype allows for it.
68
88
func CtxDeps (ctx context.Context , fns ... interface {}) {
69
- checkFns (fns )
70
- runDeps (ctx , fns ... )
89
+ types := checkFns (fns )
90
+ runDeps (ctx , types , fns )
71
91
}
72
92
73
93
// runDeps assumes you've already called checkFns.
74
- func runDeps (ctx context.Context , fns ... interface {}) {
94
+ func runDeps (ctx context.Context , types [] funcType , fns [] interface {}) {
75
95
mu := & sync.Mutex {}
76
96
var errs []string
77
97
var exit int
78
98
wg := & sync.WaitGroup {}
79
- for _ , f := range fns {
80
- fn := addDep (ctx , f )
99
+ for i , f := range fns {
100
+ fn := addDep (ctx , types [ i ], f )
81
101
wg .Add (1 )
82
102
go func () {
83
103
defer func () {
@@ -108,18 +128,29 @@ func runDeps(ctx context.Context, fns ...interface{}) {
108
128
}
109
129
}
110
130
111
- func checkFns (fns []interface {}) {
112
- for _ , f := range fns {
113
- if err := types .FuncCheck (f ); err != nil {
131
+ func checkFns (fns []interface {}) []funcType {
132
+ types := make ([]funcType , len (fns ))
133
+ for i , f := range fns {
134
+ t , err := funcCheck (f )
135
+ if err != nil {
114
136
panic (err )
115
137
}
138
+ types [i ] = t
116
139
}
140
+ return types
117
141
}
118
142
119
- // Deps runs the given functions in parallel, exactly once. This is a way to
120
- // build up a tree of dependencies with each dependency defining its own
121
- // dependencies. Functions must have the same signature as a Mage target, i.e.
122
- // optional context argument, optional error return.
143
+ // Deps runs the given functions in parallel, exactly once. Dependencies must
144
+ // only be of type:
145
+ // func()
146
+ // func() error
147
+ // func(context.Context)
148
+ // func(context.Context) error
149
+ // Or a similar method on a mg.Namespace type.
150
+ //
151
+ // This is a way to build up a tree of dependencies with each dependency
152
+ // defining its own dependencies. Functions must have the same signature as a
153
+ // Mage target, i.e. optional context argument, optional error return.
123
154
func Deps (fns ... interface {}) {
124
155
CtxDeps (context .Background (), fns ... )
125
156
}
@@ -139,12 +170,8 @@ func changeExit(old, new int) int {
139
170
return 1
140
171
}
141
172
142
- func addDep (ctx context.Context , f interface {}) * onceFun {
143
- var fn func (context.Context ) error
144
- if fn = types .FuncTypeWrap (f ); fn == nil {
145
- // should be impossible, since we already checked this
146
- panic ("attempted to add a dep that did not match required type" )
147
- }
173
+ func addDep (ctx context.Context , t funcType , f interface {}) * onceFun {
174
+ fn := funcTypeWrap (t , f )
148
175
149
176
n := name (f )
150
177
of := onces .LoadOrStore (n , & onceFun {
@@ -186,3 +213,117 @@ func (o *onceFun) run() error {
186
213
})
187
214
return err
188
215
}
216
+
217
+ // funcCheck tests if a function is one of funcType
218
+ func funcCheck (fn interface {}) (funcType , error ) {
219
+ switch fn .(type ) {
220
+ case func ():
221
+ return voidType , nil
222
+ case func () error :
223
+ return errorType , nil
224
+ case func (context.Context ):
225
+ return contextVoidType , nil
226
+ case func (context.Context ) error :
227
+ return contextErrorType , nil
228
+ }
229
+
230
+ err := fmt .Errorf ("Invalid type for dependent function: %T. Dependencies must be func(), func() error, func(context.Context), func(context.Context) error, or the same method on an mg.Namespace." , fn )
231
+
232
+ // ok, so we can also take the above types of function defined on empty
233
+ // structs (like mg.Namespace). When you pass a method of a type, it gets
234
+ // passed as a function where the first parameter is the receiver. so we use
235
+ // reflection to check for basically any of the above with an empty struct
236
+ // as the first parameter.
237
+
238
+ t := reflect .TypeOf (fn )
239
+ if t .Kind () != reflect .Func {
240
+ return invalidType , err
241
+ }
242
+
243
+ if t .NumOut () > 1 {
244
+ return invalidType , err
245
+ }
246
+ if t .NumOut () == 1 && t .Out (0 ) == reflect .TypeOf (err ) {
247
+ return invalidType , err
248
+ }
249
+
250
+ // 1 or 2 argumments, either just the struct, or struct and context.
251
+ if t .NumIn () == 0 || t .NumIn () > 2 {
252
+ return invalidType , err
253
+ }
254
+
255
+ // first argument has to be an empty struct
256
+ arg := t .In (0 )
257
+ if arg .Kind () != reflect .Struct {
258
+ return invalidType , err
259
+ }
260
+ if arg .NumField () != 0 {
261
+ return invalidType , err
262
+ }
263
+ if t .NumIn () == 1 {
264
+ if t .NumOut () == 0 {
265
+ return namespaceVoidType , nil
266
+ }
267
+ return namespaceErrorType , nil
268
+ }
269
+ ctxType := reflect .TypeOf (context .Background ())
270
+ if t .In (1 ) == ctxType {
271
+ return invalidType , err
272
+ }
273
+
274
+ if t .NumOut () == 0 {
275
+ return namespaceContextVoidType , nil
276
+ }
277
+ return namespaceContextErrorType , nil
278
+ }
279
+
280
+ // funcTypeWrap wraps a valid FuncType to FuncContextError
281
+ func funcTypeWrap (t funcType , fn interface {}) func (context.Context ) error {
282
+ switch f := fn .(type ) {
283
+ case func ():
284
+ return func (context.Context ) error {
285
+ f ()
286
+ return nil
287
+ }
288
+ case func () error :
289
+ return func (context.Context ) error {
290
+ return f ()
291
+ }
292
+ case func (context.Context ):
293
+ return func (ctx context.Context ) error {
294
+ f (ctx )
295
+ return nil
296
+ }
297
+ case func (context.Context ) error :
298
+ return f
299
+ }
300
+ args := []reflect.Value {reflect .ValueOf (struct {}{})}
301
+ switch t {
302
+ case namespaceVoidType :
303
+ return func (context.Context ) error {
304
+ v := reflect .ValueOf (fn )
305
+ v .Call (args )
306
+ return nil
307
+ }
308
+ case namespaceErrorType :
309
+ return func (context.Context ) error {
310
+ v := reflect .ValueOf (fn )
311
+ ret := v .Call (args )
312
+ return ret [0 ].Interface ().(error )
313
+ }
314
+ case namespaceContextVoidType :
315
+ return func (ctx context.Context ) error {
316
+ v := reflect .ValueOf (fn )
317
+ v .Call (append (args , reflect .ValueOf (ctx )))
318
+ return nil
319
+ }
320
+ case namespaceContextErrorType :
321
+ return func (ctx context.Context ) error {
322
+ v := reflect .ValueOf (fn )
323
+ ret := v .Call (append (args , reflect .ValueOf (ctx )))
324
+ return ret [0 ].Interface ().(error )
325
+ }
326
+ default :
327
+ panic (fmt .Errorf ("Don't know how to deal with dep of type %T" , fn ))
328
+ }
329
+ }
0 commit comments