hector-cli 0.3.0__tar.gz → 0.5.0__tar.gz

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.
Files changed (55) hide show
  1. {hector_cli-0.3.0/hector_cli.egg-info → hector_cli-0.5.0}/PKG-INFO +53 -7
  2. {hector_cli-0.3.0 → hector_cli-0.5.0}/README.md +52 -6
  3. {hector_cli-0.3.0 → hector_cli-0.5.0}/hector/commands/__init__.py +1 -1
  4. {hector_cli-0.3.0 → hector_cli-0.5.0}/hector/commands/base.py +6 -0
  5. hector_cli-0.5.0/hector/commands/clean.py +34 -0
  6. {hector_cli-0.3.0 → hector_cli-0.5.0}/hector/commands/run.py +2 -2
  7. {hector_cli-0.3.0 → hector_cli-0.5.0}/hector/core.py +1 -1
  8. {hector_cli-0.3.0 → hector_cli-0.5.0}/hector/docker.py +20 -17
  9. {hector_cli-0.3.0 → hector_cli-0.5.0}/hector/pipeline.py +112 -16
  10. {hector_cli-0.3.0 → hector_cli-0.5.0}/hector/scaffold.py +1 -1
  11. {hector_cli-0.3.0 → hector_cli-0.5.0}/hector/validator.py +20 -81
  12. {hector_cli-0.3.0 → hector_cli-0.5.0/hector_cli.egg-info}/PKG-INFO +53 -7
  13. {hector_cli-0.3.0 → hector_cli-0.5.0}/hector_cli.egg-info/SOURCES.txt +22 -1
  14. hector_cli-0.5.0/tests/test_arguments.py +32 -0
  15. hector_cli-0.5.0/tests/test_clean.py +103 -0
  16. hector_cli-0.5.0/tests/test_cli_smoke.py +74 -0
  17. hector_cli-0.5.0/tests/test_connections.py +124 -0
  18. hector_cli-0.5.0/tests/test_core_paths.py +98 -0
  19. hector_cli-0.5.0/tests/test_core_registry.py +75 -0
  20. hector_cli-0.5.0/tests/test_core_text.py +78 -0
  21. hector_cli-0.5.0/tests/test_docker_argv.py +118 -0
  22. hector_cli-0.5.0/tests/test_generator.py +196 -0
  23. hector_cli-0.5.0/tests/test_hubs.py +53 -0
  24. hector_cli-0.5.0/tests/test_mappings.py +102 -0
  25. hector_cli-0.5.0/tests/test_matrix.py +30 -0
  26. hector_cli-0.5.0/tests/test_needs_renode_source.py +38 -0
  27. hector_cli-0.5.0/tests/test_peripherals.py +81 -0
  28. hector_cli-0.5.0/tests/test_pipeline_helpers.py +149 -0
  29. hector_cli-0.5.0/tests/test_reporters.py +70 -0
  30. hector_cli-0.5.0/tests/test_runners.py +83 -0
  31. hector_cli-0.5.0/tests/test_scaffold.py +53 -0
  32. hector_cli-0.5.0/tests/test_validator.py +141 -0
  33. hector_cli-0.5.0/tests/test_version.py +39 -0
  34. {hector_cli-0.3.0 → hector_cli-0.5.0}/LICENSE +0 -0
  35. {hector_cli-0.3.0 → hector_cli-0.5.0}/hector/__init__.py +0 -0
  36. {hector_cli-0.3.0 → hector_cli-0.5.0}/hector/__main__.py +0 -0
  37. {hector_cli-0.3.0 → hector_cli-0.5.0}/hector/commands/export.py +0 -0
  38. {hector_cli-0.3.0 → hector_cli-0.5.0}/hector/commands/init.py +0 -0
  39. {hector_cli-0.3.0 → hector_cli-0.5.0}/hector/commands/test.py +0 -0
  40. {hector_cli-0.3.0 → hector_cli-0.5.0}/hector/commands/validate.py +0 -0
  41. {hector_cli-0.3.0 → hector_cli-0.5.0}/hector/connections.py +0 -0
  42. {hector_cli-0.3.0 → hector_cli-0.5.0}/hector/dependencies.py +0 -0
  43. {hector_cli-0.3.0 → hector_cli-0.5.0}/hector/generator.py +0 -0
  44. {hector_cli-0.3.0 → hector_cli-0.5.0}/hector/hubs.py +0 -0
  45. {hector_cli-0.3.0 → hector_cli-0.5.0}/hector/mappings.py +0 -0
  46. {hector_cli-0.3.0 → hector_cli-0.5.0}/hector/modules.py +0 -0
  47. {hector_cli-0.3.0 → hector_cli-0.5.0}/hector/peripherals.py +0 -0
  48. {hector_cli-0.3.0 → hector_cli-0.5.0}/hector/reporters.py +0 -0
  49. {hector_cli-0.3.0 → hector_cli-0.5.0}/hector/runners.py +0 -0
  50. {hector_cli-0.3.0 → hector_cli-0.5.0}/hector_cli.egg-info/dependency_links.txt +0 -0
  51. {hector_cli-0.3.0 → hector_cli-0.5.0}/hector_cli.egg-info/entry_points.txt +0 -0
  52. {hector_cli-0.3.0 → hector_cli-0.5.0}/hector_cli.egg-info/requires.txt +0 -0
  53. {hector_cli-0.3.0 → hector_cli-0.5.0}/hector_cli.egg-info/top_level.txt +0 -0
  54. {hector_cli-0.3.0 → hector_cli-0.5.0}/setup.cfg +0 -0
  55. {hector_cli-0.3.0 → hector_cli-0.5.0}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hector-cli
