summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/hardware-metadata-validation.yml35
-rw-r--r--app/scripts/requirements.txt8
-rw-r--r--app/scripts/west-commands.yml5
-rw-r--r--app/scripts/west_commands/metadata.py59
-rw-r--r--schema/hardware-metadata.schema.json261
5 files changed, 368 insertions, 0 deletions
diff --git a/.github/workflows/hardware-metadata-validation.yml b/.github/workflows/hardware-metadata-validation.yml
new file mode 100644
index 0000000..3972ed8
--- /dev/null
+++ b/.github/workflows/hardware-metadata-validation.yml
@@ -0,0 +1,35 @@
+name: Hardware Metadata Validation
+
+on:
+ push:
+ paths:
+ - ".github/workflows/hardware-metadata-validation.yml"
+ - "schema/hardware-metadata.schema.json"
+ - "app/boards/**/*.zmk.yml"
+ - "app/scripts/west_commands/metadata.py"
+ pull_request:
+ paths:
+ - ".github/workflows/hardware-metadata-validation.yml"
+ - "schema/hardware-metadata.schema.json"
+ - "app/boards/**/*.zmk.yml"
+ - "app/scripts/west_commands/metadata.py"
+
+jobs:
+ validate-metadata:
+ runs-on: ubuntu-latest
+ container:
+ image: zmkfirmware/zmk-dev-arm:2.5
+ steps:
+ - uses: actions/checkout@v2
+ - name: Install dependencies
+ run: pip install -r app/scripts/requirements.txt
+ - name: West init
+ run: west init -l app
+ - name: Update modules (west update)
+ run: west update
+ - name: Export Zephyr CMake package (west zephyr-export)
+ run: west zephyr-export
+ - name: Validate Hardware Metadata
+ run: |
+ cd app
+ west metadata check
diff --git a/app/scripts/requirements.txt b/app/scripts/requirements.txt
new file mode 100644
index 0000000..60d6f3a
--- /dev/null
+++ b/app/scripts/requirements.txt
@@ -0,0 +1,8 @@
+# Copyright (c) 2021 The ZMK Contributors
+# SPDX-License-Identifier: MIT
+
+# Convert YAML to JSON for validation
+remarshal>=0.14.0
+
+# Perform our hardware metadata validation
+jsonschema>=3.2.0 \ No newline at end of file
diff --git a/app/scripts/west-commands.yml b/app/scripts/west-commands.yml
index 81d6946..64583a9 100644
--- a/app/scripts/west-commands.yml
+++ b/app/scripts/west-commands.yml
@@ -7,3 +7,8 @@ west-commands:
- name: test
class: Test
help: run ZMK testsuite
+ - file: scripts/west_commands/metadata.py
+ commands:
+ - name: metadata
+ class: Metadata
+ help: Operate on ZMK metadata files
diff --git a/app/scripts/west_commands/metadata.py b/app/scripts/west_commands/metadata.py
new file mode 100644
index 0000000..a06024c
--- /dev/null
+++ b/app/scripts/west_commands/metadata.py
@@ -0,0 +1,59 @@
+# Copyright (c) 2021 The ZMK Contributors
+# SPDX-License-Identifier: MIT
+'''Metadata command for ZMK.'''
+
+from functools import cached_property
+import glob
+import json
+from jsonschema import validate, ValidationError
+import os
+import sys
+import yaml
+from textwrap import dedent # just for nicer code indentation
+
+from west.commands import WestCommand
+from west import log # use this for user output
+
+
+class Metadata(WestCommand):
+ def __init__(self):
+ super().__init__(
+ 'metadata', # gets stored as self.name
+ 'ZMK hardware metadata commands', # self.help
+ # self.description:
+ dedent('''Operate on the board/shield metadata.'''))
+
+ def do_add_parser(self, parser_adder):
+ parser = parser_adder.add_parser(self.name,
+ help=self.help,
+ description=self.description)
+
+ parser.add_argument('subcommand', default="check",
+ help='The subcommand to run. Defaults to "check".', nargs="?")
+ return parser # gets stored as self.parser
+
+ @cached_property
+ def schema(self):
+ return json.load(
+ open("../schema/hardware-metadata.schema.json", 'r'))
+
+ def validate_file(self, file):
+ print("Validating: " + file)
+ with open(file, 'r') as stream:
+ try:
+ validate(yaml.safe_load(stream), self.schema)
+ except yaml.YAMLError as exc:
+ print("Failed loading metadata yaml: " + file)
+ print(exc)
+ return False
+ except ValidationError as vexc:
+ print("Failed validation of: " + file)
+ print(vexc)
+ return False
+ return True
+
+ def do_run(self, args, unknown_args):
+ status = all([self.validate_file(f) for f in glob.glob(
+ "boards/**/*.zmk.yml", recursive=True)])
+
+ sys.exit(0 if status else 1)
diff --git a/schema/hardware-metadata.schema.json b/schema/hardware-metadata.schema.json
new file mode 100644
index 0000000..a74c6ef
--- /dev/null
+++ b/schema/hardware-metadata.schema.json
@@ -0,0 +1,261 @@
+{
+ "$id": "https://zmkfirmware.dev/zmk.metadata.json",
+ "title": "HardwareMetadata",
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "oneOf": [
+ {
+ "$ref": "#/$defs/board"
+ },
+ {
+ "$ref": "#/$defs/shield"
+ },
+ {
+ "$ref": "#/$defs/interconnect"
+ }
+ ],
+ "$defs": {
+ "id": {
+ "type": "string",
+ "pattern": "^[a-z0-9_]+$"
+ },
+ "keyboard_siblings": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "variant": {
+ "oneOf": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "object",
+ "required": [
+ "id",
+ "features"
+ ],
+ "properties": {
+ "id": {
+ "$ref": "#/$defs/id"
+ },
+ "features": {
+ "$ref": "#/$defs/features"
+ }
+ }
+ }
+ ]
+ },
+ "features": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "enum": [
+ "keys",
+ "display",
+ "encoder",
+ "underglow",
+ "pointer"
+ ]
+ }
+ },
+ "interconnects": {
+ "type": "array",
+ "minItems": 1,
+ "items": {
+ "$ref": "#/$defs/id"
+ }
+ },
+ "sibling_details": {
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "id": {
+ "$ref": "#/$defs/id"
+ },
+ "features": {
+ "$ref": "#/$defs/features"
+ },
+ "variants": {
+ "type": "array",
+ "items": {
+ "$ref": "#/$defs/variant"
+ }
+ }
+ }
+ },
+ "interconnect": {
+ "title": "Interconnect",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "file_format",
+ "id",
+ "name",
+ "url",
+ "type"
+ ],
+ "properties": {
+ "file_format": {
+ "type": "string",
+ "const": "1"
+ },
+ "id": {
+ "$ref": "#/$defs/id"
+ },
+ "name": {
+ "type": "string"
+ },
+ "version": {
+ "type": "string"
+ },
+ "url": {
+ "type": "string",
+ "format": "uri"
+ },
+ "description": {
+ "type": "string"
+ },
+ "manufacturer": {
+ "type": "string"
+ },
+ "type": {
+ "type": "string",
+ "const": "interconnect"
+ }
+ }
+ },
+ "board": {
+ "title": "Board",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "file_format",
+ "id",
+ "name",
+ "url",
+ "arch",
+ "type",
+ "outputs"
+ ],
+ "properties": {
+ "file_format": {
+ "type": "string",
+ "const": "1"
+ },
+ "id": {
+ "$ref": "#/$defs/id"
+ },
+ "name": {
+ "type": "string"
+ },
+ "version": {
+ "type": "string"
+ },
+ "url": {
+ "type": "string",
+ "format": "uri"
+ },
+ "description": {
+ "type": "string"
+ },
+ "manufacturer": {
+ "type": "string"
+ },
+ "arch": {
+ "type": "string",
+ "pattern": "^[a-z0-9_]+$"
+ },
+ "type": {
+ "type": "string",
+ "const": "board"
+ },
+ "siblings": {
+ "$ref": "#/$defs/keyboard_siblings"
+ },
+ "outputs": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "enum": [
+ "usb",
+ "ble"
+ ]
+ }
+ },
+ "features": {
+ "$ref": "#/$defs/features"
+ },
+ "variants": {
+ "type": "array",
+ "items": {
+ "$ref": "#/$defs/variant"
+ }
+ },
+ "exposes": {
+ "$ref": "#/$defs/interconnects"
+ }
+ }
+ },
+ "shield": {
+ "title": "Shield",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "file_format",
+ "id",
+ "name",
+ "url",
+ "type",
+ "requires"
+ ],
+ "properties": {
+ "file_format": {
+ "type": "string",
+ "const": "1"
+ },
+ "id": {
+ "$ref": "#/$defs/id"
+ },
+ "name": {
+ "type": "string"
+ },
+ "url": {
+ "type": "string",
+ "format": "uri"
+ },
+ "description": {
+ "type": "string"
+ },
+ "manufacturer": {
+ "type": "string"
+ },
+ "version": {
+ "type": "string"
+ },
+ "type": {
+ "type": "string",
+ "const": "shield"
+ },
+ "features": {
+ "$ref": "#/$defs/features"
+ },
+ "variants": {
+ "type": "array",
+ "items": {
+ "$ref": "#/$defs/variant"
+ }
+ },
+ "siblings": {
+ "$ref": "#/$defs/keyboard_siblings"
+ },
+ "requires": {
+ "$ref": "#/$defs/interconnects"
+ },
+ "exposes": {
+ "$ref": "#/$defs/interconnects"
+ }
+ }
+ }
+ }
+}