Skip to content

Commit 158f2b1

Browse files
fangismCQ Bot
authored andcommitted
[rbe] Support --platform override/rewrite
Setting `rewrapper --platform` would clobber the value that comes from `--cfg fuchsia-rewrapper.cfg`. Now we can effectively dictionary-merge the platform value from both the cfg and the command-line. The --platform flag when passed through our remote_action.py wrapper will be re-written with a merged value. Other: move some code+test to cl_utils.py for better reuse. Bug: 342692553 Change-Id: Ic00e8b448aa7979e4a580ac38fea8630453c5fa1 Reviewed-on: https://fuchsia-review.googlesource.com/c/fuchsia/+/1054556 Reviewed-by: David Turner <digit@google.com> Commit-Queue: David Fang <fangism@google.com> Fuchsia-Auto-Submit: David Fang <fangism@google.com>
1 parent 0a0c308 commit 158f2b1

File tree

7 files changed

+200
-57
lines changed

7 files changed

+200
-57
lines changed

build/rbe/action_diff.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -353,7 +353,7 @@ def main(argv: Sequence[str]) -> int:
353353
# len(logs) == 2, enforced by _MAIN_ARG_PARSER.
354354

355355
cfg_path = remotetool._REPROXY_CFG
356-
reproxy_cfg = remotetool.read_config_file_lines(
356+
reproxy_cfg = cl_util.read_config_file_lines(
357357
cfg_path.read_text().splitlines()
358358
)
359359

build/rbe/cl_utils.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -367,6 +367,51 @@ def expand_paths_from_files(files: Iterable[Path]) -> Iterable[Path]:
367367
yield Path(p.replace(" ", "\\ ")) # preserve escape
368368

369369

370+
def read_config_file_lines(lines: Iterable[str]) -> Dict[str, str]:
371+
"""Parser for reading RBE config files.
372+
373+
RBE config files are text files with lines of "VAR=VALUE"
374+
(ignoring whole-line #-comments and blank lines).
375+
Spec details can be found at:
376+
https://github.com/bazelbuild/reclient/blob/main/internal/pkg/rbeflag/rbeflag.go
377+
378+
Args:
379+
lines: lines of config from a file
380+
381+
Returns:
382+
dictionary of key-value pairs read from the config file.
383+
"""
384+
result = {}
385+
for line in lines:
386+
stripped = line.strip()
387+
if stripped and not stripped.startswith("#"):
388+
key, sep, value = stripped.partition("=")
389+
if sep == "=":
390+
result[key] = value
391+
return result
392+
393+
394+
def values_dict_to_config_value(
395+
values: Dict[str, str], eq: str = "=", sep: str = ","
396+
) -> str:
397+
"""Return a string representation of a dictionary for config lines.
398+
399+
It is assumed that the order among different keys is inconsequential,
400+
so we sort the output by keys for determinism.
401+
402+
Args:
403+
values: arbitrary string dictionary of key-values
404+
eq: string to join keys and values
405+
sep: string to join key-value pairs
406+
407+
Returns:
408+
Joined string representation, ordered by keys (deterministic).
409+
"""
410+
# Keys are unique, so this effectively sorts (key, value) pairs
411+
# by key without regard to value.
412+
return sep.join(k + eq + v for k, v in sorted(values.items()))
413+
414+
370415
def keyed_flags_to_values_dict(
371416
flags: Iterable[str], convert_type: Callable[[str], Any] = None
372417
) -> Dict[str, Sequence[str]]:

build/rbe/cl_utils_test.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -522,6 +522,55 @@ def test_fuse_repeated(self):
522522
)
523523

524524

