66import os
77import shutil
88import sys
9+ from dataclasses import dataclass
910from pathlib import Path
1011from textwrap import dedent
1112
13+ import yaml
1214from west import log
1315from west .commands import CommandError , WestCommand
1416from zap_common import DEFAULT_MATTER_PATH , ZapInstaller , existing_dir_path , existing_file_path , find_zap
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
2537class 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