Skip to content

Commit a062650

Browse files
committed
scripts: west_commands: Add 'new' command
Add a new west command as helper for Zephyr projects, drivers and modules. The project creation is the default state, generating a minimal configuration for a Zephyr app. The module creation consists of a simple template to be completed by the user. Driver creation follows the `example_application` format, producing or appending the created driver into the `drivers` and `dts/bindings` directories. This west command aims to ease developers load when starting new projects, or porting existing code into drivers/modules that operate with west and Zephyr. Signed-off-by: Paulo Santos <[email protected]>
1 parent 112871a commit a062650

File tree

2 files changed

+293
-0
lines changed

2 files changed

+293
-0
lines changed

scripts/west-commands.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,3 +99,8 @@ west-commands:
9999
- name: gtags
100100
class: Gtags
101101
help: create a GNU global tags file for the current workspace
102+
- file: scripts/west_commands/new.py
103+
commands:
104+
- name: new
105+
class: New
106+
help: create new zephyr project, module or driver template

scripts/west_commands/new.py

Lines changed: 288 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,288 @@
1+
# Copyright (c) 2025 Paulo Santos ([email protected]).
2+
#
3+
# SPDX-License-Identifier: Apache-2.0
4+
5+
"""west "new" command"""
6+
7+
import argparse
8+
import textwrap
9+
from pathlib import Path
10+
from west.commands import WestCommand
11+
12+
CMAKELISTS_TXT = "CMakeLists.txt"
13+
14+
15+
def text_dedent(text: str) -> str:
16+
return textwrap.dedent(text.lstrip("\n"))
17+
18+
19+
class New(WestCommand):
20+
def __init__(self):
21+
super().__init__(
22+
"new",
23+
# Keep this in sync with the string in west-commands.yml.
24+
"create new zephyr project, module or driver template",
25+
"Create a new project, module or driver template at a given path.",
26+
accepts_unknown_args=False,
27+
)
28+
29+
self.fname: str | None = None
30+
self.rname: str | None = None
31+
self.kconfig_var_name: str | None = None
32+
33+
def do_add_parser(self, parser_adder) -> argparse.ArgumentParser:
34+
parser = parser_adder.add_parser(self.name, help=self.help, description=self.description)
35+
36+
parser.add_argument("name", help="name of the desired new object.")
37+
parser.add_argument("-m", "--module", help="create a module template.", action="store_true")
38+
parser.add_argument("-d", "--driver", help="create a driver template.", action="store_true")
39+
40+
parser.add_argument("-p", "--path", help="creation parent directory.", default=Path.cwd())
41+
42+
return parser
43+
44+
def do_run(self, args: argparse.Namespace, unknown: list[str]):
45+
if args.module and args.driver:
46+
self.err("Options '--driver' and '--module' given simultaneously.")
47+
return
48+
49+
path = Path(args.path).absolute()
50+
51+
self.rname: str = args.name.lower().strip()
52+
self.fname = self.rname.replace("-", "_")
53+
self.kconfig_var_name = self.fname.upper()
54+
55+
new_type = "project"
56+
if args.module:
57+
new_type = "module"
58+
elif args.driver:
59+
new_type = "driver"
60+
self.inf(f"Creating new '{self.rname}' {new_type} @ {path} ...")
61+
62+
try:
63+
if args.module:
64+
self.create_new_module(path / self.fname)
65+
elif args.driver:
66+
self.create_new_driver(path)
67+
else:
68+
self.create_new_project(path)
69+
except FileExistsError:
70+
self.err(f"Could not create '{self.rname}', it already exists.")
71+
72+
def create_new_module(self, module_dir: Path):
73+
src_dir = module_dir / "src"
74+
inc_dir = module_dir / "includes"
75+
zephyr_dir = module_dir / "zephyr"
76+
77+
module_dir.mkdir()
78+
src_dir.mkdir()
79+
inc_dir.mkdir()
80+
zephyr_dir.mkdir()
81+
82+
with open(module_dir / CMAKELISTS_TXT, "w") as file:
83+
file.write(
84+
text_dedent(f"""
85+
if(CONFIG_{self.kconfig_var_name})
86+
zephyr_library()
87+
zephyr_library_sources(src/{self.fname}.c)
88+
zephyr_include_directories(includes)
89+
endif ()
90+
""")
91+
)
92+
93+
with open(module_dir / "Kconfig", "w") as file:
94+
file.write(
95+
text_dedent(f"""
96+
menuconfig {self.kconfig_var_name}
97+
bool "{self.name} module"
98+
99+
if {self.kconfig_var_name}
100+
# [...]
101+
endif
102+
""")
103+
)
104+
105+
with open(src_dir / f"{self.fname}.c", "w") as file:
106+
file.write(
107+
text_dedent(f"""
108+
#include "{self.fname}.h"
109+
110+
// [...]
111+
""")
112+
)
113+
114+
with open(inc_dir / f"{self.fname}.h", "w") as file:
115+
file.write(
116+
text_dedent(f"""
117+
#ifndef {self.kconfig_var_name}_H
118+
#define {self.kconfig_var_name}_H
119+
120+
// [...]
121+
122+
#endif /* {self.kconfig_var_name}_H */
123+
""")
124+
)
125+
126+
with open(zephyr_dir / "module.yml", "w") as file:
127+
file.write(
128+
text_dedent(f"""
129+
name: {self.name}
130+
build:
131+
cmake: .
132+
kconfig: Kconfig
133+
""")
134+
)
135+
136+
def create_new_driver(self, project_dir: Path):
137+
drivers_dir = project_dir / "drivers"
138+
driver_dir = drivers_dir / self.fname
139+
bindings_dir = project_dir / "dts" / "bindings" / self.fname
140+
141+
driver_dir.mkdir(parents=True)
142+
bindings_dir.mkdir(parents=True)
143+
144+
with open(drivers_dir / CMAKELISTS_TXT, "a") as file:
145+
file.write(f"\nadd_subdirectory_ifdef(CONFIG_{self.kconfig_var_name} {self.fname})")
146+
147+
with open(drivers_dir / "Kconfig", "a") as file:
148+
file.write(f'\nrsource "{self.fname}/Kconfig"')
149+
150+
with open(driver_dir / CMAKELISTS_TXT, "w") as file:
151+
file.write(
152+
text_dedent(f"""
153+
zephyr_library()
154+
zephyr_library_sources_ifdef(CONFIG_{self.kconfig_var_name}_DRV {self.fname}_drv.c)
155+
""")
156+
)
157+
158+
with open(driver_dir / "Kconfig", "w") as file:
159+
file.write(
160+
text_dedent(f"""
161+
menuconfig {self.kconfig_var_name}
162+
bool "{self.rname.capitalize()} device drivers"
163+
help
164+
This option enables the {self.rname} drivers class.
165+
166+
if {self.kconfig_var_name}
167+
168+
config {self.kconfig_var_name}_INIT_PRIORITY
169+
int "{self.rname.capitalize()} device drivers init priority"
170+
default KERNEL_INIT_PRIORITY_DEVICE
171+
help
172+
{self.rname.capitalize()} device drivers init priority.
173+
174+
module = {self.kconfig_var_name}
175+
module-str = {self.rname}
176+
177+
rsource "Kconfig.{self.fname}_drv"
178+
179+
endif # {self.kconfig_var_name}
180+
""")
181+
)
182+
183+
with open(driver_dir / f"Kconfig.{self.fname}_drv", "w") as file:
184+
file.write(
185+
text_dedent(f"""
186+
config {self.kconfig_var_name}_DRV
187+
bool "{self.rname.capitalize()} driver"
188+
default y
189+
depends on DT_HAS_{self.kconfig_var_name}_DRV_ENABLED
190+
""")
191+
)
192+
193+
with open(driver_dir / f"{self.fname}_drv.c", "w") as file:
194+
file.write(
195+
text_dedent(f"""
196+
#define DT_DRV_COMPAT {self.fname}_drv
197+
198+
#include <zephyr/device.h>
199+
200+
#include <zephyr/devicetree.h>
201+
#include <zephyr/kernel.h>
202+
203+
struct {self.fname}_drv_data {{
204+
// [...]
205+
}};
206+
207+
struct {self.fname}_drv_config {{
208+
// [...]
209+
}};
210+
211+
static DEVICE_API({self.fname}, {self.fname}_drv_api) = {{
212+
// [...]
213+
}};
214+
215+
static int {self.fname}_drv_init(const struct device *dev)
216+
{{
217+
const struct {self.fname}_drv_config *config = dev->config;
218+
struct {self.fname}_drv_data *data = dev->data;
219+
220+
// [...]
221+
222+
return 0;
223+
}}
224+
225+
#define {self.kconfig_var_name}_DRV_DEFINE(inst) \\
226+
static struct {self.fname}_drv_data data##inst; \\
227+
static const struct {self.fname}_drv_config config##inst = {{ \\
228+
/* [...] */ \\
229+
}}; \\
230+
\\
231+
DEVICE_DT_INST_DEFINE(inst, {self.fname}_drv_init, NULL, &data##inst, \\
232+
&config##inst, POST_KERNEL, \\
233+
CONFIG_{self.kconfig_var_name}_INIT_PRIORITY, \\
234+
&{self.fname}_drv_api);
235+
236+
DT_INST_FOREACH_STATUS_OKAY({self.kconfig_var_name}_DRV_DEFINE)
237+
""")
238+
)
239+
240+
with open(bindings_dir / f"{self.fname}_drv.yml", "w") as file:
241+
file.write(
242+
text_dedent(f"""
243+
description: |
244+
A {self.rname}-drv driver...
245+
246+
compatible: "{self.rname}-drv"
247+
248+
include: base.yaml
249+
250+
properties:
251+
# [...]
252+
""")
253+
)
254+
255+
def create_new_project(self, prj_root_dir: Path):
256+
project_dir = prj_root_dir / self.fname
257+
src_dir = project_dir / "src"
258+
259+
src_dir.mkdir(parents=True)
260+
261+
with open(project_dir / CMAKELISTS_TXT, "w") as file:
262+
file.write(
263+
text_dedent(f"""
264+
cmake_minimum_required(VERSION 3.20.0)
265+
266+
find_package(Zephyr REQUIRED HINTS $ENV{{ZEPHYR_BASE}})
267+
project({self.fname})
268+
269+
target_sources(app PRIVATE src/main.c)
270+
""")
271+
)
272+
273+
with open(project_dir / "prj.conf", "w") as file:
274+
file.write("\n")
275+
276+
with open(src_dir / "main.c", "w") as file:
277+
file.write(
278+
text_dedent("""
279+
#include <zephyr/kernel.h>
280+
281+
int main(void)
282+
{{
283+
printk("Hello World from west new!\\n");
284+
285+
return 0;
286+
}}
287+
""")
288+
)

0 commit comments

Comments
 (0)