diff --git a/scripts/schemas/snippet-schema.yaml b/scripts/schemas/snippet-schema.yaml new file mode 100644 index 0000000000000..27198cba0d9b6 --- /dev/null +++ b/scripts/schemas/snippet-schema.yaml @@ -0,0 +1,82 @@ +# SPDX-License-Identifier: Apache-2.0 +# +# Copyright (c) 2022-2025, Nordic Semiconductor ASA + +# JSON Schema for snippet YAML files +# When possible, constraints and validation rules should be modeled directly in this file. + +$schema: "https://json-schema.org/draft/2020-12/schema" +$id: "https://zephyrproject.org/schemas/zephyr/snippet" +title: Zephyr snippet Schema +description: Schema for validating Zephyr snippet files +type: object +$defs: + append-schema: + # Sub-schema for appending onto CMake list variables. + # See uses under 'include: append-schema' keys below. + type: object + properties: + EXTRA_DTC_OVERLAY_FILE: + type: string + EXTRA_CONF_FILE: + type: string + SB_EXTRA_CONF_FILE: + type: string + DTS_EXTRA_CPPFLAGS: + type: string + additionalProperties: false + +properties: + name: + type: string + append: + example: | + Snippet-wide appending can be done here: + + name: foo + append: + EXTRA_DTC_OVERLAY_FILE: m3.overlay + include: append-schema + boards: + example: | + Board-specific appending can be done here: + + name: foo + boards: + qemu_cortex_m3: + append: + EXTRA_DTC_OVERLAY_FILE: m3.overlay + + Board revisions can also be used which includes additional files: + name: foo + boards: + nrf9160dk/nrf9160: + append: + # Base file will be applied first + EXTRA_DTC_OVERLAY_FILE: first.overlay + revisions: + "0.7.0": + append: + # Will be applied on top of common board file + EXTRA_DTC_OVERLAY_FILE: extra_0_7_0.overlay + type: object + patternProperties: + "(.*)": + type: object + properties: + append: + include: append-schema + revisions: + type: object + patternProperties: + "(.*)": + type: object + properties: + append: + include: append-schema + additionalProperties: false + additionalProperties: false + additionalProperties: false +required: + - name +additionalProperties: false diff --git a/scripts/schemas/snippet-schema.yml b/scripts/schemas/snippet-schema.yml deleted file mode 100644 index 319097c58f8d7..0000000000000 --- a/scripts/schemas/snippet-schema.yml +++ /dev/null @@ -1,49 +0,0 @@ -# SPDX-License-Identifier: Apache-2.0 -# -# Copyright (c) 2022, Nordic Semiconductor ASA - -# A pykwalify schema for basic validation of the snippet.yml format. - -schema;append-schema: - # Sub-schema for appending onto CMake list variables. - # See uses under 'append:' keys below. - type: map - mapping: - EXTRA_DTC_OVERLAY_FILE: - type: str - EXTRA_CONF_FILE: - type: str - SB_EXTRA_CONF_FILE: - type: str - DTS_EXTRA_CPPFLAGS: - type: str - -type: map -mapping: - name: - required: true - type: str - append: - example: | - Snippet-wide appending can be done here: - - name: foo - append: - EXTRA_DTC_OVERLAY_FILE: m3.overlay - include: append-schema - boards: - example: | - Board-specific appending can be done here: - - name: foo - boards: - qemu_cortex_m3: - append: - EXTRA_DTC_OVERLAY_FILE: m3.overlay - type: map - mapping: - regex;(.*): - type: map - mapping: - append: - include: append-schema diff --git a/scripts/snippets.py b/scripts/snippets.py index d725f08891e0a..081a7e4ec228a 100644 --- a/scripts/snippets.py +++ b/scripts/snippets.py @@ -18,26 +18,26 @@ from dataclasses import dataclass, field from pathlib import Path, PurePosixPath from typing import Dict, Iterable, List, Set +from jsonschema.exceptions import best_match import argparse import logging import os -import pykwalify.core -import pykwalify.errors import re import sys -import textwrap import yaml import platform +import jsonschema # Marker type for an 'append:' configuration. Maps variables # to the list of values to append to them. Appends = Dict[str, List[str]] +BoardRevisionAppends = Dict[str, Dict[str, List[str]]] def _new_append(): return defaultdict(list) def _new_board2appends(): - return defaultdict(_new_append) + return defaultdict(lambda: defaultdict(_new_append)) @dataclass class Snippet: @@ -46,11 +46,11 @@ class Snippet: name: str appends: Appends = field(default_factory=_new_append) - board2appends: Dict[str, Appends] = field(default_factory=_new_board2appends) + board2appends: Dict[str, BoardRevisionAppends] = field(default_factory=_new_board2appends) def process_data(self, pathobj: Path, snippet_data: dict, sysbuild: bool): '''Process the data in a snippet.yml file, after it is loaded into a - python object and validated by pykwalify.''' + python object and validated by jsonschema.''' def append_value(variable, value): if variable in ('SB_EXTRA_CONF_FILE', 'EXTRA_DTC_OVERLAY_FILE', 'EXTRA_CONF_FILE'): path = pathobj.parent / value @@ -69,10 +69,16 @@ def append_value(variable, value): if board.startswith('/') and not board.endswith('/'): _err(f"snippet file {pathobj}: board {board} starts with '/', so " "it must end with '/' to use a regular expression") + for revision, appenddata in settings.get('revisions', {}).items(): + for variable, value in appenddata.get('append', {}).items(): + if (sysbuild is True and variable[0:3] == 'SB_') or \ + (sysbuild is False and variable[0:3] != 'SB_'): + self.board2appends[board][revision][variable].append( + append_value(variable, value)) for variable, value in settings.get('append', {}).items(): if (sysbuild is True and variable[0:3] == 'SB_') or \ (sysbuild is False and variable[0:3] != 'SB_'): - self.board2appends[board][variable].append( + self.board2appends[board][""][variable].append( append_value(variable, value)) class Snippets(UserDict): @@ -168,7 +174,18 @@ def print_appends_for_board(self, board: str, appends: Appends): self.print(f'''\ # Appends for board '{board}' if("${{BOARD}}${{BOARD_QUALIFIERS}}" STREQUAL "{board}")''') - self.print_appends(appends, 1) + + # Output board variables first then board revision variables + self.print_appends(appends[""], 1) + + for revision in appends: + if revision != "": + self.print(f'''\ + # Appends for revision '{revision}' + if("${{BOARD_REVISION}}" STREQUAL "{revision}")''') + self.print_appends(appends[revision], 2) + self.print(' endif()') + self.print('endif()') def print_appends(self, appends: Appends, indent: int): @@ -181,9 +198,9 @@ def print(self, *args, **kwargs): kwargs['file'] = self.out_file print(*args, **kwargs) -# Name of the file containing the pykwalify schema for snippet.yml +# Name of the file containing the jsonschema schema for snippet.yml # files. -SCHEMA_PATH = str(Path(__file__).parent / 'schemas' / 'snippet-schema.yml') +SCHEMA_PATH = str(Path(__file__).parent / 'schemas' / 'snippet-schema.yaml') with open(SCHEMA_PATH, 'r') as f: SNIPPET_SCHEMA = yaml.safe_load(f.read()) @@ -221,10 +238,6 @@ def parse_args(): return parser.parse_args() def setup_logging(): - # Silence validation errors from pykwalify, which are logged at - # logging.ERROR level. We want to handle those ourselves as - # needed. - logging.getLogger('pykwalify').setLevel(logging.CRITICAL) logging.basicConfig(level=logging.INFO, format=' %(name)s: %(message)s') @@ -296,17 +309,15 @@ def load_snippet_yml(snippet_yml: Path) -> dict: except yaml.scanner.ScannerError: _err(f'snippets file {snippet_yml} is invalid YAML') - def pykwalify_err(e): - return f'''\ -invalid {SNIPPET_YML} file: {snippet_yml} -{textwrap.indent(e.msg, ' ')} -''' + validator_class = jsonschema.validators.validator_for(SNIPPET_SCHEMA) + validator_class.check_schema(SNIPPET_SCHEMA) + snippet_validator = validator_class(SNIPPET_SCHEMA) + errors = list(snippet_validator.iter_errors(snippet_data)) - try: - pykwalify.core.Core(source_data=snippet_data, - schema_data=SNIPPET_SCHEMA).validate() - except pykwalify.errors.PyKwalifyException as e: - _err(pykwalify_err(e)) + if errors: + sys.exit('ERROR: Malformed snippet YAML file: ' + f'{snippet_yml.as_posix()}\n' + f'{best_match(errors).message} in {best_match(errors).json_path}') name = snippet_data['name'] if not SNIPPET_NAME_RE.fullmatch(name): diff --git a/tests/cmake/snippets/Kconfig b/tests/cmake/snippets/Kconfig index 7506a377d2d98..b56985d877dd8 100644 --- a/tests/cmake/snippets/Kconfig +++ b/tests/cmake/snippets/Kconfig @@ -37,6 +37,16 @@ config TEST_TYPE_BAR_FOO help Test the snippet processing order (1. bar, 2. foo) +config TEST_TYPE_VER_CHECK + bool "Test Type: Version check" + help + Test board version snippet application + +config TEST_TYPE_VER_CHECK_SPECIFIC + bool "Test Type: Version check specific" + help + Test board version snippet application with specific board version + endchoice # Test values set by the snippet config overlays and tested by the test logic @@ -57,3 +67,14 @@ config TEST_COMMON_VAL help This option's value should be overridden by the snippet config overlays. + +# Used for testing board version snippets +config TEST_VER_CHECK_APPLIED + bool "Test version check snippet applied" + help + This option's value should be set by the ver_check snippet. + +config TEST_VER_CHECK_SPECIFIC_VERSION_APPLIED + bool "Test version check with board version snippet applied" + help + This option's value should be set by the ver_check snippet. diff --git a/tests/cmake/snippets/snippets/ver_check/snippet.yml b/tests/cmake/snippets/snippets/ver_check/snippet.yml new file mode 100644 index 0000000000000..d4d958b3933ce --- /dev/null +++ b/tests/cmake/snippets/snippets/ver_check/snippet.yml @@ -0,0 +1,9 @@ +name: ver_check +boards: + nrf9160dk/nrf9160: + revisions: + "0.7.0": + append: + EXTRA_CONF_FILE: ver_check_0_7_0.conf + append: + EXTRA_CONF_FILE: ver_check.conf diff --git a/tests/cmake/snippets/snippets/ver_check/ver_check.conf b/tests/cmake/snippets/snippets/ver_check/ver_check.conf new file mode 100644 index 0000000000000..4b735bdeb2c89 --- /dev/null +++ b/tests/cmake/snippets/snippets/ver_check/ver_check.conf @@ -0,0 +1,2 @@ +CONFIG_TEST_VER_CHECK_APPLIED=y +CONFIG_TEST_VER_CHECK_SPECIFIC_VERSION_APPLIED=n diff --git a/tests/cmake/snippets/snippets/ver_check/ver_check_0_7_0.conf b/tests/cmake/snippets/snippets/ver_check/ver_check_0_7_0.conf new file mode 100644 index 0000000000000..48d65309180c1 --- /dev/null +++ b/tests/cmake/snippets/snippets/ver_check/ver_check_0_7_0.conf @@ -0,0 +1 @@ +CONFIG_TEST_VER_CHECK_SPECIFIC_VERSION_APPLIED=y diff --git a/tests/cmake/snippets/src/main.c b/tests/cmake/snippets/src/main.c index c7ff978214278..6eb860c7eb741 100644 --- a/tests/cmake/snippets/src/main.c +++ b/tests/cmake/snippets/src/main.c @@ -19,6 +19,23 @@ #define TEST_BAR_VAL_BAR (964183) #define TEST_COMMON_VAL_BAR (109234) +/* Check board-specific snippet files */ +#if defined(CONFIG_TEST_TYPE_VER_CHECK) +#if !defined(CONFIG_TEST_VER_CHECK_APPLIED) +#error "Base ver_check snippet has not been applied" +#endif +#if defined(CONFIG_TEST_VER_CHECK_SPECIFIC_VERSION_APPLIED) +#error "Board specific ver_check snippet has wrongly been applied" +#endif +#elif defined(CONFIG_TEST_TYPE_VER_CHECK_SPECIFIC) +#if !defined(CONFIG_TEST_VER_CHECK_APPLIED) +#error "Base ver_check snippet has not been applied" +#endif +#if !defined(CONFIG_TEST_VER_CHECK_SPECIFIC_VERSION_APPLIED) +#error "Board specific ver_check snippet has not been applied" +#endif +#endif + ZTEST_SUITE(snippet_tests, NULL, NULL, NULL, NULL, NULL); ZTEST(snippet_tests, test_overlay_config) diff --git a/tests/cmake/snippets/testcase.yaml b/tests/cmake/snippets/testcase.yaml index 8de8be1c71ed3..0cd821ab1e4b9 100644 --- a/tests/cmake/snippets/testcase.yaml +++ b/tests/cmake/snippets/testcase.yaml @@ -1,12 +1,5 @@ common: tags: snippets - platform_allow: - - native_sim - - qemu_x86 - - qemu_x86_64 - - qemu_cortex_m3 - integration_platforms: - - native_sim sysbuild: false tests: @@ -14,23 +7,77 @@ tests: buildsystem.snippets.none: extra_configs: - CONFIG_TEST_TYPE_NONE=y + platform_allow: + - native_sim + - qemu_x86 + - qemu_x86_64 + - qemu_cortex_m3 + integration_platforms: + - native_sim # Test the `foo` snippet from the default application snippet root buildsystem.snippets.foo: extra_args: SNIPPET="foo" extra_configs: - CONFIG_TEST_TYPE_FOO=y + platform_allow: + - native_sim + - qemu_x86 + - qemu_x86_64 + - qemu_cortex_m3 + integration_platforms: + - native_sim # Test the `bar` snippet from an extra snippet root buildsystem.snippets.bar: extra_args: SNIPPET="bar" extra_configs: - CONFIG_TEST_TYPE_BAR=y + platform_allow: + - native_sim + - qemu_x86 + - qemu_x86_64 + - qemu_cortex_m3 + integration_platforms: + - native_sim # Test the snippet processing order (1. foo, 2. bar) buildsystem.snippets.foo_bar: extra_args: SNIPPET="foo;bar" extra_configs: - CONFIG_TEST_TYPE_FOO_BAR=y + platform_allow: + - native_sim + - qemu_x86 + - qemu_x86_64 + - qemu_cortex_m3 + integration_platforms: + - native_sim # Test the snippet processing order (1. bar, 2. foo) buildsystem.snippets.bar_foo: extra_args: SNIPPET="bar;foo" extra_configs: - CONFIG_TEST_TYPE_BAR_FOO=y + platform_allow: + - native_sim + - qemu_x86 + - qemu_x86_64 + - qemu_cortex_m3 + integration_platforms: + - native_sim + # Test the snippet board version processing + buildsystem.snippets.version_exclude: + extra_args: SNIPPET="ver_check" + extra_configs: + - CONFIG_TEST_TYPE_VER_CHECK=y + platform_allow: + - nrf9160dk@0.14.0/nrf9160 + integration_platforms: + - nrf9160dk@0.14.0/nrf9160 + build_only: true + buildsystem.snippets.version_include: + extra_args: SNIPPET="ver_check" + extra_configs: + - CONFIG_TEST_TYPE_VER_CHECK_SPECIFIC=y + platform_allow: + - nrf9160dk@0.7.0/nrf9160 + integration_platforms: + - nrf9160dk@0.7.0/nrf9160 + build_only: true