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.
- {hector_cli-0.3.0/hector_cli.egg-info → hector_cli-0.5.0}/PKG-INFO +53 -7
- {hector_cli-0.3.0 → hector_cli-0.5.0}/README.md +52 -6
- {hector_cli-0.3.0 → hector_cli-0.5.0}/hector/commands/__init__.py +1 -1
- {hector_cli-0.3.0 → hector_cli-0.5.0}/hector/commands/base.py +6 -0
- hector_cli-0.5.0/hector/commands/clean.py +34 -0
- {hector_cli-0.3.0 → hector_cli-0.5.0}/hector/commands/run.py +2 -2
- {hector_cli-0.3.0 → hector_cli-0.5.0}/hector/core.py +1 -1
- {hector_cli-0.3.0 → hector_cli-0.5.0}/hector/docker.py +20 -17
- {hector_cli-0.3.0 → hector_cli-0.5.0}/hector/pipeline.py +112 -16
- {hector_cli-0.3.0 → hector_cli-0.5.0}/hector/scaffold.py +1 -1
- {hector_cli-0.3.0 → hector_cli-0.5.0}/hector/validator.py +20 -81
- {hector_cli-0.3.0 → hector_cli-0.5.0/hector_cli.egg-info}/PKG-INFO +53 -7
- {hector_cli-0.3.0 → hector_cli-0.5.0}/hector_cli.egg-info/SOURCES.txt +22 -1
- hector_cli-0.5.0/tests/test_arguments.py +32 -0
- hector_cli-0.5.0/tests/test_clean.py +103 -0
- hector_cli-0.5.0/tests/test_cli_smoke.py +74 -0
- hector_cli-0.5.0/tests/test_connections.py +124 -0
- hector_cli-0.5.0/tests/test_core_paths.py +98 -0
- hector_cli-0.5.0/tests/test_core_registry.py +75 -0
- hector_cli-0.5.0/tests/test_core_text.py +78 -0
- hector_cli-0.5.0/tests/test_docker_argv.py +118 -0
- hector_cli-0.5.0/tests/test_generator.py +196 -0
- hector_cli-0.5.0/tests/test_hubs.py +53 -0
- hector_cli-0.5.0/tests/test_mappings.py +102 -0
- hector_cli-0.5.0/tests/test_matrix.py +30 -0
- hector_cli-0.5.0/tests/test_needs_renode_source.py +38 -0
- hector_cli-0.5.0/tests/test_peripherals.py +81 -0
- hector_cli-0.5.0/tests/test_pipeline_helpers.py +149 -0
- hector_cli-0.5.0/tests/test_reporters.py +70 -0
- hector_cli-0.5.0/tests/test_runners.py +83 -0
- hector_cli-0.5.0/tests/test_scaffold.py +53 -0
- hector_cli-0.5.0/tests/test_validator.py +141 -0
- hector_cli-0.5.0/tests/test_version.py +39 -0
- {hector_cli-0.3.0 → hector_cli-0.5.0}/LICENSE +0 -0
- {hector_cli-0.3.0 → hector_cli-0.5.0}/hector/__init__.py +0 -0
- {hector_cli-0.3.0 → hector_cli-0.5.0}/hector/__main__.py +0 -0
- {hector_cli-0.3.0 → hector_cli-0.5.0}/hector/commands/export.py +0 -0
- {hector_cli-0.3.0 → hector_cli-0.5.0}/hector/commands/init.py +0 -0
- {hector_cli-0.3.0 → hector_cli-0.5.0}/hector/commands/test.py +0 -0
- {hector_cli-0.3.0 → hector_cli-0.5.0}/hector/commands/validate.py +0 -0
- {hector_cli-0.3.0 → hector_cli-0.5.0}/hector/connections.py +0 -0
- {hector_cli-0.3.0 → hector_cli-0.5.0}/hector/dependencies.py +0 -0
- {hector_cli-0.3.0 → hector_cli-0.5.0}/hector/generator.py +0 -0
- {hector_cli-0.3.0 → hector_cli-0.5.0}/hector/hubs.py +0 -0
- {hector_cli-0.3.0 → hector_cli-0.5.0}/hector/mappings.py +0 -0
- {hector_cli-0.3.0 → hector_cli-0.5.0}/hector/modules.py +0 -0
- {hector_cli-0.3.0 → hector_cli-0.5.0}/hector/peripherals.py +0 -0
- {hector_cli-0.3.0 → hector_cli-0.5.0}/hector/reporters.py +0 -0
- {hector_cli-0.3.0 → hector_cli-0.5.0}/hector/runners.py +0 -0
- {hector_cli-0.3.0 → hector_cli-0.5.0}/hector_cli.egg-info/dependency_links.txt +0 -0
- {hector_cli-0.3.0 → hector_cli-0.5.0}/hector_cli.egg-info/entry_points.txt +0 -0
- {hector_cli-0.3.0 → hector_cli-0.5.0}/hector_cli.egg-info/requires.txt +0 -0
- {hector_cli-0.3.0 → hector_cli-0.5.0}/hector_cli.egg-info/top_level.txt +0 -0
- {hector_cli-0.3.0 → hector_cli-0.5.0}/setup.cfg +0 -0
- {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
|
+
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`
|
|
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
|
|
@@ -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
|
|
942
|
-
|
|
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`
|
|
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
|
|
@@ -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
|
|
912
|
-
|
|
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.
|
|
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", *
|
|
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,
|
|
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
|
-
|
|
158
|
-
|
|
159
|
-
|
|
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,
|
|
166
|
-
"""Run an emulation resc interactively.
|
|
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,
|
|
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
|
|
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, *
|
|
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
|
-
"""
|
|
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.
|
|
@@ -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,
|
|
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(
|
|
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")
|