3
- Version: 0.3.0
3
+ Version: 0.5.0
4
4
  Summary: YAML-driven Renode + Verilator simulation orchestrator for embedded hardware CI
5
5
  Home-page: https://hector-ci.com
6
6
  Author: Nemesis
@@ -205,6 +205,7 @@ the CLI natively with `pip` and let hector launch real per-step containers.
205
205
  | `hector run --gather-execution-metrics boardA` | Enable profiler for one specific machine |
206
206
  | `hector run --gather-execution-metrics boardA --gather-execution-metrics boardB` | Enable profiler for selected machines |
207
207
  | `hector run --renode-version 1.16.1` | Override the Renode version from `.hector.yaml` |
208
+ | `hector run --renode-image my-renode:dev` | Use a specific (e.g. self-built) Renode Docker image |
208
209
  | `hector run --renode-dir /cache/renode` | Use/cache the Renode source checkout at this path (only fetched when needed) |
209
210
  | `hector run --renode-integration-dir /cache/integration` | Likewise for the verilator-integration checkout |
210
211
  | `hector run --no-docker` | Use locally installed `renode` / `renode-test` instead of Docker |
@@ -229,11 +230,15 @@ the CLI natively with `pip` and let hector launch real per-step containers.
229
230
  | `hector validate -f board.yaml` | Validate a config file by another name |
230
231
  | `hector init` | Scaffold a starter `.hector.yaml` |
231
232
  | `hector init -f board.yaml` | Scaffold the starter config under a custom name |
233
+ | `hector clean` | Remove generated build artifacts (`.hector/resc,repl,tests`) and `results/`; keeps the Renode cache |
234
+ | `hector clean --all` | Also remove the cached Renode source checkouts under `.hector/` (forces a re-clone next build) |
235
+ | `hector clean --output my-results` | Clean a custom results directory instead of `results/` |
236
+ | `hector clean --dry-run` | Show what would be removed without deleting anything |
232
237
  | `hector --version` | Print the tool version |
233
238
 
234
239
  `hector` is structured as subcommands: `run` (simulate), `test` (run the `tests:`
235
- section), `export` (emit the command without running it), plus `init` and
236
- `validate`. Run `hector <command> --help` for the flags each one accepts.
240
+ section), `export` (emit the command without running it), plus `init`, `validate`,
241
+ and `clean`. Run `hector <command> --help` for the flags each one accepts.
237
242
 
238
243
  Every command reads (or, for `init`, writes) `.hector.yaml` by default. Point it
239
244
  at a different file with **`-f` / `--file`** — useful when one project keeps
@@ -338,7 +343,7 @@ hector run --snapshot results/step_1_Linux_boots/snapshot.save
338
343
  hector test --snapshot results/step_1_Linux_boots/snapshot.save
339
344
  ```
340
345
 
341
- When used with `--renode-version`, no `.hector.yaml` is needed at all — useful for inspecting a snapshot outside of any project:
346
+ When used with `--renode-version` (or `--renode-image`), no `.hector.yaml` is needed at all — useful for inspecting a snapshot outside of any project:
342
347
 
343
348
  ```bash
344
349
  hector run --snapshot /path/to/snapshot.save --renode-version 1.16.1
@@ -477,6 +482,10 @@ hector run --renode-args '--console'
477
482
  hector run --renode-args '--hide-log --config my.conf'
478
483
  ```
479
484
 
485
+ #### Container networking
486
+
487
+ Every container hector starts — the Renode emulation (`run`), robot tests, `shell` tests, `build:` steps and module builds — runs with `--net=host`. GDB servers (`--debug`), `tcp:` mappings and anything a step listens on are reachable from the host on the same port, and steps can reach host services on `localhost`. No port publishing is involved.
488
+
480
489
  #### Running without Docker (`--no-docker`)
481
490
 
482
491
  Call the locally installed `renode` and `renode-test` binaries directly instead of launching a Docker container. `build:` steps and `shell` tests also run on the host (their `image:` is ignored, with a warning), as do module builds.
@@ -503,6 +512,20 @@ When combined with `--snapshot` in simulation mode, `.hector.yaml` is not requir
503
512
  hector run --snapshot path/to/snapshot.save --renode-version 1.16.1
504
513
  ```
505
514
 
515
+ #### Using a custom Renode image (`--renode-image`)
516
+
517
+ By default the image is `antmicro/renode:<renode_version>`. To use any other image — a nightly, a mirror in your registry, or one you built yourself — set `renode_image:` in `.hector.yaml` or pass `--renode-image`:
518
+
519
+ ```yaml
520
+ renode_image: my-renode:dev
521
+ ```
522
+
523
+ ```bash
524
+ hector test --renode-image ghcr.io/acme/renode:1.16.1-patched
525
+ ```
526
+
527
+ Precedence: CLI flags beat the config, and either flag replaces *both* config fields (so `--renode-version 1.15.0` wins over a `renode_image:` in the file). If both fields are set in the config, `renode_image` is the image that runs and `renode_version` only picks the Renode source tag to clone. That tag is otherwise inferred from the image tag when it looks like a version (`…:1.16.1` / `…:v1.16.1`); if the config needs the source (see below) and it can't be inferred, also set `renode_version`.
528
+
506
529
  #### Renode source checkout
507
530
 
508
531
  Running a simulation does **not** need the Renode source — the binary and its bundled platforms come from the `antmicro/renode` Docker image. Hector clones the Renode source (and the verilator-integration repo) into `.hector/` **only when a config actually needs it**:
