hector-cli 0.3.0__tar.gz → 0.4.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.
- {hector_cli-0.3.0/hector_cli.egg-info → hector_cli-0.4.0}/PKG-INFO +49 -7
- {hector_cli-0.3.0 → hector_cli-0.4.0}/README.md +48 -6
- {hector_cli-0.3.0 → hector_cli-0.4.0}/hector/commands/__init__.py +1 -1
- {hector_cli-0.3.0 → hector_cli-0.4.0}/hector/commands/base.py +6 -0
- hector_cli-0.4.0/hector/commands/clean.py +34 -0
- {hector_cli-0.3.0 → hector_cli-0.4.0}/hector/commands/run.py +2 -2
- {hector_cli-0.3.0 → hector_cli-0.4.0}/hector/core.py +1 -1
- {hector_cli-0.3.0 → hector_cli-0.4.0}/hector/pipeline.py +111 -12
- {hector_cli-0.3.0 → hector_cli-0.4.0}/hector/scaffold.py +1 -1
- {hector_cli-0.3.0 → hector_cli-0.4.0}/hector/validator.py +20 -81
- {hector_cli-0.3.0 → hector_cli-0.4.0/hector_cli.egg-info}/PKG-INFO +49 -7
- {hector_cli-0.3.0 → hector_cli-0.4.0}/hector_cli.egg-info/SOURCES.txt +22 -1
- hector_cli-0.4.0/tests/test_arguments.py +32 -0
- hector_cli-0.4.0/tests/test_clean.py +103 -0
- hector_cli-0.4.0/tests/test_cli_smoke.py +74 -0
- hector_cli-0.4.0/tests/test_connections.py +124 -0
- hector_cli-0.4.0/tests/test_core_paths.py +98 -0
- hector_cli-0.4.0/tests/test_core_registry.py +75 -0
- hector_cli-0.4.0/tests/test_core_text.py +78 -0
- hector_cli-0.4.0/tests/test_docker_argv.py +101 -0
- hector_cli-0.4.0/tests/test_generator.py +196 -0
- hector_cli-0.4.0/tests/test_hubs.py +53 -0
- hector_cli-0.4.0/tests/test_mappings.py +102 -0
- hector_cli-0.4.0/tests/test_matrix.py +30 -0
- hector_cli-0.4.0/tests/test_needs_renode_source.py +38 -0
- hector_cli-0.4.0/tests/test_peripherals.py +81 -0
- hector_cli-0.4.0/tests/test_pipeline_helpers.py +149 -0
- hector_cli-0.4.0/tests/test_reporters.py +70 -0
- hector_cli-0.4.0/tests/test_runners.py +83 -0
- hector_cli-0.4.0/tests/test_scaffold.py +53 -0
- hector_cli-0.4.0/tests/test_validator.py +141 -0
- hector_cli-0.4.0/tests/test_version.py +39 -0
- {hector_cli-0.3.0 → hector_cli-0.4.0}/LICENSE +0 -0
- {hector_cli-0.3.0 → hector_cli-0.4.0}/hector/__init__.py +0 -0
- {hector_cli-0.3.0 → hector_cli-0.4.0}/hector/__main__.py +0 -0
- {hector_cli-0.3.0 → hector_cli-0.4.0}/hector/commands/export.py +0 -0
- {hector_cli-0.3.0 → hector_cli-0.4.0}/hector/commands/init.py +0 -0
- {hector_cli-0.3.0 → hector_cli-0.4.0}/hector/commands/test.py +0 -0
- {hector_cli-0.3.0 → hector_cli-0.4.0}/hector/commands/validate.py +0 -0
- {hector_cli-0.3.0 → hector_cli-0.4.0}/hector/connections.py +0 -0
- {hector_cli-0.3.0 → hector_cli-0.4.0}/hector/dependencies.py +0 -0
- {hector_cli-0.3.0 → hector_cli-0.4.0}/hector/docker.py +0 -0
- {hector_cli-0.3.0 → hector_cli-0.4.0}/hector/generator.py +0 -0
- {hector_cli-0.3.0 → hector_cli-0.4.0}/hector/hubs.py +0 -0
- {hector_cli-0.3.0 → hector_cli-0.4.0}/hector/mappings.py +0 -0
- {hector_cli-0.3.0 → hector_cli-0.4.0}/hector/modules.py +0 -0
- {hector_cli-0.3.0 → hector_cli-0.4.0}/hector/peripherals.py +0 -0
- {hector_cli-0.3.0 → hector_cli-0.4.0}/hector/reporters.py +0 -0
- {hector_cli-0.3.0 → hector_cli-0.4.0}/hector/runners.py +0 -0
- {hector_cli-0.3.0 → hector_cli-0.4.0}/hector_cli.egg-info/dependency_links.txt +0 -0
- {hector_cli-0.3.0 → hector_cli-0.4.0}/hector_cli.egg-info/entry_points.txt +0 -0
- {hector_cli-0.3.0 → hector_cli-0.4.0}/hector_cli.egg-info/requires.txt +0 -0
- {hector_cli-0.3.0 → hector_cli-0.4.0}/hector_cli.egg-info/top_level.txt +0 -0
- {hector_cli-0.3.0 → hector_cli-0.4.0}/setup.cfg +0 -0
- {hector_cli-0.3.0 → hector_cli-0.4.0}/setup.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: hector-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.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`
|
|
236
|
-
`
|
|
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
|
|
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
|
|
@@ -503,6 +508,20 @@ When combined with `--snapshot` in simulation mode, `.hector.yaml` is not requir
|
|
|
503
508
|
hector run --snapshot path/to/snapshot.save --renode-version 1.16.1
|
|
504
509
|
```
|
|
505
510
|
|
|
511
|
+
#### Using a custom Renode image (`--renode-image`)
|
|
512
|
+
|
|
513
|
+
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`:
|
|
514
|
+
|
|
515
|
+
```yaml
|
|
516
|
+
renode_image: my-renode:dev
|
|
517
|
+
```
|
|
518
|
+
|
|
519
|
+
```bash
|
|
520
|
+
hector test --renode-image ghcr.io/acme/renode:1.16.1-patched
|
|
521
|
+
```
|
|
522
|
+
|
|
523
|
+
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`.
|
|
524
|
+
|
|
506
525
|
#### Renode source checkout
|
|
507
526
|
|
|
508
527
|
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 +619,8 @@ The configuration lives in `.hector.yaml`.
|
|
|
600
619
|
| Field | Required | Description |
|
|
601
620
|
|---|---|---|
|
|
602
621
|
| `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. |
|
|
622
|
+
| `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. |
|
|
623
|
+
| `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
624
|
| `arguments` | No | Scalar defaults, overridable by env var or `--set`. |
|
|
605
625
|
| `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
626
|
| `matrix` | No | Cross-product job expansion. |
|
|
@@ -613,6 +633,8 @@ The configuration lives in `.hector.yaml`.
|
|
|
613
633
|
| `quantum` | No | Override the global time quantum (seconds). |
|
|
614
634
|
| `ci` | No | CI pipeline definitions: when and how a CI server runs this config. |
|
|
615
635
|
|
|
636
|
+
¹ At least one of `renode_version` / `renode_image` is required (or the matching CLI flag).
|
|
637
|
+
|
|
616
638
|
---
|
|
617
639
|
|
|
618
640
|
### arguments
|
|
@@ -938,8 +960,26 @@ machines:
|
|
|
938
960
|
|
|
939
961
|
- **`repl:`** is written to `.hector/repl/job_<n>_<node>.inline.repl` and loaded with
|
|
940
962
|
`machine LoadPlatformDescription` (Renode only reads platform descriptions from
|
|
941
|
-
disk). It is loaded **after** any `platform:` files
|
|
942
|
-
|
|
963
|
+
disk). It is loaded **after** any `platform:` files.
|
|
964
|
+
|
|
965
|
+
> **Patching a bundled platform: use `using`, not `platform:`.** Every
|
|
966
|
+
> `LoadPlatformDescription` is its **own description scope**, so an entry that another
|
|
967
|
+
> file created is only partly reachable from `repl:` — setting a *constructor*
|
|
968
|
+
> attribute on it fails with
|
|
969
|
+
> `E35: Constructor attribute within entry for variable that is not created`.
|
|
970
|
+
> To override one, pull the base platform into the *same* scope from inside `repl:`:
|
|
971
|
+
>
|
|
972
|
+
> ```yaml
|
|
973
|
+
> repl: |
|
|
974
|
+
> using "platforms/boards/nucleo_h753zi.repl"
|
|
975
|
+
>
|
|
976
|
+
> nvic:
|
|
977
|
+
> priorityMask: 0xFF # works: same scope that creates `nvic`
|
|
978
|
+
> ```
|
|
979
|
+
>
|
|
980
|
+
> This is what Renode's own scripts do with `LoadPlatformDescriptionFromString`.
|
|
981
|
+
> Use `platform:` alone when you're loading a board as-is, and `using` inside `repl:`
|
|
982
|
+
> when you need to patch it. See [`examples/nucleo-h753zi`](examples/nucleo-h753zi).
|
|
943
983
|
- **`resc:`** is emitted verbatim into that machine's block, **after** the platform
|
|
944
984
|
and firmware load and **before** `commands:`.
|
|
945
985
|
|
|
@@ -966,6 +1006,8 @@ other field. Worked examples:
|
|
|
966
1006
|
- [`examples/nucleo-f767zi`](examples/nucleo-f767zi) — the real motivation: a
|
|
967
1007
|
**NUCLEO-F767ZI**, which Renode 1.16.1 ships no `.repl` for at all, defined inline
|
|
968
1008
|
and running against the pinned release image.
|
|
1009
|
+
- [`examples/nucleo-h753zi`](examples/nucleo-h753zi) — the other side: a board that
|
|
1010
|
+
**is** shipped, loaded via `using` and *patched* inline, booting Hubris.
|
|
969
1011
|
|
|
970
1012
|
#### peripherals
|
|
971
1013
|
|
|
@@ -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`
|
|
206
|
-
`
|
|
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
|
|
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
|
|
@@ -473,6 +478,20 @@ When combined with `--snapshot` in simulation mode, `.hector.yaml` is not requir
|
|
|
473
478
|
hector run --snapshot path/to/snapshot.save --renode-version 1.16.1
|
|
474
479
|
```
|
|
475
480
|
|
|
481
|
+
#### Using a custom Renode image (`--renode-image`)
|
|
482
|
+
|
|
483
|
+
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`:
|
|
484
|
+
|
|
485
|
+
```yaml
|
|
486
|
+
renode_image: my-renode:dev
|
|
487
|
+
```
|
|
488
|
+
|
|
489
|
+
```bash
|
|
490
|
+
hector test --renode-image ghcr.io/acme/renode:1.16.1-patched
|
|
491
|
+
```
|
|
492
|
+
|
|
493
|
+
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`.
|
|
494
|
+
|
|
476
495
|
#### Renode source checkout
|
|
477
496
|
|
|
478
497
|
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 +589,8 @@ The configuration lives in `.hector.yaml`.
|
|
|
570
589
|
| Field | Required | Description |
|
|
571
590
|
|---|---|---|
|
|
572
591
|
| `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. |
|
|
592
|
+
| `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. |
|
|
593
|
+
| `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
594
|
| `arguments` | No | Scalar defaults, overridable by env var or `--set`. |
|
|
575
595
|
| `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
596
|
| `matrix` | No | Cross-product job expansion. |
|
|
@@ -583,6 +603,8 @@ The configuration lives in `.hector.yaml`.
|
|
|
583
603
|
| `quantum` | No | Override the global time quantum (seconds). |
|
|
584
604
|
| `ci` | No | CI pipeline definitions: when and how a CI server runs this config. |
|
|
585
605
|
|
|
606
|
+
¹ At least one of `renode_version` / `renode_image` is required (or the matching CLI flag).
|
|
607
|
+
|
|
586
608
|
---
|
|
587
609
|
|
|
588
610
|
### arguments
|
|
@@ -908,8 +930,26 @@ machines:
|
|
|
908
930
|
|
|
909
931
|
- **`repl:`** is written to `.hector/repl/job_<n>_<node>.inline.repl` and loaded with
|
|
910
932
|
`machine LoadPlatformDescription` (Renode only reads platform descriptions from
|
|
911
|
-
disk). It is loaded **after** any `platform:` files
|
|
912
|
-
|
|
933
|
+
disk). It is loaded **after** any `platform:` files.
|
|
934
|
+
|
|
935
|
+
> **Patching a bundled platform: use `using`, not `platform:`.** Every
|
|
936
|
+
> `LoadPlatformDescription` is its **own description scope**, so an entry that another
|
|
937
|
+
> file created is only partly reachable from `repl:` — setting a *constructor*
|
|
938
|
+
> attribute on it fails with
|
|
939
|
+
> `E35: Constructor attribute within entry for variable that is not created`.
|
|
940
|
+
> To override one, pull the base platform into the *same* scope from inside `repl:`:
|
|
941
|
+
>
|
|
942
|
+
> ```yaml
|
|
943
|
+
> repl: |
|
|
944
|
+
> using "platforms/boards/nucleo_h753zi.repl"
|
|
945
|
+
>
|
|
946
|
+
> nvic:
|
|
947
|
+
> priorityMask: 0xFF # works: same scope that creates `nvic`
|
|
948
|
+
> ```
|
|
949
|
+
>
|
|
950
|
+
> This is what Renode's own scripts do with `LoadPlatformDescriptionFromString`.
|
|
951
|
+
> Use `platform:` alone when you're loading a board as-is, and `using` inside `repl:`
|
|
952
|
+
> when you need to patch it. See [`examples/nucleo-h753zi`](examples/nucleo-h753zi).
|
|
913
953
|
- **`resc:`** is emitted verbatim into that machine's block, **after** the platform
|
|
914
954
|
and firmware load and **before** `commands:`.
|
|
915
955
|
|
|
@@ -936,6 +976,8 @@ other field. Worked examples:
|
|
|
936
976
|
- [`examples/nucleo-f767zi`](examples/nucleo-f767zi) — the real motivation: a
|
|
937
977
|
**NUCLEO-F767ZI**, which Renode 1.16.1 ships no `.repl` for at all, defined inline
|
|
938
978
|
and running against the pinned release image.
|
|
979
|
+
- [`examples/nucleo-h753zi`](examples/nucleo-h753zi) — the other side: a board that
|
|
980
|
+
**is** shipped, loaded via `using` and *patched* inline, booting Hubris.
|
|
939
981
|
|
|
940
982
|
#### peripherals
|
|
941
983
|
|
|
@@ -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.
|
|
70
|
+
TOOL_VERSION = "0.4.0"
|
|
71
71
|
SUPPORTED_SCHEMA_VERSIONS = {"0.1"}
|
|
72
72
|
|
|
73
73
|
RENODE_REPO_URL = "https://github.com/renode/renode.git"
|
|
@@ -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
|
-
"""
|
|
111
|
-
(git_tag, docker_image).
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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
|
|
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
|
-
|
|
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.
|
|
@@ -256,6 +280,81 @@ def _run_validate():
|
|
|
256
280
|
sys.exit(0)
|
|
257
281
|
|
|
258
282
|
|
|
283
|
+
# ---------------------------------------------------------------------------
|
|
284
|
+
# clean
|
|
285
|
+
# ---------------------------------------------------------------------------
|
|
286
|
+
|
|
287
|
+
# The Renode source / verilator-integration checkouts hector clones on demand. They
|
|
288
|
+
# live under .hector/ but are expensive to re-fetch (~200 MB), so `clean` keeps them
|
|
289
|
+
# unless --all is given.
|
|
290
|
+
_RENODE_CHECKOUTS = ("renode", "renode-verilator-integration")
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
def clean_targets(output_dir="results", include_renode=False):
|
|
294
|
+
"""The generated paths `hector clean` would remove, filtered to those that exist.
|
|
295
|
+
|
|
296
|
+
Always: the cheap build artifacts (.hector/resc, .hector/repl, .hector/tests) and
|
|
297
|
+
the results/output directory. Only with include_renode: the whole .hector/ (which
|
|
298
|
+
also takes the expensive Renode checkouts with it). Paths are relative to the cwd."""
|
|
299
|
+
if include_renode:
|
|
300
|
+
targets = [ARTIFACTS_DIR, output_dir]
|
|
301
|
+
else:
|
|
302
|
+
targets = [os.path.join(ARTIFACTS_DIR, d) for d in ("resc", "repl", "tests")]
|
|
303
|
+
targets.append(output_dir)
|
|
304
|
+
# De-dupe while preserving order, keep only what's actually there.
|
|
305
|
+
seen, existing = set(), []
|
|
306
|
+
for p in targets:
|
|
307
|
+
if p not in seen and os.path.exists(p):
|
|
308
|
+
seen.add(p)
|
|
309
|
+
existing.append(p)
|
|
310
|
+
return existing
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
def _is_within_cwd(path):
|
|
314
|
+
"""True if `path` resolves inside the project root — the guard that stops `clean`
|
|
315
|
+
from ever deleting something outside the workspace (e.g. an absolute --output)."""
|
|
316
|
+
root = os.path.realpath(os.getcwd())
|
|
317
|
+
real = os.path.realpath(path)
|
|
318
|
+
return real == root or real.startswith(root + os.sep)
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
def run_clean(output_dir="results", include_renode=False, dry_run=False):
|
|
322
|
+
"""Remove hector's generated artifacts. Returns a process exit code."""
|
|
323
|
+
import shutil
|
|
324
|
+
|
|
325
|
+
targets = clean_targets(output_dir, include_renode)
|
|
326
|
+
if not targets:
|
|
327
|
+
print("[CLEAN] Nothing to remove — the workspace is already clean.")
|
|
328
|
+
return 0
|
|
329
|
+
|
|
330
|
+
removed = 0
|
|
331
|
+
for path in targets:
|
|
332
|
+
if not _is_within_cwd(path):
|
|
333
|
+
print(f"[CLEAN] SKIP {path} — outside the project directory; refusing to remove it.")
|
|
334
|
+
continue
|
|
335
|
+
kind = "dir " if os.path.isdir(path) else "file"
|
|
336
|
+
if dry_run:
|
|
337
|
+
print(f"[CLEAN] would remove {kind} {path}")
|
|
338
|
+
continue
|
|
339
|
+
if os.path.isdir(path):
|
|
340
|
+
shutil.rmtree(path, ignore_errors=True)
|
|
341
|
+
else:
|
|
342
|
+
os.remove(path)
|
|
343
|
+
print(f"[CLEAN] removed {kind} {path}")
|
|
344
|
+
removed += 1
|
|
345
|
+
|
|
346
|
+
if dry_run:
|
|
347
|
+
print("[CLEAN] dry run — nothing was deleted.")
|
|
348
|
+
elif not include_renode and os.path.isdir(ARTIFACTS_DIR):
|
|
349
|
+
# We kept a Renode checkout; tell the user how to drop it too.
|
|
350
|
+
kept = [d for d in _RENODE_CHECKOUTS
|
|
351
|
+
if os.path.isdir(os.path.join(ARTIFACTS_DIR, d))]
|
|
352
|
+
if kept:
|
|
353
|
+
print(f"[CLEAN] kept cached Renode checkout(s) in {ARTIFACTS_DIR}/ "
|
|
354
|
+
f"({', '.join(kept)}) — use 'hector clean --all' to remove them too.")
|
|
355
|
+
return 0
|
|
356
|
+
|
|
357
|
+
|
|
259
358
|
# ---------------------------------------------------------------------------
|
|
260
359
|
# Helpers
|
|
261
360
|
# ---------------------------------------------------------------------------
|
|
@@ -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(
|
|
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")
|
|
@@ -10,7 +10,6 @@ Usage:
|
|
|
10
10
|
for w in warnings: print(f" WARNING {w.path}: {w.message}")
|
|
11
11
|
"""
|
|
12
12
|
|
|
13
|
-
import re
|
|
14
13
|
from dataclasses import dataclass
|
|
15
14
|
from typing import List, Tuple
|
|
16
15
|
|
|
@@ -20,12 +19,19 @@ from .mappings import MAPPING_BACKENDS
|
|
|
20
19
|
from .modules import MODULE_KINDS
|
|
21
20
|
from .runners import TEST_RUNNERS
|
|
22
21
|
|
|
22
|
+
# CI trigger / run-control keys. The whole .hector.yaml is a single pipeline (there is
|
|
23
|
+
# no nested `ci:` block). These belong entirely to the CI server — the CLI's run/test/
|
|
24
|
+
# export never read them and it must not act on them. They are listed here only so the
|
|
25
|
+
# CLI accepts them silently instead of flagging them; the CI backend is their sole
|
|
26
|
+
# validator (see ci/backend/hectorci/config.py).
|
|
27
|
+
_TRIGGER_TOP_KEYS = {"when", "cron", "reporters", "jobs", "fail_fast", "timeout"}
|
|
28
|
+
|
|
23
29
|
# Top-level keys defined by this schema version; anything else is flagged.
|
|
24
30
|
_KNOWN_TOP_KEYS = {
|
|
25
|
-
"version", "renode_version", "arguments", "matrix",
|
|
31
|
+
"version", "renode_version", "renode_image", "arguments", "matrix",
|
|
26
32
|
"modules", "hubs", "machines", "connections", "mappings", "build", "tests", "quantum",
|
|
27
|
-
"artifacts",
|
|
28
|
-
}
|
|
33
|
+
"artifacts",
|
|
34
|
+
} | _TRIGGER_TOP_KEYS
|
|
29
35
|
|
|
30
36
|
# Keys renamed in this schema version → new name (for a helpful migration hint).
|
|
31
37
|
_RENAMED_TOP_KEYS = {"nodes": "machines", "setup": "build", "prepare": "build"}
|
|
@@ -33,12 +39,6 @@ _RENAMED_TOP_KEYS = {"nodes": "machines", "setup": "build", "prepare": "build"}
|
|
|
33
39
|
# Test types renamed this schema version → current name (old configs still run).
|
|
34
40
|
_RENAMED_TEST_TYPES = {"bash": "shell", "docker": "shell"}
|
|
35
41
|
|
|
36
|
-
# CI pipeline schema (top-level `ci:` block — when/how the CI server runs this config).
|
|
37
|
-
_CI_EVENTS = {"push", "pull_request", "tag", "manual", "cron"}
|
|
38
|
-
_CI_PIPELINE_KEYS = {"when", "arguments", "jobs", "reporters", "fail_fast", "timeout", "cron"}
|
|
39
|
-
_CI_WHEN_KEYS = {"branch", "event", "path"}
|
|
40
|
-
_CI_TIMEOUT_RE = re.compile(r"^\d+[smh]$")
|
|
41
|
-
|
|
42
42
|
_KNOWN_NODE_KEYS = {
|
|
43
43
|
"backend", "platform", "repl", "resc", "firmware",
|
|
44
44
|
"peripherals", "init", "connections", "mappings", "commands",
|
|
@@ -80,12 +80,16 @@ def validate_config(config) -> Tuple[List[Issue], List[Issue]]:
|
|
|
80
80
|
f"({', '.join(sorted(SUPPORTED_SCHEMA_VERSIONS))}). "
|
|
81
81
|
"Some fields may not be recognised.")
|
|
82
82
|
|
|
83
|
-
# ---- renode_version (required) ----
|
|
83
|
+
# ---- renode_version / renode_image (one of them required) ----
|
|
84
84
|
rv = config.get("renode_version")
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
85
|
+
ri = config.get("renode_image")
|
|
86
|
+
if not rv and not ri:
|
|
87
|
+
err("renode_version", "Required field missing (e.g. '1.16.1') — or set "
|
|
88
|
+
"'renode_image' to use a specific Docker image.")
|
|
89
|
+
if rv and not isinstance(rv, (str, int, float)):
|
|
88
90
|
err("renode_version", f"Must be a string like '1.16.1', got {type(rv).__name__}.")
|
|
91
|
+
if ri and not isinstance(ri, str):
|
|
92
|
+
err("renode_image", f"Must be a string like 'my-renode:dev', got {type(ri).__name__}.")
|
|
89
93
|
|
|
90
94
|
# ---- unknown / renamed top-level keys ----
|
|
91
95
|
for k in config:
|
|
@@ -291,8 +295,8 @@ def validate_config(config) -> Tuple[List[Issue], List[Issue]]:
|
|
|
291
295
|
except (TypeError, ValueError):
|
|
292
296
|
err("quantum", f"Must be a positive number (seconds), got '{quantum}'.")
|
|
293
297
|
|
|
294
|
-
# ----
|
|
295
|
-
|
|
298
|
+
# ---- CI triggers (when/cron/…) are accepted but never validated or acted on
|
|
299
|
+
# here; they belong to the CI server. See _TRIGGER_TOP_KEYS. ----
|
|
296
300
|
|
|
297
301
|
return errors, warnings
|
|
298
302
|
|
|
@@ -328,71 +332,6 @@ def _validate_shell_steps(item, p, err, warn):
|
|
|
328
332
|
warn(p, "Both 'file' and 'script' — 'file' takes precedence.")
|
|
329
333
|
|
|
330
334
|
|
|
331
|
-
def _validate_ci(ci_cfg, err, warn):
|
|
332
|
-
"""Validate the top-level `ci:` block (pipeline-name -> CI settings)."""
|
|
333
|
-
if ci_cfg is None:
|
|
334
|
-
return
|
|
335
|
-
if not isinstance(ci_cfg, dict):
|
|
336
|
-
err("ci", f"Must be a mapping of pipeline-name: settings, got {type(ci_cfg).__name__}.")
|
|
337
|
-
return
|
|
338
|
-
for name, pipe in ci_cfg.items():
|
|
339
|
-
p = f"ci.{name}"
|
|
340
|
-
if not isinstance(pipe, dict):
|
|
341
|
-
err(p, f"Pipeline must be a mapping, got {type(pipe).__name__}."); continue
|
|
342
|
-
for k in pipe:
|
|
343
|
-
if k not in _CI_PIPELINE_KEYS:
|
|
344
|
-
warn(f"{p}.{k}", f"Unknown pipeline key '{k}'. "
|
|
345
|
-
f"Known keys: {', '.join(sorted(_CI_PIPELINE_KEYS))}.")
|
|
346
|
-
|
|
347
|
-
# when: { branch, event, path }
|
|
348
|
-
when = pipe.get("when")
|
|
349
|
-
events = []
|
|
350
|
-
if when is not None:
|
|
351
|
-
if not isinstance(when, dict):
|
|
352
|
-
err(f"{p}.when", f"Must be a mapping, got {type(when).__name__}.")
|
|
353
|
-
else:
|
|
354
|
-
for k in when:
|
|
355
|
-
if k not in _CI_WHEN_KEYS:
|
|
356
|
-
warn(f"{p}.when.{k}", f"Unknown 'when' key '{k}'.")
|
|
357
|
-
raw_event = when.get("event")
|
|
358
|
-
if raw_event is None:
|
|
359
|
-
events = []
|
|
360
|
-
elif not isinstance(raw_event, list):
|
|
361
|
-
err(f"{p}.when.event", f"Must be a list, got {type(raw_event).__name__}.")
|
|
362
|
-
events = []
|
|
363
|
-
else:
|
|
364
|
-
events = raw_event
|
|
365
|
-
for ev in events:
|
|
366
|
-
if not isinstance(ev, str) or ev not in _CI_EVENTS:
|
|
367
|
-
err(f"{p}.when.event", f"Unknown event {ev!r}. "
|
|
368
|
-
f"Allowed: {', '.join(sorted(_CI_EVENTS))}.")
|
|
369
|
-
|
|
370
|
-
# jobs / fail_fast
|
|
371
|
-
jobs = pipe.get("jobs")
|
|
372
|
-
if jobs is not None and (not isinstance(jobs, int) or isinstance(jobs, bool) or jobs < 1):
|
|
373
|
-
err(f"{p}.jobs", f"Must be a positive integer, got {jobs!r}.")
|
|
374
|
-
if pipe.get("fail_fast") is True and (jobs or 1) != 1:
|
|
375
|
-
err(f"{p}.fail_fast", "Only valid with jobs: 1.")
|
|
376
|
-
|
|
377
|
-
# cron (required iff event includes 'cron')
|
|
378
|
-
has_cron_event = "cron" in events
|
|
379
|
-
if has_cron_event and not isinstance(pipe.get("cron"), str):
|
|
380
|
-
err(f"{p}.cron", "A cron expression is required when event includes 'cron'.")
|
|
381
|
-
if isinstance(pipe.get("cron"), str) and not has_cron_event:
|
|
382
|
-
warn(f"{p}.cron", "cron is set but when.event does not include 'cron'.")
|
|
383
|
-
|
|
384
|
-
# timeout / reporters / arguments
|
|
385
|
-
timeout = pipe.get("timeout")
|
|
386
|
-
if timeout is not None and (not isinstance(timeout, str) or not _CI_TIMEOUT_RE.match(timeout)):
|
|
387
|
-
err(f"{p}.timeout", "Must look like '30m', '90s' or '2h'.")
|
|
388
|
-
reporters = pipe.get("reporters")
|
|
389
|
-
if reporters is not None and not isinstance(reporters, list):
|
|
390
|
-
err(f"{p}.reporters", f"Must be a list, got {type(reporters).__name__}.")
|
|
391
|
-
arguments = pipe.get("arguments")
|
|
392
|
-
if arguments is not None and not isinstance(arguments, dict):
|
|
393
|
-
err(f"{p}.arguments", f"Must be a mapping of name: value, got {type(arguments).__name__}.")
|
|
394
|
-
|
|
395
|
-
|
|
396
335
|
def _validate_connection_lines(section, path, err, warn):
|
|
397
336
|
for line in as_lines(section):
|
|
398
337
|
if "->" not in line and "<->" not in line:
|