Skip to content

Commit eaf8f2d

Browse files
[nrf noup] Extend zap-generate to process multiple files at once
Allows processing multiple zap files at once by providing a yaml file with zap file list. Signed-off-by: Arkadiusz Balys <[email protected]>
1 parent 6e275f6 commit eaf8f2d

File tree

1 file changed

+135
-82
lines changed

1 file changed

+135
-82
lines changed

scripts/west/zap_generate.py

Lines changed: 135 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,11 @@
66
import os
77
import shutil
88
import sys
9+
from dataclasses import dataclass
910
from pathlib import Path
1011
from textwrap import dedent
1112

13+
import yaml
1214
from west import log
1315
from west.commands import CommandError, WestCommand
1416
from zap_common import DEFAULT_MATTER_PATH, ZapInstaller, existing_dir_path, existing_file_path, find_zap
@@ -21,6 +23,16 @@
2123

2224
# fmt: on
2325

26+
ZEPHYR_BASE = os.environ.get('ZEPHYR_BASE', "")
27+
28+
29+
@dataclass
30+
class ZapFile:
31+
name: str = ""
32+
zap_file: Path = None
33+
full: bool = False
34+
zcl_file: Path = None
35+
2436

2537
class ZapGenerate(WestCommand):
2638

@@ -47,6 +59,8 @@ def do_add_parser(self, parser_adder):
4759
parser.add_argument('-f', '--full', action='store_true', help='Generate full data model files')
4860
parser.add_argument('-k', '--keep-previous', action='store_true', help='Keep previously generated files')
4961
parser.add_argument('-j', '--zcl', action='store_true', help='Generate clusters from zcl.json')
62+
parser.add_argument('-y', '--yaml', type=existing_file_path,
63+
help='Yaml file containing list of zap files to be used for generation. The file must contain the first entry as "base_dir" which is the relative path to the ZEPHYR_BASE directory. Then each other entry must contain the "name", "zap_file" and optionally "full" and "zcl_file"')
5064
return parser
5165

5266
def build_command(self, zap_file_path, output_path, templates_path=None):
@@ -60,98 +74,137 @@ def build_command(self, zap_file_path, output_path, templates_path=None):
6074

6175
def do_run(self, args, unknown_args):
6276
self.zap_generate_path = args.matter_path / "scripts/tools/zap/generate.py"
63-
64-
if args.zap_file:
65-
zap_file_path = args.zap_file.absolute()
66-
else:
67-
zap_file_path = find_zap()
68-
69-
if not zap_file_path:
70-
raise CommandError("No valid .zap file provided")
71-
72-
if args.output:
73-
output_path = args.output.absolute()
74-
else:
75-
output_path = zap_file_path.parent / "zap-generated"
76-
77-
templates_path = args.matter_path / "src/app/common/templates/templates.json"
7877
app_templates_path = args.matter_path / "src/app/zap-templates/app-templates.json"
7978

79+
if args.yaml and args.zap_file:
80+
raise CommandError("Cannot use both -y and -z at the same time")
81+
8082
zap_installer = ZapInstaller(args.matter_path)
8183
zap_installer.update_zap_if_needed()
8284

8385
# make sure that the generate.py script uses the proper zap_cli binary (handled by west)
8486
os.environ["ZAP_INSTALL_PATH"] = str(zap_installer.get_zap_cli_path().parent.absolute())
8587