@@ -600,7 +623,8 @@ The configuration lives in `.hector.yaml`.
600
623
  | Field | Required | Description |
601
624
  |---|---|---|
602
625
  | `version` | No | Schema version (currently `"0.1"`). Warns if unrecognised. |
603
- | `renode_version` | Yes | Renode version, no leading `v` (e.g. `1.16.1`). Selects both the git tag for cloning and the Docker image tag. |
626
+ | `renode_version` | Yes¹ | Renode version, no leading `v` (e.g. `1.16.1`). Selects both the git tag for cloning and the Docker image tag. |
627
+ | `renode_image` | Yes¹ | Full Renode Docker image to use instead of `antmicro/renode:<renode_version>` (e.g. a self-built `my-renode:dev`). See [custom Renode image](#using-a-custom-renode-image---renode-image). |
604
628
  | `arguments` | No | Scalar defaults, overridable by env var or `--set`. |
605
629
  | `build` | No | Pre-sim shell steps run once per job in containers (compile firmware, fetch/generate files, …). Interpolated with the job's arguments/matrix variables. |
606
630
  | `matrix` | No | Cross-product job expansion. |
@@ -613,6 +637,8 @@ The configuration lives in `.hector.yaml`.
613
637
  | `quantum` | No | Override the global time quantum (seconds). |
614
638
  | `ci` | No | CI pipeline definitions: when and how a CI server runs this config. |
615
639
 
640
+ ¹ At least one of `renode_version` / `renode_image` is required (or the matching CLI flag).
641
+
616
642
  ---
617
643
 
618
644
  ### arguments
@@ -938,8 +964,26 @@ machines:
938
964
 
939
965
  - **`repl:`** is written to `.hector/repl/job_<n>_<node>.inline.repl` and loaded with
940
966
  `machine LoadPlatformDescription` (Renode only reads platform descriptions from
941
- disk). It is loaded **after** any `platform:` files, so it can extend or override
942
- them — list a released board under `platform:` and patch it with `repl:`.
967
+ disk). It is loaded **after** any `platform:` files.
968
+
969
+ > **Patching a bundled platform: use `using`, not `platform:`.** Every
970
+ > `LoadPlatformDescription` is its **own description scope**, so an entry that another
971
+ > file created is only partly reachable from `repl:` — setting a *constructor*
972
+ > attribute on it fails with
973
+ > `E35: Constructor attribute within entry for variable that is not created`.
974
+ > To override one, pull the base platform into the *same* scope from inside `repl:`:
975
+ >
976
+ > ```yaml
977
+ > repl: |
978
+ > using "platforms/boards/nucleo_h753zi.repl"
979
+ >
980
+ > nvic:
981
+ > priorityMask: 0xFF # works: same scope that creates `nvic`
982
+ > ```
983
+ >
984
+ > This is what Renode's own scripts do with `LoadPlatformDescriptionFromString`.
985
+ > Use `platform:` alone when you're loading a board as-is, and `using` inside `repl:`
986
+ > when you need to patch it. See [`examples/nucleo-h753zi`](examples/nucleo-h753zi).
943
987
  - **`resc:`** is emitted verbatim into that machine's block, **after** the platform
944
988
  and firmware load and **before** `commands:`.
945
989
 
@@ -966,6 +1010,8 @@ other field. Worked examples:
966
1010
  - [`examples/nucleo-f767zi`](examples/nucleo-f767zi) — the real motivation: a
967
1011
  **NUCLEO-F767ZI**, which Renode 1.16.1 ships no `.repl` for at all, defined inline
968
1012
  and running against the pinned release image.
1013
+ - [`examples/nucleo-h753zi`](examples/nucleo-h753zi) — the other side: a board that
1014
+ **is** shipped, loaded via `using` and *patched* inline, booting Hubris.
969
1015
 
970
1016
  #### peripherals
971
1017
 
@@ -175,6 +175,7 @@ the CLI natively with `pip` and let hector launch real per-step containers.
175
175
  | `hector run --gather-execution-metrics boardA` | Enable profiler for one specific machine |
176
176
  | `hector run --gather-execution-metrics boardA --gather-execution-metrics boardB` | Enable profiler for selected machines |
177
177
  | `hector run --renode-version 1.16.1` | Override the Renode version from `.hector.yaml` |
178
+ | `hector run --renode-image my-renode:dev` | Use a specific (e.g. self-built) Renode Docker image |
178
179
  | `hector run --renode-dir /cache/renode` | Use/cache the Renode source checkout at this path (only fetched when needed) |
179
180
  | `hector run --renode-integration-dir /cache/integration` | Likewise for the verilator-integration checkout |
180
181
  | `hector run --no-docker` | Use locally installed `renode` / `renode-test` instead of Docker |
@@ -199,11 +200,15 @@ the CLI natively with `pip` and let hector launch real per-step containers.
199
200
  | `hector validate -f board.yaml` | Validate a config file by another name |
200
201
  | `hector init` | Scaffold a starter `.hector.yaml` |
201
202
  | `hector init -f board.yaml` | Scaffold the starter config under a custom name |
203
+ | `hector clean` | Remove generated build artifacts (`.hector/resc,repl,tests`) and `results/`; keeps the Renode cache |
204
+ | `hector clean --all` | Also remove the cached Renode source checkouts under `.hector/` (forces a re-clone next build) |
205
+ | `hector clean --output my-results` | Clean a custom results directory instead of `results/` |
206
+ | `hector clean --dry-run` | Show what would be removed without deleting anything |
202
207
  | `hector --version` | Print the tool version |
