Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

scripts: west: genboard: use JSON schema #18891

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions scripts/requirements-base.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
west>=1.0.0
certifi >=2024.7.4 # from requests above; https://nvd.nist.gov/vuln/detail/CVE-2024-39689
jsonschema
78 changes: 43 additions & 35 deletions scripts/west_commands/genboard/ncs_genboard.py
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can I ask to add a logging messages to stdout that unambiguously inform about the successful generation of the board at the end of the process, please. And other one that informs about the errors?

We are going to integrate with this tool on windows, linux, and macOs which makes logging through the stdout the simplest way to interface through.

For example "The board has been successfully generated" and "Failed to generate board - schema validation error" and "Failed to generate board - (...) whatever other recognizable failure occurred".

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added some messages, pls check

Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@
# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause

from pathlib import Path
import re
import json
import shutil

from jinja2 import Environment, FileSystemLoader
from west.commands import WestCommand
from west import log
from yaml import load
import jsonschema

try:
from yaml import CLoader as Loader
Expand All @@ -19,9 +20,7 @@
SCRIPT_DIR = Path(__file__).absolute().parent
TEMPLATE_DIR = SCRIPT_DIR / "templates"
CONFIG = SCRIPT_DIR / "config.yml"

VENDOR_RE = re.compile(r"^[a-zA-Z0-9_-]+$")
BOARD_RE = re.compile(r"^[a-zA-Z0-9_-]+$")
SCHEMA = SCRIPT_DIR / "schema.json"


class NcsGenboard(WestCommand):
Expand All @@ -36,31 +35,38 @@ def do_add_parser(self, parser_adder):
self.name, help=self.help, description=self.description
)

