From f52e709d634058006107934a7888a8e7488ec54d Mon Sep 17 00:00:00 2001 From: Jordan Bonser Date: Wed, 6 Nov 2024 12:18:18 +0000 Subject: [PATCH 1/2] refactor(run): group tools into their own dataclass --- labgrid/run/run.py | 33 ++++++++------------------------- 1 file changed, 8 insertions(+), 25 deletions(-) diff --git a/labgrid/run/run.py b/labgrid/run/run.py index c75f47ce..05787a5e 100644 --- a/labgrid/run/run.py +++ b/labgrid/run/run.py @@ -134,24 +134,6 @@ def arguments(prsr: ArgumentParser) -> None: action=StateAction, default=environ.get("BZL_LG_FINAL_STATE", None), ) - prsr.add_argument( - "--mktemp", - help="A `mktemp` binary for the target.", - type=runfile, - default=runfile("ape/ape/assimilate/mktemp.ape/mktemp"), - ) - prsr.add_argument( - "--rm", - help="A `rm` binary for the target.", - type=runfile, - default=runfile("ape/ape/assimilate/rm.ape/rm"), - ) - prsr.add_argument( - "--mkdir", - help="A `mkdir` binary for the target.", - type=runfile, - default=runfile("ape/ape/assimilate/mkdir.ape/mkdir"), - ) prsr.add_argument( "--get", help="Transfer files from target at end of execution. Relative paths are resolved to the execution root on both the local and remote. `LOCAL` can be omitted, which will use the same path for remote and local.", @@ -186,6 +168,12 @@ class Get: return base / path return path +@dataclass +class Tools: + mktemp: Path = runfile("ape/ape/assimilate/mktemp.ape/mktemp") + rm: Path = runfile("ape/ape/assimilate/rm.ape/rm") + mkdir: Path = runfile("ape/ape/assimilate/mkdir.ape/mkdir") + env: Path = runfile("ape/ape/assimilate/env.ape/env") # TODO: move this out to `bazel.labgrid.run` on day to allow downstream to re-use def run( @@ -193,10 +181,8 @@ def run( *arguments: str, config: Path, state: State, - mktemp: Path, - mkdir: Path, - rm: Path, get: Iterator[Get], + tools: Tools = Tools() ) -> int: # Allow any Bazel binaries used in the configuration to work try: @@ -211,7 +197,7 @@ def run( # Retrieve the communication protocols shell = target.get_driver("CommandProtocol") transfer = target.get_driver("FileTransferProtocol") - with TemporaryDirectory(shell, transfer, mktemp, rm, mkdir) as temp: + with TemporaryDirectory(shell, transfer, tools.mktemp, tools.rm, tools.mkdir) as temp: # Transfer the provided program over to the device remote = temp / program.name transfer.put(f"{program}", f"{remote}") @@ -255,9 +241,6 @@ def main(exe: Path, *args: str) -> int: *parsed.arguments, state=parsed.state, config=parsed.config, - mktemp=parsed.mktemp, - rm=parsed.rm, - mkdir=parsed.mkdir, get=parsed.get, ) except CalledProcessError as e: -- GitLab From 9e79728824a297110e22900ada698828eaddb0b4 Mon Sep 17 00:00:00 2001 From: Jordan Bonser Date: Wed, 6 Nov 2024 12:24:16 +0000 Subject: [PATCH 2/2] feat(run): add env variable passing --- e2e/docker/BUILD.bazel | 24 +++++++++++++++++ e2e/docker/world.log | 1 + labgrid/run/BUILD.bazel | 6 +++++ labgrid/run/run.py | 59 ++++++++++++++++++++++++++++++----------- 4 files changed, 75 insertions(+), 15 deletions(-) create mode 100644 e2e/docker/world.log diff --git a/e2e/docker/BUILD.bazel b/e2e/docker/BUILD.bazel index 5a6dbe3c..4c1c2cd0 100644 --- a/e2e/docker/BUILD.bazel +++ b/e2e/docker/BUILD.bazel @@ -90,3 +90,27 @@ labgrid_genrule( ) for textfile in ("output.txt", "stdout.log") ] + +labgrid_genrule( + name = "hello_world", + srcs = ["@ape//ape:printenv"], + cmd = "$(location @rules_labgrid//labgrid/run) --env HELLO=world -- $(location @ape//ape:printenv) HELLO > $@", + outs = ["hello_world.log"], + platform = ":platform", + tools = ["@rules_labgrid//labgrid/run"], + tags = [ + "manual", + "requires-docker", + ], +) + +diff_file_test( + name = "hello_world_test", + size = "small", + a = ":world.log", + b = ":hello_world.log", + tags = [ + "manual", + "requires-docker", + ], +) diff --git a/e2e/docker/world.log b/e2e/docker/world.log new file mode 100644 index 00000000..cc628ccd --- /dev/null +++ b/e2e/docker/world.log @@ -0,0 +1 @@ +world diff --git a/labgrid/run/BUILD.bazel b/labgrid/run/BUILD.bazel index 3a26240d..d65f0a73 100644 --- a/labgrid/run/BUILD.bazel +++ b/labgrid/run/BUILD.bazel @@ -16,6 +16,11 @@ labgrid_config_transition( srcs = ["@ape//ape:rm"], ) +labgrid_config_transition( + name = "env", + srcs = ["@ape//ape:env"], +) + py_binary( name = "run", srcs = ["run.py"], @@ -23,6 +28,7 @@ py_binary( ":mkdir", ":mktemp", ":rm", + ":env", ], tags = ["manual"], visibility = ["//visibility:public"], diff --git a/labgrid/run/run.py b/labgrid/run/run.py index 05787a5e..52e306ae 100644 --- a/labgrid/run/run.py +++ b/labgrid/run/run.py @@ -1,9 +1,10 @@ #! /usr/bin/env python3 from __future__ import annotations +from typing import Any -from argparse import REMAINDER, Action, ArgumentParser, Namespace +from argparse import REMAINDER, Action, ArgumentParser, ArgumentTypeError, Namespace from dataclasses import dataclass, replace -from os import environ, linesep +from os import environ, linesep, getenv from pathlib import Path, PurePath from shlex import join from subprocess import CalledProcessError @@ -40,6 +41,12 @@ def runfile(path: str) -> Path: raise RunfileNotFoundError(f"runfile not found: {path}") return resolved +@dataclass +class Tools: + mktemp: Path = runfile("ape/ape/assimilate/mktemp.ape/mktemp") + rm: Path = runfile("ape/ape/assimilate/rm.ape/rm") + mkdir: Path = runfile("ape/ape/assimilate/mkdir.ape/mkdir") + env: Path = runfile("ape/ape/assimilate/env.ape/env") @dataclass(frozen=True) class State: @@ -99,6 +106,17 @@ class StateAction(Action): state = replace(state, **{self.attr: values}) setattr(namespace, self.dest, state) +def env(arg: str) -> str: + key, sep, value = arg.partition("=") + if not sep: + value = getenv(key) + + if value is None: + raise ArgumentTypeError(f"Env var {key} does not exist in host environment") + else: + return f"{key}={value}" + + return arg def arguments(prsr: ArgumentParser) -> None: prsr.add_argument( @@ -134,6 +152,17 @@ def arguments(prsr: ArgumentParser) -> None: action=StateAction, default=environ.get("BZL_LG_FINAL_STATE", None), ) + prsr.add_argument( + "--env", + help=("The environment variables to be passed to the target for execution of the program. " + "Suports VAR=VAL syntax or uses the host environment variable if a single key is" + "specified."), + action="append", + required=False, + default=[], + metavar="KEY[=VALUE]", + type=env, + ) prsr.add_argument( "--get", help="Transfer files from target at end of execution. Relative paths are resolved to the execution root on both the local and remote. `LOCAL` can be omitted, which will use the same path for remote and local.", @@ -168,13 +197,6 @@ class Get: return base / path return path -@dataclass -class Tools: - mktemp: Path = runfile("ape/ape/assimilate/mktemp.ape/mktemp") - rm: Path = runfile("ape/ape/assimilate/rm.ape/rm") - mkdir: Path = runfile("ape/ape/assimilate/mkdir.ape/mkdir") - env: Path = runfile("ape/ape/assimilate/env.ape/env") - # TODO: move this out to `bazel.labgrid.run` on day to allow downstream to re-use def run( program: Path, @@ -182,6 +204,7 @@ def run( config: Path, state: State, get: Iterator[Get], + env: Iterator[str], tools: Tools = Tools() ) -> int: # Allow any Bazel binaries used in the configuration to work @@ -190,8 +213,7 @@ def run( except KeyError: pass # Start up Docker container with LabGrid - env = Environment(str(config)) - target = env.get_target() + target = Environment(str(config)).get_target() strategy = target.get_driver("Strategy") with transition(strategy, state.initial, state.desired, state.final): # Retrieve the communication protocols @@ -202,17 +224,23 @@ def run( remote = temp / program.name transfer.put(f"{program}", f"{remote}") - # Transfer runfiles + # Transfer program runfiles # TODO: implement a `BazelFileTransfer` that transfers runfiles implicitly in `put` # Make that the default file transfer implementation so _any_ file transfer automatically gets the runfiles as well src_runfiles = program.with_suffix(".runfiles") dest_runfiles = remote.with_suffix(".runfiles") if src_runfiles.exists(): transfer.put(f"{src_runfiles}", f"{dest_runfiles}") + + # Transfer 'env' binary over to the device + transfer.put(f"{tools.env}", f"{temp / tools.env.name}") + + # Construct the CLI using env, the env vars and then the program and args. + cmd = join((f"{remote}", *arguments)) + cmd = f"cd {temp} && {temp / tools.env.name} {' '.join(env)} {cmd}" + # Run the transferred program - out, err, code = shell.run( - f"cd {temp} && " + join((f"{remote}", *arguments)) - ) + out, err, code = shell.run(cmd) for line in out: stdout.write(f"{line}{linesep}") for line in err: @@ -242,6 +270,7 @@ def main(exe: Path, *args: str) -> int: state=parsed.state, config=parsed.config, get=parsed.get, + env=parsed.env, ) except CalledProcessError as e: print(f"fatal: subprocess failed: {e}", file=stderr) -- GitLab