86-
# Make sure that output directory exists
87-
output_path.mkdir(exist_ok=True)
88+
zap_files: list[ZapFile] = []
89+
90+
# Load the yaml file
91+
if args.yaml:
92+
with open(args.yaml, 'r') as f:
93+
zaps = yaml.load(f, Loader=yaml.FullLoader)
94+
base_dir = zaps[0].get('base_dir', "")
95+
if not base_dir:
96+
raise CommandError("base_dir is not set in the yaml file")
97+
98+
for zap in zaps:
99+
if zap.get('zap_file'):
100+
name = zap.get('name', "")
101+
zap_file = Path(ZEPHYR_BASE, base_dir, zap.get('zap_file', ""))
102+
if not zap_file.exists():
103+
raise CommandError(f"ZAP file {zap_file} does not exist")
104+
zcl_file = Path(ZEPHYR_BASE, base_dir, zap.get('zcl_file', "")) if zap.get('zcl_file', "") else None
105+
if zcl_file and not zcl_file.exists():
106+
raise CommandError(f"ZCL file {zcl_file} does not exist")
107+
full = zap.get('full', False)
108+
109+
zap_entry = ZapFile(name=name, zap_file=zap_file, full=full, zcl_file=zcl_file)
110+
zap_files.append(zap_entry)
111+
else:
112+
if args.zap_file:
113+
zap_file_path = args.zap_file.absolute()
114+
else:
115+
zap_file_path = find_zap()
116+
117+
if not zap_file_path:
118+
raise CommandError("No valid .zap file provided")
119+
120+
zap_files.append(ZapFile(name=zap_file_path.stem, zap_file=Path(zap_file_path), full=args.full, zcl_file=args.zcl))
121+
122+
# Generate the zap file
123+
for zap in zap_files:
124+
if args.output:
125+
output_path = args.output.absolute()
126+
else:
127+
output_path = zap.zap_file.parent / "zap-generated"
128+
129+
# Make sure that output directory exists
130+
output_path.mkdir(exist_ok=True)
131+
132+
if not args.keep_previous:
133+
self.clear_generated_files(output_path)
134+
if args.full:
135+
log.inf(f"Clearing output directory: {output_path}")
136+
shutil.rmtree(output_path)
137+
output_path.mkdir(exist_ok=True)
138+
139+
log.inf('----------------------------------------------------------')
140+
log.inf(f"Generating source files for: {zap.name}")
141+
log.inf(f"ZAP file: {zap.zap_file}")
142+
log.inf(f"Output path: {output_path}")
143+
log.inf(f"App templates path: {app_templates_path}")
144+
log.inf(f"Full: {args.full}")
145+
log.inf(f"Keep previous: {args.keep_previous}")
146+
log.inf(f"ZCL file: {zap.zcl_file}")
147+
log.inf('----------------------------------------------------------')
148+
149+
# Generate source files
150+
self.check_call(self.build_command(zap.zap_file, output_path, app_templates_path))
151+
152+
# Generate .matter file
153+
self.check_call(self.build_command(zap.zap_file, output_path))
88154

