-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathexpansion.bzl
795 lines (697 loc) · 38.3 KB
/
expansion.bzl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
"""
Module containing functions that aid in environment variable expansion.
This could be replaced with the same impl via Skylib dependency, if/when PR is merged.
See https://github.com/bazelbuild/bazel-skylib/pull/486.
"""
_CONSIDERED_KEY_FORMATS = ("${}", "${{{}}}", "$({})")
def _valid_char_for_env_var_name(char):
"""
Determines if the given character could be used as a part of variable name.
Args:
char: (Required) A string (intended to be length 1) to be checked.
Returns:
True if the character could be a part of a variable name. False otherwise.
"""
return char.isalnum() or char == "_"
def _find_env_var_name_index_index(
string,
str_len,
search_start_index,
special_ending_char = None):
"""
Searches for the end of a variable name in the given string, starting from the given index.
Search will start from `search_start_index` and conclude once a character, which cannot be part
of a variable name, is encountered or until the end of the string is reached.
Args:
string: (Required) The string to search through.
str_len: (Required) The precomputed length of the given `string` parameter.
search_start_index: (Required) The index to start searching from. This is intended to be
somewhere within (the start?) of a variable name.
special_ending_char: (Optional) A special character which will count as the end of the
variable name. This can be used for `$(VAR)`, `${VAR}`, or similar.
This replaces the "valid variable name character" checking,
allowing for other characters to occur before the given special
ending character.
If set to `None`, no special character will be checked for
(only checking for non-variable characters or the end of the
string).
The default value is `None`.
Returns:
The index (with respect to the start of `string`) of the last character of the variable
name.
"""
for offset in range(str_len - search_start_index):
index = search_start_index + offset
char = string[index]
if special_ending_char:
if char == special_ending_char:
return index
elif not _valid_char_for_env_var_name(char):
return index - 1
return str_len - 1
def _even_count_dollar_sign_repeat(containing_str, end_of_dollar_signs_index):
"""
Searches backwards through the given string, counting the contiguous `$` characters.
An even number of `$` characters is indicative of escaped variables, which should not be
expanded (left as is in a string).
Args:
containing_str: (Required) The string to search through.
end_of_dollar_signs_index: (Required) The index of the end of the contiguous `$`
characters in `containing_str`. This is the starting
index for the backwards search.
Returns:
True if the set of contiguous `$` characters has even length. False if the length is odd.
"""
dollar_sign_count = 0
for index in range(end_of_dollar_signs_index, -1, -1):
if containing_str[index] != "$":
break
dollar_sign_count += 1
return (dollar_sign_count % 2) == 0
def _key_to_be_expanded(str_with_key, key, start_of_key_index):
"""
Examines the given string and determines if the given "key" should be expanded.
The "key" was located within the given string (as a substring). This function
determines whether the key is complete and is to be expanded.
Args:
str_with_key: (Required) The string that `key` is found within.
key: (Required) The found substring in `str_with_key` which needs to possibly be
expanded.
start_of_key_index: (Required) The index where `key` was found within `str_with_key`.
Returns:
True if the found key is complete (not a substring of another potential key) and is not
escaped (even number of preceding `$`).
"""
# Check that the string at index is prefixed with an even number of `$`.
# An even number means that the last `$` is escaped.
if _even_count_dollar_sign_repeat(str_with_key, start_of_key_index):
return False
# Check that the key is correctly matched.
# Specifically, check the key isn't matching to another key (substring).
key_mismatch = False
if key[-1] not in (")", "}"):
index_after_key = start_of_key_index + len(key)
key_mismatch = (
(index_after_key < len(str_with_key)) and
_valid_char_for_env_var_name(str_with_key[index_after_key])
)
return not key_mismatch
def _fail_validation(fail_instead_of_return, found_errors_list, failure_message):
"""
This is called when a failure has occured and handles propagation of a failure message.
Will either call `fail()` with the given failure message (to hard fail immediately) or append
the given failure message to the given list.
Args:
fail_instead_of_return: (Required) If set to True, `fail()` will be called (will not
return). If set to False, `found_errors_list` will be appended to
and the function will return normally.
found_errors_list: (Required) In/out list for error messages to be appended into. Will
only be used if `fail_instead_of_return` is False.
failure_message: (Required) Failure message to be either passed to `fail()` or
appended into `found_errors_list`.
"""
if fail_instead_of_return:
fail(failure_message)
else:
found_errors_list.append(failure_message)
def _validate_unterminated_expression(
expanded_val,
fail_instead_of_return,
found_errors,
dollar_sign_index,
next_char_after_dollar_sign):
"""
Checks if given string contains an unterminated expression of the form `$(VAR)` or `${VAR}`.
If the given variable/expression is of the correct form, and unterminated, an error will be
noted (either by calling `fail()` or by appending it into the given error list).
Args:
expanded_val: (Required) The string which contains a `$` preceding a variable (to be
expanded).
fail_instead_of_return: (Required) If set to True, `fail()` will be called (will not
return) when an unterminated variable is found. If set to False,
`found_errors` will be appended to and the function will return
normally.
found_errors: (Required) In/out list for error messages to be appended into. Will only be
used if `fail_instead_of_return` is False.
dollar_sign_index: (Required) The index of the `$` at the start of the expression.
next_char_after_dollar_sign: (Required) The character that immediately follows the `$`.
Returns:
The validaity of the string.
Returns False if the variable was of the form and unterminated. Returns True otherwise.
"""
if next_char_after_dollar_sign == "(":
if expanded_val.find(")", dollar_sign_index + 1) < 0:
unterminated_expr = expanded_val[dollar_sign_index:]
_fail_validation(
fail_instead_of_return,
found_errors,
"Unterminated '$(...)' expression ('{}' in '{}').".format(unterminated_expr, expanded_val),
)
return False
elif next_char_after_dollar_sign == "{":
if expanded_val.find("}", dollar_sign_index + 1) < 0:
unterminated_expr = expanded_val[dollar_sign_index:]
_fail_validation(
fail_instead_of_return,
found_errors,
"Unterminated '${{...}}' expression ('{}' in '{}').".format(unterminated_expr, expanded_val),
)
return False
return True
def _validate_unexpanded_expression(
expanded_val,
fail_instead_of_return,
str_len,
found_errors,
dollar_sign_index,
next_char_after_dollar_sign):
"""
Always generates an error for the given string (containing unexpanded variable).
The given string contains a variable which unexpanded (and is not escaped), an error will be
noted (either by calling `fail()` or by appending it into the given error list).
Args:
expanded_val: (Required) The string which contains a `$` preceding a variable (to be
expanded).
fail_instead_of_return: (Required) If set to True, `fail()` will be called (will not
return). If set to False, `found_errors` will be appended to and
the function will return normally.
str_len: (Required) The precomputed length of the given `expanded_val` parameter.
found_errors: (Required) In/out list for error messages to be appended into. Will only be
used if `fail_instead_of_return` is False.
dollar_sign_index: (Required) The index of the `$` at the start of the unexpanded
expression.
next_char_after_dollar_sign: (Required) The character that immediately follows the `$`.
"""
# Find special ending char, if wrapped expression.
special_ending_char = None
if next_char_after_dollar_sign == "(":
special_ending_char = ")"
elif next_char_after_dollar_sign == "{":
special_ending_char = "}"
# Find info for unexpanded expression and fail.
name_end_index = _find_env_var_name_index_index(
expanded_val,
str_len,
dollar_sign_index + 1,
special_ending_char = special_ending_char,
)
_fail_validation(
fail_instead_of_return,
found_errors,
"Unexpanded expression ('{}' in '{}').".format(
expanded_val[dollar_sign_index:name_end_index + 1],
expanded_val,
),
)
def _validate_all_keys_expanded(expanded_val, fail_instead_of_return):
"""
Iterates over the entire given string, searching for any unexpanded variables/expressions.
If any unexpanded/unterminated variables/expressions are found, an error will be noted (either
by calling `fail()` and hard failing immediately, or by collecting all such found errors and
returning it in a list).
Args:
expanded_val: (Required) The string to be checked for any potentially unescaped and
unexpanded/unterminated variables/expressions.
fail_instead_of_return: (Required) If set to True, `fail()` will be called (will not
return) when the first error has been found. If set to False, the
function will return normally and return a list of all found
errors.
Returns:
A list of found errors. Each element in the list is a failure message with details about
the unescaped and unexpanded/unterminated variable/expression. The list will be empty if
no such expressions were found. This function does not return if `fail_instead_of_return`
was set to True (`fail()` will be called).
"""
str_len = len(expanded_val)
str_iter = 0
found_errors = []
# Max iterations at the length of the str; will likely break out earlier.
for _ in range(str_len):
if str_iter >= str_len:
break
next_dollar_sign_index = expanded_val.find("$", str_iter)
if next_dollar_sign_index < 0:
break
str_iter = next_dollar_sign_index + 1
# Check for unterminated (non-escaped) ending dollar sign(s).
if next_dollar_sign_index == str_len - 1:
if not _even_count_dollar_sign_repeat(expanded_val, next_dollar_sign_index):
_fail_validation(
fail_instead_of_return,
found_errors,
"Unterminated '$' expression in '{}'.".format(expanded_val),
)
# No error if escaped. Still at end of string, break out.
break
next_char_after_dollar_sign = expanded_val[next_dollar_sign_index + 1]
# Check for continued dollar signs string (no need to handle yet).
if next_char_after_dollar_sign == "$":
continue
# Check for escaped dollar signs (which are ok).
if _even_count_dollar_sign_repeat(expanded_val, next_dollar_sign_index):
continue
# Check for unterminated expressions.
if _validate_unterminated_expression(
expanded_val,
fail_instead_of_return,
found_errors,
next_dollar_sign_index,
next_char_after_dollar_sign,
):
# If not unterminated, it's unexpanded.
_validate_unexpanded_expression(
expanded_val,
fail_instead_of_return,
str_len,
found_errors,
next_dollar_sign_index,
next_char_after_dollar_sign,
)
return found_errors
def _expand_key_in_str(key, val, unexpanded_str):
"""
Expand the given key, by replacing it with the given value, in the given string.
The given `key` may or may not be contained in the given string `unexpanded_str`.
If the given key is found, it will be expanded/replaced by the given `val` string.
The key is given in its full formatted form with preceding `$` (`$VAR`, `$(VAR)`, `${VAR}`,
`$(VAR VAL)`, etc).
The key will not be expanded if it is escaped (an even number of contiguous `$` characters at
the start) or if the found key is a substring of another potential key (e.g. `$VAR` will not be
expanded if the found location is `$VARIABLE`).
The given key will be replaced (as appropriate) for all occurences within the given string.
Args:
key: (Required) The key to search for (within the given string, `unexpanded_str`) and
replace all occurences of the key with the given replacement value, `val`.
val: (Required) The value to replace all found occurences of the given key, `key`, into
the given string, `unexpanded_str`.
unexpanded_str: (Required) The string to search for `key` and replace with `val`.
Returns:
A copy of `unexpanded_str` with all occurences of `key` replaced with `val` (as necessary).
The returned string will be `unexpanded_str` (not a copy), if `key` is not found/expanded.
"""
key_len = len(key)
val_len = len(val)
searched_index = 0
expanded_str = unexpanded_str
# Max iterations at the length of the str; will likely break out earlier.
for _ in range(len(expanded_str)):
used_key_index = expanded_str.find(key, searched_index)
if used_key_index < 0:
break
if _key_to_be_expanded(expanded_str, key, used_key_index):
# Only replace this one instance that we have verified (count = 1).
# Avoid extra string splicing, if possible.
if searched_index == 0:
expanded_str = expanded_str.replace(key, val, 1)
else:
expanded_str = (
expanded_str[0:searched_index - 1] +
expanded_str[searched_index:].replace(key, val, 1)
)
searched_index += val_len
else:
searched_index += key_len
return expanded_str
def _expand_all_keys_in_str_from_dict(replacement_dict, unexpanded_str):
"""
Uses the given dictionary to replace keys with values in the given string.
Each key is intended to be a variable name (e.g. `VARIABLE_NAME`), which can be wrapped with
`$`, `$( )`, or `${ }` (to express the given example key as the "formatted key"
`$VARIABLE_NAME`, `$(VARIABLE_NAME)`, or `${VARIABLE_NAME}`). The corresponding value (in the
dict) is to be used as the intended replacement string when any matching formatted key (of the
given variable name key) is found within the given string, `unexpanded_str`.
Args:
replacement_dict: (Required) The set of key/value pairs to be used for search/replacement
within the given `unexpanded_str` string.
unexpanded_str: (Required) The string to search for the formatted versions of each key
set within `replacement_dict`, where each found occurence will be
expanded/replaced with the associated value.
Returns:
A copy of `unexpanded_str` with all occurences of each key (when formatted into an
unexpanded variable) within `replacement_dict` replaced with corresponding value (as
necessary).
The returned string will be `unexpanded_str` (not a copy), if no expansion occurs.
"""
# Manually expand variables based on the var dict.
# Do not use `ctx.expand_make_variables()` as it will error out if any variable expands to
# `$(location <name>)` (or similar) instead of leaving it untouched.
expanded_val = unexpanded_str
for avail_key, corresponding_val in replacement_dict.items():
if expanded_val.find(avail_key) < 0:
continue
formatted_keys = [key_format.format(avail_key) for key_format in _CONSIDERED_KEY_FORMATS]
# Skip self-references (e.g. {"VAR": "$(VAR)"})
# This may happen (and is ok) for the `env` attribute, where keys can be reused to be
# expanded by the resolved dict.
if corresponding_val in formatted_keys:
continue
# Expand each format style of this key, if it exists.
for formatted_key in formatted_keys:
expanded_val = _expand_key_in_str(formatted_key, corresponding_val, expanded_val)
return expanded_val
def _expand_all_keys_in_str(
expand_location,
resolved_replacement_dict,
env_replacement_dict,
unexpanded_str):
"""
Uses the given dictionaries to replace keys with values in the given string.
Each key, in the given dictionaries, is intended to be a variable name (e.g. `VARIABLE_NAME`),
which can be wrapped with `$`, `$( )`, or `${ }` (to express the given example key as the
"formatted key" `$VARIABLE_NAME`, `$(VARIABLE_NAME)`, or `${VARIABLE_NAME}`). The corresponding
value (in the dict) is to be used as the intended replacement string when any matching
formatted key (of the given variable name key) is found within the given string,
`unexpanded_str`.
Expansion happens iteratively. In each iteration, three steps occur:
1) If `expand_location` is not `None`, it will be invoked to replace any occurrences of
`$(location ...)` (or similar). Technically, this function can execute any high-priority
expansion logic -- but it is intended for functionality similar to `ctx.expand_location()`.
2) Each variable name key in `env_replacement_dict` will be searched for (in `unexpanded_str`)
and expanded into the corresponding value within the dict for the given found variable
name. This is intended for the use with the `env` attribute for a given target (but
supports any general "higher priority" dict replacement).
3) Each variable name key in `resolved_replacement_dict` will be searched for (in
`unexpanded_str`) and expanded into the corresponding value within the dict for the given
found variable name. This is intended for the use with `ctx.var` which contains toolchain
resolved key/values (but supports any general "lower priority" dict replacement).
Args:
expand_location: (Required) A None-able function used for optional "location"
expansion logic (`$(location ...)` or similar).
resolved_replacement_dict: (Required) A set of key/value pairs to be used for
search/replacement within the given `unexpanded_str` string.
Replacement logic will occur after (lower priority) replacement
for `env_replacement_dict`.
env_replacement_dict: (Required) A set of key/value pairs to be used for
search/replacement within the given `unexpanded_str` string.
Replacement logic will occur before (higher priority) replacement
for `resolved_replacement_dict`.
unexpanded_str: (Required) The string to perform expansion variable upon (optionally
invoke `expand_location`, and search for the formatted versions of each
key set within `env_replacement_dict` and `resolved_replacement_dict`,
where each found occurence will be expanded/replaced with the
associated value).
Returns:
A copy of `unexpanded_str` with all occurences of each key (when formatted into an
unexpanded variable) within `replacement_dict` replaced with corresponding value (as
necessary).
The returned string will be `unexpanded_str` (not a copy), if no expansion occurs.
"""
if unexpanded_str.find("$") < 0:
return unexpanded_str
expanded_val = unexpanded_str
prev_val = expanded_val
# Max iterations at the length of the str; will likely break out earlier.
for _ in range(len(expanded_val)):
# First let's try the safe `location` (et al) expansion logic.
# `$VAR`, `$(VAR)`, and `${VAR}` will be left untouched.
if expand_location:
expanded_val = expand_location(expanded_val)
# Break early if nothing left to expand.
if expanded_val.find("$") < 0:
break
# Expand values first from the `env` attribute, then by the toolchain resolved values.
expanded_val = _expand_all_keys_in_str_from_dict(env_replacement_dict, expanded_val)
expanded_val = _expand_all_keys_in_str_from_dict(resolved_replacement_dict, expanded_val)
# Break out early if nothing changed in this iteration.
if prev_val == expanded_val:
break
prev_val = expanded_val
return expanded_val
def _expand_with_manual_dict(resolution_dict, source_env_dict, validate_expansion = False):
"""
Recursively expands all values in `source_env_dict` using the given lookup data.
All keys of `source_env_dict` are returned in the resultant dict with values expanded by
lookups via `resolution_dict` dict.
This function does not modify any of the given parameters.
Args:
resolution_dict: (Required) A dictionary with resolved key/value pairs to be used for
lookup when resolving values. This may come from toolchains (via
`ctx.var`) or other sources.
source_env_dict: (Required) The source for all desired expansions. All key/value pairs
will appear within the returned dictionary, with all values fully
expanded by lookups in `resolution_dict`.
validate_expansion: (Optional) If set to True, all expanded strings will be validated to
ensure that no unexpanded (but seemingly expandable) values remain. If
any unexpanded values are found, `fail()` will be called. The
validation logic is the same as
`expansion.validate_expansions_in_dict()`.
Default value is False.
Returns:
A new dict with all key/values from `source_env_dict`, where all values have been recursively
expanded.
"""
expanded_envs = {}
for env_key, unexpanded_val in source_env_dict.items():
expanded_val = _expand_all_keys_in_str(
None, # No `expand_location` available
resolution_dict,
source_env_dict,
unexpanded_val,
)
if validate_expansion:
_validate_all_keys_expanded(expanded_val, fail_instead_of_return = True)
expanded_envs[env_key] = expanded_val
return expanded_envs
def _expand_with_manual_dict_and_location(
expand_location,
resolution_dict,
source_env_dict,
validate_expansion = False):
"""
Recursively expands all values in `source_env_dict` using the given logic / lookup data.
All keys of `source_env_dict` are returned in the resultant dict with values expanded by
location expansion logic via `expand_location` and by lookups via `resolution_dict` dict.
This function does not modify any of the given parameters.
Args:
expand_location: (Required) A function that takes in a string and properly replaces
`$(location ...)` (and similar) with the corresponding values. This
likely should correspond to `ctx.expand_location()`.
resolution_dict: (Required) A dictionary with resolved key/value pairs to be used for
lookup when resolving values. This may come from toolchains (via
`ctx.var`) or other sources.
source_env_dict: (Required) The source for all desired expansions. All key/value pairs
will appear within the returned dictionary, with all values fully
expanded by the logic expansion logic of `expand_location` and by
lookup in `resolution_dict`.
validate_expansion: (Optional) If set to True, all expanded strings will be validated to
ensure that no unexpanded (but seemingly expandable) values remain. If
any unexpanded values are found, `fail()` will be called. The
validation logic is the same as
`expansion.validate_expansions_in_dict()`.
Default value is False.
Returns:
A new dict with all key/values from `source_env_dict`, where all values have been recursively
expanded.
"""
expanded_envs = {}
for env_key, unexpanded_val in source_env_dict.items():
expanded_val = _expand_all_keys_in_str(
expand_location,
resolution_dict,
source_env_dict,
unexpanded_val,
)
if validate_expansion:
_validate_all_keys_expanded(expanded_val, fail_instead_of_return = True)
expanded_envs[env_key] = expanded_val
return expanded_envs
def _expand_with_toolchains(
ctx,
source_env_dict,
additional_lookup_dict = None,
validate_expansion = False):
"""
Recursively expands all values in `source_env_dict` using the given lookup data.
All keys of `source_env_dict` are returned in the resultant dict with values expanded by
lookups via `ctx.var` dict (unioned with optional `additional_lookup_dict` parameter).
Expansion occurs recursively through all given dicts.
This function does not modify any of the given parameters.
Args:
ctx: (Required) The bazel context object. This is used to access the `ctx.var` member
for use as the "resolution dict". This makes use of providers from toolchains for
environment variable expansion.
source_env_dict: (Required) The source for all desired expansions. All key/value pairs
will appear within the returned dictionary, with all values fully
expanded by lookups in `ctx.var` and optional `additional_lookup_dict`.
additional_lookup_dict: (Optional) Additional dict to be used with `ctx.var` (union) for
variable expansion.
validate_expansion: (Optional) If set to True, all expanded strings will be validated
to ensure that no unexpanded (but seemingly expandable) values
remain. If any unexpanded values are found, `fail()` will be
called. The validation logic is the same as
`expansion.validate_expansions_in_dict()`.
Default value is False.
Returns:
A new dict with all key/values from `source_env_dict`, where all values have been recursively
expanded.
"""
additional_lookup_dict = additional_lookup_dict or {}
return _expand_with_manual_dict(
ctx.var | additional_lookup_dict,
source_env_dict,
validate_expansion = validate_expansion,
)
def _expand_with_toolchains_and_location(
ctx,
deps,
source_env_dict,
additional_lookup_dict = None,
validate_expansion = False):
"""
Recursively expands all values in `source_env_dict` using the `ctx` logic / lookup data.
All keys of `source_env_dict` are returned in the resultant dict with values expanded by
location expansion logic via `ctx.expand_location` and by lookups via `ctx.var` dict (unioned
with optional `additional_lookup_dict` parameter).
This function does not modify any of the given parameters.
Args:
ctx: (Required) The bazel context object. This is used to access the `ctx.var` member
for use as the "resolution dict". This makes use of providers from toolchains for
environment variable expansion. This object is also used for the
`ctx.expand_location` method to handle `$(location ...)` (and similar) expansion
logic.
deps: (Required) The set of targets used with `ctx.expand_location` for expanding
`$(location ...)` (and similar) expressions.
source_env_dict: (Required) The source for all desired expansions. All key/value pairs
will appear within the returned dictionary, with all values fully
expanded by the logic expansion logic of `expand_location` and by
lookups in `ctx.var` and optional `additional_lookup_dict`.
additional_lookup_dict: (Optional) Additional dict to be used with `ctx.var` (union) for
variable expansion.
validate_expansion: (Optional) If set to True, all expanded strings will be validated
to ensure that no unexpanded (but seemingly expandable) values
remain. If any unexpanded values are found, `fail()` will be
called. The validation logic is the same as
`expansion.validate_expansions_in_dict()`.
Default value is False.
Returns:
A new dict with all key/values from `source_env_dict`, where all values have been recursively
expanded.
"""
def _simpler_expand_location(input_str):
return ctx.expand_location(input_str, deps)
additional_lookup_dict = additional_lookup_dict or {}
return _expand_with_manual_dict_and_location(
_simpler_expand_location,
ctx.var | additional_lookup_dict,
source_env_dict,
validate_expansion = validate_expansion,
)
def _expand_with_toolchains_attr(
ctx,
env_attr_name = "env",
additional_lookup_dict = None,
validate_expansion = False):
"""
Recursively expands all values in "env" attr dict using the `ctx` lookup data.
All keys of `env` attribute are returned in the resultant dict with values expanded by
lookups via `ctx.var` dict (unioned with optional `additional_lookup_dict` parameter).
The attribute used can be changed (instead of `env`) via the optional `env_attr_name` paramter.
This function does not modify any of the given parameters.
Args:
ctx: (Required) The bazel context object. This is used to access the `ctx.var` member
for use as the "resolution dict". This makes use of providers from toolchains for
environment variable expansion. This object is also used to retrieve various
necessary attributes via `ctx.attr.<attr_name>`.
env_attr_name: (Optional) The name of the attribute that is used as the source for all
desired expansions. All key/value pairs will appear within the returned
dictionary, with all values fully expanded by lookups in `ctx.var` and
optional `additional_lookup_dict`.
Default value is "env".
additional_lookup_dict: (Optional) Additional dict to be used with `ctx.var` (union) for
variable expansion.
validate_expansion: (Optional) If set to True, all expanded strings will be validated
to ensure that no unexpanded (but seemingly expandable) values
remain. If any unexpanded values are found, `fail()` will be
called. The validation logic is the same as
`expansion.validate_expansions_in_dict()`.
Default value is False.
Returns:
A new dict with all key/values from source attribute (default "env" attribute), where all
values have been recursively expanded.
"""
return _expand_with_toolchains(
ctx,
getattr(ctx.attr, env_attr_name),
additional_lookup_dict = additional_lookup_dict,
validate_expansion = validate_expansion,
)
def _expand_with_toolchains_and_location_attr(
ctx,
deps_attr_name = "deps",
env_attr_name = "env",
additional_lookup_dict = None,
validate_expansion = False):
"""
Recursively expands all values in "env" attr dict using the `ctx` logic / lookup data.
All keys of `env` attribute are returned in the resultant dict with values expanded by
location expansion logic via `ctx.expand_location` and by lookups via `ctx.var` dict (unioned
with optional `additional_lookup_dict` parameter).
This function does not modify any of the given parameters.
Args:
ctx: (Required) The bazel context object. This is used to access the `ctx.var` member
for use as the "resolution dict". This makes use of providers from toolchains for
environment variable expansion. This object is also used for the
`ctx.expand_location` method to handle `$(location ...)` (and similar) expansion
logic. This object is also used to retrieve various necessary attributes via
`ctx.attr.<attr_name>`.
deps_attr_name: (Optional) The name of the attribute which contains the set of targets used
with `ctx.expand_location` for expanding `$(location ...)` (and similar)
expressions.
Default value is "deps".
env_attr_name: (Optional) The name of the attribute that is used as the source for all
desired expansions. All key/value pairs will appear within the returned
dictionary, with all values fully expanded by lookups in `ctx.var` and
optional `additional_lookup_dict`.
Default value is "env".
additional_lookup_dict: (Optional) Additional dict to be used with `ctx.var` (union) for
variable expansion.
validate_expansion: (Optional) If set to True, all expanded strings will be validated
to ensure that no unexpanded (but seemingly expandable) values
remain. If any unexpanded values are found, `fail()` will be
called. The validation logic is the same as
`expansion.validate_expansions_in_dict()`.
Default value is False.
Returns:
A new dict with all key/values from source attribute (default "env" attribute), where all
values have been recursively expanded.
"""
return _expand_with_toolchains_and_location(
ctx,
getattr(ctx.attr, deps_attr_name),
getattr(ctx.attr, env_attr_name),
additional_lookup_dict = additional_lookup_dict,
validate_expansion = validate_expansion,
)
def _validate_expansions(expanded_values, fail_instead_of_return = True):
"""
Validates all given strings to no longer have unexpanded expressions.
Validates all expanded strings in `expanded_values` to ensure that no unexpanded (but seemingly
expandable) values remain.
Any unterminated or unexpanded expressions of the form `$VAR`, $(VAR)`, or `${VAR}` will result
in an error (with fail message).
Args:
expanded_values: (Required) List of string values to validate.
fail_instead_of_return: (Optional) If set to True, `fail()` will be called upon first
invalid (unexpanded) value found. If set to False, error
messages will be collected and returned (no failure will
occur); it will be the caller's responsibility to check the
returned list.
Default value is True.
Returns:
A list with all found invalid (unexpanded) values. Will be an empty list if all values are
completely expanded. This function will not return if there were unexpanded substrings and if
`fail_instead_of_return` is set to True (due to `fail()` being called).
"""
found_errors = []
for expanded_val in expanded_values:
found_errors += _validate_all_keys_expanded(expanded_val, fail_instead_of_return)
return found_errors
expansion = struct(
expand_with_manual_dict = _expand_with_manual_dict,
expand_with_manual_dict_and_location = _expand_with_manual_dict_and_location,
expand_with_toolchains = _expand_with_toolchains,
expand_with_toolchains_attr = _expand_with_toolchains_attr,
expand_with_toolchains_and_location = _expand_with_toolchains_and_location,
expand_with_toolchains_and_location_attr = _expand_with_toolchains_and_location_attr,
validate_expansions = _validate_expansions,
)