203
208
 
204
209
  `hector` is structured as subcommands: `run` (simulate), `test` (run the `tests:`
205
- section), `export` (emit the command without running it), plus `init` and
206
- `validate`. Run `hector <command> --help` for the flags each one accepts.
210
+ section), `export` (emit the command without running it), plus `init`, `validate`,
211
+ and `clean`. Run `hector <command> --help` for the flags each one accepts.
207
212
 
208
213
  Every command reads (or, for `init`, writes) `.hector.yaml` by default. Point it
209
214
  at a different file with **`-f` / `--file`** — useful when one project keeps
@@ -308,7 +313,7 @@ hector run --snapshot results/step_1_Linux_boots/snapshot.save
308
313
  hector test --snapshot results/step_1_Linux_boots/snapshot.save
309
314
  ```
310
315
 
311
- When used with `--renode-version`, no `.hector.yaml` is needed at all — useful for inspecting a snapshot outside of any project:
316
+ When used with `--renode-version` (or `--renode-image`), no `.hector.yaml` is needed at all — useful for inspecting a snapshot outside of any project:
312
317
 
313
318
  ```bash
314
319
  hector run --snapshot /path/to/snapshot.save --renode-version 1.16.1
@@ -447,6 +452,10 @@ hector run --renode-args '--console'
447
452
  hector run --renode-args '--hide-log --config my.conf'
448
453
  ```
449
454
 
455
+ #### Container networking
456
+
457
+ Every container hector starts — the Renode emulation (`run`), robot tests, `shell` tests, `build:` steps and module builds — runs with `--net=host`. GDB servers (`--debug`), `tcp:` mappings and anything a step listens on are reachable from the host on the same port, and steps can reach host services on `localhost`. No port publishing is involved.
458
+
450
459
  #### Running without Docker (`--no-docker`)
451
460
 
452
461
  Call the locally installed `renode` and `renode-test` binaries directly instead of launching a Docker container. `build:` steps and `shell` tests also run on the host (their `image:` is ignored, with a warning), as do module builds.
@@ -473,6 +482,20 @@ When combined with `--snapshot` in simulation mode, `.hector.yaml` is not requir
473
482
  hector run --snapshot path/to/snapshot.save --renode-version 1.16.1
474
483
  ```
475
484
 
485
+ #### Using a custom Renode image (`--renode-image`)
486
+
487
+ By default the image is `antmicro/renode:<renode_version>`. To use any other image — a nightly, a mirror in your registry, or one you built yourself — set `renode_image:` in `.hector.yaml` or pass `--renode-image`:
488
+
489
+ ```yaml
490
+ renode_image: my-renode:dev
491
+ ```
492
+
493
+ ```bash
494
+ hector test --renode-image ghcr.io/acme/renode:1.16.1-patched
495
+ ```
496
+
497
+ Precedence: CLI flags beat the config, and either flag replaces *both* config fields (so `--renode-version 1.15.0` wins over a `renode_image:` in the file). If both fields are set in the config, `renode_image` is the image that runs and `renode_version` only picks the Renode source tag to clone. That tag is otherwise inferred from the image tag when it looks like a version (`…:1.16.1` / `…:v1.16.1`); if the config needs the source (see below) and it can't be inferred, also set `renode_version`.
498
+
476
499
  #### Renode source checkout
477
500
 
478
501
  Running a simulation does **not** need the Renode source — the binary and its bundled platforms come from the `antmicro/renode` Docker image. Hector clones the Renode source (and the verilator-integration repo) into `.hector/` **only when a config actually needs it**:
@@ -570,7 +593,8 @@ The configuration lives in `.hector.yaml`.
570
593
  | Field | Required | Description |
571
594
  |---|---|---|
572
595
  | `version` | No | Schema version (currently `"0.1"`). Warns if unrecognised. |
573
- | `renode_version` | Yes | Renode version, no leading `v` (e.g. `1.16.1`). Selects both the git tag for cloning and the Docker image tag. |
596
+ | `renode_version` | Yes¹ | Renode version, no leading `v` (e.g. `1.16.1`). Selects both the git tag for cloning and the Docker image tag. |
597
+ | `renode_image` | Yes¹ | Full Renode Docker image to use instead of `antmicro/renode:<renode_version>` (e.g. a self-built `my-renode:dev`). See [custom Renode image](#using-a-custom-renode-image---renode-image). |
574
598
  | `arguments` | No | Scalar defaults, overridable by env var or `--set`. |
575
599
  | `build` | No | Pre-sim shell steps run once per job in containers (compile firmware, fetch/generate files, …). Interpolated with the job's arguments/matrix variables. |
576
600
  | `matrix` | No | Cross-product job expansion. |
@@ -583,6 +607,8 @@ The configuration lives in `.hector.yaml`.
583
607
  | `quantum` | No | Override the global time quantum (seconds). |
584
608
  | `ci` | No | CI pipeline definitions: when and how a CI server runs this config. |
585
609
 
610
+ ¹ At least one of `renode_version` / `renode_image` is required (or the matching CLI flag).
611
+
586
612
  ---
587
613
 
588
614
  ### arguments
@@ -908,8 +934,26 @@ machines:
908
934
 
909
935
  - **`repl:`** is written to `.hector/repl/job_<n>_<node>.inline.repl` and loaded with
910
936
  `machine LoadPlatformDescription` (Renode only reads platform descriptions from
911
- disk). It is loaded **after** any `platform:` files, so it can extend or override
912
- them — list a released board under `platform:` and patch it with `repl:`.
937
+ disk). It is loaded **after** any `platform:` files.
938
+
939
+ > **Patching a bundled platform: use `using`, not `platform:`.** Every
940
+ > `LoadPlatformDescription` is its **own description scope**, so an entry that another
941
+ > file created is only partly reachable from `repl:` — setting a *constructor*
942
+ > attribute on it fails with
943
+ > `E35: Constructor attribute within entry for variable that is not created`.
944
+ > To override one, pull the base platform into the *same* scope from inside `repl:`:
945
+ >
946
+ > ```yaml
947
+ > repl: |
948
+ > using "platforms/boards/nucleo_h753zi.repl"
949
+ >
950
+ > nvic:
951
+ > priorityMask: 0xFF # works: same scope that creates `nvic`
952
+ > ```
953
+ >
954
+ > This is what Renode's own scripts do with `LoadPlatformDescriptionFromString`.
955
+ > Use `platform:` alone when you're loading a board as-is, and `using` inside `repl:`
956
+ > when you need to patch it. See [`examples/nucleo-h753zi`](examples/nucleo-h753zi).
913
957
  - **`resc:`** is emitted verbatim into that machine's block, **after** the platform