parser.add_argument(
"-o", "--output", required=True, type=Path, help="Output directory"
group = parser.add_mutually_exclusive_group(required=True)
group.add_argument(
"-s", "--json-schema", action="store_true", help="Provide JSON schema"
)
parser.add_argument("-e", "--vendor", required=True, help="Vendor name")
parser.add_argument("-b", "--board", required=True, help="Board name")
parser.add_argument(
"-d", "--board-desc", required=True, help="Board description"
group.add_argument(
"-r", "--json-schema-response", type=str, help="JSON schema response"
)
parser.add_argument("-s", "--soc", required=True, help="SoC")
parser.add_argument("-v", "--variant", required=True, help="Variant")

return parser

def do_run(self, args, unknown_args):
with open(SCHEMA, "r") as f:
schema = json.loads(f.read())

if args.json_schema:
print(json.dumps(schema))
return

with open(CONFIG, "r") as f:
config = load(f, Loader=Loader)

# validate input
if not VENDOR_RE.match(args.vendor):
log.err(f"Invalid vendor name: {args.vendor}")
return
input = json.loads(args.json_schema_response)
gmarull marked this conversation as resolved.
Show resolved Hide resolved

if not BOARD_RE.match(args.board):
log.err(f"Invalid board name: {args.board}")
return
try:
jsonschema.validate(input, schema)
except jsonschema.ValidationError as e:
raise Exception("Board configuration is not valid") from e

soc_parts = input["soc"].split("-")
req_soc = soc_parts[0].lower()
req_variant = soc_parts[1].lower()

series = None
soc = None
Expand All @@ -69,18 +75,18 @@ def do_run(self, args, unknown_args):
break

for soc_ in product["socs"]:
if args.soc == soc_["name"]:
if req_soc == soc_["name"]:
series = product["series"]
soc = soc_
break

if not series:
log.err(f"Invalid/unsupported SoC: {args.soc}")
log.err(f"Invalid/unsupported SoC: {req_soc}")
return

targets = []
for variant in soc["variants"]:
if args.variant == variant["name"]:
if req_variant == variant["name"]:
if "cores" in variant:
for core in variant["cores"]:
target = {
Expand Down Expand Up @@ -119,7 +125,7 @@ def do_run(self, args, unknown_args):
break

if not targets:
log.err(f"Invalid/unsupported variant: {args.variant}")
log.err(f"Invalid/unsupported variant: {req_variant}")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it make sense to validate soc and variant after the validation in line 62. passed? All supported combinations of socs and variants can be listed in the schema.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this means the internal config file is not aligned, should really be catched in tests...

return

# prepare Jinja environment
Expand All @@ -129,16 +135,16 @@ def do_run(self, args, unknown_args):
loader=FileSystemLoader(TEMPLATE_DIR / series),
)

env.globals["vendor"] = args.vendor
env.globals["board"] = args.board
env.globals["board_desc"] = args.board_desc
env.globals["vendor"] = input["vendor"]
env.globals["board"] = input["board"]
env.globals["board_desc"] = input["description"]
env.globals["series"] = series
env.globals["soc"] = args.soc
env.globals["variant"] = args.variant
env.globals["soc"] = req_soc
env.globals["variant"] = req_variant
env.globals["targets"] = targets

# render templates/copy files
out_dir = args.output / args.vendor / args.board
out_dir = Path(input["root"]) / "boards" / input["vendor"] / input["board"]
if not out_dir.exists():
out_dir.mkdir(parents=True)

Expand All @@ -147,14 +153,14 @@ def do_run(self, args, unknown_args):
shutil.copy(tmpl, out_dir)

tmpl = TEMPLATE_DIR / series / "board-pinctrl.dtsi"
shutil.copy(tmpl, out_dir / f"{ args.board }-pinctrl.dtsi")
shutil.copy(tmpl, out_dir / f"{ input['board'] }-pinctrl.dtsi")

tmpl = env.get_template("board.cmake.jinja2")
with open(out_dir / "board.cmake", "w") as f:
f.write(tmpl.render())

tmpl = env.get_template("Kconfig.board.jinja2")
with open(out_dir / f"Kconfig.{args.board}", "w") as f:
with open(out_dir / f"Kconfig.{input['board']}", "w") as f:
f.write(tmpl.render())

tmpl = env.get_template("board.yml.jinja2")
Expand All @@ -168,23 +174,23 @@ def do_run(self, args, unknown_args):
# nrf53 specific files
if series == "nrf53":
tmpl = env.get_template("board-cpuapp_partitioning.dtsi.jinja2")
with open(out_dir / f"{ args.board }-cpuapp_partitioning.dtsi", "w") as f:
with open(out_dir / f"{ input['board'] }-cpuapp_partitioning.dtsi", "w") as f:
f.write(tmpl.render(config))

tmpl = TEMPLATE_DIR / series / "board-shared_sram.dtsi"
shutil.copy(tmpl, out_dir / f"{ args.board }-shared_sram.dtsi")
shutil.copy(tmpl, out_dir / f"{ input['board'] }-shared_sram.dtsi")

# nrf91 specific files
if series == "nrf91":
tmpl = env.get_template("board-partitioning.dtsi.jinja2")
with open(out_dir / f"{ args.board }-partitioning.dtsi", "w") as f:
with open(out_dir / f"{ input['board'] }-partitioning.dtsi", "w") as f:
f.write(tmpl.render(config))

# per-target files
for target in targets:
name = args.board
name = input["board"]
if target.get("core"):
name += f"_{args.soc}_{target['core']}"
name += f"_{req_soc}_{target['core']}"
if target["ns"]:
name += "_ns"
if target["xip"]:
Expand All @@ -201,3 +207,5 @@ def do_run(self, args, unknown_args):
tmpl = env.get_template("board_twister.yml.jinja2")
with open(out_dir / f"{name}.yml", "w") as f:
f.write(tmpl.render(target=target))

print(f"Board {input['board']} generated successfully")
54 changes: 54 additions & 0 deletions scripts/west_commands/genboard/schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
{
"title": "NCS board",
"type": "object",
"required": [
"board",
"description",
"vendor",
"soc",
"root"
],
"properties": {
"board": {
"title": "Board name",
"type": "string",
"pattern": "^[a-zA-Z0-9_-]+$"
},
"description": {
"title": "Description",
"type": "string"
},
"vendor": {
"title": "Vendor name",
"type": "string",
"pattern": "^[a-zA-Z0-9_-]+$"
},
"soc": {
"title": "SoC",
"type": "string",
"enum": [
"nRF52805-CAAA",
"nRF52810-QFAA",
"nRF52811-QFAA",
"nRF52820-QDAA",
"nRF52832-CIAA",
"nRF52832-QFAA",
"nRF52832-QFAB",
"nRF52833-QDAA",
"nRF52833-QIAA",
"nRF52840-QFAA",
"nRF52840-QIAA",
"nRF5340-QKAA",
"nRF54L15-QFAA",
"nRF9131-LACA",
"nRF9151-LACA",
"nRF9160-SICA",
"nRF9161-LACA"
]
},
"root": {
"title": "Board root",
"type": "string"
}
}
}
77 changes: 37 additions & 40 deletions scripts/west_commands/genboard/west-ncs-genboard-test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -8,64 +8,61 @@ NCS_BASE="$SCRIPTDIR/../../.."
SOC=${1}

declare -a SOCS=(
"nrf52805 caaa"
"nrf52810 qfaa"
"nrf52811 qfaa"
"nrf52820 qdaa"
"nrf52832 ciaa"
"nrf52832 qfaa"
"nrf52832 qfab"
"nrf52833 qdaa"
"nrf52833 qiaa"
"nrf52840 qfaa"
"nrf52840 qiaa"
"nrf5340 qkaa"
"nrf54l15 qfaa"
"nrf9131 laca"
"nrf9151 laca"
"nrf9160 sica"
"nrf9161 laca"
"nRF52805-CAAA"
"nRF52810-QFAA"
"nRF52811-QFAA"
"nRF52820-QDAA"
"nRF52832-CIAA"
"nRF52832-QFAA"
"nRF52832-QFAB"
"nRF52833-QDAA"
"nRF52833-QIAA"
"nRF52840-QFAA"
"nRF52840-QIAA"
"nRF5340-QKAA"
"nRF54L15-QFAA"
"nRF9131-LACA"
"nRF9151-LACA"
"nRF9160-SICA"
"nRF9161-LACA"
)

HELLO_WORLD="$NCS_BASE/../zephyr/samples/hello_world"

rm -rf $NCS_BASE/boards/testvnd

for soc in "${SOCS[@]}"; do
read -a socarr <<< "$soc"
soc_parts=(${soc//-/ })
soc_name=$(echo ${soc_parts[0]} | tr "[:upper:]" "[:lower:]")
soc_variant=$(echo ${soc_parts[1]} | tr "[:upper:]" "[:lower:]")

if [ ! -z "$SOC" ] && [ $SOC != ${socarr[0]} ]; then
echo "Skipping ${socarr[0]} (not requested)"
if [ ! -z "$SOC" ] && [ $SOC != ${soc} ]; then
echo "Skipping $soc (not requested)"
continue
fi

board=brd_${socarr[0]}_${socarr[1]}
board=brd_${soc_name}_${soc_variant}

echo "Generating board: $board"

west ncs-genboard \
-o $NCS_BASE/boards \
-e "testvnd" \
-b $board \
-d "Test Board" \
-s ${socarr[0]} \
-v ${socarr[1]} \
west ncs-genboard --json-schema-response \
"{\"board\": \"$board\", \"description\": \"Test Board\", \"vendor\": \"testvnd\", \"soc\": \"$soc\", \"root\": \"$NCS_BASE\"}"

echo "Building hello_world for: $board"

if [[ ${socarr[0]} == nrf52* ]]; then
if [[ $soc == nRF52* ]]; then
west build -p -b $board $HELLO_WORLD
elif [[ ${socarr[0]} == nrf53* ]]; then
west build -p -b $board/${socarr[0]}/cpuapp $HELLO_WORLD
west build -p -b $board/${socarr[0]}/cpuapp/ns $HELLO_WORLD
west build -p -b $board/${socarr[0]}/cpunet $HELLO_WORLD
elif [[ ${socarr[0]} == nrf54l* ]]; then
west build -p -b $board/${socarr[0]}/cpuapp $HELLO_WORLD
# west build -p -b $board/${socarr[0]}/cpuflpr $HELLO_WORLD
# west build -p -b $board/${socarr[0]}/cpuflpr/xip $HELLO_WORLD
elif [[ ${socarr[0]} == nrf91* ]]; then
west build -p -b $board/${socarr[0]} $HELLO_WORLD
west build -p -b $board/${socarr[0]}/ns $HELLO_WORLD
elif [[ $soc == nRF53* ]]; then
west build -p -b $board/$soc_name/cpuapp $HELLO_WORLD
west build -p -b $board/$soc_name/cpuapp/ns $HELLO_WORLD
west build -p -b $board/$soc_name/cpunet $HELLO_WORLD
elif [[ $soc == nRF54L* ]]; then
west build -p -b $board/$soc_name/cpuapp $HELLO_WORLD
# west build -p -b $board/$soc_name/cpuflpr $HELLO_WORLD
# west build -p -b $board/$soc_name/cpuflpr/xip $HELLO_WORLD
elif [[ $soc == nRF91* ]]; then
west build -p -b $board/$soc_name $HELLO_WORLD
west build -p -b $board/$soc_name/ns $HELLO_WORLD
fi

rm -rf $NCS_BASE/boards/testvnd
Expand Down
Loading