#!/usr/bin/env python3 # Copyright (c) 2022 Project CHIP Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import logging import sys import click try: import coloredlogs _has_coloredlogs = True except ImportError: _has_coloredlogs = False try: from matter_idl.matter_idl_parser import CreateParser except ImportError: import os sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), 'py_matter_idl'))) from matter_idl.matter_idl_parser import CreateParser # isort: off from matter_idl.generators import FileSystemGeneratorStorage, GeneratorStorage from matter_idl.generators.registry import CodeGenerator, GENERATORS class ListGeneratedFilesStorage(GeneratorStorage): """ A storage that prints out file names that would have content in them. """ def __init__(self): super().__init__() def get_existing_data(self, relative_path: str): return None # stdout has no pre-existing data def write_new_data(self, relative_path: str, content: str): print(relative_path) # Supported log levels, mapping string values required for argument # parsing into logging constants __LOG_LEVELS__ = { 'debug': logging.DEBUG, 'info': logging.INFO, 'warn': logging.WARN, 'fatal': logging.FATAL, } @click.command() @click.option( '--log-level', default='INFO', type=click.Choice(__LOG_LEVELS__.keys(), case_sensitive=False), help='Determines the verbosity of script output') @click.option( '--generator', '-g', default='java-jni', help='What code generator to run. The choices are: '+'|'.join(GENERATORS.keys())+'. ' + 'When using custom, provide the plugin path using `--generator custom::` syntax. ' + 'For example, `--generator custom:./my_plugin:my_plugin_module` will load `./my_plugin/my_plugin_module/__init.py__` ' + 'that defines a subclass of CodeGenerator named CustomGenerator.') @click.option( '--option', multiple=True, help="Extra generator options, of the form: --option key:value") @click.option( '--output-dir', type=click.Path(exists=False), default=".", help='Where to generate the code') @click.option( '--dry-run', default=False, is_flag=True, help='If to actually generate') @click.option( '--name-only', default=False, is_flag=True, help='Output just a list of file names that would be generated') @click.option( '--expected-outputs', type=click.Path(exists=True), default=None, help='A file containing all expected outputs. Script will fail if outputs do not match') @click.argument( 'idl_path', type=click.Path(exists=True)) def main(log_level, generator, option, output_dir, dry_run, name_only, expected_outputs, idl_path): """ Parses MATTER IDL files (.matter) and performs SDK code generation as set up by the program arguments. """ if _has_coloredlogs: coloredlogs.install(level=__LOG_LEVELS__[ log_level], fmt='%(asctime)s %(levelname)-7s %(message)s') else: logging.basicConfig( level=__LOG_LEVELS__[log_level], format='%(asctime)s %(levelname)-7s %(message)s', datefmt='%Y-%m-%d %H:%M:%S' ) if name_only: storage = ListGeneratedFilesStorage() else: storage = FileSystemGeneratorStorage(output_dir) logging.info("Parsing idl from %s" % idl_path) idl_tree = CreateParser().parse(open(idl_path, "rt").read()) plugin_module = None if generator.startswith('custom:'): # check that the plugin path is provided custom_params = generator.split(':') if len(custom_params) != 3: logging.fatal("Custom generator format not valid. Please use --generator custom::") sys.exit(1) (generator, plugin_path, plugin_module) = custom_params logging.info("Using CustomGenerator at plugin path %s.%s" % (plugin_path, plugin_module)) sys.path.append(plugin_path) generator = 'CUSTOM' extra_args = {} for o in option: if ':' not in o: logging.fatal("Please specify options as ':'. %r is not valid. " % o) sys.exit(1) key, value = o.split(':') extra_args[key] = value logging.info("Running code generator %s" % generator) generator = CodeGenerator.FromString(generator).Create(storage, idl=idl_tree, plugin_module=plugin_module, **extra_args) generator.render(dry_run) if expected_outputs: with open(expected_outputs, 'rt') as fin: expected = set() for line in fin.readlines(): line = line.strip() if line: expected.add(line) if expected != storage.generated_paths: logging.fatal("expected and generated files do not match.") extra = storage.generated_paths - expected missing = expected - storage.generated_paths for name in extra: logging.fatal(" '%s' was generated but not expected" % name) for name in missing: logging.fatal(" '%s' was expected but not generated" % name) sys.exit(1) logging.info("Done") if __name__ == '__main__': main(auto_envvar_prefix='CHIP')