jig-dsl 0.1.0a1__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.
- jig_dsl-0.1.0a1/LICENSE +21 -0
- jig_dsl-0.1.0a1/PKG-INFO +181 -0
- jig_dsl-0.1.0a1/README.md +120 -0
- jig_dsl-0.1.0a1/pyproject.toml +192 -0
- jig_dsl-0.1.0a1/setup.cfg +4 -0
- jig_dsl-0.1.0a1/src/jig/__init__.py +13 -0
- jig_dsl-0.1.0a1/src/jig/__main__.py +7 -0
- jig_dsl-0.1.0a1/src/jig/artifacts.py +77 -0
- jig_dsl-0.1.0a1/src/jig/cli.py +439 -0
- jig_dsl-0.1.0a1/src/jig/contracts.py +220 -0
- jig_dsl-0.1.0a1/src/jig/errors.py +61 -0
- jig_dsl-0.1.0a1/src/jig/ext/__init__.py +2 -0
- jig_dsl-0.1.0a1/src/jig/ext/airhdl/__init__.py +189 -0
- jig_dsl-0.1.0a1/src/jig/ext/cosim/__init__.py +390 -0
- jig_dsl-0.1.0a1/src/jig/ext/cosim/templates/jig_axil_bram.sv +114 -0
- jig_dsl-0.1.0a1/src/jig/ext/cosim/templates/jig_axil_byte_lanes.sv +100 -0
- jig_dsl-0.1.0a1/src/jig/ext/cosim/templates/jig_axil_decoder.sv +203 -0
- jig_dsl-0.1.0a1/src/jig/ext/cosim/templates/jig_axil_regs.sv +99 -0
- jig_dsl-0.1.0a1/src/jig/ext/cosim/templates/jig_dp_bram.sv +49 -0
- jig_dsl-0.1.0a1/src/jig/ext/cosim/templates/sim_main.cpp.in +75 -0
- jig_dsl-0.1.0a1/src/jig/ext/csp/__init__.py +2 -0
- jig_dsl-0.1.0a1/src/jig/ext/csp/cmp.py +113 -0
- jig_dsl-0.1.0a1/src/jig/ext/csp/dsl.py +620 -0
- jig_dsl-0.1.0a1/src/jig/ext/csp/extension.py +31 -0
- jig_dsl-0.1.0a1/src/jig/ext/csp/node.py +618 -0
- jig_dsl-0.1.0a1/src/jig/ext/csp/observer.py +90 -0
- jig_dsl-0.1.0a1/src/jig/ext/csp/protocol.py +328 -0
- jig_dsl-0.1.0a1/src/jig/ext/csp/rdp.py +390 -0
- jig_dsl-0.1.0a1/src/jig/ext/csp/settings.py +166 -0
- jig_dsl-0.1.0a1/src/jig/ext/zynq7000/__init__.py +2 -0
- jig_dsl-0.1.0a1/src/jig/ext/zynq7000/clocks.py +171 -0
- jig_dsl-0.1.0a1/src/jig/ext/zynq7000/driver.py +403 -0
- jig_dsl-0.1.0a1/src/jig/ext/zynq7000/ps7init.py +133 -0
- jig_dsl-0.1.0a1/src/jig/lang/__init__.py +114 -0
- jig_dsl-0.1.0a1/src/jig/lang/grammar/jig.tx +322 -0
- jig_dsl-0.1.0a1/src/jig/lang/interpreter.py +1512 -0
- jig_dsl-0.1.0a1/src/jig/lang/library.py +747 -0
- jig_dsl-0.1.0a1/src/jig/lang/parser.py +363 -0
- jig_dsl-0.1.0a1/src/jig/lang/pygments_lexer.py +201 -0
- jig_dsl-0.1.0a1/src/jig/lang/stdlib/console.jig +35 -0
- jig_dsl-0.1.0a1/src/jig/lang/stdlib/records.jig +9 -0
- jig_dsl-0.1.0a1/src/jig/lang/stdlib/text.jig +24 -0
- jig_dsl-0.1.0a1/src/jig/lang/values.py +213 -0
- jig_dsl-0.1.0a1/src/jig/models/Arm/CortexA9GlobalTimer.cs +290 -0
- jig_dsl-0.1.0a1/src/jig/models/Arm/CortexA9PrivateTimerWatchdog.cs +283 -0
- jig_dsl-0.1.0a1/src/jig/models/Arm/GicDistributorHooks.cs +99 -0
- jig_dsl-0.1.0a1/src/jig/models/Arm/PL310.cs +134 -0
- jig_dsl-0.1.0a1/src/jig/models/Cadence/CadenceSPI.cs +384 -0
- jig_dsl-0.1.0a1/src/jig/models/Cadence/CadenceUART.cs +658 -0
- jig_dsl-0.1.0a1/src/jig/models/Cadence/CadenceWDT.cs +184 -0
- jig_dsl-0.1.0a1/src/jig/models/Contracts/ContractQueries.cs +63 -0
- jig_dsl-0.1.0a1/src/jig/models/Contracts/IAnalogInputs.cs +31 -0
- jig_dsl-0.1.0a1/src/jig/models/Contracts/IPowerCycled.cs +24 -0
- jig_dsl-0.1.0a1/src/jig/models/Contracts/IRealTimeClock.cs +27 -0
- jig_dsl-0.1.0a1/src/jig/models/Contracts/ISpiNorFlash.cs +27 -0
- jig_dsl-0.1.0a1/src/jig/models/Contracts/IStorage.cs +28 -0
- jig_dsl-0.1.0a1/src/jig/models/Harness/AirhdlRegisterBlock.cs +232 -0
- jig_dsl-0.1.0a1/src/jig/models/Harness/Bridge.cs +758 -0
- jig_dsl-0.1.0a1/src/jig/models/Harness/CanBus.cs +487 -0
- jig_dsl-0.1.0a1/src/jig/models/Harness/RegisterProgram.cs +123 -0
- jig_dsl-0.1.0a1/src/jig/models/Harness/Stepping.cs +68 -0
- jig_dsl-0.1.0a1/src/jig/models/Harness/UartPort.cs +221 -0
- jig_dsl-0.1.0a1/src/jig/models/ST/STM32U5_ADC4.cs +116 -0
- jig_dsl-0.1.0a1/src/jig/models/ST/STM32U5_FDCAN.cs +1314 -0
- jig_dsl-0.1.0a1/src/jig/models/ST/STM32U5_LPTIM.cs +537 -0
- jig_dsl-0.1.0a1/src/jig/models/ST/STM32U5_RCC.cs +419 -0
- jig_dsl-0.1.0a1/src/jig/models/ST/STM32U5_RTC.cs +903 -0
- jig_dsl-0.1.0a1/src/jig/models/ST/STM32U5_TAMP.cs +176 -0
- jig_dsl-0.1.0a1/src/jig/models/Xilinx/XilinxAxiCdma.cs +232 -0
- jig_dsl-0.1.0a1/src/jig/models/Xilinx/XilinxAxiGpio.cs +174 -0
- jig_dsl-0.1.0a1/src/jig/models/Xilinx/XilinxAxiQuadSpi.cs +339 -0
- jig_dsl-0.1.0a1/src/jig/models/Xilinx/Zynq7000_BootROM.cs +435 -0
- jig_dsl-0.1.0a1/src/jig/models/Xilinx/Zynq7000_CAN.cs +750 -0
- jig_dsl-0.1.0a1/src/jig/models/Xilinx/Zynq7000_DDRC.cs +65 -0
- jig_dsl-0.1.0a1/src/jig/models/Xilinx/Zynq7000_DevC.cs +431 -0
- jig_dsl-0.1.0a1/src/jig/models/Xilinx/Zynq7000_EFuse.cs +37 -0
- jig_dsl-0.1.0a1/src/jig/models/Xilinx/Zynq7000_GPIO.cs +326 -0
- jig_dsl-0.1.0a1/src/jig/models/Xilinx/Zynq7000_PlDomain.cs +113 -0
- jig_dsl-0.1.0a1/src/jig/models/Xilinx/Zynq7000_QSPI.cs +545 -0
- jig_dsl-0.1.0a1/src/jig/models/Xilinx/Zynq7000_SLCR.cs +368 -0
- jig_dsl-0.1.0a1/src/jig/platforms/st/stm32u5.repl +254 -0
- jig_dsl-0.1.0a1/src/jig/platforms/st/stm32u575.repl +49 -0
- jig_dsl-0.1.0a1/src/jig/platforms/st/stm32u595.repl +52 -0
- jig_dsl-0.1.0a1/src/jig/platforms/xilinx/zynq7000.repl +160 -0
- jig_dsl-0.1.0a1/src/jig/plugin.py +359 -0
- jig_dsl-0.1.0a1/src/jig/project.py +357 -0
- jig_dsl-0.1.0a1/src/jig/py.typed +0 -0
- jig_dsl-0.1.0a1/src/jig/renode/__init__.py +17 -0
- jig_dsl-0.1.0a1/src/jig/renode/__main__.py +7 -0
- jig_dsl-0.1.0a1/src/jig/renode/assets.py +117 -0
- jig_dsl-0.1.0a1/src/jig/renode/bridge.py +217 -0
- jig_dsl-0.1.0a1/src/jig/renode/commands.py +547 -0
- jig_dsl-0.1.0a1/src/jig/renode/discover.py +615 -0
- jig_dsl-0.1.0a1/src/jig/renode/doctor.py +309 -0
- jig_dsl-0.1.0a1/src/jig/renode/install.py +492 -0
- jig_dsl-0.1.0a1/src/jig/renode/models.py +381 -0
- jig_dsl-0.1.0a1/src/jig/renode/platforms.py +42 -0
- jig_dsl-0.1.0a1/src/jig/renode/process.py +349 -0
- jig_dsl-0.1.0a1/src/jig/renode/session.py +398 -0
- jig_dsl-0.1.0a1/src/jig/renode/toolchain.py +137 -0
- jig_dsl-0.1.0a1/src/jig/repl.py +59 -0
- jig_dsl-0.1.0a1/src/jig/report/__init__.py +1 -0
- jig_dsl-0.1.0a1/src/jig/report/html.py +209 -0
- jig_dsl-0.1.0a1/src/jig/report/report.py +351 -0
- jig_dsl-0.1.0a1/src/jig/report/summary.py +122 -0
- jig_dsl-0.1.0a1/src/jig/runtime/__init__.py +1 -0
- jig_dsl-0.1.0a1/src/jig/runtime/environment.py +274 -0
- jig_dsl-0.1.0a1/src/jig/runtime/recorder.py +111 -0
- jig_dsl-0.1.0a1/src/jig/runtime/scheduler.py +375 -0
- jig_dsl-0.1.0a1/src/jig/system/__init__.py +1 -0
- jig_dsl-0.1.0a1/src/jig/system/boards.py +750 -0
- jig_dsl-0.1.0a1/src/jig/system/registry.py +411 -0
- jig_dsl-0.1.0a1/src/jig/system/renode_shim.py +45 -0
- jig_dsl-0.1.0a1/src/jig/system/socs/__init__.py +189 -0
- jig_dsl-0.1.0a1/src/jig/system/socs/stm32.py +331 -0
- jig_dsl-0.1.0a1/src/jig/system/system.py +805 -0
- jig_dsl-0.1.0a1/src/jig/testing/__init__.py +493 -0
- jig_dsl-0.1.0a1/src/jig/transports/__init__.py +1 -0
- jig_dsl-0.1.0a1/src/jig/transports/can.py +125 -0
- jig_dsl-0.1.0a1/src/jig/transports/uart.py +82 -0
- jig_dsl-0.1.0a1/src/jig_dsl.egg-info/PKG-INFO +181 -0
- jig_dsl-0.1.0a1/src/jig_dsl.egg-info/SOURCES.txt +158 -0
- jig_dsl-0.1.0a1/src/jig_dsl.egg-info/dependency_links.txt +1 -0
- jig_dsl-0.1.0a1/src/jig_dsl.egg-info/entry_points.txt +9 -0
- jig_dsl-0.1.0a1/src/jig_dsl.egg-info/requires.txt +35 -0
- jig_dsl-0.1.0a1/src/jig_dsl.egg-info/top_level.txt +1 -0
- jig_dsl-0.1.0a1/tests/test_boards.py +301 -0
- jig_dsl-0.1.0a1/tests/test_bridge.py +148 -0
- jig_dsl-0.1.0a1/tests/test_cli.py +119 -0
- jig_dsl-0.1.0a1/tests/test_cosim_build.py +146 -0
- jig_dsl-0.1.0a1/tests/test_csp.py +117 -0
- jig_dsl-0.1.0a1/tests/test_csp_ext.py +299 -0
- jig_dsl-0.1.0a1/tests/test_discover.py +238 -0
- jig_dsl-0.1.0a1/tests/test_dsl.py +405 -0
- jig_dsl-0.1.0a1/tests/test_environment.py +99 -0
- jig_dsl-0.1.0a1/tests/test_framework.py +243 -0
- jig_dsl-0.1.0a1/tests/test_hdl.py +131 -0
- jig_dsl-0.1.0a1/tests/test_install.py +299 -0
- jig_dsl-0.1.0a1/tests/test_language.py +239 -0
- jig_dsl-0.1.0a1/tests/test_model_sets.py +156 -0
- jig_dsl-0.1.0a1/tests/test_multiboard.py +141 -0
- jig_dsl-0.1.0a1/tests/test_nodes.py +104 -0
- jig_dsl-0.1.0a1/tests/test_observer.py +95 -0
- jig_dsl-0.1.0a1/tests/test_plugin.py +319 -0
- jig_dsl-0.1.0a1/tests/test_project.py +254 -0
- jig_dsl-0.1.0a1/tests/test_public_hygiene.py +248 -0
- jig_dsl-0.1.0a1/tests/test_pygments_lexer.py +161 -0
- jig_dsl-0.1.0a1/tests/test_rdp.py +149 -0
- jig_dsl-0.1.0a1/tests/test_renode_bridge.py +382 -0
- jig_dsl-0.1.0a1/tests/test_renode_commands.py +212 -0
- jig_dsl-0.1.0a1/tests/test_renode_reports.py +67 -0
- jig_dsl-0.1.0a1/tests/test_renode_smoke.py +46 -0
- jig_dsl-0.1.0a1/tests/test_renode_system.py +55 -0
- jig_dsl-0.1.0a1/tests/test_reports.py +399 -0
- jig_dsl-0.1.0a1/tests/test_runtime.py +160 -0
- jig_dsl-0.1.0a1/tests/test_simulation.py +727 -0
- jig_dsl-0.1.0a1/tests/test_systems.py +347 -0
- jig_dsl-0.1.0a1/tests/test_toolchain.py +37 -0
- jig_dsl-0.1.0a1/tests/test_zedboard_example.py +76 -0
- jig_dsl-0.1.0a1/tests/test_zynq7000.py +125 -0
jig_dsl-0.1.0a1/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Graham Power
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
jig_dsl-0.1.0a1/PKG-INFO
ADDED
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: jig-dsl
|
|
3
|
+
Version: 0.1.0a1
|
|
4
|
+
Summary: A test language and harness for firmware running on the Renode emulator
|
|
5
|
+
Author: Graham Power
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/GNPower/Jig
|
|
8
|
+
Project-URL: Documentation, https://jig-dsl.readthedocs.io
|
|
9
|
+
Project-URL: Repository, https://github.com/GNPower/Jig
|
|
10
|
+
Project-URL: Issues, https://github.com/GNPower/Jig/issues
|
|
11
|
+
Project-URL: Changelog, https://github.com/GNPower/Jig/blob/main/CHANGELOG.md
|
|
12
|
+
Keywords: renode,emulation,firmware,testing,embedded,zynq,stm32,csp
|
|
13
|
+
Classifier: Development Status :: 3 - Alpha
|
|
14
|
+
Classifier: Environment :: Console
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: Operating System :: MacOS
|
|
17
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
18
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
19
|
+
Classifier: Programming Language :: Python :: 3
|
|
20
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
24
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
25
|
+
Classifier: Topic :: Software Development :: Embedded Systems
|
|
26
|
+
Classifier: Topic :: Software Development :: Testing
|
|
27
|
+
Classifier: Topic :: System :: Emulators
|
|
28
|
+
Classifier: Typing :: Typed
|
|
29
|
+
Requires-Python: >=3.11
|
|
30
|
+
Description-Content-Type: text/markdown
|
|
31
|
+
License-File: LICENSE
|
|
32
|
+
Requires-Dist: textX<5,>=4
|
|
33
|
+
Requires-Dist: PyYAML<7,>=6
|
|
34
|
+
Requires-Dist: platformdirs<5,>=4
|
|
35
|
+
Requires-Dist: packaging>=23
|
|
36
|
+
Provides-Extra: test
|
|
37
|
+
Requires-Dist: pytest<10,>=8; extra == "test"
|
|
38
|
+
Requires-Dist: pytest-cov<8,>=6; extra == "test"
|
|
39
|
+
Provides-Extra: docs
|
|
40
|
+
Requires-Dist: sphinx<10,>=8.2; extra == "docs"
|
|
41
|
+
Requires-Dist: sphinx-rtd-theme<4,>=3.0; extra == "docs"
|
|
42
|
+
Requires-Dist: myst-parser<6,>=4.0; extra == "docs"
|
|
43
|
+
Provides-Extra: lint
|
|
44
|
+
Requires-Dist: ruff==0.17.0; extra == "lint"
|
|
45
|
+
Requires-Dist: pymarkdownlnt==0.9.40; extra == "lint"
|
|
46
|
+
Requires-Dist: zizmor==1.30.1; extra == "lint"
|
|
47
|
+
Provides-Extra: typecheck
|
|
48
|
+
Requires-Dist: jig-dsl[test]; extra == "typecheck"
|
|
49
|
+
Requires-Dist: mypy==2.4.0; extra == "typecheck"
|
|
50
|
+
Requires-Dist: types-PyYAML==6.0.12.20260906; extra == "typecheck"
|
|
51
|
+
Requires-Dist: nox==2026.8.17; extra == "typecheck"
|
|
52
|
+
Provides-Extra: release
|
|
53
|
+
Requires-Dist: build==1.6.1; extra == "release"
|
|
54
|
+
Requires-Dist: twine==7.0.0; extra == "release"
|
|
55
|
+
Requires-Dist: bump-my-version==1.5.1; extra == "release"
|
|
56
|
+
Provides-Extra: audit
|
|
57
|
+
Requires-Dist: pip-audit==2.10.1; extra == "audit"
|
|
58
|
+
Provides-Extra: dev
|
|
59
|
+
Requires-Dist: jig-dsl[lint,release,test,typecheck]; extra == "dev"
|
|
60
|
+
Dynamic: license-file
|
|
61
|
+
|
|
62
|
+
# Jig
|
|
63
|
+
|
|
64
|
+
[](https://pypi.org/project/jig-dsl/)
|
|
65
|
+
[](https://pypi.org/project/jig-dsl/)
|
|
66
|
+
[](https://github.com/GNPower/Jig/actions/workflows/ci.yml)
|
|
67
|
+
[](https://codecov.io/gh/GNPower/Jig)
|
|
68
|
+
[](https://jig-dsl.readthedocs.io/en/latest/)
|
|
69
|
+
[](https://github.com/GNPower/Jig/blob/main/LICENSE)
|
|
70
|
+
|
|
71
|
+
**A test language and harness for firmware running on the Renode
|
|
72
|
+
emulator.**
|
|
73
|
+
|
|
74
|
+
Full documentation: <https://jig-dsl.readthedocs.io>
|
|
75
|
+
|
|
76
|
+
## Overview
|
|
77
|
+
|
|
78
|
+
Jig runs test plans, written as `.jig` files, against a users firmware
|
|
79
|
+
on [Renode](https://renode.io). A plan declares the boards it needs, the
|
|
80
|
+
buse interfaces between them and the communication protocols the harness uses, then
|
|
81
|
+
drives the system over those interfaces in simulated time:
|
|
82
|
+
|
|
83
|
+
```jig
|
|
84
|
+
import "zed.jig"
|
|
85
|
+
|
|
86
|
+
system "two ZedBoards and a host computer" {
|
|
87
|
+
board a: "zed-a"
|
|
88
|
+
board b: "zed-b"
|
|
89
|
+
bus can0: can(bitrate: 1 Mbit/s, members: [a.can, b.can])
|
|
90
|
+
node host: csp(address: 10, link: a.kiss)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
setup {
|
|
94
|
+
parallel {
|
|
95
|
+
task zed_a { booted(a, 1) }
|
|
96
|
+
task zed_b { booted(b, 2) }
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
test "the host computer reaches zed-b through zed-a" {
|
|
101
|
+
check host.ping(b) < 50 ms
|
|
102
|
+
check can0.traffic().routes["10->2:1"] >= 1
|
|
103
|
+
}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
This is the start of `examples/zedboard/plans/multiboard.jig`. The setup
|
|
107
|
+
waits, on both boards at once, for the boot banner and the first prompt
|
|
108
|
+
(`booted` comes from the example's plan library, `lib/zed.jig`). The test
|
|
109
|
+
then pings zed-b from the host computer through zed-a using the lightweight
|
|
110
|
+
network protocol [LibCSP](https://github.com/libcsp/libcsp).
|
|
111
|
+
|
|
112
|
+
Every check's result is printed in a report with the simulated time it ran
|
|
113
|
+
at. Each run gets a fingerprint, a SHA-256 digest of everything it decided
|
|
114
|
+
and measured in simulated time, so `jig run --repeat 3` proves a plan gives
|
|
115
|
+
the same result three times, on any host.
|
|
116
|
+
|
|
117
|
+
Boards are data: a `board.yaml` names the platform (in this case the
|
|
118
|
+
[ZedBoard][zedboard], running a Zynq-7020 SoC), the Renode platform
|
|
119
|
+
description, the firmware and the interfaces plans use. Users must bring
|
|
120
|
+
their own C# models and Python plugins for any peripherals not directly
|
|
121
|
+
supported by Jig.
|
|
122
|
+
|
|
123
|
+
[zedboard]: https://www.avnet.com/americas/products/avnet-boards/avnet-board-families/zedboard/
|
|
124
|
+
|
|
125
|
+
## Requirements
|
|
126
|
+
|
|
127
|
+
- Python 3.11 or later.
|
|
128
|
+
- Renode 1.17.0. Jig finds an installed copy, or `jig renode install`
|
|
129
|
+
downloads the release from Renode's GitHub page after you agree, and
|
|
130
|
+
checks its size and SHA-256 digest.
|
|
131
|
+
- For the ZedBoard example's firmware, `arm-none-eabi-gcc`.
|
|
132
|
+
- For RTL co-simulation, Linux with Verilator.
|
|
133
|
+
|
|
134
|
+
## Installation
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
python -m pip install jig-dsl
|
|
138
|
+
jig renode install
|
|
139
|
+
jig doctor
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## Quick Start
|
|
143
|
+
|
|
144
|
+
The repository's ZedBoard example runs two Zynq-7020 boards on one CAN
|
|
145
|
+
bus, with a host computer on a serial link to the first:
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
git clone https://github.com/GNPower/Jig.git
|
|
149
|
+
cd Jig
|
|
150
|
+
python examples/zedboard/firmware/build.py
|
|
151
|
+
jig run examples/zedboard/plans/multiboard.jig --html out/report.html
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
`build.py` needs `arm-none-eabi-gcc` on the `PATH`. The tutorials in the
|
|
155
|
+
documentation build plans like this one step by step.
|
|
156
|
+
|
|
157
|
+
## Features
|
|
158
|
+
|
|
159
|
+
- **A plan language for firmware tests**: consoles, network requests, checks,
|
|
160
|
+
waits, loops, parallel tasks, timelines and recorded values, all in
|
|
161
|
+
simulated time.
|
|
162
|
+
- **Several boards in one emulation**, on bus interfaces the harness carries
|
|
163
|
+
and watches, with repeatable timing between them.
|
|
164
|
+
- **Zynq-7000 and STM32 drivers**: JTAG-style and Quad-SPI boots through
|
|
165
|
+
Jig's Zynq-7000 models, and ELF boots on Renode's STM32 platforms.
|
|
166
|
+
- **CSP in libcsp 1.6's wire format**: KISS and CAN links, RDP
|
|
167
|
+
connections, and a bus observer that reassembles every packet.
|
|
168
|
+
- **Board plugins** through `jig.plugin`, a versioned API for plan
|
|
169
|
+
functions, board members, decoders and device drivers.
|
|
170
|
+
- **C# models compiled by Renode at start-up**, with interfaces that board
|
|
171
|
+
chip models implement so plans can drive them.
|
|
172
|
+
- **Reports for people and CI**: an HTML page with plots, JUnit XML, a JSON
|
|
173
|
+
summary, run directories with every console transcript and packet, and
|
|
174
|
+
exit codes that differentiate failed tests from broken setups.
|
|
175
|
+
- **RTL co-simulation** of programmable-logic blocks with Verilator.
|
|
176
|
+
- **A localhost bridge to Renode** with a token per run, in place of
|
|
177
|
+
Renode's network-facing Robot Framework server.
|
|
178
|
+
|
|
179
|
+
## License
|
|
180
|
+
|
|
181
|
+
MIT: see [LICENSE](https://github.com/GNPower/Jig/blob/main/LICENSE).
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# Jig
|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/project/jig-dsl/)
|
|
4
|
+
[](https://pypi.org/project/jig-dsl/)
|
|
5
|
+
[](https://github.com/GNPower/Jig/actions/workflows/ci.yml)
|
|
6
|
+
[](https://codecov.io/gh/GNPower/Jig)
|
|
7
|
+
[](https://jig-dsl.readthedocs.io/en/latest/)
|
|
8
|
+
[](https://github.com/GNPower/Jig/blob/main/LICENSE)
|
|
9
|
+
|
|
10
|
+
**A test language and harness for firmware running on the Renode
|
|
11
|
+
emulator.**
|
|
12
|
+
|
|
13
|
+
Full documentation: <https://jig-dsl.readthedocs.io>
|
|
14
|
+
|
|
15
|
+
## Overview
|
|
16
|
+
|
|
17
|
+
Jig runs test plans, written as `.jig` files, against a users firmware
|
|
18
|
+
on [Renode](https://renode.io). A plan declares the boards it needs, the
|
|
19
|
+
buse interfaces between them and the communication protocols the harness uses, then
|
|
20
|
+
drives the system over those interfaces in simulated time:
|
|
21
|
+
|
|
22
|
+
```jig
|
|
23
|
+
import "zed.jig"
|
|
24
|
+
|
|
25
|
+
system "two ZedBoards and a host computer" {
|
|
26
|
+
board a: "zed-a"
|
|
27
|
+
board b: "zed-b"
|
|
28
|
+
bus can0: can(bitrate: 1 Mbit/s, members: [a.can, b.can])
|
|
29
|
+
node host: csp(address: 10, link: a.kiss)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
setup {
|
|
33
|
+
parallel {
|
|
34
|
+
task zed_a { booted(a, 1) }
|
|
35
|
+
task zed_b { booted(b, 2) }
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
test "the host computer reaches zed-b through zed-a" {
|
|
40
|
+
check host.ping(b) < 50 ms
|
|
41
|
+
check can0.traffic().routes["10->2:1"] >= 1
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
This is the start of `examples/zedboard/plans/multiboard.jig`. The setup
|
|
46
|
+
waits, on both boards at once, for the boot banner and the first prompt
|
|
47
|
+
(`booted` comes from the example's plan library, `lib/zed.jig`). The test
|
|
48
|
+
then pings zed-b from the host computer through zed-a using the lightweight
|
|
49
|
+
network protocol [LibCSP](https://github.com/libcsp/libcsp).
|
|
50
|
+
|
|
51
|
+
Every check's result is printed in a report with the simulated time it ran
|
|
52
|
+
at. Each run gets a fingerprint, a SHA-256 digest of everything it decided
|
|
53
|
+
and measured in simulated time, so `jig run --repeat 3` proves a plan gives
|
|
54
|
+
the same result three times, on any host.
|
|
55
|
+
|
|
56
|
+
Boards are data: a `board.yaml` names the platform (in this case the
|
|
57
|
+
[ZedBoard][zedboard], running a Zynq-7020 SoC), the Renode platform
|
|
58
|
+
description, the firmware and the interfaces plans use. Users must bring
|
|
59
|
+
their own C# models and Python plugins for any peripherals not directly
|
|
60
|
+
supported by Jig.
|
|
61
|
+
|
|
62
|
+
[zedboard]: https://www.avnet.com/americas/products/avnet-boards/avnet-board-families/zedboard/
|
|
63
|
+
|
|
64
|
+
## Requirements
|
|
65
|
+
|
|
66
|
+
- Python 3.11 or later.
|
|
67
|
+
- Renode 1.17.0. Jig finds an installed copy, or `jig renode install`
|
|
68
|
+
downloads the release from Renode's GitHub page after you agree, and
|
|
69
|
+
checks its size and SHA-256 digest.
|
|
70
|
+
- For the ZedBoard example's firmware, `arm-none-eabi-gcc`.
|
|
71
|
+
- For RTL co-simulation, Linux with Verilator.
|
|
72
|
+
|
|
73
|
+
## Installation
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
python -m pip install jig-dsl
|
|
77
|
+
jig renode install
|
|
78
|
+
jig doctor
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Quick Start
|
|
82
|
+
|
|
83
|
+
The repository's ZedBoard example runs two Zynq-7020 boards on one CAN
|
|
84
|
+
bus, with a host computer on a serial link to the first:
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
git clone https://github.com/GNPower/Jig.git
|
|
88
|
+
cd Jig
|
|
89
|
+
python examples/zedboard/firmware/build.py
|
|
90
|
+
jig run examples/zedboard/plans/multiboard.jig --html out/report.html
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
`build.py` needs `arm-none-eabi-gcc` on the `PATH`. The tutorials in the
|
|
94
|
+
documentation build plans like this one step by step.
|
|
95
|
+
|
|
96
|
+
## Features
|
|
97
|
+
|
|
98
|
+
- **A plan language for firmware tests**: consoles, network requests, checks,
|
|
99
|
+
waits, loops, parallel tasks, timelines and recorded values, all in
|
|
100
|
+
simulated time.
|
|
101
|
+
- **Several boards in one emulation**, on bus interfaces the harness carries
|
|
102
|
+
and watches, with repeatable timing between them.
|
|
103
|
+
- **Zynq-7000 and STM32 drivers**: JTAG-style and Quad-SPI boots through
|
|
104
|
+
Jig's Zynq-7000 models, and ELF boots on Renode's STM32 platforms.
|
|
105
|
+
- **CSP in libcsp 1.6's wire format**: KISS and CAN links, RDP
|
|
106
|
+
connections, and a bus observer that reassembles every packet.
|
|
107
|
+
- **Board plugins** through `jig.plugin`, a versioned API for plan
|
|
108
|
+
functions, board members, decoders and device drivers.
|
|
109
|
+
- **C# models compiled by Renode at start-up**, with interfaces that board
|
|
110
|
+
chip models implement so plans can drive them.
|
|
111
|
+
- **Reports for people and CI**: an HTML page with plots, JUnit XML, a JSON
|
|
112
|
+
summary, run directories with every console transcript and packet, and
|
|
113
|
+
exit codes that differentiate failed tests from broken setups.
|
|
114
|
+
- **RTL co-simulation** of programmable-logic blocks with Verilator.
|
|
115
|
+
- **A localhost bridge to Renode** with a token per run, in place of
|
|
116
|
+
Renode's network-facing Robot Framework server.
|
|
117
|
+
|
|
118
|
+
## License
|
|
119
|
+
|
|
120
|
+
MIT: see [LICENSE](https://github.com/GNPower/Jig/blob/main/LICENSE).
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=77.0.3"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "jig-dsl"
|
|
7
|
+
version = "0.1.0a1"
|
|
8
|
+
description = "A test language and harness for firmware running on the Renode emulator"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.11"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
license-files = ["LICENSE"]
|
|
13
|
+
authors = [{ name = "Graham Power" }]
|
|
14
|
+
keywords = ["renode", "emulation", "firmware", "testing", "embedded", "zynq", "stm32", "csp"]
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Development Status :: 3 - Alpha",
|
|
17
|
+
"Environment :: Console",
|
|
18
|
+
"Intended Audience :: Developers",
|
|
19
|
+
"Operating System :: MacOS",
|
|
20
|
+
"Operating System :: Microsoft :: Windows",
|
|
21
|
+
"Operating System :: POSIX :: Linux",
|
|
22
|
+
"Programming Language :: Python :: 3",
|
|
23
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
24
|
+
"Programming Language :: Python :: 3.11",
|
|
25
|
+
"Programming Language :: Python :: 3.12",
|
|
26
|
+
"Programming Language :: Python :: 3.13",
|
|
27
|
+
"Programming Language :: Python :: 3.14",
|
|
28
|
+
"Topic :: Software Development :: Embedded Systems",
|
|
29
|
+
"Topic :: Software Development :: Testing",
|
|
30
|
+
"Topic :: System :: Emulators",
|
|
31
|
+
"Typing :: Typed",
|
|
32
|
+
]
|
|
33
|
+
dependencies = [
|
|
34
|
+
"textX>=4,<5",
|
|
35
|
+
"PyYAML>=6,<7",
|
|
36
|
+
"platformdirs>=4,<5",
|
|
37
|
+
"packaging>=23",
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
[project.urls]
|
|
41
|
+
Homepage = "https://github.com/GNPower/Jig"
|
|
42
|
+
Documentation = "https://jig-dsl.readthedocs.io"
|
|
43
|
+
Repository = "https://github.com/GNPower/Jig"
|
|
44
|
+
Issues = "https://github.com/GNPower/Jig/issues"
|
|
45
|
+
Changelog = "https://github.com/GNPower/Jig/blob/main/CHANGELOG.md"
|
|
46
|
+
|
|
47
|
+
# The tool sets noxfile.py installs per session, CI and `dev` use the same pins.
|
|
48
|
+
[project.optional-dependencies]
|
|
49
|
+
test = [
|
|
50
|
+
"pytest>=8,<10",
|
|
51
|
+
"pytest-cov>=6,<8",
|
|
52
|
+
]
|
|
53
|
+
docs = [
|
|
54
|
+
"sphinx>=8.2,<10",
|
|
55
|
+
"sphinx-rtd-theme>=3.0,<4",
|
|
56
|
+
"myst-parser>=4.0,<6",
|
|
57
|
+
]
|
|
58
|
+
lint = [
|
|
59
|
+
"ruff==0.17.0",
|
|
60
|
+
"pymarkdownlnt==0.9.40",
|
|
61
|
+
"zizmor==1.30.1",
|
|
62
|
+
]
|
|
63
|
+
typecheck = [
|
|
64
|
+
"jig-dsl[test]",
|
|
65
|
+
"mypy==2.4.0",
|
|
66
|
+
"types-PyYAML==6.0.12.20260906",
|
|
67
|
+
"nox==2026.8.17",
|
|
68
|
+
]
|
|
69
|
+
release = [
|
|
70
|
+
"build==1.6.1",
|
|
71
|
+
"twine==7.0.0",
|
|
72
|
+
"bump-my-version==1.5.1",
|
|
73
|
+
]
|
|
74
|
+
audit = [
|
|
75
|
+
"pip-audit==2.10.1",
|
|
76
|
+
]
|
|
77
|
+
dev = [
|
|
78
|
+
"jig-dsl[test,lint,typecheck,release]",
|
|
79
|
+
]
|
|
80
|
+
|
|
81
|
+
[project.scripts]
|
|
82
|
+
jig = "jig.cli:main"
|
|
83
|
+
|
|
84
|
+
# SoC families: a board's `soc:` names one (board.yaml `soc_driver:` overrides).
|
|
85
|
+
[project.entry-points."jig.socs"]
|
|
86
|
+
zynq7000 = "jig.ext.zynq7000.driver:Zynq7000Driver"
|
|
87
|
+
stm32 = "jig.system.socs.stm32:Stm32Driver"
|
|
88
|
+
|
|
89
|
+
# Highlighting for .jig plans in Sphinx, Pygments and any tool built on them.
|
|
90
|
+
[project.entry-points."pygments.lexers"]
|
|
91
|
+
jig = "jig.lang.pygments_lexer:JigLexer"
|
|
92
|
+
|
|
93
|
+
[tool.setuptools.packages.find]
|
|
94
|
+
where = ["src"]
|
|
95
|
+
|
|
96
|
+
[tool.setuptools.package-data]
|
|
97
|
+
jig = [
|
|
98
|
+
"py.typed",
|
|
99
|
+
"lang/grammar/*.tx",
|
|
100
|
+
"lang/stdlib/*.jig",
|
|
101
|
+
"models/**/*.cs",
|
|
102
|
+
"platforms/**/*.repl",
|
|
103
|
+
"ext/cosim/templates/*",
|
|
104
|
+
]
|
|
105
|
+
|
|
106
|
+
[tool.ruff]
|
|
107
|
+
line-length = 100
|
|
108
|
+
target-version = "py311"
|
|
109
|
+
# Markdown keeps to 80 columns (PyMarkdown, .markdownlint.json). The Python in
|
|
110
|
+
# its code blocks is wrapped by hand, so ruff leaves .md files alone.
|
|
111
|
+
extend-exclude = ["*.md"]
|
|
112
|
+
|
|
113
|
+
[tool.ruff.lint]
|
|
114
|
+
select = ["E", "F", "W", "I", "B", "UP", "SIM", "RUF", "PTH", "N"]
|
|
115
|
+
|
|
116
|
+
[tool.ruff.lint.per-file-ignores]
|
|
117
|
+
# Statement and expression handlers are named after the grammar's rules (_s_If, _e_Sum).
|
|
118
|
+
"src/jig/lang/interpreter.py" = ["N802"]
|
|
119
|
+
|
|
120
|
+
[tool.mypy]
|
|
121
|
+
python_version = "3.11"
|
|
122
|
+
strict = true
|
|
123
|
+
warn_unreachable = true
|
|
124
|
+
mypy_path = "src"
|
|
125
|
+
packages = ["jig"]
|
|
126
|
+
|
|
127
|
+
[[tool.mypy.overrides]]
|
|
128
|
+
module = ["textx.*", "pygments.*"]
|
|
129
|
+
ignore_missing_imports = true
|
|
130
|
+
|
|
131
|
+
[tool.pytest.ini_options]
|
|
132
|
+
# The example's plugin tests need no Renode, so they run with the unit tests.
|
|
133
|
+
testpaths = ["tests", "examples/zedboard/tests"]
|
|
134
|
+
# The Renode tier starts Renode; run it with -m renode (JIG_RENODE set).
|
|
135
|
+
addopts = "-m 'not renode and not firmware and not slow' --strict-markers"
|
|
136
|
+
markers = [
|
|
137
|
+
"renode: needs Renode (JIG_RENODE or a managed install)",
|
|
138
|
+
"firmware: needs built firmware artifacts",
|
|
139
|
+
"slow: takes more than a few seconds",
|
|
140
|
+
]
|
|
141
|
+
|
|
142
|
+
[tool.coverage.run]
|
|
143
|
+
source_pkgs = ["jig"]
|
|
144
|
+
branch = true
|
|
145
|
+
|
|
146
|
+
[tool.coverage.report]
|
|
147
|
+
show_missing = true
|
|
148
|
+
skip_covered = true
|
|
149
|
+
exclude_also = [
|
|
150
|
+
"if TYPE_CHECKING:",
|
|
151
|
+
"raise NotImplementedError",
|
|
152
|
+
"if __name__ == .__main__.:",
|
|
153
|
+
]
|
|
154
|
+
|
|
155
|
+
# PyMarkdown (`nox -s lint`) with the rules of .markdownlint.json: every rule
|
|
156
|
+
# on, and a heading may repeat one outside its own section.
|
|
157
|
+
[tool.pymarkdown]
|
|
158
|
+
plugins.md024.siblings_only = true
|
|
159
|
+
|
|
160
|
+
# One static version, here: jig.__version__ and the docs read it from the
|
|
161
|
+
# installed metadata. bump-my-version neither commits nor tags. The release
|
|
162
|
+
# commit and the tag vX.Y.Z are signed by hand. PEP 440 forms:
|
|
163
|
+
# 0.1.0.dev0 -> (pre_l) 0.1.0a1 -> (pre_n) 0.1.0a2 -> (pre_l) 0.1.0b1 ->
|
|
164
|
+
# 0.1.0rc1 -> 0.1.0 -> (patch) 0.1.1.dev1.
|
|
165
|
+
[tool.bumpversion]
|
|
166
|
+
current_version = "0.1.0a1"
|
|
167
|
+
parse = """(?x)
|
|
168
|
+
(?P<major>0|[1-9]\\d*)\\.
|
|
169
|
+
(?P<minor>0|[1-9]\\d*)\\.
|
|
170
|
+
(?P<patch>0|[1-9]\\d*)
|
|
171
|
+
(?:(?P<pre_l>\\.dev|a|b|rc)(?P<pre_n>0|[1-9]\\d*))?
|
|
172
|
+
"""
|
|
173
|
+
serialize = [
|
|
174
|
+
"{major}.{minor}.{patch}{pre_l}{pre_n}",
|
|
175
|
+
"{major}.{minor}.{patch}",
|
|
176
|
+
]
|
|
177
|
+
commit = false
|
|
178
|
+
tag = false
|
|
179
|
+
allow_dirty = true
|
|
180
|
+
|
|
181
|
+
[tool.bumpversion.parts.pre_l]
|
|
182
|
+
values = [".dev", "a", "b", "rc", "final"]
|
|
183
|
+
optional_value = "final"
|
|
184
|
+
|
|
185
|
+
[tool.bumpversion.parts.pre_n]
|
|
186
|
+
first_value = "1"
|
|
187
|
+
|
|
188
|
+
[[tool.bumpversion.files]]
|
|
189
|
+
filename = "pyproject.toml"
|
|
190
|
+
regex = true
|
|
191
|
+
search = '^version = "{current_version}"'
|
|
192
|
+
replace = 'version = "{new_version}"'
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""Jig: a test language and harness for firmware running on Renode.
|
|
2
|
+
|
|
3
|
+
The package is board-agnostic. Boards are described by data (``board.yaml``),
|
|
4
|
+
peripheral models are C# sources Renode loads, and test plans are ``.jig``
|
|
5
|
+
scripts.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
9
|
+
|
|
10
|
+
try:
|
|
11
|
+
__version__ = version("jig-dsl")
|
|
12
|
+
except PackageNotFoundError: # a source tree that was never installed
|
|
13
|
+
__version__ = "0+unknown"
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"""Content-addressed cache for generated artifacts.
|
|
2
|
+
|
|
3
|
+
Register maps, register programs and flash images are derived from files in
|
|
4
|
+
the repository (RTL, XSA, ELFs). Each artifact lives in
|
|
5
|
+
``<cache>/artifacts/<kind>-<digest>/``, where the digest covers the contents of every
|
|
6
|
+
input file and the build parameters, so a changed input produces a new entry
|
|
7
|
+
and an unchanged one is reused. Entries are built in a temporary directory and
|
|
8
|
+
renamed into place, so an interrupted build never leaves a half-written entry.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import hashlib
|
|
14
|
+
import json
|
|
15
|
+
import logging
|
|
16
|
+
import shutil
|
|
17
|
+
import tempfile
|
|
18
|
+
from collections.abc import Callable, Iterable
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
from typing import Any
|
|
21
|
+
|
|
22
|
+
from jig import project
|
|
23
|
+
from jig.errors import ConfigError
|
|
24
|
+
|
|
25
|
+
log = logging.getLogger(__name__)
|
|
26
|
+
|
|
27
|
+
_COMPLETE = ".complete"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def cache_root() -> Path:
|
|
31
|
+
return project.artifacts_dir()
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def digest(inputs: Iterable[Path], params: dict[str, Any] | None = None) -> str:
|
|
35
|
+
hasher = hashlib.sha256()
|
|
36
|
+
for path in inputs:
|
|
37
|
+
if not path.is_file():
|
|
38
|
+
raise ConfigError(f"artifact input {path} does not exist")
|
|
39
|
+
hasher.update(path.name.encode())
|
|
40
|
+
with path.open("rb") as handle:
|
|
41
|
+
for block in iter(lambda: handle.read(1 << 20), b""):
|
|
42
|
+
hasher.update(block)
|
|
43
|
+
hasher.update(json.dumps(params or {}, sort_keys=True, default=str).encode())
|
|
44
|
+
return hasher.hexdigest()[:16]
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def cached(
|
|
48
|
+
kind: str,
|
|
49
|
+
inputs: Iterable[Path],
|
|
50
|
+
build: Callable[[Path], None],
|
|
51
|
+
params: dict[str, Any] | None = None,
|
|
52
|
+
) -> Path:
|
|
53
|
+
"""Directory holding the artifact; ``build(directory)`` fills a new one."""
|
|
54
|
+
input_list = list(inputs)
|
|
55
|
+
entry = cache_root() / f"{kind}-{digest(input_list, params)}"
|
|
56
|
+
if (entry / _COMPLETE).exists():
|
|
57
|
+
return entry
|
|
58
|
+
entry.parent.mkdir(parents=True, exist_ok=True)
|
|
59
|
+
staging = Path(tempfile.mkdtemp(prefix=f"{kind}-", dir=entry.parent))
|
|
60
|
+
try:
|
|
61
|
+
log.info("building %s", entry.name)
|
|
62
|
+
build(staging)
|
|
63
|
+
(staging / _COMPLETE).write_text(
|
|
64
|
+
json.dumps(
|
|
65
|
+
{"inputs": [str(p) for p in input_list], "params": params or {}},
|
|
66
|
+
default=str,
|
|
67
|
+
indent=2,
|
|
68
|
+
),
|
|
69
|
+
encoding="utf-8",
|
|
70
|
+
)
|
|
71
|
+
if entry.exists():
|
|
72
|
+
shutil.rmtree(entry)
|
|
73
|
+
staging.rename(entry)
|
|
74
|
+
except BaseException:
|
|
75
|
+
shutil.rmtree(staging, ignore_errors=True)
|
|
76
|
+
raise
|
|
77
|
+
return entry
|