labgrid-boardkit 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- labgrid_boardkit/__init__.py +0 -0
- labgrid_boardkit/fixtures.py +77 -0
- labgrid_boardkit/helpers.py +58 -0
- labgrid_boardkit/loader.py +27 -0
- labgrid_boardkit/manifest.py +53 -0
- labgrid_boardkit/ptest/__init__.py +0 -0
- labgrid_boardkit/ptest/test_ptests.py +28 -0
- labgrid_boardkit/render.py +147 -0
- labgrid_boardkit/schema/board.schema.json +145 -0
- labgrid_boardkit/strategies/__init__.py +25 -0
- labgrid_boardkit/strategies/base.py +32 -0
- labgrid_boardkit/strategies/fastboot.py +141 -0
- labgrid_boardkit/strategies/qemu.py +86 -0
- labgrid_boardkit/strategies/sdmux.py +81 -0
- labgrid_boardkit/strategies/tftp.py +109 -0
- labgrid_boardkit/templates/client-fastboot.yaml.j2 +55 -0
- labgrid_boardkit/templates/client-qemu.yaml.j2 +44 -0
- labgrid_boardkit/templates/client-sdmux.yaml.j2 +58 -0
- labgrid_boardkit/templates/client-tftp.yaml.j2 +59 -0
- labgrid_boardkit/templates/strategy.py.j2 +19 -0
- labgrid_boardkit/tests/__init__.py +0 -0
- labgrid_boardkit/tests/test_fastboot.py +23 -0
- labgrid_boardkit/tests/test_sd.py +28 -0
- labgrid_boardkit/tests/test_tftp.py +35 -0
- labgrid_boardkit/validate.py +72 -0
- labgrid_boardkit-0.1.0.dist-info/METADATA +108 -0
- labgrid_boardkit-0.1.0.dist-info/RECORD +30 -0
- labgrid_boardkit-0.1.0.dist-info/WHEEL +4 -0
- labgrid_boardkit-0.1.0.dist-info/entry_points.txt +13 -0
- labgrid_boardkit-0.1.0.dist-info/licenses/LICENSE +21 -0
|
File without changes
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"""pytest fixtures + hooks shared across all board test suites.
|
|
2
|
+
|
|
3
|
+
Auto-loaded via the ``pytest11`` entry point in
|
|
4
|
+
``pyproject.toml``. No per-board conftest.py needed for these.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
import traceback
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
import pytest
|
|
12
|
+
|
|
13
|
+
from labgrid_boardkit.strategies.fastboot import FastbootFlashStrategy
|
|
14
|
+
from labgrid_boardkit.strategies.sdmux import SdMuxBootStrategy
|
|
15
|
+
from labgrid_boardkit.strategies.tftp import TftpBootStrategy
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def pytest_configure(config):
|
|
19
|
+
"""Auto-set ``--lg-env``.
|
|
20
|
+
|
|
21
|
+
Resolution order:
|
|
22
|
+
1. Skip if user already passed ``--lg-env``.
|
|
23
|
+
2. ``BOARD`` env var → ``$BOARD/client.yaml`` (or ``env.yaml``) rel to cwd.
|
|
24
|
+
3. Walk upward from each CLI arg looking for ``client.yaml`` / ``env.yaml``.
|
|
25
|
+
"""
|
|
26
|
+
if config.getoption("--lg-env", default=None):
|
|
27
|
+
return
|
|
28
|
+
|
|
29
|
+
board = os.environ.get("BOARD")
|
|
30
|
+
if board:
|
|
31
|
+
for candidate in (Path(board) / "client.yaml", Path(board) / "env.yaml"):
|
|
32
|
+
if candidate.is_file():
|
|
33
|
+
config.option.lg_env = str(candidate.resolve())
|
|
34
|
+
return
|
|
35
|
+
|
|
36
|
+
for arg in config.args:
|
|
37
|
+
for p in Path(arg).resolve().parents:
|
|
38
|
+
for candidate in (p / "client.yaml", p / "env.yaml"):
|
|
39
|
+
if candidate.is_file():
|
|
40
|
+
config.option.lg_env = str(candidate)
|
|
41
|
+
return
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _transition(strategy, target, state: str):
|
|
45
|
+
try:
|
|
46
|
+
strategy.transition(state)
|
|
47
|
+
except Exception as e:
|
|
48
|
+
traceback.print_exc()
|
|
49
|
+
pytest.exit(f"Transition into {state} boot shell failed: {e}", returncode=3)
|
|
50
|
+
return target.get_driver("ShellDriver")
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@pytest.fixture(scope="module")
|
|
54
|
+
def tftp(strategy, target):
|
|
55
|
+
if not isinstance(strategy, TftpBootStrategy):
|
|
56
|
+
pytest.skip("board does not use TFTP boot strategy")
|
|
57
|
+
return _transition(strategy, target, "tftp")
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@pytest.fixture(scope="module")
|
|
61
|
+
def sd(strategy, target):
|
|
62
|
+
if not isinstance(strategy, SdMuxBootStrategy):
|
|
63
|
+
pytest.skip("board does not use SD-mux boot strategy")
|
|
64
|
+
return _transition(strategy, target, "sd")
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@pytest.fixture(scope="module")
|
|
68
|
+
def fastboot(strategy, target):
|
|
69
|
+
if not isinstance(strategy, FastbootFlashStrategy):
|
|
70
|
+
pytest.skip("board does not use fastboot flash strategy")
|
|
71
|
+
return _transition(strategy, target, "shell")
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@pytest.fixture(scope="module")
|
|
75
|
+
def shell(strategy, target):
|
|
76
|
+
"""Generic shell fixture: transitions to the ``shell`` state (qemu)."""
|
|
77
|
+
return _transition(strategy, target, "shell")
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# Helper functions for the labgrid-boardkit test suites and strategies
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
|
|
5
|
+
default_bootargs = (
|
|
6
|
+
"console=tty1 "
|
|
7
|
+
"console=ttyS0,115200 "
|
|
8
|
+
"consoleblank=0 "
|
|
9
|
+
"earlycon=sbi "
|
|
10
|
+
"fsck.fix=yes "
|
|
11
|
+
"fsck.repair=yes "
|
|
12
|
+
"loglevel=7 "
|
|
13
|
+
"net.ifnames=0 "
|
|
14
|
+
"no_console_suspend "
|
|
15
|
+
"plymouth.ignore-serial-consoles "
|
|
16
|
+
"rootwait "
|
|
17
|
+
"rw "
|
|
18
|
+
"splash "
|
|
19
|
+
"systemd.journald.storage=volatile "
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
default_serverip = "192.168.40.166"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def kernel_version_in(version: str, uname: str) -> bool:
|
|
26
|
+
"""True if the kernel recipe's version (as CI takes it from the deployed
|
|
27
|
+
kernel's file name, e.g. "7.4-rc1") is the one ``uname`` reports. The
|
|
28
|
+
kernel always has a sublevel, so an rc reports "7.4.0-rc1"."""
|
|
29
|
+
return version in uname or re.sub(r"^(\d+\.\d+)(?=-|$)", r"\1.0", version) in uname
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def uboot_stage(strategy):
|
|
33
|
+
if strategy.staged:
|
|
34
|
+
return
|
|
35
|
+
strategy.target.activate(strategy.tftp)
|
|
36
|
+
|
|
37
|
+
for name, image in strategy.target.env.config.get_images().items():
|
|
38
|
+
if name.startswith("tftp-"):
|
|
39
|
+
strategy.tftp.stage(image)
|
|
40
|
+
|
|
41
|
+
strategy.target.deactivate(strategy.tftp)
|
|
42
|
+
strategy.staged = True
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def uboot_set_server_ip(strategy, serverip=default_serverip):
|
|
46
|
+
strategy.uboot.run("setenv autoload no")
|
|
47
|
+
# run_check: a failed dhcp/tftpboot should fail here, not as a confusing
|
|
48
|
+
# boot timeout later.
|
|
49
|
+
strategy.uboot.run_check("dhcp", timeout=10)
|
|
50
|
+
strategy.uboot.run(f"setenv serverip {serverip}")
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def uboot_set_bootargs(strategy, bootargs=default_bootargs):
|
|
54
|
+
strategy.uboot.run(f"setenv bootargs {bootargs}")
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def uboot_tftpboot_file(strategy, loadaddr, board_name, file_name):
|
|
58
|
+
strategy.uboot.run_check(f"tftpboot {loadaddr} {board_name}/{file_name}")
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""Registry lookup for strategy families.
|
|
2
|
+
|
|
3
|
+
Strategies are registered as ``labgrid_boardkit.strategies`` entry points in
|
|
4
|
+
``pyproject.toml``. Third-party packages can add families the same way.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from importlib.metadata import entry_points
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class UnknownStrategy(KeyError):
|
|
11
|
+
pass
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def available_strategies() -> dict[str, str]:
|
|
15
|
+
"""Return {family_name: dotted-path} for every registered strategy."""
|
|
16
|
+
return {ep.name: ep.value for ep in entry_points(group="labgrid_boardkit.strategies")}
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def strategy_for(manifest: dict):
|
|
20
|
+
"""Look up a strategy class by ``manifest['strategy']``."""
|
|
21
|
+
name = manifest.get("strategy")
|
|
22
|
+
if not name:
|
|
23
|
+
raise UnknownStrategy("board.yaml is missing top-level 'strategy: <family>' key")
|
|
24
|
+
eps = {ep.name: ep for ep in entry_points(group="labgrid_boardkit.strategies")}
|
|
25
|
+
if name not in eps:
|
|
26
|
+
raise UnknownStrategy(f"strategy {name!r} not registered (available: {sorted(eps)})")
|
|
27
|
+
return eps[name].load()
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"""Load and merge board.yaml manifests.
|
|
2
|
+
|
|
3
|
+
``inherits:`` in a board.yaml lists paths (relative to the board dir) to
|
|
4
|
+
defaults files. Files are deep-merged in order; the board.yaml wins.
|
|
5
|
+
|
|
6
|
+
``variants:`` maps a name to overrides describing another way to boot the same
|
|
7
|
+
board, usually for a different Yocto MACHINE (e.g. a mainline-kernel BSP that
|
|
8
|
+
TFTP-boots while the default flashes storage). A variant is deep-merged over
|
|
9
|
+
the base manifest.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from copy import deepcopy
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
|
|
15
|
+
import yaml
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _read_yaml(path: Path) -> dict:
|
|
19
|
+
with path.open() as f:
|
|
20
|
+
return yaml.safe_load(f) or {}
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _deep_merge(base: dict, over: dict) -> dict:
|
|
24
|
+
"""Recursively merge ``over`` into ``base``. Lists and scalars are replaced."""
|
|
25
|
+
out = deepcopy(base)
|
|
26
|
+
for k, v in over.items():
|
|
27
|
+
if k in out and isinstance(out[k], dict) and isinstance(v, dict):
|
|
28
|
+
out[k] = _deep_merge(out[k], v)
|
|
29
|
+
else:
|
|
30
|
+
out[k] = deepcopy(v)
|
|
31
|
+
return out
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def load_board(board_dir: str | Path, variant: str | None = None) -> dict:
|
|
35
|
+
"""Load ``board.yaml`` from a board directory, applying ``inherits:`` defaults
|
|
36
|
+
and, if given, the named ``variants:`` entry. The result has no ``variants``."""
|
|
37
|
+
board_dir = Path(board_dir)
|
|
38
|
+
board_yaml = _read_yaml(board_dir / "board.yaml")
|
|
39
|
+
|
|
40
|
+
merged: dict = {}
|
|
41
|
+
for rel in board_yaml.get("inherits", []):
|
|
42
|
+
merged = _deep_merge(merged, _read_yaml((board_dir / rel).resolve()))
|
|
43
|
+
merged = _deep_merge(merged, board_yaml)
|
|
44
|
+
merged.pop("inherits", None)
|
|
45
|
+
variants = merged.pop("variants", {})
|
|
46
|
+
if variant is not None:
|
|
47
|
+
merged = _deep_merge(merged, variants[variant])
|
|
48
|
+
return merged
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def board_variants(board_dir: str | Path) -> list[str]:
|
|
52
|
+
"""Names of the ``variants:`` declared in a board's ``board.yaml``."""
|
|
53
|
+
return list(_read_yaml(Path(board_dir) / "board.yaml").get("variants", {}))
|
|
File without changes
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""ptest-runner suite for any qemu board, e.g.
|
|
2
|
+
|
|
3
|
+
uv run pytest --lg-env boards/qemux86-64/env.yaml --pyargs labgrid_boardkit.ptest
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import os
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class TestPtests:
|
|
10
|
+
def test_system_booted(self, shell):
|
|
11
|
+
stdout, stderr, rc = shell.run("echo hello")
|
|
12
|
+
assert rc == 0
|
|
13
|
+
assert "hello" in stdout
|
|
14
|
+
|
|
15
|
+
def test_kernel_version(self, shell):
|
|
16
|
+
stdout, stderr, rc = shell.run("uname -r")
|
|
17
|
+
assert rc == 0
|
|
18
|
+
assert len(stdout) > 0
|
|
19
|
+
print(f"Kernel version: {stdout[0]}")
|
|
20
|
+
|
|
21
|
+
def test_ptests(self, shell):
|
|
22
|
+
timeout = int(os.environ.get("PTEST_TIMEOUT", 1200))
|
|
23
|
+
cmd = "ptest-runner"
|
|
24
|
+
# ptest-runner kills a ptest silent for this long (its default: 300s)
|
|
25
|
+
if runner_timeout := os.environ.get("PTEST_RUNNER_TIMEOUT"):
|
|
26
|
+
cmd += f" -t {int(runner_timeout)}"
|
|
27
|
+
stdout, stderr, rc = shell.run(cmd, timeout=timeout)
|
|
28
|
+
assert rc == 0
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
"""Render labgrid client.yaml (or env.yaml for qemu) from a board manifest.
|
|
2
|
+
|
|
3
|
+
Each ``variants:`` entry renders to ``client-<variant>.yaml`` alongside. All
|
|
4
|
+
boards' Yocto MACHINEs are also indexed in ``boards/machines.tsv``, a flat
|
|
5
|
+
table the CI shell steps can read without a YAML parser.
|
|
6
|
+
|
|
7
|
+
CLI:
|
|
8
|
+
boardkit-render # renders client.yaml for every boards/*/board.yaml under the repo root (cwd)
|
|
9
|
+
boardkit-render <board_dir> [...] # renders only the given boards (the index always covers all of them)
|
|
10
|
+
boardkit-render --check # non-destructive; exit 1 if any rendered output differs
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import argparse
|
|
14
|
+
import sys
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
|
|
17
|
+
from jinja2 import Environment, PackageLoader, StrictUndefined
|
|
18
|
+
|
|
19
|
+
from labgrid_boardkit.manifest import board_variants, load_board
|
|
20
|
+
|
|
21
|
+
_STRATEGY_TO_TEMPLATE = {
|
|
22
|
+
"tftp": ("client-tftp.yaml.j2", "client.yaml"),
|
|
23
|
+
"sdmux": ("client-sdmux.yaml.j2", "client.yaml"),
|
|
24
|
+
"fastboot": ("client-fastboot.yaml.j2", "client.yaml"),
|
|
25
|
+
"qemu": ("client-qemu.yaml.j2", "env.yaml"),
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
INDEX_NAME = "machines.tsv"
|
|
29
|
+
INDEX_COLUMNS = (
|
|
30
|
+
"machine",
|
|
31
|
+
"board",
|
|
32
|
+
"variant",
|
|
33
|
+
"strategy",
|
|
34
|
+
"remote_place",
|
|
35
|
+
"tftp_subdir",
|
|
36
|
+
"initramfs",
|
|
37
|
+
"nightly",
|
|
38
|
+
"client",
|
|
39
|
+
"image_dir",
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _env() -> Environment:
|
|
44
|
+
return Environment(
|
|
45
|
+
loader=PackageLoader("labgrid_boardkit", "templates"),
|
|
46
|
+
undefined=StrictUndefined,
|
|
47
|
+
keep_trailing_newline=True,
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _template(board_dir: Path, manifest: dict, variant: str | None) -> tuple[str, str]:
|
|
52
|
+
"""Return ``(template_name, output_file_name)`` for a manifest."""
|
|
53
|
+
strategy = manifest.get("strategy")
|
|
54
|
+
if strategy not in _STRATEGY_TO_TEMPLATE:
|
|
55
|
+
raise ValueError(f"{board_dir}: unknown strategy {strategy!r}")
|
|
56
|
+
template_name, out_name = _STRATEGY_TO_TEMPLATE[strategy]
|
|
57
|
+
if variant is not None:
|
|
58
|
+
out_name = out_name.replace(".yaml", f"-{variant}.yaml")
|
|
59
|
+
return template_name, out_name
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def render(board_dir: str | Path, variant: str | None = None) -> tuple[Path, str]:
|
|
63
|
+
"""Return ``(output_path, rendered_yaml_text)`` for a board or one of its variants."""
|
|
64
|
+
board_dir = Path(board_dir)
|
|
65
|
+
manifest = load_board(board_dir, variant)
|
|
66
|
+
template_name, out_name = _template(board_dir, manifest, variant)
|
|
67
|
+
tmpl = _env().get_template(template_name)
|
|
68
|
+
return board_dir / out_name, tmpl.render(manifest=manifest)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def render_index(board_dirs: list[Path]) -> str:
|
|
72
|
+
"""Return the tab-separated MACHINE index: one row per board and variant."""
|
|
73
|
+
lines = ["\t".join(INDEX_COLUMNS)]
|
|
74
|
+
for board_dir in board_dirs:
|
|
75
|
+
manifests = [(v, load_board(board_dir, v)) for v in [None, *board_variants(board_dir)]]
|
|
76
|
+
# CI boots a machine's first row unless asked for a boot_method, so a
|
|
77
|
+
# variant marked `default: true` goes ahead of the board's other rows.
|
|
78
|
+
defaults = [m.get("yocto_machine") for v, m in manifests if v and m.get("default")]
|
|
79
|
+
if len(defaults) != len(set(defaults)):
|
|
80
|
+
raise ValueError(f"{board_dir}: more than one default variant for a yocto_machine")
|
|
81
|
+
manifests.sort(key=lambda vm: not (vm[0] and vm[1].get("default")))
|
|
82
|
+
for variant, m in manifests:
|
|
83
|
+
tftp = m.get("tftp", {})
|
|
84
|
+
row = {
|
|
85
|
+
"machine": m.get("yocto_machine"),
|
|
86
|
+
"board": board_dir.name,
|
|
87
|
+
"variant": variant,
|
|
88
|
+
"strategy": m.get("strategy"),
|
|
89
|
+
"remote_place": m.get("remote_place"),
|
|
90
|
+
"tftp_subdir": tftp.get("subdir", board_dir.name),
|
|
91
|
+
# "fit": kernel, DTB and initramfs come bundled in one FIT image.
|
|
92
|
+
"initramfs": (
|
|
93
|
+
"fit"
|
|
94
|
+
if tftp.get("kernel") == "fitImage"
|
|
95
|
+
else "yes"
|
|
96
|
+
if "initramfs" in tftp
|
|
97
|
+
else "no"
|
|
98
|
+
),
|
|
99
|
+
"nightly": "no" if m.get("exclude_from_nightly") else "yes",
|
|
100
|
+
"client": _template(board_dir, m, variant)[1],
|
|
101
|
+
"image_dir": m["fastboot"]["image_dir"] if m["strategy"] == "fastboot" else None,
|
|
102
|
+
}
|
|
103
|
+
lines.append("\t".join(str(row[c] or "-") for c in INDEX_COLUMNS))
|
|
104
|
+
return "\n".join(lines) + "\n"
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def _find_boards(root: Path) -> list[Path]:
|
|
108
|
+
return [p.parent for p in sorted(root.glob("*/board.yaml"))]
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def main(argv: list[str] | None = None) -> int:
|
|
112
|
+
parser = argparse.ArgumentParser(description="Render labgrid client.yaml from board.yaml")
|
|
113
|
+
parser.add_argument("board_dirs", nargs="*", type=Path)
|
|
114
|
+
parser.add_argument(
|
|
115
|
+
"--check",
|
|
116
|
+
action="store_true",
|
|
117
|
+
help="Exit non-zero if any rendered output differs from the on-disk file.",
|
|
118
|
+
)
|
|
119
|
+
args = parser.parse_args(argv)
|
|
120
|
+
|
|
121
|
+
root = args.board_dirs[0].parent if args.board_dirs else Path.cwd() / "boards"
|
|
122
|
+
all_boards = _find_boards(root)
|
|
123
|
+
board_dirs = args.board_dirs or all_boards
|
|
124
|
+
if not board_dirs:
|
|
125
|
+
print("no boards found", file=sys.stderr)
|
|
126
|
+
return 2
|
|
127
|
+
|
|
128
|
+
outputs = [render(bd, v) for bd in board_dirs for v in [None, *board_variants(bd)]]
|
|
129
|
+
outputs.append((root / INDEX_NAME, render_index(all_boards)))
|
|
130
|
+
|
|
131
|
+
diffs = 0
|
|
132
|
+
for out_path, rendered in outputs:
|
|
133
|
+
if args.check:
|
|
134
|
+
existing = out_path.read_text() if out_path.exists() else ""
|
|
135
|
+
if existing != rendered:
|
|
136
|
+
diffs += 1
|
|
137
|
+
print(f"DIFF {out_path}")
|
|
138
|
+
else:
|
|
139
|
+
print(f"ok {out_path}")
|
|
140
|
+
else:
|
|
141
|
+
out_path.write_text(rendered)
|
|
142
|
+
print(f"wrote {out_path}")
|
|
143
|
+
return 1 if (args.check and diffs) else 0
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
if __name__ == "__main__":
|
|
147
|
+
sys.exit(main())
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://github.com/threexc/labgrid-boardkit/board.schema.json",
|
|
4
|
+
"title": "labgrid-boardkit board manifest",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"required": ["name", "strategy", "arch"],
|
|
7
|
+
"additionalProperties": true,
|
|
8
|
+
"properties": {
|
|
9
|
+
"name": { "type": "string", "pattern": "^[a-z0-9][a-z0-9-]*$" },
|
|
10
|
+
"arch": { "type": "string" },
|
|
11
|
+
"description": { "type": "string" },
|
|
12
|
+
"strategy": { "enum": ["tftp", "sdmux", "fastboot", "qemu"] },
|
|
13
|
+
"remote_place": { "type": "string" },
|
|
14
|
+
"image_source": { "type": "string" },
|
|
15
|
+
"yocto_machine": { "type": "string" },
|
|
16
|
+
"exclude_from_nightly": { "type": "boolean" },
|
|
17
|
+
"default": {
|
|
18
|
+
"type": "boolean",
|
|
19
|
+
"description": "Set in a variant: CI boots this variant, not the base board, for its yocto_machine unless a workflow's boot_method says otherwise."
|
|
20
|
+
},
|
|
21
|
+
"inherits": {
|
|
22
|
+
"type": "array",
|
|
23
|
+
"items": { "type": "string" },
|
|
24
|
+
"description": "Paths to defaults.yaml files, merged in order (later overrides earlier). board.yaml wins."
|
|
25
|
+
},
|
|
26
|
+
"boot": {
|
|
27
|
+
"type": "object",
|
|
28
|
+
"properties": {
|
|
29
|
+
"method": { "type": "string" },
|
|
30
|
+
"bootargs": { "type": "string" },
|
|
31
|
+
"kernel_addr": { "type": "string" },
|
|
32
|
+
"dtb_addr": { "type": "string" },
|
|
33
|
+
"ramdisk_addr": { "type": "string" }
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
"tftp": {
|
|
37
|
+
"type": "object",
|
|
38
|
+
"properties": {
|
|
39
|
+
"subdir": { "type": "string" },
|
|
40
|
+
"kernel": { "type": "string" },
|
|
41
|
+
"dtb": { "type": "string" },
|
|
42
|
+
"initramfs": { "type": "string" }
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
"uboot": { "type": "object" },
|
|
46
|
+
"shell": {
|
|
47
|
+
"type": "object",
|
|
48
|
+
"properties": {
|
|
49
|
+
"username": { "type": "string" },
|
|
50
|
+
"password": { "type": ["string", "null"] },
|
|
51
|
+
"prompt": { "type": "string" },
|
|
52
|
+
"login_prompt": { "type": "string" }
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
"image": {
|
|
56
|
+
"type": "object",
|
|
57
|
+
"properties": {
|
|
58
|
+
"label": { "type": "string" },
|
|
59
|
+
"path": { "type": "string" }
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
"tools": { "type": "object" },
|
|
63
|
+
"buttons": {
|
|
64
|
+
"type": "array",
|
|
65
|
+
"items": { "type": "string" },
|
|
66
|
+
"description": "Relay-driven buttons, each exported as <name>-<button>-gpio-1"
|
|
67
|
+
},
|
|
68
|
+
"fastboot": {
|
|
69
|
+
"type": "object",
|
|
70
|
+
"properties": {
|
|
71
|
+
"image_dir": { "type": "string" },
|
|
72
|
+
"recovery_button": { "type": "string" },
|
|
73
|
+
"reset_button": { "type": "string" },
|
|
74
|
+
"stage": { "type": "array", "items": { "type": "string" } },
|
|
75
|
+
"flash": {
|
|
76
|
+
"type": "object",
|
|
77
|
+
"additionalProperties": { "type": ["string", "null"] },
|
|
78
|
+
"description": "Partition name to image file, in flash order. null skips a partition (a variant dropping one of the base board's)."
|
|
79
|
+
},
|
|
80
|
+
"press_time": { "type": "number" },
|
|
81
|
+
"power_on_delay": { "type": "number" },
|
|
82
|
+
"continue_delay": { "type": "number" },
|
|
83
|
+
"stage_retries": { "type": "integer" }
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
"qemu": {
|
|
87
|
+
"type": "object",
|
|
88
|
+
"properties": {
|
|
89
|
+
"binary": { "type": "string" },
|
|
90
|
+
"machine": { "type": "string" },
|
|
91
|
+
"cpu": { "type": "string" },
|
|
92
|
+
"memory": { "type": ["integer", "string"] },
|
|
93
|
+
"virtio_bus": { "enum": ["mmio", "pci"] },
|
|
94
|
+
"accel": { "type": "array", "items": { "type": "string" } },
|
|
95
|
+
"ssh_port": { "type": "integer" },
|
|
96
|
+
"telnet_port": { "type": "integer" }
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
"images": { "type": "object" },
|
|
100
|
+
"strategy_class": { "type": "string" }
|
|
101
|
+
},
|
|
102
|
+
"allOf": [
|
|
103
|
+
{
|
|
104
|
+
"if": { "properties": { "strategy": { "const": "tftp" } }, "required": ["strategy"] },
|
|
105
|
+
"then": {
|
|
106
|
+
"required": ["boot", "tftp"],
|
|
107
|
+
"properties": {
|
|
108
|
+
"boot": {
|
|
109
|
+
"required": ["bootargs", "kernel_addr"]
|
|
110
|
+
},
|
|
111
|
+
"tftp": {
|
|
112
|
+
"required": ["subdir", "kernel"]
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
"if": { "properties": { "strategy": { "const": "sdmux" } }, "required": ["strategy"] },
|
|
119
|
+
"then": {
|
|
120
|
+
"required": ["image"],
|
|
121
|
+
"properties": {
|
|
122
|
+
"image": { "required": ["label", "path"] }
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
"if": { "properties": { "strategy": { "const": "fastboot" } }, "required": ["strategy"] },
|
|
128
|
+
"then": {
|
|
129
|
+
"required": ["fastboot", "buttons"],
|
|
130
|
+
"properties": {
|
|
131
|
+
"fastboot": { "required": ["image_dir", "recovery_button", "reset_button", "flash"] }
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
"if": { "properties": { "strategy": { "const": "qemu" } }, "required": ["strategy"] },
|
|
137
|
+
"then": {
|
|
138
|
+
"required": ["qemu", "images"],
|
|
139
|
+
"properties": {
|
|
140
|
+
"qemu": { "required": ["binary", "machine"] }
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
]
|
|
145
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""labgrid-boardkit strategy families.
|
|
2
|
+
|
|
3
|
+
Import the family classes from here or (preferred at scale) resolve them by
|
|
4
|
+
name via ``labgrid_boardkit.loader.strategy_for(manifest)``, which consults
|
|
5
|
+
the ``labgrid_boardkit.strategies`` entry-point registry declared in ``pyproject.toml``.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from labgrid_boardkit.strategies.base import BoardStrategy
|
|
9
|
+
from labgrid_boardkit.strategies.fastboot import FastbootFlashStrategy, FastbootStatus
|
|
10
|
+
from labgrid_boardkit.strategies.qemu import ExternalQemuStrategy, QemuBootStrategy, QemuStatus
|
|
11
|
+
from labgrid_boardkit.strategies.sdmux import SdMuxBootStrategy, SdMuxStatus
|
|
12
|
+
from labgrid_boardkit.strategies.tftp import TftpBootStrategy, TftpStatus
|
|
13
|
+
|
|
14
|
+
__all__ = [
|
|
15
|
+
"BoardStrategy",
|
|
16
|
+
"ExternalQemuStrategy",
|
|
17
|
+
"FastbootFlashStrategy",
|
|
18
|
+
"FastbootStatus",
|
|
19
|
+
"QemuBootStrategy",
|
|
20
|
+
"QemuStatus",
|
|
21
|
+
"SdMuxBootStrategy",
|
|
22
|
+
"SdMuxStatus",
|
|
23
|
+
"TftpBootStrategy",
|
|
24
|
+
"TftpStatus",
|
|
25
|
+
]
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
import attr
|
|
4
|
+
from labgrid.strategy.common import Strategy
|
|
5
|
+
|
|
6
|
+
from labgrid_boardkit.manifest import load_board
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@attr.s(eq=False)
|
|
10
|
+
class BoardStrategy(Strategy):
|
|
11
|
+
"""Base for all labgrid-boardkit strategies.
|
|
12
|
+
|
|
13
|
+
Two ways to attach a manifest to a strategy instance:
|
|
14
|
+
|
|
15
|
+
1. **Per-board subclass** (default): the concrete subclass sets ``manifest``
|
|
16
|
+
as a class attribute, typically ``manifest = load_board(Path(__file__).parent)``.
|
|
17
|
+
|
|
18
|
+
2. **``manifest_path`` kwarg** (zero-Python boards): pass ``manifest_path``
|
|
19
|
+
from labgrid ``client.yaml``. The base ``__attrs_post_init__`` loads it.
|
|
20
|
+
Path is resolved absolute or relative to cwd.
|
|
21
|
+
|
|
22
|
+
Family subclasses (tftp, sdmux, ...) must call ``super().__attrs_post_init__()``.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
manifest_path = attr.ib(default=None)
|
|
26
|
+
|
|
27
|
+
manifest: dict = {}
|
|
28
|
+
|
|
29
|
+
def __attrs_post_init__(self):
|
|
30
|
+
super().__attrs_post_init__()
|
|
31
|
+
if self.manifest_path is not None:
|
|
32
|
+
self.manifest = load_board(Path(self.manifest_path))
|