@@ -96,6 +96,145 @@ def _DepsetBuilder_build(self):
9696 kwargs ["order" ] = self ._order [0 ]
9797 return depset (direct = self .direct , transitive = self .transitive , ** kwargs )
9898
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+
99238def _RunfilesBuilder ():
100239 """Creates a `RunfilesBuilder`.
101240
@@ -177,6 +316,91 @@ def _RunfilesBuilder_build(self, ctx, **kwargs):
177316 ** kwargs
178317 ).merge_all (self .runfiles )
179318
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+
180404# Skylib's types module doesn't have is_file, so roll our own
181405def _is_file (value ):
182406 return type (value ) == "File"
@@ -187,4 +411,8 @@ def _is_runfiles(value):
187411builders = struct (
188412 DepsetBuilder = _DepsetBuilder ,
189413 RunfilesBuilder = _RunfilesBuilder ,
414+ RuleBuilder = _RuleBuilder ,
415+ TransitionBuilder = _TransitionBuilder ,
416+ SetBuilder = _SetBuilder ,
417+ Optional = _Optional ,
190418)
0 commit comments