Skip to content
Draft
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
30 changes: 30 additions & 0 deletions samples/extensions/code_sample_categories.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
from copier_template_extensions import ContextHook


class Categories(ContextHook):
def hook(self, context):
context["categories"] = {
"Application Development": "application_development",
"Architecture-dependent Samples": "arch",
"Basic": "basic",
"Bluetooth": "bluetooth",
"Boards": "boards",
"C++": "cpp",
"Data Structures": "data_structures",
"Drivers": "drivers",
"Hello World": "hello_world",
"Kernel and Scheduler": "kernel",
"External modules": "modules",
"Networking": "net",
"Philosophers": "philosophers",
"POSIX API": "posix",
"PSA": "psa",
"Sensors": "sensor",
"Shields": "shields",
"Subsystems": "subsys",
"Synchronization": "synchronization",
"Sysbuild": "sysbuild",
"Test": "test",
"TF-M Integration": "tfm_integration",
"Userspace": "userspace",
}
2 changes: 2 additions & 0 deletions scripts/requirements-base.txt
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ requests>=2.32.0
semver
tqdm>=4.67.1
reuse
copier
copier-template-extensions

# for ram/rom reports
anytree
Expand Down
5 changes: 5 additions & 0 deletions scripts/west-commands.yml
Original file line number Diff line number Diff line change
Expand Up @@ -99,3 +99,8 @@
- name: gtags
class: Gtags
help: create a GNU global tags file for the current workspace
- file: scripts/west_commands/template.py
commands:
- name: template
class: Template
help: use built-in templates to create new samples, drivers, tests, etc.

Check warning on line 106 in scripts/west-commands.yml

View workflow job for this annotation

GitHub Actions / Run compliance checks on patch series (PR)

YAMLLint (new-line-at-end-of-file)

scripts/west-commands.yml:106 no new line character at the end of file
95 changes: 95 additions & 0 deletions scripts/west_commands/template.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
# Copyright (c) 2025
#
# SPDX-License-Identifier: Apache-2.0

import argparse
import textwrap
from pathlib import Path

from copier import Worker
from west.commands import WestCommand


class Template(WestCommand):
"""Use built-in templates to create new samples, drivers, tests, etc.

Initial implementation supports creating a new sample using the Copier
template engine. Additional template types can be added over time.
"""

def __init__(self):
super().__init__(
"template",
"use built-in templates to create new samples, drivers, tests, etc.",
"Create Zephyr content from templates (initially: samples)",
accepts_unknown_args=False,
)

def do_add_parser(self, parser_adder):
parser = parser_adder.add_parser(
self.name,
help=self.help,
formatter_class=argparse.RawDescriptionHelpFormatter,
description=self.description,
epilog=textwrap.dedent("""
Examples:

- Create a new sample and answer prompts interactively:
west template --type sample
"""),
)

parser.add_argument(
"-t",
"--type",
choices=["sample"],
default="sample",
help="Type of template to instantiate (default: sample)",
)

# parser.add_argument(
# "-o",
# "--output",
# dest="output",
# metavar="DIR",
# type=Path,
# help=(
# "Destination root directory. For samples, this should typically be the "
# "'samples' directory. Defaults to <workspace>/zephyr/samples."
# ),
# )

return parser

def do_run(self, args, _):
# Determine template kind (only 'sample' for now)
template_kind = args.type

# Resolve workspace and zephyr repository paths
workspace_topdir = Path(self.topdir)
zephyr_repo_dir = workspace_topdir / "zephyr"

if not zephyr_repo_dir.is_dir():
self.die(f"Could not locate Zephyr repository at {zephyr_repo_dir}")

templates_root = Path(__file__).parent / "templates"
if template_kind == "sample":
copier_template_dir = templates_root / "sample"
default_output_dir = zephyr_repo_dir / "samples"
else:
self.die(f"Unsupported template type: {template_kind}")
return

Check warning on line 81 in scripts/west_commands/template.py

View workflow job for this annotation

GitHub Actions / Run compliance checks on patch series (PR)

W0101

scripts/west_commands/template.py:81 Unreachable code (unreachable)

if not copier_template_dir.is_dir():
self.die(f"Built-in template not found. Expected at: {copier_template_dir}")

output_dir = default_output_dir
output_dir.mkdir(parents=True, exist_ok=True)

with Worker(
src_path=str(copier_template_dir),
dst_path=str(output_dir),
unsafe=True,
quiet=True,
) as worker:
worker.run_copy()
57 changes: 57 additions & 0 deletions scripts/west_commands/templates/extensions/context_hook.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import subprocess

Check warning on line 1 in scripts/west_commands/templates/extensions/context_hook.py

View workflow job for this annotation

GitHub Actions / Run compliance checks on patch series (PR)

R0801

scripts/west_commands/templates/extensions/context_hook.py:1 Similar lines in 2 files ==code_sample_categories:[5:30] ==context_hook:[11:37] context["categories"] = { "Application Development": "application_development", "Architecture-dependent Samples": "arch", "Basic": "basic", "Bluetooth": "bluetooth", "Boards": "boards", "C++": "cpp", "Data Structures": "data_structures", "Drivers": "drivers", "Hello World": "hello_world", "Kernel and Scheduler": "kernel", "External modules": "modules", "Networking": "net", "Philosophers": "philosophers", "POSIX API": "posix", "PSA": "psa", "Sensors": "sensor", "Shields": "shields", "Subsystems": "subsys", "Synchronization": "synchronization", "Sysbuild": "sysbuild", "Test": "test", "TF-M Integration": "tfm_integration", "Userspace": "userspace", } (duplicate-code)
import sys