914
958
  and firmware load and **before** `commands:`.
915
959
 
@@ -936,6 +980,8 @@ other field. Worked examples:
936
980
  - [`examples/nucleo-f767zi`](examples/nucleo-f767zi) — the real motivation: a
937
981
  **NUCLEO-F767ZI**, which Renode 1.16.1 ships no `.repl` for at all, defined inline
938
982
  and running against the pinned release image.
983
+ - [`examples/nucleo-h753zi`](examples/nucleo-h753zi) — the other side: a board that
984
+ **is** shipped, loaded via `using` and *patched* inline, booting Hubris.
939
985
 
940
986
  #### peripherals
941
987
 
@@ -12,7 +12,7 @@ from ..core import TOOL_VERSION
12
12
  from .base import COMMANDS, Command, add_common_arguments, add_simulation_arguments
13
13
 
14
14
  # Importing each module runs its @COMMANDS.register(...) decorator.
15
- from . import run, test, export, init, validate # noqa: E402,F401
15
+ from . import run, test, export, init, validate, clean # noqa: E402,F401
16
16
 
17
17
  __all__ = ["COMMANDS", "Command", "build_parser",
18
18
  "add_common_arguments", "add_simulation_arguments"]
@@ -53,6 +53,12 @@ def add_common_arguments(parser):
53
53
  help="Renode version to use (e.g. 1.16.1). Overrides renode_version from "
54
54
  ".hector.yaml.",
55
55
  )