89-
if not args.keep_previous:
90-
self.clear_generated_files(output_path)
91155
if args.full:
92-
log.inf(f"Clearing output directory: {output_path}")
93-
shutil.rmtree(output_path)
94-
output_path.mkdir(exist_ok=True)
95-
96-
# Generate source files
97-
self.check_call(self.build_command(zap_file_path, output_path, app_templates_path))
98-
99-
# Generate .matter file
100-
self.check_call(self.build_command(zap_file_path, output_path))
101-
102-
if args.full:
103-
# Full build is about generating an apropertiate Matter data model files in a specific directory layout.
104-
# Currently, we must align to the following directory layout:
105-
# sample/
106-
# |_ src/
107-
# |_ default_zap/
108-
# |_ zcl.xml
109-
# |_ sample.zap
110-
# |_ sample.matter
111-
# |_ clusters/
112-
# |_ *All clusters*
113-
# |_ app-common
114-
# |_ zap-generated/
115-
# |_ attributes/
116-
# |_ ids/
117-
#
118-
# Generation of the full data model files consist of three steps:
119-
# 1. ZAPGenerateTarget generates "app-common/zap-generated" directory and a part of "clusters/" directory.
120-
# It generates Attrbutes.h/cpp, Events.h/cpp, Commands.h/cpp files for each cluster.
121-
# 2. JinjaCodegenTarget generates "clusters/" directory.
122-
# It generates "AttributeIds.h/cpp", "EventIds.h", "CommandIds.h" files for each cluster.
123-
# It generates also BUILD.gn files that are used to configure build system.
124-
#
125-
# Currently, we must call JinjaCodegenTarget twice:
126-
# - for all clusters using controller-clusters.matter file to generate all clusters defined in the Matter spec.
127-
# - for the sample's .matter file to generate the new data model files that are not defined in the Matter spec.
128-
#
129-
# To generate the full data model files, we utilizes classes defined in the matter/scripts/tools/zap_regen_all.py file.
130-
# These classes are supposed to be called from the matter root directory, so we must temporarily change the current working directory to the matter root directory.
131-
zcl_file = args.zcl or zap_file_path.parent / "zcl.json"
132-
zap_input = ZapInput.FromPropertiesJson(zcl_file)
133-
template = 'src/app/common/templates/templates.json'
134-
zap_output_dir = output_path / 'app-common' / 'zap-generated'
135-
codegen_output_dir = output_path / 'clusters'
136-
137-
# Temporarily change directory to matter_path so JinjaCodegenTarget and ZAPGenerateTarget can find their scripts
138-
original_cwd = os.getcwd()
139-
os.chdir(args.matter_path)
140-
try:
141-
ZAPGenerateTarget(zap_input, template=template, output_dir=zap_output_dir).generate()
142-
JinjaCodegenTarget(
143-
generator="cpp-sdk",
144-
idl_path="src/controller/data_model/controller-clusters.matter",
145-
output_directory=codegen_output_dir).generate()
146-
JinjaCodegenTarget(
147-
generator="cpp-sdk",
148-
idl_path=zap_file_path.with_suffix(".matter"),
149-
output_directory=codegen_output_dir).generate()
150-
finally:
151-
# Restore original working directory
152-
os.chdir(original_cwd)
153-
154-
log.inf(f"Done. Files generated in {output_path}")
156+
# Full build is about generating an apropertiate Matter data model files in a specific directory layout.
157+
# Currently, we must align to the following directory layout:
158+
# sample/
159+
# |_ src/
160+
# |_ default_zap/
161+
# |_ zcl.xml
162+
# |_ sample.zap
163+
# |_ sample.matter
164+
# |_ clusters/
165+
# |_ *All clusters*
166+
# |_ app-common
167+
# |_ zap-generated/
168+
# |_ attributes/
169+
# |_ ids/
170+
#
171+
# Generation of the full data model files consist of three steps:
172+
# 1. ZAPGenerateTarget generates "app-common/zap-generated" directory and a part of "clusters/" directory.
173+
# It generates Attrbutes.h/cpp, Events.h/cpp, Commands.h/cpp files for each cluster.
174+
# 2. JinjaCodegenTarget generates "clusters/" directory.
175+
# It generates "AttributeIds.h/cpp", "EventIds.h", "CommandIds.h" files for each cluster.
176+
# It generates also BUILD.gn files that are used to configure build system.
177+
#
178+
# Currently, we must call JinjaCodegenTarget twice:
179+
# - for all clusters using controller-clusters.matter file to generate all clusters defined in the Matter spec.
180+
# - for the sample's .matter file to generate the new data model files that are not defined in the Matter spec.
181+
#
182+
# To generate the full data model files, we utilizes classes defined in the matter/scripts/tools/zap_regen_all.py file.
183+
# These classes are supposed to be called from the matter root directory, so we must temporarily change the current working directory to the matter root directory.
184+
zcl_file = zap.zcl_file or zap.zap_file.parent / "zcl.json"
185+
zap_input = ZapInput.FromPropertiesJson(zcl_file)
186+
template = 'src/app/common/templates/templates.json'
187+
zap_output_dir = output_path / 'app-common' / 'zap-generated'
188+
codegen_output_dir = output_path / 'clusters'
189+
190+
# Temporarily change directory to matter_path so JinjaCodegenTarget and ZAPGenerateTarget can find their scripts
191+
original_cwd = os.getcwd()
192+
os.chdir(args.matter_path)
193+
try:
194+
ZAPGenerateTarget(zap_input, template=template, output_dir=zap_output_dir).generate()
195+
JinjaCodegenTarget(
196+
generator="cpp-sdk",
197+
idl_path="src/controller/data_model/controller-clusters.matter",
198+
output_directory=codegen_output_dir).generate()
199+
JinjaCodegenTarget(
200+
generator="cpp-sdk",
201+
idl_path=zap.zap_file.with_suffix(".matter"),
202+
output_directory=codegen_output_dir).generate()
203+
finally:
204+
# Restore original working directory
205+
os.chdir(original_cwd)
206+
207+
log.inf(f"Done. Files generated in {output_path}")
155208

156209
def clear_generated_files(self, path: Path):
157210
log.inf("Clearing previously generated files:")

0 commit comments

Comments
 (0)