525+
class ReadConfigFileLinesTests(unittest.TestCase):
526+
def test_empty_file(self):
527+
self.assertEqual(cl_utils.read_config_file_lines([]), dict())
528+
529+
def test_ignore_blank_lines(self):
530+
self.assertEqual(
531+
cl_utils.read_config_file_lines(["", "\t", "\n"]), dict()
532+
)
533+
534+
def test_ignore_comments(self):
535+
self.assertEqual(
536+
cl_utils.read_config_file_lines(["####", "# comment"]), dict()
537+
)
538+
539+
def test_ignore_non_key_value_pairs(self):
540+
self.assertEqual(
541+
cl_utils.read_config_file_lines(["value-only"]), dict()
542+
)
543+
544+
def test_key_value(self):
545+
self.assertEqual(
546+
cl_utils.read_config_file_lines(["key=value"]), {"key": "value"}
547+
)
548+
549+
def test_last_wins(self):
550+
self.assertEqual(
551+
cl_utils.read_config_file_lines(["key=value-1", "key=value-2"]),
552+
{"key": "value-2"},
553+
)
554+
555+
556+
class ValuesDictToConfigValueTests(unittest.TestCase):
557+
def test_empty(self):
558+
self.assertEqual(cl_utils.values_dict_to_config_value({}), "")
559+
560+
def test_one_value(self):
561+
self.assertEqual(
562+
cl_utils.values_dict_to_config_value({"a": "bb"}), "a=bb"
563+
)
564+
565+
def test_bunch_of_values_must_be_key_sorted(self):
566+
self.assertEqual(
567+
cl_utils.values_dict_to_config_value(
568+
{"a": "yy", "zz": "aa", "pp": "qqq"}
569+
),
570+
"a=yy,pp=qqq,zz=aa",
571+
)
572+
573+
525574
class KeyedFlagsToValuesDictTests(unittest.TestCase):
526575
def test_empty(self):
527576
self.assertEqual(

build/rbe/remote_action.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -909,6 +909,7 @@ def __init__(
909909
input_list_paths: Sequence[Path] = None,
910910
output_files: Sequence[Path] = None,
911911
output_dirs: Sequence[Path] = None,
912+
platform: str = None,
912913
disable: bool = False,
913914
verbose: bool = False,
914915
save_temps: bool = False,
@@ -944,6 +945,7 @@ def __init__(
944945
current working dir.
945946
output_dirs: directories to be fetched after remote execution, relative to the
946947
current working dir.
948+
platform: rewrapper --platform, containing remote execution parameters
947949
disable: if true, execute locally.
948950
verbose: if true, print more information about what is happening.
949951
compare_with_local: if true, also run locally and compare outputs.
@@ -1012,6 +1014,9 @@ def __init__(
10121014
# (which can be verified with --compare [compare_with_local]).
10131015
self._local_only_command = local_only_command or command
10141016

1017+
# platform is handled by specially, by merging with cfg.
1018+
self._platform = platform
1019+
10151020
# Detect some known rewrapper options
10161021
(
10171022
self._rewrapper_known_options,
@@ -1170,6 +1175,14 @@ def _generate_options(self) -> Iterable[str]:
11701175
if self.exec_strategy:
11711176
yield f"--exec_strategy={self.exec_strategy}"
11721177

1178+
if self.platform:
1179+
# Then merge the value from --cfg and --platform to override
1180+
# the value from the --cfg. Yield the rewritten flag.
1181+
platform_value = cl_utils.values_dict_to_config_value(
1182+
self.merged_platform
1183+
)
1184+
yield f"--platform={platform_value}"
1185+
11731186
yield from self._options
11741187

11751188
@property
@@ -1193,6 +1206,43 @@ def download_regex(self) -> Optional[str]:
11931206
def download_outputs(self) -> bool:
11941207
return self._rewrapper_known_options.download_outputs
11951208

1209+
@property
1210+
def platform(self) -> Optional[str]:
1211+
return self._platform
1212+
1213+
@property
1214+
def merged_platform(self) -> Dict[str, str]:
1215+
"""Combined platform values from --cfg and --platform."""
1216+
merged_values: Dict[str, str] = {}
1217+
1218+
def take_dict_last_values(key_values: str) -> Dict[str, str]:
1219+
return {
1220+
k: cl_utils.last_value_or_default(v, "")
1221+
for k, v in cl_utils.keyed_flags_to_values_dict(
1222+
key_values.split(",")
1223+
).items()
1224+
}
1225+
1226+
cfg = self.config
1227+
if cfg:
1228+
rewrapper_cfg: Dict[str, str] = cl_utils.read_config_file_lines(
1229+
cfg.read_text().splitlines()
1230+
)
1231+
cfg_platform = rewrapper_cfg.get("platform", "")
1232+
if cfg_platform:
1233+
# in case of multiple/conflicting values, take the last one
1234+
merged_values.update(take_dict_last_values(cfg_platform))
1235+
1236+
# TODO(b/342692553): merge RBE_platform environment variable
1237+
1238+
cl_platform = self.platform
1239+
if cl_platform:
1240+
# override earlier values
1241+
# in case of multiple/conflicting values, take the last one
1242+
merged_values.update(take_dict_last_values(cl_platform))
1243+
1244+
return merged_values
1245+
11961246
@property
11971247
def need_download_stub_predicate(self) -> Callable[[Path], bool]:
11981248
"""Return a function that indicates whether an output path will require a download stub."""
@@ -2346,6 +2396,11 @@ def inherit_main_arg_parser_flags(
23462396
metavar="DIRS",
23472397
help="Specify additional remote output directories, comma-separated, relative to the current working dir (repeatable, cumulative). Note: This is different than `rewrapper --output_directories`, which expects exec_root-relative paths.",
23482398
)
2399+
rewrapper_group.add_argument(
2400+
"--platform",
2401+
type=str,
2402+
help="The rewrapper platform variable specifies remote execution parameters, such as OS type, image, worker constraints, etc. The value specified in this wrapper is intercepted and merged with the value found in the --cfg.",
2403+
)
23492404

23502405
main_group = parser.add_argument_group(
23512406
title="common",
@@ -2504,6 +2559,7 @@ def remote_action_from_args(
25042559
input_list_paths=input_list_paths,
25052560
output_files=output_files,
25062561
output_dirs=output_dirs,
2562+
platform=main_args.platform,
25072563
disable=main_args.local,
25082564
verbose=main_args.verbose,
25092565
label=main_args.label,

build/rbe/remote_action_test.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -834,10 +834,56 @@ def test_cfg(self):
834834
cfg = Path("other.cfg")
835835
main_args, other = p.parse_known_args([f"--cfg={cfg}", "--", "echo"])
836836
self.assertEqual(main_args.cfg, cfg)
837+
self.assertEqual(other, [])
837838
action = remote_action.remote_action_from_args(main_args)
839+
self.assertEqual(action.config, cfg)
838840
self.assertEqual(action.local_only_command, ["echo"])
839841
self.assertEqual(action.options, ["--cfg", str(cfg)])
840842

843+
def test_platform_merge_override(self):
844+
p = self._make_main_parser()
845+
cfg = Path("other.cfg")
846+
platform_value = "foo=zoo,alice=bob"
847+
# Test both styles of flags.
848+
test_flag_variants = (
849+
[f"--platform={platform_value}"],
850+
["--platform", platform_value],
851+
)
852+
for flags in test_flag_variants:
853+
main_args, remote_options = p.parse_known_args(
854+
[f"--cfg={cfg}"] + flags + ["--", "echo"]
855+
)
856+
self.assertEqual(main_args.cfg, cfg)
857+
self.assertEqual(main_args.platform, platform_value)
858+
action = remote_action.remote_action_from_args(
859+
main_args=main_args,
860+
remote_options=remote_options,
861+
)
862+
self.assertEqual(action.config, cfg)
863+
self.assertEqual(action.platform, platform_value)
864+
self.assertEqual(action.local_only_command, ["echo"])
865+
with mock.patch.object(
866+
Path,
867+
"read_text",
868+
return_value="\n".join(
869+
[
870+
"parameter_this=1",
871+
"parameter_that=do_not_care",
872+
"platform=foo=bar,baz=quux",
873+
]
874+
),
875+
) as mock_read_cfg:
876+
self.assertEqual(
877+
action.options,
878+
[
879+
"--cfg",
880+
str(cfg),
881+
"--platform=alice=bob,baz=quux,foo=zoo",
882+
],
883+
)
884+
885+
mock_read_cfg.assert_called_once_with()
886+
841887
def test_bindir(self):
842888
p = self._make_main_parser()
843889
bindir = Path("/usr/local/bin")

build/rbe/remotetool.py

Lines changed: 3 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -230,30 +230,6 @@ def parse_show_action_output(lines: Iterable[str]) -> ShowActionResult:
230230
)
231231

232232

233-
def read_config_file_lines(lines: Iterable[str]) -> Dict[str, str]:
234-
"""Parser for reading RBE config files.
235-
236-
RBE config files are text files with lines of "VAR=VALUE"
237-
(ignoring whole-line #-comments and blank lines).
238-
Spec details can be found at:
239-
https://github.com/bazelbuild/reclient/blob/main/internal/pkg/rbeflag/rbeflag.go
240-
241-
Args:
242-
lines: lines of config from a file
243-
244-
Returns:
245-
dictionary of key-value pairs read from the config file.
246-
"""
247-
result = {}
248-
for line in lines:
249-
stripped = line.strip()
250-
if stripped and not stripped.startswith("#"):
251-
key, sep, value = stripped.partition("=")
252-
if sep == "=":
253-
result[key] = value
254-
return result
255-
256-
257233
class RemoteTool(object):
258234
def __init__(self, reproxy_cfg: Path):
259235
self._reproxy_cfg = reproxy_cfg
@@ -409,7 +385,9 @@ def download_dir(
409385

410386

411387
def configure_remotetool(cfg: Path) -> RemoteTool:
412-
return RemoteTool(read_config_file_lines(cfg.read_text().splitlines()))
388+
return RemoteTool(
389+
cl_utils.read_config_file_lines(cfg.read_text().splitlines())
390+
)
413391

414392

415393
def main(argv: Sequence[str]) -> int:

build/rbe/remotetool_test.py

Lines changed: 0 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -224,37 +224,6 @@ def test_missing_section(self):
224224
remotetool.parse_show_action_output([])
225225

226226

227-
class ReadConfigFileLinesTests(unittest.TestCase):
228-
def test_empty_file(self):
229-
self.assertEqual(remotetool.read_config_file_lines([]), dict())
230-
231-
def test_ignore_blank_lines(self):
232-
self.assertEqual(
233-
remotetool.read_config_file_lines(["", "\t", "\n"]), dict()
234-
)
235-
236-
def test_ignore_comments(self):
237-
self.assertEqual(
238-
remotetool.read_config_file_lines(["####", "# comment"]), dict()
239-
)
240-
241-
def test_ignore_non_key_value_pairs(self):
242-
self.assertEqual(
243-
remotetool.read_config_file_lines(["value-only"]), dict()
244-
)
245-
246-
def test_key_value(self):
247-
self.assertEqual(
248-
remotetool.read_config_file_lines(["key=value"]), {"key": "value"}
249-
)
250-
251-
def test_last_wins(self):
252-
self.assertEqual(
253-
remotetool.read_config_file_lines(["key=value-1", "key=value-2"]),
254-
{"key": "value-2"},
255-
)
256-
257-
258227
_TEST_CFG = {
259228
"service": "other.remote.service.com:443",
260229
"instance": "projects/your-project/instance/default",

0 commit comments

Comments
 (0)