56
+ parser.add_argument(
57
+ "--renode-image", metavar="IMAGE", dest="renode_image",
58
+ help="Renode Docker image to use instead of antmicro/renode:<version> (any "
59
+ "image, e.g. a self-built one: my-renode:dev). Overrides renode_image / "
60
+ "renode_version from .hector.yaml.",
61
+ )
56
62
  parser.add_argument(
57
63
  "--renode-dir", metavar="PATH", dest="renode_dir", default=None,
58
64
  help="Location of the Renode source checkout (only fetched when a config builds "
@@ -0,0 +1,34 @@
1
+ # SPDX-License-Identifier: AGPL-3.0-or-later
2
+ """`hector clean` — remove the files hector generates (the .hector/ build artifacts
3
+ and the results directory), so a project can be reset to a pristine checkout."""
4
+
5
+ from .. import pipeline
6
+ from .base import COMMANDS, Command
7
+
8
+
9
+ @COMMANDS.register("clean")
10
+ class CleanCommand(Command):
11
+ name = "clean"
12
+ help = "Remove generated files (.hector/ build outputs and the results directory)."
13
+
14
+ def add_arguments(self, parser):
15
+ parser.add_argument(
16
+ "--output", metavar="DIR", default="results",
17
+ help="Results directory to remove (default: results). Match the --output "
18
+ "you ran with if you used a custom one.",
19
+ )
20
+ parser.add_argument(
21
+ "--all", action="store_true", dest="all",
22
+ help="Also remove the cached Renode / verilator-integration source "
23
+ "checkouts under .hector/ — a full re-clone (~200 MB) happens on the "
24
+ "next build. Omit to keep them for fast iteration.",
25
+ )
26
+ parser.add_argument(
27
+ "-n", "--dry-run", action="store_true", dest="dry_run",
28
+ help="List what would be removed without deleting anything.",
29
+ )
30
+
31
+ def execute(self, args):
32
+ return pipeline.run_clean(output_dir=args.output,
33
+ include_renode=args.all,
34
+ dry_run=args.dry_run)
@@ -17,8 +17,8 @@ class RunCommand(Command):
17
17
  def execute(self, args):
18
18
  pipeline.apply_global_flags(args)
19
19
 
20
- # Convenience fast path: a snapshot + pinned version needs no .hector.yaml.
21
- if args.snapshot and args.renode_version:
20
+ # Convenience fast path: a snapshot + pinned version/image needs no .hector.yaml.
21
+ if args.snapshot and (args.renode_version or args.renode_image):
22
22
  pipeline.snapshot_fastpath(args)
23
23
  return 0
24
24
 
@@ -67,7 +67,7 @@ class Registry:
67
67
  # ==========================================================================
68
68
  # CONSTANTS
69
69
  # ==========================================================================
70
- TOOL_VERSION = "0.3.0"
70
+ TOOL_VERSION = "0.5.0"
71
71
  SUPPORTED_SCHEMA_VERSIONS = {"0.1"}
72
72
 
73
73
  RENODE_REPO_URL = "https://github.com/renode/renode.git"
@@ -60,6 +60,13 @@ def _extra_mounts():
60
60
  return args
61
61
 
62
62
 
63
+ # Every container shares the host's network stack: GDB servers, tcp: mappings and
64
+ # anything a build/test step listens on are reachable from the host as-is, and steps
65
+ # can reach host services (e.g. a local registry or a TCP-mapped UART) on localhost.
66
+ # No -p publishing is needed (Docker ignores it in this mode anyway).
67
+ _HOST_NETWORK = ["--net=host"]
68
+
69
+
63
70
  def _runtime_flags(runtime):
64
71
  """Map aggregated RuntimeOptions (capabilities/devices/root, declared by mappings,
65
72
  hubs, modules) to `docker run` flags.
@@ -89,7 +96,7 @@ def run_build(script, image):
89
96
  subprocess.run(["bash", "-c", script], check=True)
90
97
  return
91
98
  subprocess.run(
92
- ["docker", "run", "--rm", "-u", "0:0",
99
+ ["docker", "run", "--rm", *_HOST_NETWORK, "-u", "0:0",
93
100
  *_workspace_mount(), *_extra_mounts(), image, "bash", "-c", script],
94
101
  check=True,
95
102
  )
@@ -128,8 +135,8 @@ def run_in_container(script, image, live=False):
128
135
  if core.NO_DOCKER:
129
136
  cmd = ["bash", "-c", f"set -ex\n{script}"]
130
137
  else:
131
- cmd = ["docker", "run", "--rm", *_workspace_mount(), *_extra_mounts(),
132
- *_user_flags(), image, "sh", "-c", f"set -ex\n{script}"]
138
+ cmd = ["docker", "run", "--rm", *_HOST_NETWORK, *_workspace_mount(),
139
+ *_extra_mounts(), *_user_flags(), image, "sh", "-c", f"set -ex\n{script}"]
133
140
  if live:
134
141
  proc = subprocess.Popen(cmd, stdout=subprocess.PIPE,
135
142
  stderr=subprocess.STDOUT, text=True)
@@ -145,7 +152,7 @@ def run_in_container(script, image, live=False):
145
152
  return proc.stdout, proc.returncode == 0
146
153
 
147
154
 
148
- def build_renode_argv(resc_filename, image, ports=None, extra_args=None, runtime=None):
155
+ def build_renode_argv(resc_filename, image, extra_args=None, runtime=None):
149
156
  """Build the exact argv that would run an emulation resc — the local `renode`
150
157
  invocation under --no-docker, otherwise the full `docker run ... renode` command.
151
158
 
@@ -154,20 +161,18 @@ def build_renode_argv(resc_filename, image, ports=None, extra_args=None, runtime
154
161
  runtime: aggregated RuntimeOptions whose docker flags (caps/devices/root) are added."""
155
162
  if core.NO_DOCKER:
156
163
  return ["renode", resc_filename] + (extra_args or [])
157
- cmd = ["docker", "run", "-it", "--rm", *_xdisplay_args(), "--net=host",
158
- *_workspace_mount(), *_extra_mounts(), *_user_flags(), *_runtime_flags(runtime)]
159
- for port in (ports or []):
160
- cmd += ["-p", f"{port}:{port}"]
161
- cmd += [image, "renode", resc_filename] + (extra_args or [])
162
- return cmd
164
+ return ["docker", "run", "-it", "--rm", *_xdisplay_args(), *_HOST_NETWORK,
165
+ *_workspace_mount(), *_extra_mounts(), *_user_flags(), *_runtime_flags(runtime),
166
+ image, "renode", resc_filename] + (extra_args or [])
163
167
 
164
168
 
165
- def run_renode(resc_filename, image, ports=None, extra_args=None, output_dir="results", runtime=None):
166
- """Run an emulation resc interactively. In debug mode, expose GDB ports.
169
+ def run_renode(resc_filename, image, extra_args=None, output_dir="results", runtime=None):
170
+ """Run an emulation resc interactively. GDB ports (--debug) are reachable from the
171
+ host through the host network.
167
172
  runtime: aggregated RuntimeOptions whose docker flags (caps/devices/root) are added."""
168
173
  if not core.NO_DOCKER:
169
174
  os.makedirs(os.path.join(os.getcwd(), output_dir), exist_ok=True)
170
- subprocess.run(build_renode_argv(resc_filename, image, ports, extra_args, runtime))
175
+ subprocess.run(build_renode_argv(resc_filename, image, extra_args, runtime))
171
176
 
172
177
 
173
178
  def run_renode_test(robot_file_host, image, results_dir_host,
@@ -177,8 +182,7 @@ def run_renode_test(robot_file_host, image, results_dir_host,
177
182
  resc_host_path: passed as --variable RESC:<path> so .robot files can load
178
183
  the generated emulation script via ${RESC}.
179
184
  extra_args: optional list of additional flags forwarded verbatim to renode-test.
180
- interactive: attach stdin/tty and add --net=host (useful with Pause Execution
181
- or when TCP ports need to be reachable from the host during the test).
185
+ interactive: attach stdin/tty and X display (useful with Pause Execution).
182
186
  """
183
187
  os.makedirs(results_dir_host, exist_ok=True)
184
188
  if core.NO_DOCKER:
@@ -190,9 +194,8 @@ def run_renode_test(robot_file_host, image, results_dir_host,
190
194
  result = subprocess.run(cmd)
191
195
  return result.returncode == 0
192
196
  flags = ["-it"] if interactive else []
193
- net = ["--net=host"] if interactive else []
194
197
  display = _xdisplay_args() if interactive else []
195
- cmd = ["docker", "run", "--rm", *flags, *net, *display,
198
+ cmd = ["docker", "run", "--rm", *flags, *_HOST_NETWORK, *display,
196
199
  *_workspace_mount(), *_extra_mounts(), *_user_flags(), *_runtime_flags(runtime),
197
200
  image, "renode-test", to_container_path(robot_file_host),
198
201
  "-r", to_container_path(results_dir_host)]
@@ -11,6 +11,7 @@ Phases per job:
11
11
 
12
12
  import copy
13
13
  import os
14
+ import re
14
15
  import shlex
15
16
  import sys
16
17
  from dataclasses import dataclass, field
@@ -106,15 +107,35 @@ def load_config_or_exit():
106
107
  return config
107
108
 
108
109
 
110
+ _VERSION_TAG_RE = re.compile(r"^v?\d+\.\d+\.\d+$")
111
+
112
+
109
113
  def resolve_renode_image(config, args):
110
- """Pin the Renode version from --renode-version or the config; return
111
- (git_tag, docker_image)."""
112
- renode_version = getattr(args, "renode_version", None) or config.get("renode_version")
113
- if not renode_version:
114
- raise ValueError("Top-level 'renode_version' is required (e.g. '1.16.1'), "
115
- "or pass --renode-version on the command line.")
116
- normalized = str(renode_version).lstrip("v")
117
- return f"v{normalized}", f"antmicro/renode:{normalized}"
114
+ """Pick the Renode Docker image and the git tag of the matching source; return
115
+ (git_tag, docker_image).
116
+
117
+ The image is either an explicit `renode_image` (any image, including a self-built
118
+ one) or `antmicro/renode:<renode_version>`. CLI flags beat the config, and a flag
119
+ of either kind replaces both config fields — so `--renode-version` still wins over
120
+ a `renode_image:` in the file. The git tag (only used to clone the Renode source)
121
+ comes from the version, or from the image tag when it looks like one; it is None
122
+ when neither tells us, and only matters if the config needs the source."""
123
+ flag_version = getattr(args, "renode_version", None)
124
+ flag_image = getattr(args, "renode_image", None)
125
+ if flag_version or flag_image:
126
+ version, image = flag_version, flag_image
127
+ else:
128
+ version, image = config.get("renode_version"), config.get("renode_image")
129
+ if not version and not image:
130
+ raise ValueError("Top-level 'renode_version' (e.g. '1.16.1') or 'renode_image' "
131
+ "is required, or pass --renode-version / --renode-image on the "
132
+ "command line.")
133
+ normalized = str(version).lstrip("v") if version else None
134
+ image = str(image) if image else f"antmicro/renode:{normalized}"
135
+ if not normalized:
136
+ tag = image.rsplit("/", 1)[-1].partition(":")[2]
137
+ normalized = tag.lstrip("v") if _VERSION_TAG_RE.match(tag) else None
138
+ return (f"v{normalized}" if normalized else None), image
118
139
 
119
140
 
120
141
  def _resolve_set_overrides(args):
@@ -149,6 +170,10 @@ def build_job_config(action, config, args, *, test_mode=False):
149
170
  # Clone Renode only when the config actually needs the source; otherwise skip it
150
171
  # entirely (a ~200 MB clone) and leave the framework vars empty.
151
172
  if _needs_renode_source(config):
173
+ if not git_tag:
174
+ raise ValueError(f"This config needs the Renode source, but the version of "
175
+ f"'{docker_image}' can't be inferred from its tag — also set "
176
+ "'renode_version' (or --renode-version) to pick the checkout.")
152
177
  renode_dir, integration_dir = prepare_dependencies(
153
178
  git_tag,
154
179
  renode_dir=getattr(args, "renode_dir", None),
@@ -210,13 +235,12 @@ def execute_jobs(cfg, args):
210
235
 
211
236
 
212
237
  def snapshot_fastpath(args):
213
- """`run --snapshot --renode-version` with no .hector.yaml: write a snapshot
214
- loader resc and boot it directly, skipping config/dependency machinery."""
238
+ """`run --snapshot --renode-version|--renode-image` with no .hector.yaml: write a
239
+ snapshot loader resc and boot it directly, skipping config/dependency machinery."""
215
240
  if not os.path.isfile(args.snapshot):
216
241
  print(f"[ERROR] Snapshot not found: {args.snapshot}", file=sys.stderr)
217
242
  sys.exit(1)
218
- normalized = args.renode_version.lstrip("v")
219
- docker_image = f"antmicro/renode:{normalized}"
243
+ _, docker_image = resolve_renode_image({}, args)
220
244
  resc_root = os.path.join(ARTIFACTS_DIR, "resc")
221
245
  os.makedirs(resc_root, exist_ok=True)
222
246
  # No .hector.yaml here, so debug machine names come straight from --debug.
@@ -227,8 +251,7 @@ def snapshot_fastpath(args):
227
251
  no_start=getattr(args, "no_start", False)))
228
252
  print(f"[SNAP] {args.snapshot}"
229
253
  + (f" (+GDB: {debug_config})" if debug_config else ""))
230
- run_renode(snap_resc, docker_image, ports=list(debug_config.values()) or None,
231
- extra_args=shlex.split(args.renode_args or ""), output_dir=args.output)
254
+ run_renode(snap_resc, docker_image, extra_args=shlex.split(args.renode_args or ""), output_dir=args.output)
232
255
 
233
256
 
234
257
  def _run_validate():
@@ -256,6 +279,81 @@ def _run_validate():
256
279
  sys.exit(0)
257
280
 
258
281
 
282
+ # ---------------------------------------------------------------------------
283
+ # clean
284
+ # ---------------------------------------------------------------------------
285
+
286
+ # The Renode source / verilator-integration checkouts hector clones on demand. They
287
+ # live under .hector/ but are expensive to re-fetch (~200 MB), so `clean` keeps them
288
+ # unless --all is given.
289
+ _RENODE_CHECKOUTS = ("renode", "renode-verilator-integration")
290
+
291
+
292
+ def clean_targets(output_dir="results", include_renode=False):
293
+ """The generated paths `hector clean` would remove, filtered to those that exist.
294
+
295
+ Always: the cheap build artifacts (.hector/resc, .hector/repl, .hector/tests) and
296
+ the results/output directory. Only with include_renode: the whole .hector/ (which
297
+ also takes the expensive Renode checkouts with it). Paths are relative to the cwd."""
298
+ if include_renode:
299
+ targets = [ARTIFACTS_DIR, output_dir]
300
+ else:
301
+ targets = [os.path.join(ARTIFACTS_DIR, d) for d in ("resc", "repl", "tests")]
302
+ targets.append(output_dir)
303
+ # De-dupe while preserving order, keep only what's actually there.
304
+ seen, existing = set(), []
305
+ for p in targets:
306
+ if p not in seen and os.path.exists(p):
307
+ seen.add(p)
308
+ existing.append(p)
309
+ return existing
310
+
311
+
312
+ def _is_within_cwd(path):
313
+ """True if `path` resolves inside the project root — the guard that stops `clean`
314
+ from ever deleting something outside the workspace (e.g. an absolute --output)."""
315
+ root = os.path.realpath(os.getcwd())
316
+ real = os.path.realpath(path)
317
+ return real == root or real.startswith(root + os.sep)
318
+
319
+
320
+ def run_clean(output_dir="results", include_renode=False, dry_run=False):
321
+ """Remove hector's generated artifacts. Returns a process exit code."""
322
+ import shutil
323
+
324
+ targets = clean_targets(output_dir, include_renode)
325
+ if not targets:
326
+ print("[CLEAN] Nothing to remove — the workspace is already clean.")
327
+ return 0
328
+
329
+ removed = 0
330
+ for path in targets:
331
+ if not _is_within_cwd(path):
332
+ print(f"[CLEAN] SKIP {path} — outside the project directory; refusing to remove it.")
333
+ continue
334
+ kind = "dir " if os.path.isdir(path) else "file"
335
+ if dry_run:
336
+ print(f"[CLEAN] would remove {kind} {path}")
337
+ continue
338
+ if os.path.isdir(path):
339
+ shutil.rmtree(path, ignore_errors=True)
340
+ else:
341
+ os.remove(path)
342
+ print(f"[CLEAN] removed {kind} {path}")
343
+ removed += 1
344
+
345
+ if dry_run:
346
+ print("[CLEAN] dry run — nothing was deleted.")
347
+ elif not include_renode and os.path.isdir(ARTIFACTS_DIR):
348
+ # We kept a Renode checkout; tell the user how to drop it too.
349
+ kept = [d for d in _RENODE_CHECKOUTS
350
+ if os.path.isdir(os.path.join(ARTIFACTS_DIR, d))]
351
+ if kept:
352
+ print(f"[CLEAN] kept cached Renode checkout(s) in {ARTIFACTS_DIR}/ "
353
+ f"({', '.join(kept)}) — use 'hector clean --all' to remove them too.")
354
+ return 0
355
+
356
+
259
357
  # ---------------------------------------------------------------------------
260
358
  # Helpers
261
359
  # ---------------------------------------------------------------------------
@@ -425,7 +523,6 @@ def action_simulate(state, cfg, result):
425
523
  if not state.resc_filename:
426
524
  raise ValueError("`hector run` needs at least one machine in 'machines:'.")
427
525
  run_renode(_resolve_active_resc(state, cfg), cfg.docker_image,
428
- ports=list(state.debug_config.values()) or None,
429
526
  extra_args=cfg.renode_args, output_dir=cfg.output_dir,
430
527
  runtime=state.runtime)
431
528
 
@@ -437,7 +534,6 @@ def action_export(state, cfg, result):
437
534
  if not state.resc_filename:
438
535
  raise ValueError("`hector export` needs at least one machine in 'machines:'.")
439
536
  argv = build_renode_argv(_resolve_active_resc(state, cfg), cfg.docker_image,
440
- ports=list(state.debug_config.values()) or None,
441
537
  extra_args=cfg.renode_args, runtime=state.runtime)
442
538
  line = shlex.join(argv)
443
539
  print(line)
@@ -46,7 +46,7 @@ def scaffold_init(target=".hector.yaml"):
46
46
  name .hector.yaml; override via -f/--file)."""
47
47
  if os.path.exists(target):
48
48
  print(f"[INIT] {target} already exists — not overwriting.")
49
- print(f"[INIT] Delete or rename it, then run 'hector init' again.")
49
+ print("[INIT] Delete or rename it, then run 'hector init' again.")
50
50
  sys.exit(1)
51
51
 
52
52
  print("Hector project initialisation")