| # Licensed to the Apache Software Foundation (ASF) under one |
| # or more contributor license agreements. See the NOTICE file |
| # distributed with this work for additional information |
| # regarding copyright ownership. The ASF licenses this file |
| # to you 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. |
| """ |
| Provides support for micro targets (microTVM). |
| """ |
| import argparse |
| import os |
| from pathlib import Path |
| import shutil |
| import sys |
| |
| from . import TVMCException |
| from .main import register_parser |
| from .arguments import TVMCSuppressedArgumentParser |
| from .project import ( |
| get_project_options, |
| get_and_check_options, |
| get_project_dir, |
| ) |
| |
| try: |
| import tvm.micro.project as project |
| from tvm.micro import get_microtvm_template_projects |
| from tvm.micro.build import MicroTVMTemplateProjectNotFoundError |
| from tvm.micro.project_api.server import ServerError |
| from tvm.micro.project_api.client import ProjectAPIServerNotFoundError |
| |
| SUPPORT_MICRO = True |
| except (ImportError, NameError): |
| SUPPORT_MICRO = False |
| |
| |
| @register_parser |
| def add_micro_parser(subparsers, main_parser, json_params): |
| """Includes parser for 'micro' context and associated subcommands: |
| create-project (create), build, and flash. |
| """ |
| |
| if SUPPORT_MICRO is False: |
| # Don't create 'tvmc micro' parser. |
| return |
| |
| # Probe available default platform templates. |
| templates = {} |
| for p in ("zephyr", "arduino"): |
| try: |
| templates[p] = get_microtvm_template_projects(p) |
| except MicroTVMTemplateProjectNotFoundError: |
| pass |
| |
| micro = subparsers.add_parser("micro", help="select micro context.") |
| micro.set_defaults(func=drive_micro) |
| |
| micro_parser = micro.add_subparsers(title="subcommands") |
| # Selecting a subcommand under 'micro' is mandatory |
| micro_parser.required = True |
| micro_parser.dest = "subcommand" |
| |
| # 'create_project' subcommand |
| create_project_parser = micro_parser.add_parser( |
| "create-project", |
| aliases=["create"], |
| help="create a project template of a given type or given a template dir.", |
| ) |
| create_project_parser.set_defaults(subcommand_handler=create_project_handler) |
| create_project_parser.add_argument( |
| "project_dir", |
| help="project dir where the new project based on the template dir will be created.", |
| ) |
| create_project_parser.add_argument("MLF", help="Model Library Format (MLF) .tar archive.") |
| create_project_parser.add_argument( |
| "-f", |
| "--force", |
| action="store_true", |
| help="force project creating even if the specified project directory already exists.", |
| ) |
| |
| # 'build' subcommand |
| build_parser = micro_parser.add_parser( |
| "build", |
| help="build a project dir, generally creating an image to be flashed, e.g. zephyr.elf.", |
| ) |
| build_parser.set_defaults(subcommand_handler=build_handler) |
| build_parser.add_argument("project_dir", help="project dir to build.") |
| build_parser.add_argument("-f", "--force", action="store_true", help="Force rebuild.") |
| |
| # 'flash' subcommand |
| flash_parser = micro_parser.add_parser( |
| "flash", help="flash the built image on a given micro target." |
| ) |
| flash_parser.set_defaults(subcommand_handler=flash_handler) |
| flash_parser.add_argument("project_dir", help="project dir where the built image is.") |
| |
| # For each platform add arguments detected automatically using Project API info query. |
| |
| # Create subparsers for the platforms under 'create-project', 'build', and 'flash' subcommands. |
| help_msg = ( |
| "you must select a platform from the list. You can pass '-h' for a selected " |
| "platform to list its options." |
| ) |
| create_project_platforms_parser = create_project_parser.add_subparsers( |
| title="platforms", help=help_msg, dest="platform" |
| ) |
| build_platforms_parser = build_parser.add_subparsers( |
| title="platforms", help=help_msg, dest="platform" |
| ) |
| flash_platforms_parser = flash_parser.add_subparsers( |
| title="platforms", help=help_msg, dest="platform" |
| ) |
| |
| subcmds = { |
| # API method name Parser associated to method Handler func to call after parsing |
| "generate_project": [create_project_platforms_parser, create_project_handler], |
| "build": [build_platforms_parser, build_handler], |
| "flash": [flash_platforms_parser, flash_handler], |
| } |
| |
| # Helper to add a platform parser to a subcmd parser. |
| def _add_parser(parser, platform): |
| platform_name = platform[0].upper() + platform[1:] + " platform" |
| platform_parser = parser.add_parser( |
| platform, add_help=False, help=f"select {platform_name}." |
| ) |
| platform_parser.set_defaults(platform=platform) |
| return platform_parser |
| |
| parser_by_subcmd = {} |
| for subcmd, subcmd_parser_handler in subcmds.items(): |
| subcmd_parser = subcmd_parser_handler[0] |
| subcmd_parser.required = True # Selecting a platform or template is mandatory |
| parser_by_platform = {} |
| for platform in templates: |
| new_parser = _add_parser(subcmd_parser, platform) |
| parser_by_platform[platform] = new_parser |
| |
| # Besides adding the parsers for each default platform (like Zephyr and Arduino), add a |
| # parser for 'template' to deal with adhoc projects/platforms. |
| new_parser = subcmd_parser.add_parser( |
| "template", add_help=False, help="select an adhoc template." |
| ) |
| new_parser.add_argument( |
| "--template-dir", required=True, help="Project API template directory." |
| ) |
| new_parser.set_defaults(platform="template") |
| parser_by_platform["template"] = new_parser |
| |
| parser_by_subcmd[subcmd] = parser_by_platform |
| |
| disposable_parser = TVMCSuppressedArgumentParser(main_parser) |
| try: |
| known_args, _ = disposable_parser.parse_known_args() |
| except TVMCException: |
| return |
| |
| try: |
| subcmd = known_args.subcommand |
| platform = known_args.platform |
| except AttributeError: |
| # No subcommand or platform, hence no need to augment the parser for micro targets. |
| return |
| |
| # Augment parser with project options. |
| |
| if platform == "template": |
| # adhoc template |
| template_dir = str(Path(known_args.template_dir).resolve()) |
| else: |
| # default template |
| template_dir = templates[platform] |
| |
| try: |
| template = project.TemplateProject.from_directory(template_dir) |
| except ProjectAPIServerNotFoundError: |
| sys.exit(f"Error: Project API server not found in {template_dir}!") |
| |
| template_info = template.info() |
| |
| options_by_method = get_project_options(template_info) |
| |
| # TODO(gromero): refactor to remove this map. |
| subcmd_to_method = { |
| "create-project": "generate_project", |
| "create": "generate_project", |
| "build": "build", |
| "flash": "flash", |
| } |
| |
| method = subcmd_to_method[subcmd] |
| parser_by_subcmd_n_platform = parser_by_subcmd[method][platform] |
| _, handler = subcmds[method] |
| |
| parser_by_subcmd_n_platform.formatter_class = ( |
| # Set raw help text so help_text format works |
| argparse.RawTextHelpFormatter |
| ) |
| parser_by_subcmd_n_platform.set_defaults( |
| subcommand_handler=handler, |
| valid_options=options_by_method[method], |
| template_dir=template_dir, |
| ) |
| |
| required = any([opt["required"] for opt in options_by_method[method]]) |
| nargs = "+" if required else "*" |
| |
| help_text_by_option = [opt["help_text"] for opt in options_by_method[method]] |
| help_text = "\n\n".join(help_text_by_option) + "\n\n" |
| |
| parser_by_subcmd_n_platform.add_argument( |
| "--project-option", required=required, metavar="OPTION=VALUE", nargs=nargs, help=help_text |
| ) |
| |
| parser_by_subcmd_n_platform.add_argument( |
| "-h", |
| "--help", |
| "--list-options", |
| action="help", |
| help="show this help message which includes platform-specific options and exit.", |
| ) |
| |
| for one_entry in json_params: |
| micro.set_defaults(**one_entry) |
| |
| |
| def drive_micro(args): |
| # Call proper handler based on subcommand parsed. |
| args.subcommand_handler(args) |
| |
| |
| def create_project_handler(args): |
| """Creates a new project dir.""" |
| project_dir = get_project_dir(args.project_dir) |
| |
| if os.path.exists(project_dir): |
| if args.force: |
| shutil.rmtree(project_dir) |
| else: |
| raise TVMCException( |
| "The specified project dir already exists. " |
| "To force overwriting it use '-f' or '--force'." |
| ) |
| |
| template_dir = str(Path(args.template_dir).resolve()) |
| if not os.path.exists(template_dir): |
| raise TVMCException(f"Template directory {template_dir} does not exist!") |
| |
| mlf_path = str(Path(args.MLF).resolve()) |
| if not os.path.exists(mlf_path): |
| raise TVMCException(f"MLF file {mlf_path} does not exist!") |
| |
| options = get_and_check_options(args.project_option, args.valid_options) |
| |
| try: |
| project.generate_project_from_mlf(template_dir, project_dir, mlf_path, options) |
| except ServerError as error: |
| print("The following error occurred on the Project API server side: \n", error) |
| sys.exit(1) |
| |
| |
| def build_handler(args): |
| """Builds a firmware image given a project dir.""" |
| project_dir = get_project_dir(args.project_dir) |
| |
| if not os.path.exists(project_dir): |
| raise TVMCException(f"{project_dir} doesn't exist.") |
| |
| if os.path.exists(project_dir + "/build"): |
| if args.force: |
| shutil.rmtree(project_dir + "/build") |
| else: |
| raise TVMCException( |
| f"There is already a build in {project_dir}. " |
| "To force rebuild it use '-f' or '--force'." |
| ) |
| |
| options = get_and_check_options(args.project_option, args.valid_options) |
| |
| try: |
| prj = project.GeneratedProject.from_directory(project_dir, options=options) |
| prj.build() |
| except ServerError as error: |
| print("The following error occurred on the Project API server side: ", error) |
| sys.exit(1) |
| |
| |
| def flash_handler(args): |
| """Flashes a firmware image to a target device given a project dir.""" |
| |
| project_dir = get_project_dir(args.project_dir) |
| |
| if not os.path.exists(project_dir + "/build"): |
| raise TVMCException(f"Could not find a build in {project_dir}") |
| |
| options = get_and_check_options(args.project_option, args.valid_options) |
| |
| try: |
| prj = project.GeneratedProject.from_directory(project_dir, options=options) |
| prj.flash() |
| except ServerError as error: |
| print("The following error occurred on the Project API server side: ", error) |
| sys.exit(1) |