@@ -96,6 +96,145 @@ def _DepsetBuilder_build(self):
96
96
kwargs ["order" ] = self ._order [0 ]
97
97
return depset (direct = self .direct , transitive = self .transitive , ** kwargs )
98
98
99
+ def _Optional (* initial ):
100
+ """A wrapper for a re-assignable value that may or may not be set.
101
+
102
+ This allows structs to have attributes that aren't inherently mutable
103
+ and must be re-assigned to have their value updated.
104
+
105
+ Args:
106
+ *initial: A single vararg to be the initial value, or no args
107
+ to leave it unset.
108
+
109
+ Returns:
110
+ {type}`Optional`
111
+ """
112
+ if len (initial ) > 1 :
113
+ fail ("Only zero or one positional arg allowed" )
114
+
115
+ # buildifier: disable=uninitialized
116
+ self = struct (
117
+ _value = list (initial ),
118
+ present = lambda * a , ** k : _Optional_present (self , * a , ** k ),
119
+ set = lambda * a , ** k : _Optional_set (self , * a , ** k ),
120
+ get = lambda * a , ** k : _Optional_get (self , * a , ** k ),
121
+ )
122
+ return self
123
+
124
+ def _Optional_set (self , value ):
125
+ """Sets the value of the optional.
126
+
127
+ Args:
128
+ self: implicitly added
129
+ value: the value to set.
130
+ """
131
+ if len (self ._value ) == 0 :
132
+ self ._value .append (value )
133
+ else :
134
+ self ._value [0 ] = value
135
+
136
+ def _Optional_get (self ):
137
+ """Gets the value of the optional, or error.
138
+
139
+ Args:
140
+ self: implicitly added
141
+
142
+ Returns:
143
+ The stored value, or error if not set.
144
+ """
145
+ if not len (self ._value ):
146
+ fail ("Value not present" )
147
+ return self ._value [0 ]
148
+
149
+ def _Optional_present (self ):
150
+ """Tells if a value is present.
151
+
152
+ Args:
153
+ self: implicitly added
154
+
155
+ Returns:
156
+ {type}`bool` True if the value is set, False if not.
157
+ """
158
+ return len (self ._value ) > 0
159
+
160
+ def _RuleBuilder (implementation = None , ** kwargs ):
161
+ """Builder for creating rules.
162
+
163
+ Args:
164
+ implementation: {type}`callable` The rule implementation function.
165
+ **kwargs: The same as the `rule()` function, but using builders
166
+ for the non-mutable Bazel objects.
167
+ """
168
+
169
+ # buildifier: disable=uninitialized
170
+ self = struct (
171
+ attrs = dict (kwargs .pop ("attrs" , None ) or {}),
172
+ cfg = kwargs .pop ("cfg" , None ) or _TransitionBuilder (),
173
+ exec_groups = dict (kwargs .pop ("exec_groups" , None ) or {}),
174
+ executable = _Optional (),
175
+ fragments = list (kwargs .pop ("fragments" , None ) or []),
176
+ implementation = _Optional (implementation ),
177
+ extra_kwargs = kwargs ,
178
+ provides = list (kwargs .pop ("provides" , None ) or []),
179
+ test = _Optional (),
180
+ toolchains = list (kwargs .pop ("toolchains" , None ) or []),
181
+ build = lambda * a , ** k : _RuleBuilder_build (self , * a , ** k ),
182
+ to_kwargs = lambda * a , ** k : _RuleBuilder_to_kwargs (self , * a , ** k ),
183
+ )
184
+ if "test" in kwargs :
185
+ self .test .set (kwargs .pop ("test" ))
186
+ if "executable" in kwargs :
187
+ self .executable .set (kwargs .pop ("executable" ))
188
+ return self
189
+
190
+ def _RuleBuilder_build (self , debug = "" ):
191
+ """Builds a `rule` object
192
+
193
+ Args:
194
+ self: implicitly added
195
+ debug: {type}`str` If set, prints the args used to create the rule.
196
+
197
+ Returns:
198
+ {type}`rule`
199
+ """
200
+ kwargs = self .to_kwargs ()
201
+ if debug :
202
+ lines = ["=" * 80 , "rule kwargs: {}:" .format (debug )]
203
+ for k , v in sorted (kwargs .items ()):
204
+ lines .append (" {}={}" .format (k , v ))
205
+ print ("\n " .join (lines )) # buildifier: disable=print
206
+ return rule (** kwargs )
207
+
208
+ def _RuleBuilder_to_kwargs (self ):
209
+ """Builds the arguments for calling `rule()`.
210
+
211
+ Args:
212
+ self: implicitly added
213
+
214
+ Returns:
215
+ {type}`dict`
216
+ """
217
+ kwargs = {}
218
+ if self .executable .present ():
219
+ kwargs ["executable" ] = self .executable .get ()
220
+ if self .test .present ():
221
+ kwargs ["test" ] = self .test .get ()
222
+
223
+ kwargs .update (
224
+ implementation = self .implementation .get (),
225
+ cfg = self .cfg .build () if self .cfg .implementation .present () else None ,
226
+ attrs = {
227
+ k : (v .build () if hasattr (v , "build" ) else v )
228
+ for k , v in self .attrs .items ()
229
+ },
230
+ exec_groups = self .exec_groups ,
231
+ fragments = self .fragments ,
232
+ provides = self .provides ,
233
+ toolchains = self .toolchains ,
234
+ )
235
+ kwargs .update (self .extra_kwargs )
236
+ return kwargs
237
+
99
238
def _RunfilesBuilder ():
100
239
"""Creates a `RunfilesBuilder`.
101
240
@@ -177,6 +316,91 @@ def _RunfilesBuilder_build(self, ctx, **kwargs):
177
316
** kwargs
178
317
).merge_all (self .runfiles )
179
318
319
+ def _SetBuilder (initial = None ):
320
+ """Builder for list of unique values.
321
+
322
+ Args:
323
+ initial: {type}`list | None` The initial values.
324
+
325
+ Returns:
326
+ {type}`SetBuilder`
327
+ """
328
+ initial = {} if not initial else {v : None for v in initial }
329
+
330
+ # buildifier: disable=uninitialized
331
+ self = struct (
332
+ # TODO - Switch this to use set() builtin when available
333
+ # https://bazel.build/rules/lib/core/set
334
+ _values = initial ,
335
+ update = lambda * a , ** k : _SetBuilder_update (self , * a , ** k ),
336
+ build = lambda * a , ** k : _SetBuilder_build (self , * a , ** k ),
337
+ )
338
+ return self
339
+
340
+ def _SetBuilder_build (self ):
341
+ """Builds the values into a list
342
+
343
+ Returns:
344
+ {type}`list`
345
+ """
346
+ return self ._values .keys ()
347
+
348
+ def _SetBuilder_update (self , * others ):
349
+ """Adds values to the builder.
350
+
351
+ Args:
352
+ self: implicitly added
353
+ *others: {type}`list` values to add to the set.
354
+ """
355
+ for other in others :
356
+ for value in other :
357
+ if value not in self ._values :
358
+ self ._values [value ] = None
359
+
360
+ def _TransitionBuilder (implementation = None , inputs = None , outputs = None , ** kwargs ):
361
+ """Builder for transition objects.
362
+
363
+ Args:
364
+ implementation: {type}`callable` the transition implementation function.
365
+ inputs: {type}`list[str]` the inputs for the transition.
366
+ outputs: {type}`list[str]` the outputs of the transition.
367
+ **kwargs: Extra keyword args to use when building.
368
+
369
+ Returns:
370
+ {type}`TransitionBuilder`
371
+ """
372
+
373
+ # buildifier: disable=uninitialized
374
+ self = struct (
375
+ implementation = _Optional (implementation ),
376
+ # Bazel requires transition.inputs to have unique values, so use set
377
+ # semantics so extenders of a transition can easily add/remove values.
378
+ # TODO - Use set builtin instead of custom builder, when available.
379
+ # https://bazel.build/rules/lib/core/set
380
+ inputs = _SetBuilder (inputs ),
381
+ # Bazel requires transition.inputs to have unique values, so use set
382
+ # semantics so extenders of a transition can easily add/remove values.
383
+ # TODO - Use set builtin instead of custom builder, when available.
384
+ # https://bazel.build/rules/lib/core/set
385
+ outputs = _SetBuilder (outputs ),
386
+ extra_kwargs = kwargs ,
387
+ build = lambda * a , ** k : _TransitionBuilder_build (self , * a , ** k ),
388
+ )
389
+ return self
390
+
391
+ def _TransitionBuilder_build (self ):
392
+ """Creates a transition from the builder.
393
+
394
+ Returns:
395
+ {type}`transition`
396
+ """
397
+ return transition (
398
+ implementation = self .implementation .get (),
399
+ inputs = self .inputs .build (),
400
+ outputs = self .outputs .build (),
401
+ ** self .extra_kwargs
402
+ )
403
+
180
404
# Skylib's types module doesn't have is_file, so roll our own
181
405
def _is_file (value ):
182
406
return type (value ) == "File"
@@ -187,4 +411,8 @@ def _is_runfiles(value):
187
411
builders = struct (
188
412
DepsetBuilder = _DepsetBuilder ,
189
413
RunfilesBuilder = _RunfilesBuilder ,
414
+ RuleBuilder = _RuleBuilder ,
415
+ TransitionBuilder = _TransitionBuilder ,
416
+ SetBuilder = _SetBuilder ,
417
+ Optional = _Optional ,
190
418
)
0 commit comments