from copier_template_extensions import ContextHook

# Cache for boards list to avoid repeated subprocess calls
_cached_boards = None


class ZephyrContextHook(ContextHook):
def hook(self, context):
context["categories"] = {
"Application Development": "application_development",
"Architecture-dependent Samples": "arch",
"Basic": "basic",
"Bluetooth": "bluetooth",
"Boards": "boards",
"C++": "cpp",
"Data Structures": "data_structures",
"Drivers": "drivers",
"Hello World": "hello_world",
"Kernel and Scheduler": "kernel",
"External modules": "modules",
"Networking": "net",
"Philosophers": "philosophers",
"POSIX API": "posix",
"PSA": "psa",
"Sensors": "sensor",
"Shields": "shields",
"Subsystems": "subsys",
"Synchronization": "synchronization",
"Sysbuild": "sysbuild",
"Test": "test",
"TF-M Integration": "tfm_integration",
"Userspace": "userspace",
}

global _cached_boards
if _cached_boards is None:
try:
# a bit dirty but will do for now :)
result = subprocess.run(
["west", "boards", "--format={name}"],
capture_output=True,
text=True,
check=True,
)
boards = [
line.strip() for line in result.stdout.strip().split('\n') if line.strip()
]
_cached_boards = boards
except (subprocess.CalledProcessError, FileNotFoundError) as e:
# Fallback to a default list if west command fails
print(f"Warning: Failed to get board list from 'west boards': {e}", file=sys.stderr)
_cached_boards = ["wio_terminal"]

context["valid_boards"] = _cached_boards
45 changes: 45 additions & 0 deletions scripts/west_commands/templates/sample/copier.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
_jinja_extensions:
- copier_templates_extensions.TemplateExtensionLoader
- ../extensions/context_hook.py:ZephyrContextHook

_tasks:
- "echo"
- "echo 🎉 Code sample '{{ sample_name }}' is now available in {{ _copier_conf.dst_path }}/{{ sample_name }}"

Check warning on line 7 in scripts/west_commands/templates/sample/copier.yml

View workflow job for this annotation

GitHub Actions / Run compliance checks on patch series (PR)

YAMLLint (line-length)

scripts/west_commands/templates/sample/copier.yml:7 line too long (110 > 100 characters)
- "echo 'You can build it with the following command:'"
- "echo 'west build -b qemu_x86 {{ _copier_conf.dst_path }}/{{ sample_name }}'"

sample_name:
type: str
help: "Name of the code sample? (directory name under samples/)"

reference_board:
type: str
help: |
Which board should be used as reference for the code sample?
It will be mentioned in the README.rst file and used as the default integration
platform for the tests.
choices: ["native_sim", "qemu_x86", "other"]

reference_board_other:
type: str
when: "{{ reference_board == 'other' }}"
help: "Board name"
validator: >-
{% if reference_board_other not in valid_boards %}
Invalid board name
{% endif %}

category:
type: str
help: "Category of the code sample" # this is not used at the moment, just here for testing purposes

Check warning on line 34 in scripts/west_commands/templates/sample/copier.yml

View workflow job for this annotation

GitHub Actions / Run compliance checks on patch series (PR)

YAMLLint (line-length)

scripts/west_commands/templates/sample/copier.yml:34 line too long (102 > 100 characters)
choices: "{{ categories }}"

options:
type: str
help: "Selection options you want to enable for the sample"
multiselect: true
default: '["CONFIG_LOG","CONFIG_CONSOLE"]'
choices:
Logging: CONFIG_LOG
Console: CONFIG_CONSOLE
Shell: CONFIG_SHELL
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
cmake_minimum_required(VERSION 3.20.0)
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project({{ sample_name }})

target_sources(app PRIVATE src/main.c)
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
.. zephyr:code-sample:: {{ sample_name }}
:name: A descriptive short name for the sample
:relevant-api: space-separated list of Doxygen groups of APIs this sample is a good showcase of

Short text description of the sample. It is recommended to word this as if you were completing
the sentence "This code sample shows how to ...").

Overview
********

[A longer description about the sample and what it does]

Requirements
************

[List of required software and hardware components. Provide pointers to
hardware components such as sensors and shields]

Building and Running
********************

.. zephyr-app-commands::
:zephyr-app: samples/{{ sample_name }}
:host-os: unix
:board: {{ reference_board if reference_board != 'other' else reference_board_other }}
:goals: run
:compact:

References
**********

.. target-notes::

[ Links to external references such as datasheets or additional documentation]


Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Kconfig options for sample '{{ sample_name }}'
{%- for option in options %}
{{ option }}=y
{%- endfor %}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# todo :)
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#include <zephyr/kernel.h>
#include <zephyr/sys/printk.h>

int main(void)
{
while (1) {
printk("Hello from sample '{{ sample_name }}'!\n");
k_sleep(K_SECONDS(1));
}
return 0;
}
Loading