diff --git a/e2e/docker/BUILD.bazel b/e2e/docker/BUILD.bazel index 21e9f3b9dd2a6f3709995c31b3dc925ced713a33..5a6dbe3cdec54ed59ca6565f4139252b2fceb24f 100644 --- a/e2e/docker/BUILD.bazel +++ b/e2e/docker/BUILD.bazel @@ -1,8 +1,9 @@ load("@rules_diff//diff/file/test:defs.bzl", "diff_file_test") load("@rules_labgrid//labgrid/config/toolchain:defs.bzl", "labgrid_config_toolchain") +load("@rules_labgrid//labgrid/genrule:defs.bzl", "labgrid_genrule") load("@rules_labgrid//labgrid/run:defs.bzl", "labgrid_run") -load("@rules_python//python:defs.bzl", "py_binary") load("@rules_labgrid//labgrid/state:defs.bzl", "labgrid_state") +load("@rules_python//python:defs.bzl", "py_binary") # A constraint to register the toolchain to constraint_setting(name = "device") @@ -66,14 +67,26 @@ labgrid_run( ], ) -# Check that the output of `/proc/version` from within the container is what we expect -diff_file_test( - name = "test", - size = "small", - a = ":version.txt", - b = ":stdout.log", - tags = [ - "manual", - "requires-docker", - ], +labgrid_genrule( + name = "copy", + srcs = ["@ape//ape:cp"], + outs = ["output.txt"], + cmd = "$(location @rules_labgrid//labgrid/run) --get output.txt:$(location output.txt) $(location @ape//ape:cp) /etc/os-release output.txt", + platform = ":platform", + tools = ["@rules_labgrid//labgrid/run"], ) + +[ + # Check that the output of `/proc/version` from within the container is what we expect + diff_file_test( + name = "test-{textfile}".format(textfile = textfile), + size = "small", + a = ":version.txt", + b = textfile, + tags = [ + "manual", + "requires-docker", + ], + ) + for textfile in ("output.txt", "stdout.log") +] diff --git a/labgrid/executor/executor.py b/labgrid/executor/executor.py index 49d5e0754c12bb450ce7d6f2ee57a0c7130a7258..409a1702817b26118773d40b1207121e65563e45 100644 --- a/labgrid/executor/executor.py +++ b/labgrid/executor/executor.py @@ -77,7 +77,6 @@ def main(exe: Path, *args: str) -> int: args = (f"@{baked}", *args) parsed = prsr.parse_args(args) - cmd = (parsed.program, *parsed.arguments) # A workaround for invoking APE binaries on Apple Silicon diff --git a/labgrid/run/run.py b/labgrid/run/run.py index 93162a9d8754f20bbc7cb9946c4e4741b767b869..71584b7667b7e108df410db09a773113bba72cb2 100644 --- a/labgrid/run/run.py +++ b/labgrid/run/run.py @@ -2,7 +2,8 @@ from __future__ import annotations import argparse -from argparse import ArgumentParser +import os +from argparse import REMAINDER, Action, ArgumentParser from contextlib import contextmanager from dataclasses import dataclass from os import environ, linesep @@ -10,6 +11,7 @@ from pathlib import Path, PurePath from shlex import join from subprocess import CalledProcessError from sys import argv, stderr, stdout +from typing import Collection, Iterator, Protocol, Tuple from python.runfiles import Runfiles @@ -35,7 +37,7 @@ def arguments(prsr: ArgumentParser) -> None: "program", metavar="PROG", help="The program to run on the device.", type=Path ) prsr.add_argument( - "arguments", metavar="ARG", nargs="*", help="Command to run over SSH." + "arguments", metavar="ARG", nargs=REMAINDER, help="Command to run over SSH." ) prsr.add_argument( "--config", @@ -79,6 +81,39 @@ def arguments(prsr: ArgumentParser) -> None: 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.", + type=get, + metavar="REMOTE[:LOCAL]", + action="append", + default=[], + ) + + +def get(value: str) -> Get: + remote, found, local = value.partition(":") + if found: + return Get(PurePath(remote), PurePath(local)) + return Get(PurePath(remote), PurePath(remote)) + + +@dataclass(frozen=True) +class Get: + remote: PurePath + local: PurePath + + def resolve(self, remote: PurePath) -> Get: + return Get( + remote=self.join(remote, self.remote), + local=self.join(environ.get("BAZEL_GEN_DIR", Path.cwd()), self.local), + ) + + @staticmethod + def join(base: PurePath, path: PurePath) -> PurePath: + if not path.is_absolute(): + return base / path + return path @dataclass @@ -102,13 +137,13 @@ def run( mktemp: Path, mkdir: Path, rm: Path, + get: Iterator[Get], ) -> int: # Allow any Bazel binaries used in the configuration to work try: del environ["RUNFILES_DIR"] except KeyError: pass - # Start up Docker container with LabGrid env = Environment(str(config)) target = env.get_target() @@ -130,11 +165,20 @@ def run( if src_runfiles.exists(): transfer.put(f"{src_runfiles}", f"{dest_runfiles}") # Run the transferred program - out, err, code = shell.run(join((f"{remote}", *arguments))) + out, err, code = shell.run( + f"cd {temp} && " + join((f"{remote}", *arguments)) + ) for line in out: stdout.write(f"{line}{linesep}") for line in err: stderr.write(f"{line}{linesep}") + + if code == 0: # copy the files back from the target + + for unresolved in get: + resolved = unresolved.resolve(temp) + transfer.get(resolved.remote, resolved.local) + return code @@ -145,7 +189,6 @@ def main(exe: Path, *args: str) -> int: prsr.set_defaults(state=State()) arguments(prsr) - parsed = prsr.parse_args(args) try: @@ -157,6 +200,7 @@ def main(exe: Path, *args: str) -> int: mktemp=parsed.mktemp, rm=parsed.rm, mkdir=parsed.mkdir, + get=parsed.get, ) except CalledProcessError as e: print(f"fatal: subprocess failed: {e}", file=stderr)