Skip to content

Commit f45a2ff

Browse files
authored
Merge pull request #988 from zapta/main
Transition the in-memory representation of programmer definitions fro…
2 parents 706f19f + 4c6bd97 commit f45a2ff

5 files changed

Lines changed: 47 additions & 103 deletions

File tree

apio/commands/apio_api.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -406,7 +406,9 @@ def _get_project_cli(
406406
pr.programmer_id
407407
),
408408
}
409-
programmer_dict["definition"] = pr.programmer_info
409+
programmer_dict["definition"] = proto_util.proto_to_json_dict(
410+
pr.programmer_definition
411+
)
410412
section_dict["programmer"] = programmer_dict
411413

412414
# -- Add section
@@ -625,9 +627,11 @@ def _get_programmers_cli(
625627
section = {}
626628
for (
627629
programmer_id,
628-
programmer_info,
630+
programmer_definition,
629631
) in apio_ctx.definitions.programmers.items():
630-
section[programmer_id] = programmer_info
632+
section[programmer_id] = proto_util.proto_to_json_dict(
633+
programmer_definition
634+
)
631635

632636
top_dict["programmers"] = section
633637

apio/managers/apio_definitions.py

Lines changed: 17 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,13 @@
1212
import re
1313
from pathlib import Path
1414
from typing import Dict, Set, Tuple, Optional
15-
from jsonschema import validate
16-
from jsonschema.exceptions import ValidationError
1715
import json5
1816
from apio.utils import proto_util
1917
from apio.common.apio_console import cout, cerror
2018
from apio.common.proto.apio_definitions_pb2 import (
2119
BoardDefinition,
2220
FpgaDefinition,
21+
ProgrammerDefinition,
2322
)
2423

2524
# -- Boards definitions file name.
@@ -32,83 +31,13 @@
3231
PROGRAMMERS_JSONC = "programmers.jsonc"
3332

3433
# -- A regex for validating boards, fpgas, and programmers ids.
35-
ID_FORMAT = re.compile(r"^[a-z][a-z0-9-]*$")
36-
37-
38-
# -- JSON schema for validating a single fpga definition in fpga.jsonc.
39-
# -- The fields 'part-num' and 'size' are for information only.
40-
FPGA_SCHEMA = schema = {
41-
"$schema": "http://json-schema.org/draft-07/schema#",
42-
"type": "object",
43-
"properties": {
44-
"part-num": {"type": "string"},
45-
"arch": {
46-
"type": "string",
47-
"enum": ["ice40", "ecp5", "gowin", "xilinx"],
48-
},
49-
"size": {"type": "string"},
50-
"ice40-params": {
51-
"type": "object",
52-
"properties": {
53-
"type": {"type": "string"},
54-
"package": {"type": "string"},
55-
},
56-
"required": ["type", "package"],
57-
"additionalProperties": False,
58-
},
59-
"ecp5-params": {
60-
"type": "object",
61-
"properties": {
62-
"type": {"type": "string"},
63-
"package": {"type": "string"},
64-
"speed": {"type": "string"},
65-
},
66-
"required": ["type", "package", "speed"],
67-
"additionalProperties": False,
68-
},
69-
"gowin-params": {
70-
"type": "object",
71-
"properties": {
72-
"yosys-family": {"type": "string"},
73-
"nextpnr-family": {"type": "string"},
74-
"packer-device": {"type": "string"},
75-
},
76-
"required": ["yosys-family", "nextpnr-family", "packer-device"],
77-
"additionalProperties": False,
78-
},
79-
"xilinx-params": {
80-
"type": "object",
81-
"properties": {
82-
"family": {"type": "string"},
83-
"yosys-arch": {"type": "string"},
84-
"package": {"type": "string"},
85-
"speed": {"type": "string"},
86-
},
87-
"required": ["family", "yosys-arch", "package", "speed"],
88-
"additionalProperties": False,
89-
},
90-
},
91-
"required": ["part-num", "arch", "size"],
92-
"additionalProperties": False,
93-
}
94-
95-
96-
# -- JSON schema for validating a single programmer definition in
97-
# -- programmers.jsonc.
98-
PROGRAMMER_SCHEMA = {
99-
"$schema": "http://json-schema.org/draft-07/schema#",
100-
"type": "object",
101-
"required": ["command", "args"],
102-
"properties": {"command": {"type": "string"}, "args": {"type": "string"}},
103-
"additionalProperties": False,
104-
}
34+
DEFINITION_ID_FORMAT = re.compile(r"^[a-z][a-z0-9-]*$")
10535

10636

10737
class ApioDefinitions:
10838
"""Contains the apio definitions in the form of json dictionaries."""
10939

11040
# pylint: disable=too-many-instance-attributes
111-
# pylint: disable=too-many-locals
11241

11342
def __init__(
11443
self,
@@ -146,7 +75,7 @@ def __init__(
14675
# -- Validate boards definitions. Optional project custom definition
14776
# -- supersede apio standard definitions.
14877
for board_id in self.boards:
149-
if not ID_FORMAT.match(board_id):
78+
if not DEFINITION_ID_FORMAT.match(board_id):
15079
cerror(f"Board id has an invalid format: {board_id}")
15180
sys.exit(1)
15281

@@ -169,7 +98,7 @@ def __init__(
16998

17099
# -- Validate fpgas definitions.
171100
for fpga_id, fpga_definition in self.fpgas.items():
172-
if not ID_FORMAT.match(fpga_id):
101+
if not DEFINITION_ID_FORMAT.match(fpga_id):
173102
cerror(f"FPGA id has an invalid format: {fpga_id}")
174103
sys.exit(1)
175104
proto_util.check_is_required(fpga_definition, "part_num")
@@ -185,25 +114,27 @@ def __init__(
185114

186115
# -- Load programmers definitions. Optional project custom definition
187116
# -- supersede apio standard definitions.
188-
self.programmers, self.custom_programmers_ids = self._load_definitions(
117+
programmers_json, self.custom_programmers_ids = self._load_definitions(
189118
PROGRAMMERS_JSONC,
190119
self._package_definitions_dir,
191120
self._project_definitions_dir,
192121
)
193122

123+
# -- Convert the programmers definition dicts to FpgaDefinition protos.
124+
self.programmers: Dict[str, ProgrammerDefinition] = {}
125+
for programmer_id, definition_dict in programmers_json.items():
126+
definition = proto_util.proto_from_json_dict(
127+
definition_dict,
128+
ProgrammerDefinition,
129+
f"Failed to parse programmer definition '{programmer_id}",
130+
)
131+
self.programmers[programmer_id] = definition
132+
194133
# -- Validate programmers definitions.
195-
for programmer_id, programmer_info in self.programmers.items():
196-
if not ID_FORMAT.match(programmer_id):
134+
for programmer_id in self.programmers:
135+
if not DEFINITION_ID_FORMAT.match(programmer_id):
197136
cerror(f"Programmer id has an invalid format: {programmer_id}")
198137
sys.exit(1)
199-
try:
200-
validate(instance=programmer_info, schema=PROGRAMMER_SCHEMA)
201-
except ValidationError as e:
202-
cerror(
203-
f"Invalid programmer definition [{programmer_id}]: "
204-
f"{e.message}"
205-
)
206-
sys.exit(1)
207138

208139
# -- Check references from boards to fpga and programmers
209140
for board_id, board_definition in self.boards.items():

apio/managers/programmers.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -171,16 +171,19 @@ def _construct_cmd_template(apio_ctx: ApioContext) -> str:
171171

172172
pr = apio_ctx.project_resources
173173
board_definition = pr.board_definition
174-
programmer_info = pr.programmer_info
174+
programmer_definition = pr.programmer_definition
175175

176176
# -- Here when using the standard command.
177177

178178
# -- Start building the template with the programmer binary name.
179179
# -- E.g. "openFPGAloader". "command" is a validated required field.
180-
cmd_template = programmer_info["command"]
180+
proto_util.check_is_required(programmer_definition, "command")
181+
cmd_template = programmer_definition.command
181182

182-
# -- Append the optional args template from the programmer.
183-
args = pr.programmer_info.get("args", "")
183+
# -- Append the optional args template from the programmer. The 'args'
184+
# -- field is required but may be empty.
185+
proto_util.check_is_required(programmer_definition, "args")
186+
args = programmer_definition.args
184187
if args:
185188
cmd_template += " "
186189
cmd_template += args

apio/utils/resource_util.py

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
"""Utilities related to the Apio resource files."""
22

33
import sys
4-
from typing import Any, Dict
54
from dataclasses import dataclass
65
from jsonschema import validate
76
from jsonschema.exceptions import ValidationError
@@ -11,6 +10,7 @@
1110
from apio.common.proto.apio_definitions_pb2 import (
1211
BoardDefinition,
1312
FpgaDefinition,
13+
ProgrammerDefinition,
1414
)
1515

1616

@@ -23,7 +23,7 @@ class ProjectResources:
2323
fpga_id: str
2424
fpga_definition: FpgaDefinition
2525
programmer_id: str
26-
programmer_info: Dict[str, Any]
26+
programmer_definition: ProgrammerDefinition
2727

2828

2929
# -- JSON schema for validating config.jsonc.
@@ -127,10 +127,7 @@ def collect_project_resources(
127127

128128
# -- Get programmer id and info.
129129
programmer_id = board_definition.programmer.id
130-
programmer_info = definitions.programmers.get(programmer_id, None)
131-
if programmer_info is None:
132-
cerror(f"Unknown programmer id '{programmer_id}'.")
133-
sys.exit(1)
130+
programmer_definition = definitions.programmers[programmer_id]
134131

135132
# -- Create the project resources bundle.
136133
project_resources = ProjectResources(
@@ -139,7 +136,7 @@ def collect_project_resources(
139136
fpga_id,
140137
fpga_definition,
141138
programmer_id,
142-
programmer_info,
139+
programmer_definition,
143140
)
144141

145142
# -- All done

tests/unit_tests/managers/test_apio_definitions.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -228,9 +228,18 @@ def test_loading_with_custom_programmer(apio_runner: ApioRunner):
228228
assert "alhambra-ii" in definitions.boards
229229
assert "ice40hx4k-tq144-8k" in definitions.fpgas
230230

231-
assert definitions.programmers["iceprog"] == programmer_info1
231+
programmer_definition1 = definitions.programmers["iceprog"]
232232
assert (
233-
definitions.programmers["my-custom-programmer"] == programmer_info2
233+
proto_util.proto_to_json_dict(programmer_definition1)
234+
== programmer_info1
235+
)
236+
237+
programmer_definition2 = definitions.programmers[
238+
"my-custom-programmer"
239+
]
240+
assert (
241+
proto_util.proto_to_json_dict(programmer_definition2)
242+
== programmer_info2
234243
)
235244

236245
assert definitions.is_custom_programmer("iceprog")
@@ -343,7 +352,7 @@ def test_loading_invalid_custom_programmer(apio_runner: ApioRunner):
343352

344353
# -- Verify
345354
assert e.value.code == 1
346-
assert "'command' is a required property" in log.out
355+
assert "Missing required field 'command'" in log.out
347356

348357

349358
def test_loading_invalid_custom_board_id(apio_runner: ApioRunner):

0 commit comments

Comments
 (0)