From ab86e79312c0e4a1226d7bf30c201c1a3341fea9 Mon Sep 17 00:00:00 2001 From: Zapta Date: Mon, 18 Nov 2024 06:39:03 -0800 Subject: [PATCH 01/22] Renamed the click context variables from 'ctx' to 'cmd_ctx' as a preperation for renaming resources to api_context. No change in behavior. --- apio/__main__.py | 17 ++++----- apio/cmd_util.py | 71 ++++++++++++++++++++++---------------- apio/commands/boards.py | 11 +++--- apio/commands/build.py | 5 ++- apio/commands/clean.py | 5 ++- apio/commands/create.py | 5 ++- apio/commands/drivers.py | 15 ++++---- apio/commands/examples.py | 13 ++++--- apio/commands/graph.py | 7 ++-- apio/commands/install.py | 13 ++++--- apio/commands/lint.py | 5 ++- apio/commands/modify.py | 7 ++-- apio/commands/packages.py | 23 ++++++------ apio/commands/raw.py | 9 +++-- apio/commands/report.py | 5 ++- apio/commands/sim.py | 4 +-- apio/commands/system.py | 15 ++++---- apio/commands/test.py | 5 ++- apio/commands/time.py | 5 ++- apio/commands/uninstall.py | 13 ++++--- apio/commands/upgrade.py | 5 ++- apio/commands/upload.py | 5 ++- apio/commands/verify.py | 5 ++- 23 files changed, 129 insertions(+), 139 deletions(-) diff --git a/apio/__main__.py b/apio/__main__.py index b9fb4e48..5809d993 100644 --- a/apio/__main__.py +++ b/apio/__main__.py @@ -13,7 +13,6 @@ import string import re from typing import List -from click.core import Context import click from apio import util @@ -95,8 +94,6 @@ def reformat_apio_help(original_help: str) -> str: by category. """ - # -- No command typed: show help - # if ctx.invoked_subcommand is None: # -- The auto generated click help lines (apio --help) help_lines = original_help.split("\n") @@ -155,7 +152,7 @@ def __init__(self, *args, **kwargs): # -- Return a list of all the available commands # @override - def list_commands(self, ctx): + def list_commands(self, ctx: click.core.Context): # -- All the python files inside the apio/commands folder are commands, # -- except __init__.py # -- Create the list @@ -178,7 +175,7 @@ def list_commands(self, ctx): # -- INPUT: # -- * cmd_name: Apio command name # @override - def get_command(self, ctx, cmd_name: string): + def get_command(self, ctx: click.core.Context, cmd_name: string): nnss = {} # -- Get the python filename asociated with the give command @@ -200,7 +197,7 @@ def get_command(self, ctx, cmd_name: string): return nnss.get("cli") # @override - def get_help(self, ctx: Context) -> str: + def get_help(self, ctx: click.core.Context) -> str: """Formats the help into a string and returns it. Calls :meth:`format_help` internally. @@ -248,15 +245,15 @@ def context_settings(): ) @click.pass_context @click.version_option() -def cli(ctx: Context): +def cli(cmd_ctx: click.core.Context): """This function is executed when apio is invoked without any parameter. It prints the high level usage text of Apio. """ # -- If no command was typed show top help. Equivalent to 'apio -h'. - if ctx.invoked_subcommand is None: - click.secho(ctx.get_help()) + if cmd_ctx.invoked_subcommand is None: + click.secho(cmd_ctx.get_help()) # -- If there is a command, it is executed when this function is finished # -- Debug: print the command invoked - # print(f"{ctx.invoked_subcommand}") + # print(f"{cmd_ctx.invoked_subcommand}") diff --git a/apio/cmd_util.py b/apio/cmd_util.py index 8b7cf661..93868ded 100644 --- a/apio/cmd_util.py +++ b/apio/cmd_util.py @@ -19,29 +19,30 @@ DEPRECATED_MARKER = "[DEPRECATED]" -def fatal_usage_error(ctx: click.Context, msg: str) -> None: +def fatal_usage_error(cmd_ctx: click.Context, msg: str) -> None: """Prints a an error message and command help hint, and exists the program with an error status. - ctx: The context that was passed to the command. + cmd_ctx: The context that was passed to the command. msg: A single line short error message. """ # Mimiking the usage error message from click/exceptions.py. # E.g. "Try 'apio packages -h' for help." - click.secho(ctx.get_usage()) + click.secho(cmd_ctx.get_usage()) click.secho( - f"Try '{ctx.command_path} {ctx.help_option_names[0]}' for help." + f"Try '{cmd_ctx.command_path} {cmd_ctx.help_option_names[0]}' " + "for help." ) click.secho() click.secho(f"Error: {msg}", fg="red") - ctx.exit(1) + cmd_ctx.exit(1) def _get_params_objs( - ctx: click.Context, + cmd_ctx: click.Context, ) -> Dict[str, Union[click.Option, click.Argument]]: """Return a mapping from param id to param obj.""" result = {} - for param_obj in ctx.command.get_params(ctx): + for param_obj in cmd_ctx.command.get_params(cmd_ctx): assert isinstance(param_obj, (click.Option, click.Argument)), type( param_obj ) @@ -50,7 +51,7 @@ def _get_params_objs( def _params_ids_to_aliases( - ctx: click.Context, params_ids: List[str] + cmd_ctx: click.Context, params_ids: List[str] ) -> List[str]: """Maps param ids to their respective user facing canonical aliases. The order of the params is in the inptut list is preserved. @@ -63,7 +64,7 @@ def _params_ids_to_aliases( e.g. "PACKAGES" for the argument packages. """ # Param id -> param obj. - params_dict = _get_params_objs(ctx) + params_dict = _get_params_objs(cmd_ctx) # Map the param ids to their canonical aliases. result = [] @@ -84,39 +85,43 @@ def _params_ids_to_aliases( return result -def _is_param_specified(ctx, param_id) -> bool: +def _is_param_specified(cmd_ctx, param_id) -> bool: """Determine if the param with given id was specified in the command line.""" # Mapping: param id -> param obj. - params_dict = _get_params_objs(ctx) + params_dict = _get_params_objs(cmd_ctx) # Get the official status. - param_src = ctx.get_parameter_source(param_id) + param_src = cmd_ctx.get_parameter_source(param_id) is_specified = param_src == click.core.ParameterSource.COMMANDLINE # A special case for repeating arguments. Click considers the # empty tuple value to come with the command line but we consider # it to come from the default. is_arg = isinstance(params_dict[param_id], click.Argument) if is_specified and is_arg: - arg_value = ctx.params[param_id] + arg_value = cmd_ctx.params[param_id] if arg_value == tuple(): is_specified = False # All done return is_specified -def _specified_params(ctx: click.Context, param_ids: List[str]) -> List[str]: +def _specified_params( + cmd_ctx: click.Context, param_ids: List[str] +) -> List[str]: """Returns the subset of param ids that were used in the command line. The original order of the list is preserved. For definition of params and param ids see check_exclusive_params(). """ result = [] for param_id in param_ids: - if _is_param_specified(ctx, param_id): + if _is_param_specified(cmd_ctx, param_id): result.append(param_id) return result -def check_at_most_one_param(ctx: click.Context, param_ids: List[str]) -> None: +def check_at_most_one_param( + cmd_ctx: click.Context, param_ids: List[str] +) -> None: """Checks that at most one of given params were specified in the command line. If more than one param was specified, exits the program with a message and error status. @@ -127,15 +132,19 @@ def check_at_most_one_param(ctx: click.Context, param_ids: List[str]) -> None: is nameof(param_var1, param_var2, ...) """ # The the subset of ids of params that where used in the command. - specified_param_ids = _specified_params(ctx, param_ids) + specified_param_ids = _specified_params(cmd_ctx, param_ids) # If more 2 or more print an error and exit. if len(specified_param_ids) >= 2: - canonical_aliases = _params_ids_to_aliases(ctx, specified_param_ids) + canonical_aliases = _params_ids_to_aliases( + cmd_ctx, specified_param_ids + ) aliases_str = ", ".join(canonical_aliases) - fatal_usage_error(ctx, f"[{aliases_str}] are mutually exclusive.") + fatal_usage_error(cmd_ctx, f"[{aliases_str}] are mutually exclusive.") -def check_exactly_one_param(ctx: click.Context, param_ids: List[str]) -> None: +def check_exactly_one_param( + cmd_ctx: click.Context, param_ids: List[str] +) -> None: """Checks that at exactly one of given params is specified in the command line. If more or less than one params is specified, exits the program with a message and error status. @@ -146,15 +155,19 @@ def check_exactly_one_param(ctx: click.Context, param_ids: List[str]) -> None: is nameof(param_var1, param_var2, ...) """ # The the subset of ids of params that where used in the command. - specified_param_ids = _specified_params(ctx, param_ids) + specified_param_ids = _specified_params(cmd_ctx, param_ids) # If more 2 or more print an error and exit. if len(specified_param_ids) != 1: - canonical_aliases = _params_ids_to_aliases(ctx, param_ids) + canonical_aliases = _params_ids_to_aliases(cmd_ctx, param_ids) aliases_str = ", ".join(canonical_aliases) - fatal_usage_error(ctx, f"One of [{aliases_str}] must be specified.") + fatal_usage_error( + cmd_ctx, f"One of [{aliases_str}] must be specified." + ) -def check_at_least_one_param(ctx: click.Context, param_ids: List[str]) -> None: +def check_at_least_one_param( + cmd_ctx: click.Context, param_ids: List[str] +) -> None: """Checks that at least one of given params is specified in the command line. If none of the params is specified, exits the program with a message and error status. @@ -165,13 +178,13 @@ def check_at_least_one_param(ctx: click.Context, param_ids: List[str]) -> None: is nameof(param_var1, param_var2, ...) """ # The the subset of ids of params that where used in the command. - specified_param_ids = _specified_params(ctx, param_ids) + specified_param_ids = _specified_params(cmd_ctx, param_ids) # If more 2 or more print an error and exit. if len(specified_param_ids) < 1: - canonical_aliases = _params_ids_to_aliases(ctx, param_ids) + canonical_aliases = _params_ids_to_aliases(cmd_ctx, param_ids) aliases_str = ", ".join(canonical_aliases) fatal_usage_error( - ctx, f"At list one of [{aliases_str}] must be specified." + cmd_ctx, f"At list one of [{aliases_str}] must be specified." ) @@ -216,10 +229,10 @@ class ApioCommand(click.Command): commands that contains deprecated ApioOptions. """ - def _num_deprecated_options(self, ctx: click.Context) -> None: + def _num_deprecated_options(self, cmd_ctx: click.Context) -> None: """Returns the number of deprecated options of this command.""" deprecated_options = 0 - for param in self.get_params(ctx): + for param in self.get_params(cmd_ctx): if isinstance(param, ApioOption) and param.deprecated: deprecated_options += 1 return deprecated_options diff --git a/apio/commands/boards.py b/apio/commands/boards.py index 97daeaad..4734f801 100644 --- a/apio/commands/boards.py +++ b/apio/commands/boards.py @@ -10,7 +10,6 @@ from pathlib import Path from varname import nameof import click -from click.core import Context from apio.resources import Resources from apio import cmd_util from apio.commands import options @@ -61,7 +60,7 @@ @options.list_option_gen(help="List supported FPGA boards.") @list_fpgas_option def cli( - ctx: Context, + cmd_ctx: click.core.Context, # Options project_dir: Path, list_: bool, @@ -72,7 +71,7 @@ def cli( """ # Make sure these params are exclusive. - cmd_util.check_at_most_one_param(ctx, nameof(list_, fpgas)) + cmd_util.check_at_most_one_param(cmd_ctx, nameof(list_, fpgas)) # -- Access to the apio resources. We need project scope since the project # -- may override the list of boards. @@ -81,12 +80,12 @@ def cli( # -- Option 1: List boards if list_: resources.list_boards() - ctx.exit(0) + cmd_ctx.exit(0) # -- Option 2: List fpgas if fpgas: resources.list_fpgas() - ctx.exit(0) + cmd_ctx.exit(0) # -- No options: show help - click.secho(ctx.get_help()) + click.secho(cmd_ctx.get_help()) diff --git a/apio/commands/build.py b/apio/commands/build.py index 28075204..8dfbbb71 100644 --- a/apio/commands/build.py +++ b/apio/commands/build.py @@ -9,7 +9,6 @@ from pathlib import Path import click -from click.core import Context from apio.managers.scons import SCons from apio import cmd_util from apio.commands import options @@ -57,7 +56,7 @@ @options.type_option_gen(deprecated=True) @options.pack_option_gen(deprecated=True) def cli( - ctx: Context, + cmd_ctx: click.core.Context, # Options project_dir: Path, verbose: bool, @@ -103,7 +102,7 @@ def cli( ) # -- Done! - ctx.exit(exit_code) + cmd_ctx.exit(exit_code) # Advanced notes: https://github.com/FPGAwars/apio/wiki/Commands#apio-build diff --git a/apio/commands/clean.py b/apio/commands/clean.py index 42e45e1d..2befa5a9 100644 --- a/apio/commands/clean.py +++ b/apio/commands/clean.py @@ -9,7 +9,6 @@ from pathlib import Path import click -from click.core import Context from apio.managers.scons import SCons from apio import cmd_util from apio.commands import options @@ -45,7 +44,7 @@ @options.verbose_option @options.board_option_gen(deprecated=True) def cli( - ctx: Context, + cmd_ctx: click.core.Context, # Options project_dir: Path, verbose: bool, @@ -64,4 +63,4 @@ def cli( exit_code = scons.clean({"board": board, "verbose": {"all": verbose}}) # -- Done! - ctx.exit(exit_code) + cmd_ctx.exit(exit_code) diff --git a/apio/commands/create.py b/apio/commands/create.py index 14e02be5..41939d26 100644 --- a/apio/commands/create.py +++ b/apio/commands/create.py @@ -9,7 +9,6 @@ from pathlib import Path import click -from click.core import Context from apio.managers.project import Project, DEFAULT_TOP_MODULE, PROJECT_FILENAME from apio import util from apio import cmd_util @@ -60,7 +59,7 @@ @options.project_dir_option @options.sayyes def cli( - ctx: Context, + cmd_ctx: click.core.Context, # Options board: str, top_module: str, @@ -86,4 +85,4 @@ def cli( ok = Project.create_ini(resources, board, top_module, sayyes) exit_code = 0 if ok else 1 - ctx.exit(exit_code) + cmd_ctx.exit(exit_code) diff --git a/apio/commands/drivers.py b/apio/commands/drivers.py index 870e9c1b..b7187705 100644 --- a/apio/commands/drivers.py +++ b/apio/commands/drivers.py @@ -9,7 +9,6 @@ from varname import nameof import click -from click.core import Context from apio.managers.drivers import Drivers from apio import cmd_util from apio.resources import Resources @@ -82,7 +81,7 @@ @serial_install_option @serial_uninstall_option def cli( - ctx: Context, + cmd_ctx: click.core.Context, # Options: ftdi_install: bool, ftdi_uninstall: bool, @@ -93,7 +92,7 @@ def cli( # Make sure these params are exclusive. cmd_util.check_at_most_one_param( - ctx, + cmd_ctx, nameof(ftdi_install, ftdi_uninstall, serial_install, serial_uninstall), ) @@ -104,22 +103,22 @@ def cli( # -- FTDI install option if ftdi_install: exit_code = drivers.ftdi_install() - ctx.exit(exit_code) + cmd_ctx.exit(exit_code) # -- FTDI uninstall option if ftdi_uninstall: exit_code = drivers.ftdi_uninstall() - ctx.exit(exit_code) + cmd_ctx.exit(exit_code) # -- Serial install option if serial_install: exit_code = drivers.serial_install() - ctx.exit(exit_code) + cmd_ctx.exit(exit_code) # -- Serial uninstall option if serial_uninstall: exit_code = drivers.serial_uninstall() - ctx.exit(exit_code) + cmd_ctx.exit(exit_code) # -- No options. Show the help - click.secho(ctx.get_help()) + click.secho(cmd_ctx.get_help()) diff --git a/apio/commands/examples.py b/apio/commands/examples.py index 2df7d912..6d2e1a04 100644 --- a/apio/commands/examples.py +++ b/apio/commands/examples.py @@ -10,7 +10,6 @@ from pathlib import Path from varname import nameof import click -from click.core import Context from apio.managers.examples import Examples from apio import cmd_util from apio.commands import options @@ -80,7 +79,7 @@ @options.project_dir_option @options.sayno def cli( - ctx: Context, + cmd_ctx: click.core.Context, # Options list_: bool, fetch_dir: str, @@ -91,11 +90,11 @@ def cli( """Manage verilog examples.\n Install with `apio packages --install examples`""" - ctx.get_help() + cmd_ctx.get_help() # Make sure these params are exclusive. cmd_util.check_exactly_one_param( - ctx, nameof(list_, fetch_dir, fetch_files) + cmd_ctx, nameof(list_, fetch_dir, fetch_files) ) # -- Access to the Drivers @@ -105,16 +104,16 @@ def cli( # -- Option: Copy the directory if fetch_dir: exit_code = examples.copy_example_dir(fetch_dir, project_dir, sayno) - ctx.exit(exit_code) + cmd_ctx.exit(exit_code) # -- Option: Copy only the example files (not the initial folders) if fetch_files: exit_code = examples.copy_example_files( fetch_files, project_dir, sayno ) - ctx.exit(exit_code) + cmd_ctx.exit(exit_code) # -- Option: List all the available examples assert list_ exit_code = examples.list_examples() - ctx.exit(exit_code) + cmd_ctx.exit(exit_code) diff --git a/apio/commands/graph.py b/apio/commands/graph.py index 98bd0f0e..79d20350 100644 --- a/apio/commands/graph.py +++ b/apio/commands/graph.py @@ -9,7 +9,6 @@ from pathlib import Path import click -from click.core import Context from varname import nameof from apio.managers.scons import SCons from apio import cmd_util @@ -78,7 +77,7 @@ @options.top_module_option_gen(help="Set the name of the top module to graph.") @options.verbose_option def cli( - ctx: Context, + cmd_ctx: click.core.Context, # Options pdf: bool, png: bool, @@ -88,7 +87,7 @@ def cli( ): """Implements the apio graph command.""" # -- Sanity check the options. - cmd_util.check_at_most_one_param(ctx, nameof(pdf, png)) + cmd_util.check_at_most_one_param(cmd_ctx, nameof(pdf, png)) # -- Construct the graph spec to pass to scons. # -- For now it's trivial. @@ -115,4 +114,4 @@ def cli( ) # -- Done! - ctx.exit(exit_code) + cmd_ctx.exit(exit_code) diff --git a/apio/commands/install.py b/apio/commands/install.py index 14adf7d9..421a4c45 100644 --- a/apio/commands/install.py +++ b/apio/commands/install.py @@ -11,7 +11,6 @@ from typing import Tuple from varname import nameof import click -from click.core import Context from apio.managers.old_installer import Installer from apio.resources import Resources from apio import cmd_util @@ -72,7 +71,7 @@ def install_packages( @options.project_dir_option @options.verbose_option def cli( - ctx: Context, + cmd_ctx: click.core.Context, # Arguments packages: Tuple[str], # Options @@ -93,7 +92,7 @@ def cli( ) # Make sure these params are exclusive. - cmd_util.check_at_most_one_param(ctx, nameof(packages, all_, list_)) + cmd_util.check_at_most_one_param(cmd_ctx, nameof(packages, all_, list_)) # -- Load the resources. We don't care about project specific resources. resources = Resources( @@ -104,7 +103,7 @@ def cli( # -- Install the given apio packages if packages: install_packages(packages, resources, force, verbose) - ctx.exit(0) + cmd_ctx.exit(0) # -- Install all the available packages (if any) if all_: @@ -112,12 +111,12 @@ def cli( install_packages( resources.platform_packages.keys(), resources, force, verbose ) - ctx.exit(0) + cmd_ctx.exit(0) # -- List all the packages (installed or not) if list_: resources.list_packages() - ctx.exit(0) + cmd_ctx.exit(0) # -- Invalid option. Just show the help - click.secho(ctx.get_help()) + click.secho(cmd_ctx.get_help()) diff --git a/apio/commands/lint.py b/apio/commands/lint.py index 5efe1776..ecb6b1f2 100644 --- a/apio/commands/lint.py +++ b/apio/commands/lint.py @@ -9,7 +9,6 @@ from pathlib import Path import click -from click.core import Context from apio.managers.scons import SCons from apio import cmd_util from apio.commands import options @@ -86,7 +85,7 @@ @warn_option @options.project_dir_option def cli( - ctx: Context, + cmd_ctx: click.core.Context, # Options top_module: str, all_: bool, @@ -111,4 +110,4 @@ def cli( "warn": warn, } ) - ctx.exit(exit_code) + cmd_ctx.exit(exit_code) diff --git a/apio/commands/modify.py b/apio/commands/modify.py index 92a8b3d5..0ba91b71 100644 --- a/apio/commands/modify.py +++ b/apio/commands/modify.py @@ -10,7 +10,6 @@ from pathlib import Path from varname import nameof import click -from click.core import Context from apio.managers.project import Project from apio import cmd_util from apio.commands import options @@ -51,7 +50,7 @@ @options.top_module_option_gen(help="Set the top level module name.") @options.project_dir_option def cli( - ctx: Context, + cmd_ctx: click.core.Context, # Options board: str, top_module: str, @@ -60,7 +59,7 @@ def cli( """Modify the project file.""" # At least one of these options are required. - cmd_util.check_at_least_one_param(ctx, nameof(board, top_module)) + cmd_util.check_at_least_one_param(cmd_ctx, nameof(board, top_module)) # Load resources. resources = Resources(project_dir=project_dir, project_scope=True) @@ -69,4 +68,4 @@ def cli( ok = Project.modify_ini_file(resources, board, top_module) exit_code = 0 if ok else 1 - ctx.exit(exit_code) + cmd_ctx.exit(exit_code) diff --git a/apio/commands/packages.py b/apio/commands/packages.py index b56d35b5..b807e974 100644 --- a/apio/commands/packages.py +++ b/apio/commands/packages.py @@ -11,7 +11,6 @@ from typing import Tuple, List from varname import nameof import click -from click.core import Context from apio.managers import installer from apio.resources import Resources from apio import cmd_util, pkg_util, util @@ -196,7 +195,7 @@ def _list(resources: Resources, verbose: bool) -> int: @options.sayyes @options.verbose_option def cli( - ctx: Context, + cmd_ctx: click.core.Context, # Arguments packages: Tuple[str], # Options @@ -215,13 +214,13 @@ def cli( # Validate the option combination. cmd_util.check_exactly_one_param( - ctx, nameof(list_, install, uninstall, fix) + cmd_ctx, nameof(list_, install, uninstall, fix) ) - cmd_util.check_at_most_one_param(ctx, nameof(list_, force)) - cmd_util.check_at_most_one_param(ctx, nameof(uninstall, force)) - cmd_util.check_at_most_one_param(ctx, nameof(fix, force)) - cmd_util.check_at_most_one_param(ctx, nameof(list_, packages)) - cmd_util.check_at_most_one_param(ctx, nameof(fix, packages)) + cmd_util.check_at_most_one_param(cmd_ctx, nameof(list_, force)) + cmd_util.check_at_most_one_param(cmd_ctx, nameof(uninstall, force)) + cmd_util.check_at_most_one_param(cmd_ctx, nameof(fix, force)) + cmd_util.check_at_most_one_param(cmd_ctx, nameof(list_, packages)) + cmd_util.check_at_most_one_param(cmd_ctx, nameof(fix, packages)) # -- Load the resources. We don't care about project specific resources. resources = Resources( @@ -231,16 +230,16 @@ def cli( if install: exit_code = _install(resources, packages, force, verbose) - ctx.exit(exit_code) + cmd_ctx.exit(exit_code) if uninstall: exit_code = _uninstall(resources, packages, verbose, sayyes) - ctx.exit(exit_code) + cmd_ctx.exit(exit_code) if fix: exit_code = _fix(resources, verbose) - ctx.exit(exit_code) + cmd_ctx.exit(exit_code) # -- Here it must be --list. exit_code = _list(resources, verbose) - ctx.exit(exit_code) + cmd_ctx.exit(exit_code) diff --git a/apio/commands/raw.py b/apio/commands/raw.py index 9fecec68..de2ffb4c 100644 --- a/apio/commands/raw.py +++ b/apio/commands/raw.py @@ -8,7 +8,6 @@ """Implementation of 'apio raw' command""" import click -from click.core import Context from apio import util, pkg_util, cmd_util from apio.resources import Resources @@ -58,7 +57,7 @@ @click.argument("cmd", metavar="COMMAND", required=False) @verbose_option def cli( - ctx: Context, + cmd_ctx: click.core.Context, # Arguments cmd: str, # Options @@ -69,7 +68,7 @@ def cli( """ if not cmd and not env: - cmd_util.fatal_usage_error(ctx, "Missing an option or a command") + cmd_util.fatal_usage_error(cmd_ctx, "Missing an option or a command") # -- Set the system env for the packages. This both dumps the env settings # -- if --env option is specifies and prepare the env for the command @@ -84,6 +83,6 @@ def cli( # -- Invoke the command. exit_code = util.call(cmd) - ctx.exit(exit_code) + cmd_ctx.exit(exit_code) - ctx.exit(0) + cmd_ctx.exit(0) diff --git a/apio/commands/report.py b/apio/commands/report.py index 0e3080ab..9c31cf44 100644 --- a/apio/commands/report.py +++ b/apio/commands/report.py @@ -9,7 +9,6 @@ from pathlib import Path import click -from click.core import Context from apio.managers.scons import SCons from apio import cmd_util from apio.commands import options @@ -53,7 +52,7 @@ @options.type_option_gen(deprecated=True) @options.pack_option_gen(deprecated=True) def cli( - ctx: Context, + cmd_ctx: click.core.Context, # Options project_dir: Path, verbose: bool, @@ -88,4 +87,4 @@ def cli( ) # -- Done! - ctx.exit(exit_code) + cmd_ctx.exit(exit_code) diff --git a/apio/commands/sim.py b/apio/commands/sim.py index bffd9437..a3c62edf 100644 --- a/apio/commands/sim.py +++ b/apio/commands/sim.py @@ -51,7 +51,7 @@ @click.argument("testbench", nargs=1, required=True) @options.project_dir_option def cli( - ctx, + cmd_ctx, # Arguments testbench: str, # Options @@ -69,4 +69,4 @@ def cli( exit_code = scons.sim({"testbench": testbench}) # -- Done! - ctx.exit(exit_code) + cmd_ctx.exit(exit_code) diff --git a/apio/commands/system.py b/apio/commands/system.py index 9d88ba76..ff26a9cd 100644 --- a/apio/commands/system.py +++ b/apio/commands/system.py @@ -10,7 +10,6 @@ from pathlib import Path from varname import nameof import click -from click.core import Context from apio import util from apio import cmd_util from apio.managers.system import System @@ -102,7 +101,7 @@ @info_option @platforms_option def cli( - ctx: Context, + cmd_ctx: click.core.Context, # Options project_dir: Path, lsftdi: bool, @@ -116,7 +115,7 @@ def cli( # Make sure these params are exclusive. cmd_util.check_exactly_one_param( - ctx, nameof(lsftdi, lsusb, lsserial, info, platforms) + cmd_ctx, nameof(lsftdi, lsusb, lsserial, info, platforms) ) # Load the various resource files. @@ -128,17 +127,17 @@ def cli( # -- List all connected ftdi devices if lsftdi: exit_code = system.lsftdi() - ctx.exit(exit_code) + cmd_ctx.exit(exit_code) # -- List all connected USB devices if lsusb: exit_code = system.lsusb() - ctx.exit(exit_code) + cmd_ctx.exit(exit_code) # -- List all connected serial devices if lsserial: exit_code = system.lsserial() - ctx.exit(exit_code) + cmd_ctx.exit(exit_code) # -- Show system information if info: @@ -158,7 +157,7 @@ def cli( click.secho("Apio packages ", nl=False) click.secho(util.get_packages_dir(), fg="cyan") - ctx.exit(0) + cmd_ctx.exit(0) if platforms: click.secho( @@ -176,7 +175,7 @@ def cli( f" {platform_id:18} {package_selector:20} {description}", fg=fg, ) - ctx.exit(0) + cmd_ctx.exit(0) # -- Error, no option selected. assert 0, "Non reachable" diff --git a/apio/commands/test.py b/apio/commands/test.py index 58db58ca..c6f8f512 100644 --- a/apio/commands/test.py +++ b/apio/commands/test.py @@ -9,7 +9,6 @@ from pathlib import Path import click -from click.core import Context from apio.managers.scons import SCons from apio import cmd_util from apio.commands import options @@ -51,7 +50,7 @@ @options.project_dir_option # @options.testbench def cli( - ctx: Context, + cmd_ctx: click.core.Context, # Arguments testbench_file: str, # Options @@ -64,4 +63,4 @@ def cli( scons = SCons(resources) exit_code = scons.test({"testbench": testbench_file}) - ctx.exit(exit_code) + cmd_ctx.exit(exit_code) diff --git a/apio/commands/time.py b/apio/commands/time.py index f5fc2487..01629d6b 100644 --- a/apio/commands/time.py +++ b/apio/commands/time.py @@ -9,7 +9,6 @@ from pathlib import Path import click -from click.core import Context from apio.managers.scons import SCons from apio import cmd_util from apio.commands import options @@ -46,7 +45,7 @@ @options.type_option_gen(deprecated=False) @options.pack_option_gen(deprecated=False) def cli( - ctx: Context, + cmd_ctx: click.core.Context, # Options project_dir: Path, verbose: bool, @@ -90,4 +89,4 @@ def cli( ) # -- Done! - ctx.exit(exit_code) + cmd_ctx.exit(exit_code) diff --git a/apio/commands/uninstall.py b/apio/commands/uninstall.py index 77e0a7f1..d38778db 100644 --- a/apio/commands/uninstall.py +++ b/apio/commands/uninstall.py @@ -11,7 +11,6 @@ from typing import Tuple from varname import nameof import click -from click.core import Context from apio.managers.old_installer import Installer from apio import cmd_util from apio.resources import Resources @@ -69,7 +68,7 @@ def _uninstall(packages: list, resources: Resources, sayyes, verbose: bool): @options.sayyes @options.verbose_option def cli( - ctx: Context, + cmd_ctx: click.core.Context, # Arguments packages: Tuple[str], # Options @@ -88,7 +87,7 @@ def cli( ) # Make sure these params are exclusive. - cmd_util.check_at_most_one_param(ctx, nameof(packages, list_, all_)) + cmd_util.check_at_most_one_param(cmd_ctx, nameof(packages, list_, all_)) # -- Load the resources. resources = Resources( @@ -99,7 +98,7 @@ def cli( # -- Uninstall the given apio packages if packages: _uninstall(packages, resources, sayyes, verbose) - ctx.exit(0) + cmd_ctx.exit(0) # -- Uninstall all the packages if all_: @@ -107,12 +106,12 @@ def cli( packages = resources.profile.packages # -- Uninstall them! _uninstall(packages, resources, sayyes, verbose) - ctx.exit(0) + cmd_ctx.exit(0) # -- List all the packages (installed or not) if list_: resources.list_packages() - ctx.exit(0) + cmd_ctx.exit(0) # -- Invalid option. Just show the help - click.secho(ctx.get_help()) + click.secho(cmd_ctx.get_help()) diff --git a/apio/commands/upgrade.py b/apio/commands/upgrade.py index 9971a71e..02f42f9f 100644 --- a/apio/commands/upgrade.py +++ b/apio/commands/upgrade.py @@ -9,7 +9,6 @@ import importlib.metadata import click -from click.core import Context from packaging import version from apio.util import get_pypi_latest_version from apio import cmd_util @@ -35,7 +34,7 @@ cls=cmd_util.ApioCommand, ) @click.pass_context -def cli(ctx: Context): +def cli(cmd_ctx: click.core.Context): """Check the latest Apio version.""" # -- Get the current apio version from the python package installed @@ -47,7 +46,7 @@ def cli(ctx: Context): # -- There was an error getting the version from pypi if latest_version is None: - ctx.exit(1) + cmd_ctx.exit(1) # -- Print information about apio (Debug) print(f"Local Apio version: {current_version}") diff --git a/apio/commands/upload.py b/apio/commands/upload.py index da7b0909..64030a51 100644 --- a/apio/commands/upload.py +++ b/apio/commands/upload.py @@ -9,7 +9,6 @@ from pathlib import Path import click -from click.core import Context from apio.managers.scons import SCons from apio.managers.drivers import Drivers from apio import cmd_util @@ -76,7 +75,7 @@ @options.top_module_option_gen(deprecated=True) @options.board_option_gen(deprecated=True) def cli( - ctx: Context, + cmd_ctx: click.core.Context, # Options project_dir: Path, serial_port: str, @@ -131,7 +130,7 @@ def cli( drivers.post_upload() # -- Done! - ctx.exit(exit_code) + cmd_ctx.exit(exit_code) # Advanced notes: https://github.com/FPGAwars/apio/wiki/Commands#apio-upload diff --git a/apio/commands/verify.py b/apio/commands/verify.py index c6413e94..d03a0a4a 100644 --- a/apio/commands/verify.py +++ b/apio/commands/verify.py @@ -9,7 +9,6 @@ from pathlib import Path import click -from click.core import Context from apio.managers.scons import SCons from apio import cmd_util from apio.commands import options @@ -37,7 +36,7 @@ @options.verbose_option @options.board_option_gen(deprecated=True) def cli( - ctx: Context, + cmd_ctx: click.core.Context, # Options project_dir: Path, verbose: bool, @@ -65,4 +64,4 @@ def cli( ) # -- Done! - ctx.exit(exit_code) + cmd_ctx.exit(exit_code) From 1d58a9d89a98c8e0a7edf3d9002d660d0c61e5b0 Mon Sep 17 00:00:00 2001 From: Zapta Date: Mon, 18 Nov 2024 07:23:37 -0800 Subject: [PATCH 02/22] Renamed Resources to ApiContext and resources to api_context. Will renamem resources.py in a follow up change. --- apio/commands/boards.py | 10 +++--- apio/commands/build.py | 10 +++--- apio/commands/clean.py | 10 +++--- apio/commands/create.py | 8 ++--- apio/commands/drivers.py | 6 ++-- apio/commands/examples.py | 8 ++--- apio/commands/graph.py | 10 +++--- apio/commands/install.py | 17 ++++----- apio/commands/lint.py | 10 +++--- apio/commands/modify.py | 8 ++--- apio/commands/packages.py | 49 ++++++++++++------------- apio/commands/raw.py | 8 ++--- apio/commands/report.py | 10 +++--- apio/commands/sim.py | 10 +++--- apio/commands/system.py | 14 ++++---- apio/commands/test.py | 10 +++--- apio/commands/time.py | 10 +++--- apio/commands/uninstall.py | 21 +++++------ apio/commands/upload.py | 14 ++++---- apio/commands/verify.py | 10 +++--- apio/managers/arguments.py | 25 ++++++------- apio/managers/drivers.py | 66 +++++++++++++++++----------------- apio/managers/examples.py | 18 +++++----- apio/managers/installer.py | 66 +++++++++++++++++----------------- apio/managers/old_installer.py | 30 ++++++++-------- apio/managers/project.py | 17 +++++---- apio/managers/scons.py | 58 +++++++++++++++--------------- apio/managers/system.py | 14 ++++---- apio/pkg_util.py | 56 +++++++++++++++-------------- apio/resources.py | 22 ++++++------ 30 files changed, 322 insertions(+), 303 deletions(-) diff --git a/apio/commands/boards.py b/apio/commands/boards.py index 4734f801..b5b53bc1 100644 --- a/apio/commands/boards.py +++ b/apio/commands/boards.py @@ -10,7 +10,7 @@ from pathlib import Path from varname import nameof import click -from apio.resources import Resources +from apio.resources import ApioContext from apio import cmd_util from apio.commands import options @@ -73,18 +73,18 @@ def cli( # Make sure these params are exclusive. cmd_util.check_at_most_one_param(cmd_ctx, nameof(list_, fpgas)) - # -- Access to the apio resources. We need project scope since the project + # -- Create an apio context. We need project scope since the project # -- may override the list of boards. - resources = Resources(project_dir=project_dir, project_scope=True) + apio_ctx = ApioContext(project_dir=project_dir, project_scope=True) # -- Option 1: List boards if list_: - resources.list_boards() + apio_ctx.list_boards() cmd_ctx.exit(0) # -- Option 2: List fpgas if fpgas: - resources.list_fpgas() + apio_ctx.list_fpgas() cmd_ctx.exit(0) # -- No options: show help diff --git a/apio/commands/build.py b/apio/commands/build.py index 8dfbbb71..e657bec0 100644 --- a/apio/commands/build.py +++ b/apio/commands/build.py @@ -12,7 +12,7 @@ from apio.managers.scons import SCons from apio import cmd_util from apio.commands import options -from apio.resources import Resources +from apio.resources import ApioContext # --------------------------- @@ -78,9 +78,11 @@ def cli( # by means of the scons tool # https://www.scons.org/documentation.html - # -- Create the scons object - resources = Resources(project_dir=project_dir, project_scope=True) - scons = SCons(resources) + # -- Create apio context. + apio_ctx = ApioContext(project_dir=project_dir, project_scope=True) + + # -- Create the scons manager. + scons = SCons(apio_ctx) # R0801: Similar lines in 2 files # pylint: disable=R0801 diff --git a/apio/commands/clean.py b/apio/commands/clean.py index 2befa5a9..bc94211f 100644 --- a/apio/commands/clean.py +++ b/apio/commands/clean.py @@ -12,7 +12,7 @@ from apio.managers.scons import SCons from apio import cmd_util from apio.commands import options -from apio.resources import Resources +from apio.resources import ApioContext # --------------------------- @@ -55,9 +55,11 @@ def cli( by apio commands. """ - # -- Create the scons object - resources = Resources(project_dir=project_dir, project_scope=True) - scons = SCons(resources) + # -- Create the apio context. + apio_ctx = ApioContext(project_dir=project_dir, project_scope=True) + + # -- Create the scons manager. + scons = SCons(apio_ctx) # -- Build the project with the given parameters exit_code = scons.clean({"board": board, "verbose": {"all": verbose}}) diff --git a/apio/commands/create.py b/apio/commands/create.py index 41939d26..90cf486e 100644 --- a/apio/commands/create.py +++ b/apio/commands/create.py @@ -13,7 +13,7 @@ from apio import util from apio import cmd_util from apio.commands import options -from apio.resources import Resources +from apio.resources import ApioContext # --------------------------- @@ -74,15 +74,15 @@ def cli( if not top_module: top_module = DEFAULT_TOP_MODULE - # -- Load resources. We use project scope in case the project dir + # -- Create an apio context. We use project scope in case the project dir # -- already has a custom boards.json file so we validate 'board' # -- against that board list. - resources = Resources(project_dir=project_dir, project_scope=True) + apio_ctx = ApioContext(project_dir=project_dir, project_scope=True) project_dir = util.get_project_dir(project_dir) # Create the apio.ini file - ok = Project.create_ini(resources, board, top_module, sayyes) + ok = Project.create_ini(apio_ctx, board, top_module, sayyes) exit_code = 0 if ok else 1 cmd_ctx.exit(exit_code) diff --git a/apio/commands/drivers.py b/apio/commands/drivers.py index b7187705..4bf96730 100644 --- a/apio/commands/drivers.py +++ b/apio/commands/drivers.py @@ -11,7 +11,7 @@ import click from apio.managers.drivers import Drivers from apio import cmd_util -from apio.resources import Resources +from apio.resources import ApioContext # --------------------------- # -- COMMAND SPECIFIC OPTIONS @@ -97,8 +97,8 @@ def cli( ) # -- Access to the Drivers - resources = Resources(project_scope=False) - drivers = Drivers(resources) + apio_ctx = ApioContext(project_scope=False) + drivers = Drivers(apio_ctx) # -- FTDI install option if ftdi_install: diff --git a/apio/commands/examples.py b/apio/commands/examples.py index 6d2e1a04..af7ff419 100644 --- a/apio/commands/examples.py +++ b/apio/commands/examples.py @@ -13,7 +13,7 @@ from apio.managers.examples import Examples from apio import cmd_util from apio.commands import options -from apio.resources import Resources +from apio.resources import ApioContext # --------------------------- # -- COMMAND SPECIFIC OPTIONS @@ -97,9 +97,9 @@ def cli( cmd_ctx, nameof(list_, fetch_dir, fetch_files) ) - # -- Access to the Drivers - resources = Resources(project_scope=False) - examples = Examples(resources) + # -- Access to the examples. + apio_ctx = ApioContext(project_scope=False) + examples = Examples(apio_ctx) # -- Option: Copy the directory if fetch_dir: diff --git a/apio/commands/graph.py b/apio/commands/graph.py index 79d20350..8582b86a 100644 --- a/apio/commands/graph.py +++ b/apio/commands/graph.py @@ -13,7 +13,7 @@ from apio.managers.scons import SCons from apio import cmd_util from apio.commands import options -from apio.resources import Resources +from apio.resources import ApioContext # --------------------------- # -- COMMAND SPECIFIC OPTIONS @@ -98,11 +98,11 @@ def cli( else: graph_spec = "svg" - # -- Load apio resources. - resources = Resources(project_dir=project_dir, project_scope=True) + # -- Create an apio context. + apio_ctx = ApioContext(project_dir=project_dir, project_scope=True) - # -- Create the scons object. - scons = SCons(resources) + # -- Create the scons manager. + scons = SCons(apio_ctx) # -- Graph the project with the given parameters exit_code = scons.graph( diff --git a/apio/commands/install.py b/apio/commands/install.py index 421a4c45..c913f910 100644 --- a/apio/commands/install.py +++ b/apio/commands/install.py @@ -12,7 +12,7 @@ from varname import nameof import click from apio.managers.old_installer import Installer -from apio.resources import Resources +from apio.resources import ApioContext from apio import cmd_util from apio.commands import options @@ -21,7 +21,7 @@ # pylint: disable=R0801 def install_packages( packages: list, - resources: Resources, + apio_ctx: ApioContext, force: bool, verbose: bool, ): @@ -39,7 +39,7 @@ def install_packages( modifiers = Installer.Modifiers( force=force, checkversion=True, verbose=verbose ) - installer = Installer(package, resources, modifiers) + installer = Installer(package, apio_ctx, modifiers) # -- Install the package! installer.install() @@ -94,28 +94,29 @@ def cli( # Make sure these params are exclusive. cmd_util.check_at_most_one_param(cmd_ctx, nameof(packages, all_, list_)) - # -- Load the resources. We don't care about project specific resources. - resources = Resources( + # -- Create an apio context. We don't care about project specific + # -- configuration. + apio_ctx = ApioContext( project_dir=project_dir, project_scope=False, ) # -- Install the given apio packages if packages: - install_packages(packages, resources, force, verbose) + install_packages(packages, apio_ctx, force, verbose) cmd_ctx.exit(0) # -- Install all the available packages (if any) if all_: # -- Install all the available packages for this platform! install_packages( - resources.platform_packages.keys(), resources, force, verbose + apio_ctx.platform_packages.keys(), apio_ctx, force, verbose ) cmd_ctx.exit(0) # -- List all the packages (installed or not) if list_: - resources.list_packages() + apio_ctx.list_packages() cmd_ctx.exit(0) # -- Invalid option. Just show the help diff --git a/apio/commands/lint.py b/apio/commands/lint.py index ecb6b1f2..3454704e 100644 --- a/apio/commands/lint.py +++ b/apio/commands/lint.py @@ -12,7 +12,7 @@ from apio.managers.scons import SCons from apio import cmd_util from apio.commands import options -from apio.resources import Resources +from apio.resources import ApioContext # --------------------------- @@ -96,9 +96,11 @@ def cli( ): """Lint the verilog code.""" - # -- Create the scons object - resources = Resources(project_dir=project_dir, project_scope=True) - scons = SCons(resources) + # -- Create the apio context. + apio_ctx = ApioContext(project_dir=project_dir, project_scope=True) + + # -- Create the scons manager. + scons = SCons(apio_ctx) # -- Lint the project with the given parameters exit_code = scons.lint( diff --git a/apio/commands/modify.py b/apio/commands/modify.py index 0ba91b71..4eee2fb2 100644 --- a/apio/commands/modify.py +++ b/apio/commands/modify.py @@ -13,7 +13,7 @@ from apio.managers.project import Project from apio import cmd_util from apio.commands import options -from apio.resources import Resources +from apio.resources import ApioContext # --------------------------- @@ -61,11 +61,11 @@ def cli( # At least one of these options are required. cmd_util.check_at_least_one_param(cmd_ctx, nameof(board, top_module)) - # Load resources. - resources = Resources(project_dir=project_dir, project_scope=True) + # Create an apio context. + apio_ctx = ApioContext(project_dir=project_dir, project_scope=True) # Create the apio.ini file - ok = Project.modify_ini_file(resources, board, top_module) + ok = Project.modify_ini_file(apio_ctx, board, top_module) exit_code = 0 if ok else 1 cmd_ctx.exit(exit_code) diff --git a/apio/commands/packages.py b/apio/commands/packages.py index b807e974..db48cbb3 100644 --- a/apio/commands/packages.py +++ b/apio/commands/packages.py @@ -12,40 +12,40 @@ from varname import nameof import click from apio.managers import installer -from apio.resources import Resources +from apio.resources import ApioContext from apio import cmd_util, pkg_util, util from apio.commands import options def _install( - resources: Resources, packages: List[str], force: bool, verbose: bool + apio_ctx: ApioContext, packages: List[str], force: bool, verbose: bool ) -> int: """Handles the --install operation. Returns exit code.""" - click.secho(f"Platform id '{resources.platform_id}'") + click.secho(f"Platform id '{apio_ctx.platform_id}'") # -- If packages where specified, install all packages that are valid # -- for this platform. if not packages: - packages = resources.platform_packages.keys() + packages = apio_ctx.platform_packages.keys() # -- Install the packages, one by one. for package in packages: installer.install_package( - resources, package_spec=package, force=force, verbose=verbose + apio_ctx, package_spec=package, force=force, verbose=verbose ) return 0 def _uninstall( - resources: Resources, packages: List[str], verbose: bool, sayyes: bool + apio_ctx: ApioContext, packages: List[str], verbose: bool, sayyes: bool ) -> int: """Handles the --uninstall operation. Returns exit code.""" # -- If packages where specified, uninstall all packages that are valid # -- for this platform. if not packages: - packages = resources.platform_packages.keys() + packages = apio_ctx.platform_packages.keys() # -- Ask the user for confirmation if not ( @@ -59,47 +59,47 @@ def _uninstall( return 1 # -- Here when going on with the uninstallation. - click.secho(f"Platform id '{resources.platform_id}'") + click.secho(f"Platform id '{apio_ctx.platform_id}'") # -- Uninstall the packages, one by one for package in packages: installer.uninstall_package( - resources, package_spec=package, verbose=verbose + apio_ctx, package_spec=package, verbose=verbose ) return 0 -def _fix(resources: Resources, verbose: bool) -> int: +def _fix(apio_ctx: ApioContext, verbose: bool) -> int: """Handles the --fix operation. Returns exit code.""" # -- Scan the availeable and installed packages. - scan = pkg_util.scan_packages(resources) + scan = pkg_util.scan_packages(apio_ctx) # -- Fix any errors. if scan.num_errors(): - installer.fix_packages(resources, scan, verbose) + installer.fix_packages(apio_ctx, scan, verbose) else: click.secho("No errors to fix") # -- Show the new state - new_scan = pkg_util.scan_packages(resources) - pkg_util.list_packages(resources, new_scan) + new_scan = pkg_util.scan_packages(apio_ctx) + pkg_util.list_packages(apio_ctx, new_scan) return 0 -def _list(resources: Resources, verbose: bool) -> int: +def _list(apio_ctx: ApioContext, verbose: bool) -> int: """Handles the --list operation. Returns exit code.""" if verbose: - click.secho(f"Platform id '{resources.platform_id}'") + click.secho(f"Platform id '{apio_ctx.platform_id}'") # -- Scan the available and installed packages. - scan = pkg_util.scan_packages(resources) + scan = pkg_util.scan_packages(apio_ctx) # -- List the findings. - pkg_util.list_packages(resources, scan) + pkg_util.list_packages(apio_ctx, scan) # -- Print an hint or summary based on the findings. if scan.num_errors(): @@ -222,24 +222,25 @@ def cli( cmd_util.check_at_most_one_param(cmd_ctx, nameof(list_, packages)) cmd_util.check_at_most_one_param(cmd_ctx, nameof(fix, packages)) - # -- Load the resources. We don't care about project specific resources. - resources = Resources( + # -- Create the apio context. We don't care about project specific + # -- configuration. + apio_ctx = ApioContext( project_dir=project_dir, project_scope=False, ) if install: - exit_code = _install(resources, packages, force, verbose) + exit_code = _install(apio_ctx, packages, force, verbose) cmd_ctx.exit(exit_code) if uninstall: - exit_code = _uninstall(resources, packages, verbose, sayyes) + exit_code = _uninstall(apio_ctx, packages, verbose, sayyes) cmd_ctx.exit(exit_code) if fix: - exit_code = _fix(resources, verbose) + exit_code = _fix(apio_ctx, verbose) cmd_ctx.exit(exit_code) # -- Here it must be --list. - exit_code = _list(resources, verbose) + exit_code = _list(apio_ctx, verbose) cmd_ctx.exit(exit_code) diff --git a/apio/commands/raw.py b/apio/commands/raw.py index de2ffb4c..b44db03e 100644 --- a/apio/commands/raw.py +++ b/apio/commands/raw.py @@ -9,7 +9,7 @@ import click from apio import util, pkg_util, cmd_util -from apio.resources import Resources +from apio.resources import ApioContext # --------------------------- @@ -74,12 +74,12 @@ def cli( # -- if --env option is specifies and prepare the env for the command # -- execution below. if cmd or env: - resources = Resources(project_scope=False) - pkg_util.set_env_for_packages(resources, verbose=env) + apio_ctx = ApioContext(project_scope=False) + pkg_util.set_env_for_packages(apio_ctx, verbose=env) if cmd: # -- Make sure that at least the oss-cad-suite is installed. - pkg_util.check_required_packages(["oss-cad-suite"], resources) + pkg_util.check_required_packages(["oss-cad-suite"], apio_ctx) # -- Invoke the command. exit_code = util.call(cmd) diff --git a/apio/commands/report.py b/apio/commands/report.py index 9c31cf44..53e8e7f9 100644 --- a/apio/commands/report.py +++ b/apio/commands/report.py @@ -12,7 +12,7 @@ from apio.managers.scons import SCons from apio import cmd_util from apio.commands import options -from apio.resources import Resources +from apio.resources import ApioContext # --------------------------- @@ -65,9 +65,11 @@ def cli( ): """Analyze the design and report timing.""" - # -- Create the scons object - resources = Resources(project_dir=project_dir, project_scope=True) - scons = SCons(resources) + # -- Create the apio context. + apio_ctx = ApioContext(project_dir=project_dir, project_scope=True) + + # -- Create the scons manager. + scons = SCons(apio_ctx) # Run scons exit_code = scons.report( diff --git a/apio/commands/sim.py b/apio/commands/sim.py index a3c62edf..8d8975c4 100644 --- a/apio/commands/sim.py +++ b/apio/commands/sim.py @@ -12,7 +12,7 @@ from apio.managers.scons import SCons from apio import cmd_util from apio.commands import options -from apio.resources import Resources +from apio.resources import ApioContext # --------------------------- @@ -61,9 +61,11 @@ def cli( file and shows graphically the signal graphs. """ - # -- Create the scons object - resources = Resources(project_dir=project_dir, project_scope=True) - scons = SCons(resources) + # -- Create the apio context. + apio_ctx = ApioContext(project_dir=project_dir, project_scope=True) + + # -- Create the scons manager. + scons = SCons(apio_ctx) # -- Simulate the project with the given parameters exit_code = scons.sim({"testbench": testbench}) diff --git a/apio/commands/system.py b/apio/commands/system.py index ff26a9cd..dfb4882b 100644 --- a/apio/commands/system.py +++ b/apio/commands/system.py @@ -13,7 +13,7 @@ from apio import util from apio import cmd_util from apio.managers.system import System -from apio.resources import Resources +from apio.resources import ApioContext from apio.commands import options # --------------------------- @@ -118,11 +118,11 @@ def cli( cmd_ctx, nameof(lsftdi, lsusb, lsserial, info, platforms) ) - # Load the various resource files. - resources = Resources(project_dir=project_dir, project_scope=False) + # Create the apio context. + apio_ctx = ApioContext(project_dir=project_dir, project_scope=False) # -- Create the system object - system = System(resources) + system = System(apio_ctx) # -- List all connected ftdi devices if lsftdi: @@ -143,7 +143,7 @@ def cli( if info: # -- Print platform id. click.secho("Platform id ", nl=False) - click.secho(resources.platform_id, fg="cyan") + click.secho(apio_ctx.platform_id, fg="cyan") # -- Print apio package directory. click.secho("Python package ", nl=False) @@ -166,10 +166,10 @@ def cli( f"{'[DESCRIPTION]'}", fg="magenta", ) - for platform_id, platform_info in resources.platforms.items(): + for platform_id, platform_info in apio_ctx.platforms.items(): description = platform_info.get("description") package_selector = platform_info.get("package_selector") - this_package = platform_id == resources.platform_id + this_package = platform_id == apio_ctx.platform_id fg = "green" if this_package else None click.secho( f" {platform_id:18} {package_selector:20} {description}", diff --git a/apio/commands/test.py b/apio/commands/test.py index c6f8f512..999f5d30 100644 --- a/apio/commands/test.py +++ b/apio/commands/test.py @@ -12,7 +12,7 @@ from apio.managers.scons import SCons from apio import cmd_util from apio.commands import options -from apio.resources import Resources +from apio.resources import ApioContext # --------------------------- @@ -58,9 +58,11 @@ def cli( ): """Implements the test command.""" - # -- Create the scons object - resources = Resources(project_dir=project_dir, project_scope=True) - scons = SCons(resources) + # -- Create the apio context. + apio_ctx = ApioContext(project_dir=project_dir, project_scope=True) + + # -- Create the scons manager. + scons = SCons(apio_ctx) exit_code = scons.test({"testbench": testbench_file}) cmd_ctx.exit(exit_code) diff --git a/apio/commands/time.py b/apio/commands/time.py index 01629d6b..eb67166f 100644 --- a/apio/commands/time.py +++ b/apio/commands/time.py @@ -12,7 +12,7 @@ from apio.managers.scons import SCons from apio import cmd_util from apio.commands import options -from apio.resources import Resources +from apio.resources import ApioContext # --------------------------- @@ -67,9 +67,11 @@ def cli( fg="yellow", ) - # -- Create the scons object - resources = Resources(project_dir=project_dir, project_scope=True) - scons = SCons(resources) + # -- Create the apio context. + apio_ctx = ApioContext(project_dir=project_dir, project_scope=True) + + # -- Create the scons manager. + scons = SCons(apio_ctx) # Run scons exit_code = scons.time( diff --git a/apio/commands/uninstall.py b/apio/commands/uninstall.py index d38778db..d411a351 100644 --- a/apio/commands/uninstall.py +++ b/apio/commands/uninstall.py @@ -13,13 +13,13 @@ import click from apio.managers.old_installer import Installer from apio import cmd_util -from apio.resources import Resources +from apio.resources import ApioContext from apio.commands import options # R0801: Similar lines in 2 files # pylint: disable=R0801 -def _uninstall(packages: list, resources: Resources, sayyes, verbose: bool): +def _uninstall(packages: list, apio_ctx: ApioContext, sayyes, verbose: bool): """Uninstall the given list of packages""" # -- Ask the user for confirmation @@ -32,7 +32,7 @@ def _uninstall(packages: list, resources: Resources, sayyes, verbose: bool): modifiers = Installer.Modifiers( force=False, checkversion=False, verbose=verbose ) - installer = Installer(package, resources, modifiers) + installer = Installer(package, apio_ctx, modifiers) # -- Uninstall the package! installer.uninstall() @@ -89,28 +89,25 @@ def cli( # Make sure these params are exclusive. cmd_util.check_at_most_one_param(cmd_ctx, nameof(packages, list_, all_)) - # -- Load the resources. - resources = Resources( - project_dir=project_dir, - project_scope=False, - ) + # -- Create the apio context. + apio_ctx = ApioContext(project_dir=project_dir, project_scope=False) # -- Uninstall the given apio packages if packages: - _uninstall(packages, resources, sayyes, verbose) + _uninstall(packages, apio_ctx, sayyes, verbose) cmd_ctx.exit(0) # -- Uninstall all the packages if all_: # -- Get all the installed apio packages - packages = resources.profile.packages + packages = apio_ctx.profile.packages # -- Uninstall them! - _uninstall(packages, resources, sayyes, verbose) + _uninstall(packages, apio_ctx, sayyes, verbose) cmd_ctx.exit(0) # -- List all the packages (installed or not) if list_: - resources.list_packages() + apio_ctx.list_packages() cmd_ctx.exit(0) # -- Invalid option. Just show the help diff --git a/apio/commands/upload.py b/apio/commands/upload.py index 64030a51..91e34f4b 100644 --- a/apio/commands/upload.py +++ b/apio/commands/upload.py @@ -13,7 +13,7 @@ from apio.managers.drivers import Drivers from apio import cmd_util from apio.commands import options -from apio.resources import Resources +from apio.resources import ApioContext # --------------------------- @@ -91,16 +91,18 @@ def cli( ): """Implements the upload command.""" - # -- Create a drivers object - resources = Resources(project_dir=project_dir, project_scope=True) - drivers = Drivers(resources) + # -- Create a apio context. + apio_ctx = ApioContext(project_dir=project_dir, project_scope=True) + + # -- Create the drivers manager. + drivers = Drivers(apio_ctx) # -- Only for MAC # -- Operation to do before uploading a design in MAC drivers.pre_upload() - # -- Create the SCons object - scons = SCons(resources) + # -- Create the scons manager + scons = SCons(apio_ctx) # -- Construct the configuration params to pass to SCons # -- from the arguments diff --git a/apio/commands/verify.py b/apio/commands/verify.py index d03a0a4a..af52db11 100644 --- a/apio/commands/verify.py +++ b/apio/commands/verify.py @@ -12,7 +12,7 @@ from apio.managers.scons import SCons from apio import cmd_util from apio.commands import options -from apio.resources import Resources +from apio.resources import ApioContext # --------------------------- @@ -51,9 +51,11 @@ def cli( fg="yellow", ) - # -- Crete the scons object - resources = Resources(project_dir=project_dir, project_scope=True) - scons = SCons(resources) + # -- Crete the apio context. + apio_ctx = ApioContext(project_dir=project_dir, project_scope=True) + + # -- Create the scons manager. + scons = SCons(apio_ctx) # -- Verify the project with the given parameters exit_code = scons.verify( diff --git a/apio/managers/arguments.py b/apio/managers/arguments.py index 3f6c5daa..0ae802af 100644 --- a/apio/managers/arguments.py +++ b/apio/managers/arguments.py @@ -7,12 +7,9 @@ from functools import wraps from typing import Dict, Tuple - import click from apio.managers.project import Project, DEFAULT_TOP_MODULE - -# -- Class for accesing api resources (boards, fpgas...) -from apio.resources import Resources +from apio.resources import ApioContext # ----- Constant for accesing dicctionaries @@ -87,7 +84,7 @@ def outer(*args): # pylint: disable=R0912 # @debug_params def process_arguments( - config_ini: Dict, resources: Resources, project: Project + config_ini: Dict, apio_ctx: ApioContext, project: Project ) -> Tuple: # noqa """Get the final CONFIGURATION, depending on the board and arguments passed in the command line. @@ -104,7 +101,7 @@ def process_arguments( 'verbose': dict //-- Verbose level 'top-module`: str //-- Top module name } - * Resources: Object for accessing the apio resources + * apio_ctx: Object for accessing the apio configuration and state. * Project: the contentn of apio.ini, possibly empty if does not exist. * OUTPUT: * Return a tuple (flags, board, arch) @@ -174,11 +171,11 @@ def process_arguments( # -- First, check if the board is valid # -- If not, exit - if config[BOARD] not in resources.boards: + if config[BOARD] not in apio_ctx.boards: raise ValueError(f"unknown board: {config[BOARD]}") # -- Read the FPGA name for the current board - fpga = resources.boards.get(config[BOARD]).get(FPGA) + fpga = apio_ctx.boards.get(config[BOARD]).get(FPGA) # -- Add it to the current configuration update_config_item(config, FPGA, fpga) @@ -190,7 +187,7 @@ def process_arguments( # -- Check if the FPGA is valid # -- If not, exit - if config[FPGA] not in resources.fpgas: + if config[FPGA] not in apio_ctx.fpgas: raise ValueError(f"unknown FPGA: {config[FPGA]}") # -- Update the FPGA items according to the current board and fpga @@ -199,7 +196,7 @@ def process_arguments( # -- (The board determine the fpga and the size, but the user has # -- specificied a different size. It is a contradiction!) for item in [ARCH, TYPE, SIZE, PACK, IDCODE]: - update_config_fpga_item(config, item, resources) + update_config_fpga_item(config, item, apio_ctx) # -- We already have a final configuration # -- Check that this configuration is ok @@ -242,7 +239,7 @@ def process_arguments( click.secho("Using the default top-module: `main`", fg="blue") # -- Set the platform id. - config[PLATFORM_ID] = resources.platform_id + config[PLATFORM_ID] = apio_ctx.platform_id # -- Debug: Print current configuration # print_configuration(config) @@ -274,7 +271,7 @@ def process_arguments( return flags, config[BOARD], config[ARCH] -def update_config_fpga_item(config, item, resources): +def update_config_fpga_item(config, item, apio_ctx: ApioContext): """Update an item for the current FPGA configuration, if there is no contradiction. It raises an exception in case of contradiction: the current FPGA item @@ -285,8 +282,8 @@ def update_config_fpga_item(config, item, resources): * value: New valur for the FPGA item, if there is no contradiction """ - # -- Read the FPGA item from the apio resources - fpga_item = resources.fpgas.get(config[FPGA]).get(item) + # -- Read the FPGA item from the apio context. + fpga_item = apio_ctx.fpgas.get(config[FPGA]).get(item) # -- Update the current configuration with that item # -- and check that there are no contradictions diff --git a/apio/managers/drivers.py b/apio/managers/drivers.py index 28fce332..28f103ae 100644 --- a/apio/managers/drivers.py +++ b/apio/managers/drivers.py @@ -13,7 +13,7 @@ import click from apio import util from apio import pkg_util -from apio.resources import Resources +from apio.resources import ApioContext FTDI_INSTALL_INSTRUCTIONS_WINDOWS = """ Please follow these steps: @@ -136,26 +136,26 @@ class Drivers: # Driver to restore: mac os driver_c = "" - def __init__(self, resources: Resources) -> None: + def __init__(self, apio_ctx: ApioContext) -> None: - self.resources = resources + self.apio_ctx = apio_ctx def ftdi_install(self) -> int: """Installs the FTDI driver. Function is platform dependent. Returns a process exit code. """ - if self.resources.is_linux(): + if self.apio_ctx.is_linux(): return self._ftdi_install_linux() - if self.resources.is_darwin(): + if self.apio_ctx.is_darwin(): return self._ftdi_install_darwin() - if self.resources.is_windows(): + if self.apio_ctx.is_windows(): return self._ftdi_install_windows() click.secho( - f"Error: unknown platform type '{self.resources.platform_id}'." + f"Error: unknown platform type '{self.apio_ctx.platform_id}'." ) return 1 @@ -163,16 +163,16 @@ def ftdi_uninstall(self) -> int: """Uninstalls the FTDI driver. Function is platform dependent. Returns a process exit code. """ - if self.resources.is_linux(): + if self.apio_ctx.is_linux(): return self._ftdi_uninstall_linux() - if self.resources.is_darwin(): + if self.apio_ctx.is_darwin(): return self._ftdi_uninstall_darwin() - if self.resources.is_windows(): + if self.apio_ctx.is_windows(): return self._ftdi_uninstall_windows() - click.secho(f"Error: unknown platform '{self.resources.platform_id}'.") + click.secho(f"Error: unknown platform '{self.apio_ctx.platform_id}'.") return 1 def serial_install(self) -> int: @@ -180,46 +180,46 @@ def serial_install(self) -> int: Returns a process exit code. """ - if self.resources.is_linux(): + if self.apio_ctx.is_linux(): return self._serial_install_linux() - if self.resources.is_darwin(): + if self.apio_ctx.is_darwin(): return self._serial_install_darwin() - if self.resources.is_windows(): + if self.apio_ctx.is_windows(): return self._serial_install_windows() - click.secho(f"Error: unknown platform '{self.resources.platform_id}'.") + click.secho(f"Error: unknown platform '{self.apio_ctx.platform_id}'.") return 1 def serial_uninstall(self) -> int: """Uninstalls the serial driver. Function is platform dependent. Returns a process exit code. """ - if self.resources.is_linux(): + if self.apio_ctx.is_linux(): return self._serial_uninstall_linux() - if self.resources.is_darwin(): + if self.apio_ctx.is_darwin(): return self._serial_uninstall_darwin() - if self.resources.is_windows(): + if self.apio_ctx.is_windows(): return self._serial_uninstall_windows() - click.secho(f"Error: unknown platform '{self.resources.platform_id}'.") + click.secho(f"Error: unknown platform '{self.apio_ctx.platform_id}'.") return 1 def pre_upload(self): """Operations to do before uploading a design Only for mac platforms""" - if self.resources.is_darwin(): + if self.apio_ctx.is_darwin(): self._pre_upload_darwin() def post_upload(self): """Operations to do after uploading a design Only for mac platforms""" - if self.resources.is_darwin(): + if self.apio_ctx.is_darwin(): self._post_upload_darwin() def _ftdi_install_linux(self) -> int: @@ -368,16 +368,16 @@ def _ftdi_install_darwin(self) -> int: subprocess.call(["brew", "update"]) self._brew_install_darwin("libffi") self._brew_install_darwin("libftdi") - self.resources.profile.add_setting("macos_ftdi_drivers", True) - self.resources.profile.save() + self.apio_ctx.profile.add_setting("macos_ftdi_drivers", True) + self.apio_ctx.profile.save() click.secho("FTDI drivers installed", fg="green") return 0 def _ftdi_uninstall_darwin(self): """Uninstalls FTDI driver on darwin. Returns process status code.""" click.secho("Uninstall FTDI drivers configuration") - self.resources.profile.add_setting("macos_ftdi_drivers", False) - self.resources.profile.save() + self.apio_ctx.profile.add_setting("macos_ftdi_drivers", False) + self.apio_ctx.profile.save() click.secho("FTDI drivers uninstalled", fg="green") return 0 @@ -422,7 +422,7 @@ def _brew_install_darwin(self, brew_package): # ) def _pre_upload_darwin(self): - if self.resources.profile.settings.get("macos_ftdi_drivers", False): + if self.apio_ctx.profile.settings.get("macos_ftdi_drivers", False): # Check and unload the drivers driver_a = "com.FTDI.driver.FTDIUSBSerialDriver" driver_b = "com.apple.driver.AppleUSBFTDI" @@ -434,7 +434,7 @@ def _pre_upload_darwin(self): self.driver_c = driver_b def _post_upload_darwin(self): - if self.resources.profile.settings.get("macos_ftdi_drivers", False): + if self.apio_ctx.profile.settings.get("macos_ftdi_drivers", False): # Restore previous driver configuration if self.driver_c: subprocess.call(["sudo", "kextload", "-b", self.driver_c]) @@ -446,10 +446,10 @@ def _check_ftdi_driver_darwin(self, driver): # pylint: disable=W0703 def _ftdi_install_windows(self) -> int: # -- Check that the required packages are installed. - pkg_util.check_required_packages(["drivers"], self.resources) + pkg_util.check_required_packages(["drivers"], self.apio_ctx) # -- Get the drivers apio package base folder - drivers_base_dir = self.resources.get_package_dir("drivers") + drivers_base_dir = self.apio_ctx.get_package_dir("drivers") # -- Path to the zadig.ini file # -- It is the zadig config file @@ -485,7 +485,7 @@ def _ftdi_install_windows(self) -> int: def _ftdi_uninstall_windows(self) -> int: # -- Check that the required packages exist. - pkg_util.check_required_packages(["drivers"], self.resources) + pkg_util.check_required_packages(["drivers"], self.apio_ctx) click.secho("\nStarting the interactive Device Manager.", fg="green") click.secho(FTDI_UNINSTALL_INSTRUCTIONS_WINDOWS, fg="yellow") @@ -500,9 +500,9 @@ def _ftdi_uninstall_windows(self) -> int: # pylint: disable=W0703 def _serial_install_windows(self) -> int: # -- Check that the required packages exist. - pkg_util.check_required_packages(["drivers"], self.resources) + pkg_util.check_required_packages(["drivers"], self.apio_ctx) - drivers_base_dir = self.resources.get_package_dir("drivers") + drivers_base_dir = self.apio_ctx.get_package_dir("drivers") drivers_bin_dir = drivers_base_dir / "bin" click.secho("\nStarting the interactive Serial Installer.", fg="green") @@ -519,7 +519,7 @@ def _serial_install_windows(self) -> int: def _serial_uninstall_windows(self) -> int: # -- Check that the required packages exist. - pkg_util.check_required_packages(["drivers"], self.resources) + pkg_util.check_required_packages(["drivers"], self.apio_ctx) click.secho("\nStarting the interactive Device Manager.", fg="green") click.secho(SERIAL_UNINSTALL_INSTRUCTIONS_WINDOWS, fg="yellow") diff --git a/apio/managers/examples.py b/apio/managers/examples.py index 918dd2d7..0a21594e 100644 --- a/apio/managers/examples.py +++ b/apio/managers/examples.py @@ -13,7 +13,7 @@ import click from apio import util from apio import pkg_util -from apio.resources import Resources +from apio.resources import ApioContext # -- Error messages EXAMPLE_NOT_FOUND_MSG = """ @@ -43,20 +43,20 @@ class ExampleInfo: class Examples: """Manage the apio examples""" - def __init__(self, resources: Resources): + def __init__(self, apio_ctx: ApioContext): - # -- Access to the resources - self.resources = resources + # -- Save the apio context. + self.apio_ctx = apio_ctx # -- Folder where the example packages was installed - self.examples_dir = resources.get_package_dir("examples") + self.examples_dir = apio_ctx.get_package_dir("examples") def get_examples_infos(self) -> Optional[List[ExampleInfo]]: """Scans the examples and returns a list of ExampleInfos. Returns null if an error.""" # -- Check that the example package is installed - pkg_util.check_required_packages(["examples"], self.resources) + pkg_util.check_required_packages(["examples"], self.apio_ctx) # -- Collect the examples home dir each board. boards_dirs: List[PosixPath] = [] @@ -99,7 +99,7 @@ def list_examples(self) -> None: code, 0 if ok, non zero otherwise.""" # -- Check that the examples package is installed. - pkg_util.check_required_packages(["examples"], self.resources) + pkg_util.check_required_packages(["examples"], self.apio_ctx) # -- Get list of examples. examples: List[ExampleInfo] = self.get_examples_infos() @@ -153,7 +153,7 @@ def copy_example_dir(self, example: str, project_dir: Path, sayno: bool): """ # -- Check that the examples package is installed. - pkg_util.check_required_packages(["examples"], self.resources) + pkg_util.check_required_packages(["examples"], self.apio_ctx) # -- Get the working dir (current or given) project_dir = util.get_project_dir(project_dir, create_if_missing=True) @@ -211,7 +211,7 @@ def copy_example_files(self, example: str, project_dir: Path, sayno: bool): """ # -- Check that the examples package is installed. - pkg_util.check_required_packages(["examples"], self.resources) + pkg_util.check_required_packages(["examples"], self.apio_ctx) # -- Get the working dir (current or given) dst_example_path = util.get_project_dir( diff --git a/apio/managers/installer.py b/apio/managers/installer.py index 773578b0..b6b1766d 100644 --- a/apio/managers/installer.py +++ b/apio/managers/installer.py @@ -14,19 +14,19 @@ import click import requests from apio import util, pkg_util -from apio.resources import Resources +from apio.resources import ApioContext from apio.managers.downloader import FileDownloader from apio.managers.unpacker import FileUnpacker def _get_remote_version( - resources: Resources, package_name: str, verbose: bool + apio_ctx: ApioContext, package_name: str, verbose: bool ) -> str: """Get the recommanded package version from the remote release server. This version is not necessarily the latest one on the server. - INPUTS: - 'resources' the Resources object of this apio session. + 'apio_ctx' the context object of this apio session. 'package_name' the package name, e.g. 'oss-cad-suite'. 'verbose' indicates if to print detailed info. @@ -36,7 +36,7 @@ def _get_remote_version( """ # -- Get package inforation (originated from packages.json) - package_info = resources.get_package_info(package_name) + package_info = apio_ctx.get_package_info(package_name) # -- Get the version file URL. This is a text file with the recomanded # -- version for this package. @@ -69,7 +69,7 @@ def _get_remote_version( def _construct_package_download_url( - resources: Resources, package_name: str, target_version: str + apio_ctx: ApioContext, package_name: str, target_version: str ) -> str: """Construct the download URL for the given package name and version. @@ -79,12 +79,12 @@ def _construct_package_download_url( """ # -- Get the package info (originated from packages.json) - package_info = resources.get_package_info(package_name) + package_info = apio_ctx.get_package_info(package_name) # -- Get the package selector of this platform (the package selectors # -- are specified in platforms.json). E.g. 'darwin_arm64' - platform_id = resources.platform_id - package_selector = resources.platforms[platform_id]["package_selector"] + platform_id = apio_ctx.platform_id + package_selector = apio_ctx.platforms[platform_id]["package_selector"] # -- Get the compressed name of the package. This is base name of the # -- downloaded file. E.g. "tools-oss-cad-suite-%P-%V" @@ -208,12 +208,12 @@ def _parse_package_spec(package_spec: str) -> Tuple[str, str]: def _delete_package_dir( - resources: Resources, package_id: str, verbose: bool + apio_ctx: ApioContext, package_id: str, verbose: bool ) -> bool: """Delete the directory of the package with given name. Returns True if the packages existed. Exits with an error message on error.""" - package_dir = resources.get_package_dir(package_id) + package_dir = apio_ctx.get_package_dir(package_id) dir_found = package_dir.is_dir() if dir_found: @@ -221,7 +221,7 @@ def _delete_package_dir( click.secho(f"Deleting {str(package_dir)}") # -- Sanity check the path and delete. - package_folder_name = resources.get_package_folder_name(package_id) + package_folder_name = apio_ctx.get_package_folder_name(package_id) assert package_folder_name in str(package_dir), package_dir shutil.rmtree(package_dir) @@ -237,11 +237,11 @@ def _delete_package_dir( # pylint: disable=too-many-branches def install_package( - resources: Resources, *, package_spec: str, force: bool, verbose: bool + apio_ctx: ApioContext, *, package_spec: str, force: bool, verbose: bool ) -> None: """Install a given package. - 'resources' is the Resources object of this apio invocation. + 'apio_ctx' is the context object of this apio invocation. 'package_spec' is a package name with optional version suffix e.b. 'drivers', 'drivers@1.2.0'. 'force' indicates if to perform the installation even if a matching @@ -257,7 +257,7 @@ def install_package( package_name, target_version = _parse_package_spec(package_spec) # -- Get package information (originated from packages.json) - package_info = resources.get_package_info(package_name) + package_info = apio_ctx.get_package_info(package_name) # -- If the user didn't specify a target version we use the one recomanded # -- by the release server. @@ -265,14 +265,14 @@ def install_package( # -- Note that we use the remote version even if the current installed # -- version is ok by the version spec in distribution.json. if not target_version: - target_version = _get_remote_version(resources, package_name, verbose) + target_version = _get_remote_version(apio_ctx, package_name, verbose) click.secho(f"Target version {target_version}") # -- If not focring and the target version already installed nothing to do. if not force: # -- Get the version of the installed package, None otherwise. - installed_version = resources.profile.get_package_installed_version( + installed_version = apio_ctx.profile.get_package_installed_version( package_name, default=None ) @@ -289,7 +289,7 @@ def install_package( # -- Construct the download URL. download_url = _construct_package_download_url( - resources, package_name, target_version + apio_ctx, package_name, target_version ) if verbose: print(f"Download URL: {download_url}") @@ -299,7 +299,7 @@ def install_package( packages_dir.mkdir(exist_ok=True) # -- Prepare the package directory. - package_dir = resources.get_package_dir(package_name) + package_dir = apio_ctx.get_package_dir(package_name) # -- Downlod the package file from the remote server. local_file = _download_package_file(download_url, packages_dir) @@ -311,7 +311,7 @@ def install_package( # -- Delete the old package dir, if exists, to avoid name conflicts and # -- left over files. - _delete_package_dir(resources, package_name, verbose) + _delete_package_dir(apio_ctx, package_name, verbose) if uncompressed_name: @@ -348,8 +348,8 @@ def install_package( local_file.unlink() # -- Add package to profile and save. - resources.profile.add_package(package_name, target_version) - resources.profile.save() + apio_ctx.profile.add_package(package_name, target_version) + apio_ctx.profile.save() # -- Inform the user! click.secho( @@ -359,14 +359,14 @@ def install_package( def uninstall_package( - resources: Resources, *, package_spec: str, verbose: bool + apio_ctx: ApioContext, *, package_spec: str, verbose: bool ): """Uninstall the apio package""" # -- Parse package spec. We ignore the version silently. package_name, _ = _parse_package_spec(package_spec) - package_info = resources.platform_packages.get(package_name, None) + package_info = apio_ctx.platform_packages.get(package_name, None) if not package_info: click.secho(f"Error: no such package '{package_name}'", fg="red") sys.exit(1) @@ -375,15 +375,15 @@ def uninstall_package( click.secho(f"Uninstalling package '{package_name}'") # -- Remove the folder with all its content!! - dir_existed = _delete_package_dir(resources, package_name, verbose) + dir_existed = _delete_package_dir(apio_ctx, package_name, verbose) - installed_version = resources.profile.get_package_installed_version( + installed_version = apio_ctx.profile.get_package_installed_version( package_name, None ) # -- Remove the package from the profile file - resources.profile.remove_package(package_name) - resources.profile.save() + apio_ctx.profile.remove_package(package_name) + apio_ctx.profile.save() # -- Check that it is a folder... if dir_existed or installed_version: @@ -399,7 +399,7 @@ def uninstall_package( def fix_packages( - resources: Resources, scan: pkg_util.PackageScanResults, verbose: bool + apio_ctx: ApioContext, scan: pkg_util.PackageScanResults, verbose: bool ) -> None: """If the package scan result contains errors, fix them.""" @@ -413,15 +413,15 @@ def fix_packages( for package_id in scan.broken_package_ids: if verbose: print(f"Uninstalling broken package '{package_id}'") - _delete_package_dir(resources, package_id, verbose=False) - resources.profile.remove_package(package_id) - resources.profile.save() + _delete_package_dir(apio_ctx, package_id, verbose=False) + apio_ctx.profile.remove_package(package_id) + apio_ctx.profile.save() for package_id in scan.orphan_package_ids: if verbose: print(f"Uninstalling unknown package '{package_id}'") - resources.profile.remove_package(package_id) - resources.profile.save() + apio_ctx.profile.remove_package(package_id) + apio_ctx.profile.save() for dir_name in scan.orphan_dir_names: if verbose: diff --git a/apio/managers/old_installer.py b/apio/managers/old_installer.py index 7718d99c..96060c38 100644 --- a/apio/managers/old_installer.py +++ b/apio/managers/old_installer.py @@ -37,7 +37,7 @@ class Modifiers: def __init__( self, package: str, - resources=None, + apio_ctx=None, modifiers=Modifiers(force=False, checkversion=True, verbose=False), ): """Class initialization. Parameters: @@ -54,7 +54,7 @@ def __init__( self.version = None self.force_install = None self.packages_dir = None - self.resources = resources + self.apio_ctx = apio_ctx self.spec_version = None self.package_folder_name = None self.extension = None @@ -92,15 +92,15 @@ def __init__( # -- If the package is known... # --(It is defined in the resources/packages.json file) - if self.package in self.resources.platform_packages: + if self.package in self.apio_ctx.platform_packages: # -- Store the package dir self.packages_dir = util.get_home_dir() / dirname # Get the metadata of the given package - package_info = self.resources.platform_packages[self.package] + package_info = self.apio_ctx.platform_packages[self.package] # Get the information about the valid versions - distribution = self.resources.distribution + distribution = self.apio_ctx.distribution # Get the spectec package version self.spec_version = distribution["packages"][self.package] @@ -138,7 +138,7 @@ def __init__( # -- The package is kwnown but the version is not correct else: if ( - self.package in self.resources.profile.packages + self.package in self.apio_ctx.profile.packages and modifiers.checkversion is False ): self.packages_dir = util.get_home_dir() / dirname @@ -184,9 +184,9 @@ def _get_download_url(self, package_info: dict) -> str: # -- Map Replace the '%P' parameter with the package selector of this # -- platform (the package selectors are specified in platforms.json). - package_selector = self.resources.platforms[ - self.resources.platform_id - ]["package_selector"] + package_selector = self.apio_ctx.platforms[self.apio_ctx.platform_id][ + "package_selector" + ] self.compressed_name = compressed_name_version.replace( "%P", package_selector ) @@ -200,7 +200,7 @@ def _get_download_url(self, package_info: dict) -> str: # -- Replace the '%P' parameter self.uncompressed_name = uncompress_name_version.replace( - "%P", self.resources.platform_id + "%P", self.apio_ctx.platform_id ) # -- Build the package tarball filename @@ -303,10 +303,10 @@ def _install_package(self, dlpath: Path): dlpath.unlink() # -- Add package to profile - self.resources.profile.add_package(self.package, self.version) + self.apio_ctx.profile.add_package(self.package, self.version) # -- Save the profile - self.resources.profile.save() + self.apio_ctx.profile.save() # -- Inform the user! click.secho( @@ -369,8 +369,8 @@ def uninstall(self): ) # -- Remove the package from the profile file - self.resources.profile.remove_package(self.package) - self.resources.profile.save() + self.apio_ctx.profile.remove_package(self.package) + self.apio_ctx.profile.save() @staticmethod def _get_tarball_name(name, extension): @@ -449,7 +449,7 @@ def _download(self, url: str) -> str: """ # -- Check the installed version of the package - installed_ok = self.resources.profile.is_installed_version_ok( + installed_ok = self.apio_ctx.profile.is_installed_version_ok( self.package, self.version, self.verbose ) diff --git a/apio/managers/project.py b/apio/managers/project.py index 45114081..9fcff91d 100644 --- a/apio/managers/project.py +++ b/apio/managers/project.py @@ -15,7 +15,7 @@ from configobj import ConfigObj import click from apio import util -from apio.resources import Resources +from apio.resources import ApioContext # -- Apio projecto filename PROJECT_FILENAME = "apio.ini" @@ -38,15 +38,18 @@ def __init__(self, project_dir: Optional[Path]): @staticmethod def create_ini( - resources: Resources, board: str, top_module: str, sayyes: bool = False + apio_ctx: ApioContext, + board: str, + top_module: str, + sayyes: bool = False, ) -> bool: """Creates a new apio project file. Returns True if ok.""" # -- Construct the path - ini_path = resources.project_dir / PROJECT_FILENAME + ini_path = apio_ctx.project_dir / PROJECT_FILENAME # -- Verify that the board id is valid. - boards = resources.boards + boards = apio_ctx.boards if board not in boards.keys(): click.secho(f"Error: no such board '{board}'", fg="red") return False @@ -86,17 +89,17 @@ def create_ini( @staticmethod def modify_ini_file( - resources: Resources, board: Optional[str], top_module: Optional[str] + apio_ctx: ApioContext, board: Optional[str], top_module: Optional[str] ) -> bool: """Update the current ini file with the given optional parameters. Returns True if ok.""" # -- construct the file path. - ini_path = resources.project_dir / PROJECT_FILENAME + ini_path = apio_ctx.project_dir / PROJECT_FILENAME # -- Verify that the board id is valid. if board: - boards = resources.boards + boards = apio_ctx.boards if board not in boards.keys(): click.secho( f"Error: no such board '{board}'.\n" diff --git a/apio/managers/scons.py b/apio/managers/scons.py index 2b70736e..fbfd4083 100644 --- a/apio/managers/scons.py +++ b/apio/managers/scons.py @@ -25,7 +25,7 @@ from apio.managers.arguments import process_arguments from apio.managers.arguments import serialize_scons_flags from apio.managers.system import System -from apio.resources import Resources +from apio.resources import ApioContext from apio.managers.project import Project from apio.managers.scons_filter import SconsFilter @@ -75,17 +75,17 @@ def wrapper(*args, **kwargs): class SCons: """Class for managing the scons tools""" - def __init__(self, resources: Resources): + def __init__(self, apio_ctx: ApioContext): """Initialization.""" - # -- Cache resources. - self.resources = resources + # -- Cache the apio context. + self.apio_ctx = apio_ctx # -- Read the project file (apio.ini) - self.project = Project(resources.project_dir) + self.project = Project(apio_ctx.project_dir) self.project.read() # -- Change to the project's folder. - os.chdir(resources.project_dir) + os.chdir(apio_ctx.project_dir) @on_exception(exit_code=1) def clean(self, args) -> int: @@ -94,7 +94,7 @@ def clean(self, args) -> int: # -- Split the arguments variables, __, arch = process_arguments( - args, self.resources, self.project + args, self.apio_ctx, self.project ) # --Clean the project: run scons -c (with aditional arguments) @@ -109,7 +109,7 @@ def verify(self, args) -> int: # -- Split the arguments variables, __, arch = process_arguments( - args, self.resources, self.project + args, self.apio_ctx, self.project ) # -- Execute scons!!! @@ -128,7 +128,7 @@ def graph(self, args) -> int: # -- Split the arguments variables, _, arch = process_arguments( - args, self.resources, self.project + args, self.apio_ctx, self.project ) # -- Execute scons!!! @@ -146,7 +146,7 @@ def lint(self, args) -> int: exit code, 0 if ok.""" config = {} - __, __, arch = process_arguments(config, self.resources, self.project) + __, __, arch = process_arguments(config, self.apio_ctx, self.project) variables = serialize_scons_flags( { "all": args.get("all"), @@ -154,7 +154,7 @@ def lint(self, args) -> int: "nowarn": args.get("nowarn"), "warn": args.get("warn"), "nostyle": args.get("nostyle"), - "platform_id": self.resources.platform_id, + "platform_id": self.apio_ctx.platform_id, } ) return self._run( @@ -171,7 +171,7 @@ def sim(self, args) -> int: # -- Split the arguments variables, _, arch = process_arguments( - args, self.resources, self.project + args, self.apio_ctx, self.project ) return self._run( @@ -188,7 +188,7 @@ def test(self, args) -> int: # -- Split the arguments variables, _, arch = process_arguments( - args, self.resources, self.project + args, self.apio_ctx, self.project ) return self._run( @@ -205,7 +205,7 @@ def build(self, args) -> int: # -- Split the arguments variables, board, arch = process_arguments( - args, self.resources, self.project + args, self.apio_ctx, self.project ) # -- Execute scons!!! @@ -224,7 +224,7 @@ def time(self, args) -> int: exit code, 0 if ok.""" variables, board, arch = process_arguments( - args, self.resources, self.project + args, self.apio_ctx, self.project ) if arch not in ["ice40"]: @@ -249,7 +249,7 @@ def report(self, args) -> int: exit code, 0 if ok.""" variables, board, arch = process_arguments( - args, self.resources, self.project + args, self.apio_ctx, self.project ) return self._run( @@ -280,7 +280,7 @@ def upload(self, config: dict, prog: dict) -> int: # -- Get important information from the configuration # -- It will raise an exception if it cannot be solved flags, board, arch = process_arguments( - config, self.resources, self.project + config, self.apio_ctx, self.project ) # -- Information about the FPGA is ok! @@ -337,11 +337,11 @@ def _get_programmer(self, board: str, prog: dict) -> str: # -- Programmer type # -- Programmer name # -- USB id (vid, pid) - board_info = self.resources.boards[board] + board_info = self.apio_ctx.boards[board] # -- Check platform. If the platform is not compatible # -- with the board an exception is raised - self._check_platform(board_info, self.resources.platform_id) + self._check_platform(board_info, self.apio_ctx.platform_id) # -- Check pip packages. If the corresponding pip_packages # -- is not installed, an exception is raised @@ -353,7 +353,7 @@ def _get_programmer(self, board: str, prog: dict) -> str: # -- # -- Special case for the TinyFPGA on MACOS platforms # -- TinyFPGA BX board is not detected in MacOS HighSierra - if "tinyprog" in board_info and self.resources.is_darwin(): + if "tinyprog" in board_info and self.apio_ctx.is_darwin(): # In this case the serial check is ignored # This is the command line to execute for uploading the # circuit @@ -401,7 +401,7 @@ def _get_programmer(self, board: str, prog: dict) -> str: # -- We force an early env setting message to have # -- the programmer message closer to the error message. pkg_util.set_env_for_packages( - self.resources, + self.apio_ctx, ) click.secho("Querying programmer parameters.") @@ -422,7 +422,7 @@ def _get_programmer(self, board: str, prog: dict) -> str: # -- to give context for ftdi failures. # -- We force an early env setting message to have # -- the programmer message closer to the error message. - pkg_util.set_env_for_packages(self.resources) + pkg_util.set_env_for_packages(self.apio_ctx) click.secho("Querying serial port parameters.") # -- Check that the board is connected @@ -491,10 +491,10 @@ def _check_pip_packages(self, board_info): # -- Get the programmer information # -- Command, arguments, pip package, etc... - prog_data = self.resources.programmers[prog_type] + prog_data = self.apio_ctx.programmers[prog_type] # -- Get all the pip packages from the distribution - all_pip_packages = self.resources.distribution["pip_packages"] + all_pip_packages = self.apio_ctx.distribution["pip_packages"] # -- Get the name of the pip package of the current programmer, # -- if any (The programmer maybe in a pip package or an apio package) @@ -586,7 +586,7 @@ def _serialize_programmer( # -- * command # -- * arguments # -- * pip package - content = self.resources.programmers[prog_type] + content = self.apio_ctx.programmers[prog_type] # -- Get the command (without arguments) to execute # -- for programming the current board @@ -650,7 +650,7 @@ def _check_usb(self, board: str, board_info: dict) -> None: # -- Get the list of the connected USB devices # -- (execute the command "lsusb" from the apio System module) - system = System(self.resources) + system = System(self.apio_ctx) connected_devices = system.get_usb_devices() # -- Check if the given device (vid:pid) is connected! @@ -882,7 +882,7 @@ def _check_ftdi( # -- Get the list of the connected FTDI devices # -- (execute the command "lsftdi" from the apio System module) - system = System(self.resources) + system = System(self.apio_ctx) connected_devices = system.get_ftdi_devices() # -- No FTDI devices detected --> Error! @@ -933,11 +933,11 @@ def _run( # -- Check that the required packages are installed pkg_util.check_required_packages( - required_packages_names, self.resources + required_packages_names, self.apio_ctx ) # -- Set env path and vars to use the packages. - pkg_util.set_env_for_packages(self.resources) + pkg_util.set_env_for_packages(self.apio_ctx) # -- Execute scons return self._execute_scons(command, variables, board) diff --git a/apio/managers/system.py b/apio/managers/system.py index e1f48886..c86fc5ed 100644 --- a/apio/managers/system.py +++ b/apio/managers/system.py @@ -12,15 +12,15 @@ from apio import util from apio import pkg_util -from apio.resources import Resources +from apio.resources import ApioContext class System: # pragma: no cover """System class. Managing and execution of the system commands""" - def __init__(self, resources: Resources): + def __init__(self, apio_ctx: ApioContext): - self.resources = resources + self.apio_ctx = apio_ctx def _lsftdi_fatal_error(self, result: util.CommandResult) -> None: """Handles a failure of a 'lsftdi' command. Print message and exits.""" @@ -31,7 +31,7 @@ def _lsftdi_fatal_error(self, result: util.CommandResult) -> None: click.secho("Error: the 'lsftdi' command failed.", fg="red") # -- A special hint for zadig on windows. - if self.resources.is_windows(): + if self.apio_ctx.is_windows(): click.secho( "\n" "[Hint]: did you install the ftdi driver using " @@ -152,15 +152,15 @@ def _run_command( """ # -- Check that the required package exists. - pkg_util.check_required_packages(["oss-cad-suite"], self.resources) + pkg_util.check_required_packages(["oss-cad-suite"], self.apio_ctx) # -- Set system env for using the packages. - pkg_util.set_env_for_packages(self.resources) + pkg_util.set_env_for_packages(self.apio_ctx) # pylint: disable=fixme # TODO: Is this necessary or does windows accepts commands without # the '.exe' extension? - if self.resources.is_windows(): + if self.apio_ctx.is_windows(): command = command + ".exe" # -- Set the stdout and stderr callbacks, when executing the command diff --git a/apio/pkg_util.py b/apio/pkg_util.py index 2367cba2..cb577428 100644 --- a/apio/pkg_util.py +++ b/apio/pkg_util.py @@ -16,7 +16,7 @@ import sys import click import semantic_version -from apio.resources import Resources +from apio.resources import ApioContext from apio import util @@ -42,12 +42,12 @@ class _PackageDesc: env_func: Callable[[Path], EnvMutations] -def _get_env_mutations_for_packages(resources: Resources) -> EnvMutations: +def _get_env_mutations_for_packages(apio_ctx: ApioContext) -> EnvMutations: """Collects the env mutation for each of the defined packages, in the order they are defined.""" result = EnvMutations([], []) - for _, package_config in resources.platform_packages.items(): + for _, package_config in apio_ctx.platform_packages.items(): # -- Get the json 'env' section. We require it, even if it's empty, # -- for clarity reasons. assert "env" in package_config @@ -65,13 +65,15 @@ def _get_env_mutations_for_packages(resources: Resources) -> EnvMutations: return result -def _dump_env_mutations(mutations: EnvMutations, resources: Resources) -> None: +def _dump_env_mutations( + mutations: EnvMutations, apio_ctx: ApioContext +) -> None: """For debugging. Delete once stabalizing the new oss-cad-suite on windows.""" click.secho("Envirnment settings:", fg="magenta") # -- Print PATH mutations. - windows = resources.is_windows() + windows = apio_ctx.is_windows() for p in reversed(mutations.paths): styled_name = click.style("PATH", fg="magenta") if windows: @@ -108,7 +110,7 @@ def _apply_env_mutations(mutations: EnvMutations) -> None: __ENV_ALREADY_SET_FLAG = False -def set_env_for_packages(resources: Resources, verbose: bool = False) -> None: +def set_env_for_packages(apio_ctx: ApioContext, verbose: bool = False) -> None: """Sets the environment variables for using all the that are available for this platform, even if currently not installed. @@ -119,10 +121,10 @@ def set_env_for_packages(resources: Resources, verbose: bool = False) -> None: global __ENV_ALREADY_SET_FLAG # -- Collect the env mutations for all packages. - mutations = _get_env_mutations_for_packages(resources) + mutations = _get_env_mutations_for_packages(apio_ctx) if verbose: - _dump_env_mutations(mutations, resources) + _dump_env_mutations(mutations, apio_ctx) # -- If this is the first call, apply the mutations. These mutations are # -- temporary for the lifetime of this process and does not affect the @@ -136,7 +138,7 @@ def set_env_for_packages(resources: Resources, verbose: bool = False) -> None: def check_required_packages( - required_packages_names: List[str], resources: Resources + required_packages_names: List[str], apio_ctx: ApioContext ) -> None: """Checks that the packages whose names are in 'packages_names' are installed and have a version that meets the requirements. If any error, @@ -144,18 +146,18 @@ def check_required_packages( code. """ - installed_packages = resources.profile.packages - spec_packages = resources.distribution.get("packages") + installed_packages = apio_ctx.profile.packages + spec_packages = apio_ctx.distribution.get("packages") # -- Check packages for package_name in required_packages_names: # -- Package name must be in all_packages. Otherwise it's a programming # -- error. - if package_name not in resources.all_packages: + if package_name not in apio_ctx.all_packages: raise RuntimeError(f"Unknown package named [{package_name}]") # -- Skip if packages is not applicable to this platform. - if package_name not in resources.platform_packages: + if package_name not in apio_ctx.platform_packages: continue # -- The package is applicable to this platform. Check installed @@ -167,7 +169,7 @@ def check_required_packages( # -- Check the installed version against the required version. spec_version = spec_packages.get(package_name, "") _check_required_package( - package_name, current_version, spec_version, resources + package_name, current_version, spec_version, apio_ctx ) @@ -175,7 +177,7 @@ def _check_required_package( package_name: str, current_version: Optional[str], spec_version: str, - resources: Resources, + apio_ctx: ApioContext, ) -> None: """Checks that the package with the given packages is installed and has a version that meets the requirements. If any error, it prints an @@ -185,7 +187,7 @@ def _check_required_package( 'current_version' - the version of the install package or None if not installed. 'spec_version' - a specification of the required version. - 'resources' - the apio resources. + 'apio_ctx' - the apio context. """ # -- Case 1: Package is not installed. if current_version is None: @@ -208,7 +210,7 @@ def _check_required_package( sys.exit(1) # -- Case 3: The package's directory does not exist. - package_dir = resources.get_package_dir(package_name) + package_dir = apio_ctx.get_package_dir(package_name) if package_dir and not package_dir.is_dir(): message = f"Error: package '{package_name}' is installed but missing" click.secho(message, fg="red") @@ -294,7 +296,7 @@ def dump(self): print(f" Orphan files {self.orphan_file_names}") -def scan_packages(resources: Resources) -> PackageScanResults: +def scan_packages(apio_ctx: ApioContext) -> PackageScanResults: """Scans the available and installed packages and returns the findings as a PackageScanResults object.""" @@ -307,14 +309,14 @@ def scan_packages(resources: Resources) -> PackageScanResults: # -- Scan packages ids in platform_packages and populate # -- the installed/uninstall/broken packages lists. - for package_id in resources.platform_packages.keys(): + for package_id in apio_ctx.platform_packages.keys(): # -- Collect package's folder names in a set. For a later use. - package_folder_name = resources.get_package_folder_name(package_id) + package_folder_name = apio_ctx.get_package_folder_name(package_id) platform_folder_names.add(package_folder_name) # -- Classify the package as one of three cases. - in_profile = package_id in resources.profile.packages - has_dir = resources.get_package_dir(package_id).is_dir() + in_profile = package_id in apio_ctx.profile.packages + has_dir = apio_ctx.get_package_dir(package_id).is_dir() if in_profile and has_dir: result.installed_package_ids.append(package_id) elif not in_profile and not has_dir: @@ -324,8 +326,8 @@ def scan_packages(resources: Resources) -> PackageScanResults: # -- Scan the packagtes ids that are registered in profile as installed # -- the ones that are not platform_packages as orphans. - for package_id in resources.profile.packages: - if package_id not in resources.platform_packages: + for package_id in apio_ctx.profile.packages: + if package_id not in apio_ctx.platform_packages: result.orphan_package_ids.append(package_id) # -- Scan the packages directory and identify orphan dirs and files. @@ -360,12 +362,12 @@ def _list_section(title: str, items: List[List[str]], color: str) -> None: click.secho(dline, fg=color) -def list_packages(resources: Resources, scan: PackageScanResults) -> None: +def list_packages(apio_ctx: ApioContext, scan: PackageScanResults) -> None: """Prints in a user friendly format the results of a packages scan.""" # -- Shortcuts to reduce clutter. - get_package_version = resources.profile.get_package_installed_version - get_package_info = resources.get_package_info + get_package_version = apio_ctx.profile.get_package_installed_version + get_package_info = apio_ctx.get_package_info # --Print the installed packages, if any. if scan.installed_package_ids: diff --git a/apio/resources.py b/apio/resources.py index 934ac9b1..8d359aba 100644 --- a/apio/resources.py +++ b/apio/resources.py @@ -1,4 +1,4 @@ -"""Resources module""" +"""The apio context.""" # -*- coding: utf-8 -*- # -- This file is part of the Apio project @@ -75,8 +75,8 @@ # pylint: disable=too-many-instance-attributes -class Resources: - """Resource manager. Class for accesing to all the resources.""" +class ApioContext: + """Apio context. Class for accesing apio resources and configurations.""" def __init__( self, @@ -84,11 +84,11 @@ def __init__( project_scope: bool, project_dir: Optional[Path] = None, ): - """Initializes the Resources object. 'project dir' is an optional path - to the project dir, otherwise, the current directory is used. - 'project_scope' indicates if project specfic resources such as + """Initializes the ApioContext object. 'project dir' is an optional + path to the project dir, otherwise, the current directory is used. + 'project_scope' indicates if project specfic context such as boards.json should be loaded, if available' or that the global - default resources should be used instead. Some commands such as + default context should be used instead. Some commands such as 'apio packages' uses the global scope while commands such as 'apio build' use the project scope. """ @@ -117,7 +117,7 @@ def __init__( self.all_packages = self._load_resource(PACKAGES_JSON) # -- Expand in place the env templates in all_packages. - Resources._resolve_package_envs(self.all_packages) + ApioContext._resolve_package_envs(self.all_packages) # The subset of packages that are applicable to this platform. self.platform_packages = self._select_packages_for_platform( @@ -292,14 +292,14 @@ def _resolve_package_envs(packages: Dict[str, Dict]) -> None: # -- Expand the values in the "path" section, if any. path_section = package_env.get("path", []) for i, path_template in enumerate(path_section): - path_section[i] = Resources._expand_env_template( + path_section[i] = ApioContext._expand_env_template( path_template, package_path ) # -- Expand the values in the "vars" section, if any. vars_section = package_env.get("vars", {}) for var_name, val_template in vars_section.items(): - vars_section[var_name] = Resources._expand_env_template( + vars_section[var_name] = ApioContext._expand_env_template( val_template, package_path ) @@ -593,7 +593,7 @@ def _determine_platform_id(platforms: Dict[str, Dict]) -> str: if platform_id_override: platform_id = platform_id_override else: - platform_id = Resources._get_system_platform_id() + platform_id = ApioContext._get_system_platform_id() # -- Verify it's valid. This can be a user error if the override # -- is invalid. From 38ac5f7b005531c9c8cc039cd6cc797cee554d95 Mon Sep 17 00:00:00 2001 From: Zapta Date: Mon, 18 Nov 2024 07:29:34 -0800 Subject: [PATCH 03/22] Renamed resources.py to apio_context.py. No behavior change. --- apio/{resources.py => apio_context.py} | 0 apio/commands/boards.py | 2 +- apio/commands/build.py | 2 +- apio/commands/clean.py | 2 +- apio/commands/create.py | 2 +- apio/commands/drivers.py | 2 +- apio/commands/examples.py | 2 +- apio/commands/graph.py | 2 +- apio/commands/install.py | 2 +- apio/commands/lint.py | 2 +- apio/commands/modify.py | 2 +- apio/commands/packages.py | 2 +- apio/commands/raw.py | 2 +- apio/commands/report.py | 2 +- apio/commands/sim.py | 2 +- apio/commands/system.py | 2 +- apio/commands/test.py | 2 +- apio/commands/time.py | 2 +- apio/commands/uninstall.py | 2 +- apio/commands/upload.py | 2 +- apio/commands/verify.py | 2 +- apio/managers/arguments.py | 2 +- apio/managers/drivers.py | 2 +- apio/managers/examples.py | 2 +- apio/managers/installer.py | 2 +- apio/managers/project.py | 2 +- apio/managers/scons.py | 2 +- apio/managers/system.py | 2 +- apio/pkg_util.py | 2 +- 29 files changed, 28 insertions(+), 28 deletions(-) rename apio/{resources.py => apio_context.py} (100%) diff --git a/apio/resources.py b/apio/apio_context.py similarity index 100% rename from apio/resources.py rename to apio/apio_context.py diff --git a/apio/commands/boards.py b/apio/commands/boards.py index b5b53bc1..84daf6d7 100644 --- a/apio/commands/boards.py +++ b/apio/commands/boards.py @@ -10,7 +10,7 @@ from pathlib import Path from varname import nameof import click -from apio.resources import ApioContext +from apio.apio_context import ApioContext from apio import cmd_util from apio.commands import options diff --git a/apio/commands/build.py b/apio/commands/build.py index e657bec0..265f5444 100644 --- a/apio/commands/build.py +++ b/apio/commands/build.py @@ -12,7 +12,7 @@ from apio.managers.scons import SCons from apio import cmd_util from apio.commands import options -from apio.resources import ApioContext +from apio.apio_context import ApioContext # --------------------------- diff --git a/apio/commands/clean.py b/apio/commands/clean.py index bc94211f..9550a22f 100644 --- a/apio/commands/clean.py +++ b/apio/commands/clean.py @@ -12,7 +12,7 @@ from apio.managers.scons import SCons from apio import cmd_util from apio.commands import options -from apio.resources import ApioContext +from apio.apio_context import ApioContext # --------------------------- diff --git a/apio/commands/create.py b/apio/commands/create.py index 90cf486e..d9972496 100644 --- a/apio/commands/create.py +++ b/apio/commands/create.py @@ -13,7 +13,7 @@ from apio import util from apio import cmd_util from apio.commands import options -from apio.resources import ApioContext +from apio.apio_context import ApioContext # --------------------------- diff --git a/apio/commands/drivers.py b/apio/commands/drivers.py index 4bf96730..3a9e1970 100644 --- a/apio/commands/drivers.py +++ b/apio/commands/drivers.py @@ -11,7 +11,7 @@ import click from apio.managers.drivers import Drivers from apio import cmd_util -from apio.resources import ApioContext +from apio.apio_context import ApioContext # --------------------------- # -- COMMAND SPECIFIC OPTIONS diff --git a/apio/commands/examples.py b/apio/commands/examples.py index af7ff419..9fc9d62b 100644 --- a/apio/commands/examples.py +++ b/apio/commands/examples.py @@ -13,7 +13,7 @@ from apio.managers.examples import Examples from apio import cmd_util from apio.commands import options -from apio.resources import ApioContext +from apio.apio_context import ApioContext # --------------------------- # -- COMMAND SPECIFIC OPTIONS diff --git a/apio/commands/graph.py b/apio/commands/graph.py index 8582b86a..4aa0bf7e 100644 --- a/apio/commands/graph.py +++ b/apio/commands/graph.py @@ -13,7 +13,7 @@ from apio.managers.scons import SCons from apio import cmd_util from apio.commands import options -from apio.resources import ApioContext +from apio.apio_context import ApioContext # --------------------------- # -- COMMAND SPECIFIC OPTIONS diff --git a/apio/commands/install.py b/apio/commands/install.py index c913f910..f0fd680e 100644 --- a/apio/commands/install.py +++ b/apio/commands/install.py @@ -12,7 +12,7 @@ from varname import nameof import click from apio.managers.old_installer import Installer -from apio.resources import ApioContext +from apio.apio_context import ApioContext from apio import cmd_util from apio.commands import options diff --git a/apio/commands/lint.py b/apio/commands/lint.py index 3454704e..8a596086 100644 --- a/apio/commands/lint.py +++ b/apio/commands/lint.py @@ -12,7 +12,7 @@ from apio.managers.scons import SCons from apio import cmd_util from apio.commands import options -from apio.resources import ApioContext +from apio.apio_context import ApioContext # --------------------------- diff --git a/apio/commands/modify.py b/apio/commands/modify.py index 4eee2fb2..0e8cbb7f 100644 --- a/apio/commands/modify.py +++ b/apio/commands/modify.py @@ -13,7 +13,7 @@ from apio.managers.project import Project from apio import cmd_util from apio.commands import options -from apio.resources import ApioContext +from apio.apio_context import ApioContext # --------------------------- diff --git a/apio/commands/packages.py b/apio/commands/packages.py index db48cbb3..4de5ad69 100644 --- a/apio/commands/packages.py +++ b/apio/commands/packages.py @@ -12,7 +12,7 @@ from varname import nameof import click from apio.managers import installer -from apio.resources import ApioContext +from apio.apio_context import ApioContext from apio import cmd_util, pkg_util, util from apio.commands import options diff --git a/apio/commands/raw.py b/apio/commands/raw.py index b44db03e..cbb18172 100644 --- a/apio/commands/raw.py +++ b/apio/commands/raw.py @@ -9,7 +9,7 @@ import click from apio import util, pkg_util, cmd_util -from apio.resources import ApioContext +from apio.apio_context import ApioContext # --------------------------- diff --git a/apio/commands/report.py b/apio/commands/report.py index 53e8e7f9..49e76432 100644 --- a/apio/commands/report.py +++ b/apio/commands/report.py @@ -12,7 +12,7 @@ from apio.managers.scons import SCons from apio import cmd_util from apio.commands import options -from apio.resources import ApioContext +from apio.apio_context import ApioContext # --------------------------- diff --git a/apio/commands/sim.py b/apio/commands/sim.py index 8d8975c4..cde7bd26 100644 --- a/apio/commands/sim.py +++ b/apio/commands/sim.py @@ -12,7 +12,7 @@ from apio.managers.scons import SCons from apio import cmd_util from apio.commands import options -from apio.resources import ApioContext +from apio.apio_context import ApioContext # --------------------------- diff --git a/apio/commands/system.py b/apio/commands/system.py index dfb4882b..dbe95cdb 100644 --- a/apio/commands/system.py +++ b/apio/commands/system.py @@ -13,7 +13,7 @@ from apio import util from apio import cmd_util from apio.managers.system import System -from apio.resources import ApioContext +from apio.apio_context import ApioContext from apio.commands import options # --------------------------- diff --git a/apio/commands/test.py b/apio/commands/test.py index 999f5d30..b7d31cba 100644 --- a/apio/commands/test.py +++ b/apio/commands/test.py @@ -12,7 +12,7 @@ from apio.managers.scons import SCons from apio import cmd_util from apio.commands import options -from apio.resources import ApioContext +from apio.apio_context import ApioContext # --------------------------- diff --git a/apio/commands/time.py b/apio/commands/time.py index eb67166f..70a33185 100644 --- a/apio/commands/time.py +++ b/apio/commands/time.py @@ -12,7 +12,7 @@ from apio.managers.scons import SCons from apio import cmd_util from apio.commands import options -from apio.resources import ApioContext +from apio.apio_context import ApioContext # --------------------------- diff --git a/apio/commands/uninstall.py b/apio/commands/uninstall.py index d411a351..8ad48184 100644 --- a/apio/commands/uninstall.py +++ b/apio/commands/uninstall.py @@ -13,7 +13,7 @@ import click from apio.managers.old_installer import Installer from apio import cmd_util -from apio.resources import ApioContext +from apio.apio_context import ApioContext from apio.commands import options diff --git a/apio/commands/upload.py b/apio/commands/upload.py index 91e34f4b..ff4bee2d 100644 --- a/apio/commands/upload.py +++ b/apio/commands/upload.py @@ -13,7 +13,7 @@ from apio.managers.drivers import Drivers from apio import cmd_util from apio.commands import options -from apio.resources import ApioContext +from apio.apio_context import ApioContext # --------------------------- diff --git a/apio/commands/verify.py b/apio/commands/verify.py index af52db11..e9509760 100644 --- a/apio/commands/verify.py +++ b/apio/commands/verify.py @@ -12,7 +12,7 @@ from apio.managers.scons import SCons from apio import cmd_util from apio.commands import options -from apio.resources import ApioContext +from apio.apio_context import ApioContext # --------------------------- diff --git a/apio/managers/arguments.py b/apio/managers/arguments.py index 0ae802af..33d9d5a2 100644 --- a/apio/managers/arguments.py +++ b/apio/managers/arguments.py @@ -9,7 +9,7 @@ from typing import Dict, Tuple import click from apio.managers.project import Project, DEFAULT_TOP_MODULE -from apio.resources import ApioContext +from apio.apio_context import ApioContext # ----- Constant for accesing dicctionaries diff --git a/apio/managers/drivers.py b/apio/managers/drivers.py index 28f103ae..8cf83a81 100644 --- a/apio/managers/drivers.py +++ b/apio/managers/drivers.py @@ -13,7 +13,7 @@ import click from apio import util from apio import pkg_util -from apio.resources import ApioContext +from apio.apio_context import ApioContext FTDI_INSTALL_INSTRUCTIONS_WINDOWS = """ Please follow these steps: diff --git a/apio/managers/examples.py b/apio/managers/examples.py index 0a21594e..f94ce410 100644 --- a/apio/managers/examples.py +++ b/apio/managers/examples.py @@ -13,7 +13,7 @@ import click from apio import util from apio import pkg_util -from apio.resources import ApioContext +from apio.apio_context import ApioContext # -- Error messages EXAMPLE_NOT_FOUND_MSG = """ diff --git a/apio/managers/installer.py b/apio/managers/installer.py index b6b1766d..0bc91a26 100644 --- a/apio/managers/installer.py +++ b/apio/managers/installer.py @@ -14,7 +14,7 @@ import click import requests from apio import util, pkg_util -from apio.resources import ApioContext +from apio.apio_context import ApioContext from apio.managers.downloader import FileDownloader from apio.managers.unpacker import FileUnpacker diff --git a/apio/managers/project.py b/apio/managers/project.py index 9fcff91d..7542db7b 100644 --- a/apio/managers/project.py +++ b/apio/managers/project.py @@ -15,7 +15,7 @@ from configobj import ConfigObj import click from apio import util -from apio.resources import ApioContext +from apio.apio_context import ApioContext # -- Apio projecto filename PROJECT_FILENAME = "apio.ini" diff --git a/apio/managers/scons.py b/apio/managers/scons.py index fbfd4083..60d5e18c 100644 --- a/apio/managers/scons.py +++ b/apio/managers/scons.py @@ -25,7 +25,7 @@ from apio.managers.arguments import process_arguments from apio.managers.arguments import serialize_scons_flags from apio.managers.system import System -from apio.resources import ApioContext +from apio.apio_context import ApioContext from apio.managers.project import Project from apio.managers.scons_filter import SconsFilter diff --git a/apio/managers/system.py b/apio/managers/system.py index c86fc5ed..07e6fd15 100644 --- a/apio/managers/system.py +++ b/apio/managers/system.py @@ -12,7 +12,7 @@ from apio import util from apio import pkg_util -from apio.resources import ApioContext +from apio.apio_context import ApioContext class System: # pragma: no cover diff --git a/apio/pkg_util.py b/apio/pkg_util.py index cb577428..319668a3 100644 --- a/apio/pkg_util.py +++ b/apio/pkg_util.py @@ -16,7 +16,7 @@ import sys import click import semantic_version -from apio.resources import ApioContext +from apio.apio_context import ApioContext from apio import util From a27b645f8e8f2508e60370a8ea9924ff622dec65 Mon Sep 17 00:00:00 2001 From: Zapta Date: Mon, 18 Nov 2024 08:48:12 -0800 Subject: [PATCH 04/22] Now requiring the directory set by the environment variable APIO_PACKAGES_DIR to have in its absolute path the word 'packages' (case insensitive). The motivation is to avoid unintentional mass deletion by the rmtree operation of the packges fixer. --- apio/commands/packages.py | 7 +++-- apio/managers/installer.py | 14 +++++++-- apio/pkg_util.py | 2 +- apio/util.py | 59 +++++++++++++++++++++++++++++--------- 4 files changed, 62 insertions(+), 20 deletions(-) diff --git a/apio/commands/packages.py b/apio/commands/packages.py index 4de5ad69..e97c444e 100644 --- a/apio/commands/packages.py +++ b/apio/commands/packages.py @@ -51,7 +51,8 @@ def _uninstall( if not ( sayyes or click.confirm( - "Do you want to uninstall " f"{util.count(packages, 'package')}?" + "Do you want to uninstall " + f"{util.plurality(packages, 'package')}?" ) ): # -- User doesn't want to continue. @@ -104,11 +105,11 @@ def _list(apio_ctx: ApioContext, verbose: bool) -> int: # -- Print an hint or summary based on the findings. if scan.num_errors(): click.secho( - "[Hint] run 'apio packages -fix' to fix the errors.", fg="yellow" + "[Hint] run 'apio packages --fix' to fix the errors.", fg="yellow" ) elif scan.uninstalled_package_ids: click.secho( - "[Hint] run 'apio packages -install' to install all " + "[Hint] run 'apio packages --install' to install all " "available packages.", fg="yellow", ) diff --git a/apio/managers/installer.py b/apio/managers/installer.py index 0bc91a26..3dcda07f 100644 --- a/apio/managers/installer.py +++ b/apio/managers/installer.py @@ -222,6 +222,7 @@ def _delete_package_dir( # -- Sanity check the path and delete. package_folder_name = apio_ctx.get_package_folder_name(package_id) + assert "packages" in str(package_dir).lower(), package_dir assert package_folder_name in str(package_dir), package_dir shutil.rmtree(package_dir) @@ -406,7 +407,7 @@ def fix_packages( # -- If non verbose, print a summary message. if not verbose: click.secho( - f"Fixing {util.count(scan.num_errors(), 'package error')}." + f"Fixing {util.plurality(scan.num_errors(), 'package error')}." ) # -- Fix broken packages. @@ -426,10 +427,19 @@ def fix_packages( for dir_name in scan.orphan_dir_names: if verbose: print(f"Deleting unknown dir '{dir_name}'") - shutil.rmtree(util.get_packages_dir() / dir_name) + # -- Sanity check. Since get_packages_dir() guarranted to include + # -- the word packages, this can fail only due to programming error. + dir_path = util.get_packages_dir() / dir_name + assert "packages" in str(dir_path).lower(), dir_path + # -- Delete. + shutil.rmtree(dir_path) for file_name in scan.orphan_file_names: if verbose: print(f"Deleting unknown file '{file_name}'") + # -- Sanity check. Since get_packages_dir() guarranted to include + # -- the word packages, this can fail only due to programming error. file_path = util.get_packages_dir() / file_name + assert "packages" in str(file_path).lower(), dir_path + # -- Delete. file_path.unlink() diff --git a/apio/pkg_util.py b/apio/pkg_util.py index 319668a3..17ed4e63 100644 --- a/apio/pkg_util.py +++ b/apio/pkg_util.py @@ -415,7 +415,7 @@ def list_packages(apio_ctx: ApioContext, scan: PackageScanResults) -> None: # -- Print an error summary if scan.num_errors(): - click.secho(f"Total of {util.count(scan.num_errors(), 'error')}") + click.secho(f"Total of {util.plurality(scan.num_errors(), 'error')}") else: click.secho("No errors.") diff --git a/apio/util.py b/apio/util.py index 331f994e..2791c028 100644 --- a/apio/util.py +++ b/apio/util.py @@ -152,13 +152,14 @@ def get_path_in_apio_package(subpath: str) -> Path: def get_home_dir() -> Path: - """Get the APIO Home dir. This is the apio folder where the profle is - located and the packages installed. The APIO Home dir can be set in the - APIO_HOME_DIR environment varible or in the /etc/apio.json file (in + """Get the absolute apio home dir. This is the apio folder where the + profle is located and the packages are installed (unless APIO_PACKAGES_DIR + is used). + The apio home dir can be overridden using the APIO_HOME_DIR environment + varible or in the /etc/apio.json file (in Debian). If not set, the user_HOME/.apio folder is used by default: Ej. Linux: /home/obijuan/.apio If the folders does not exist, they are created - It returns a list with all the folders """ # -- Get the APIO_HOME_DIR env variable @@ -174,7 +175,10 @@ def get_home_dir() -> Path: else: home_dir = Path.home() / ".apio" - # -- Create the folders if they do not exist + # -- Make it absolute + home_dir = home_dir.absolute() + + # -- Create the folder if it does not exist try: home_dir.mkdir(parents=True, exist_ok=True) except PermissionError: @@ -193,26 +197,53 @@ def get_packages_dir() -> Path: * INPUT: - pkg_name: Package name (Ex. 'examples') * OUTPUT: - - The package folder (PosixPath) - (Ex. '/home/obijuan/.apio/packages/examples')) - - or None if the packageis not installed + - The package absolute folder (PosixPath) + (Ex. '/home/obijuan/.apio/packages) + The absolute path of the returned directory is guaranteed to have + the word packages in it. """ # -- Get the APIO_PACKAGES_DIR env variable # -- It returns None if it was not defined packaged_dir_override = env_options.get(env_options.APIO_PACKAGES_DIR) - # -- If specified, use the override. + # -- Handle override. if packaged_dir_override: - pkg_home_dir = Path(packaged_dir_override) + # -- Verify that the override dir contains the word packages in its + # -- absolute path. This is a safety mechanism to prevent uninentional + # -- bulk deletions in unintended directories. We check it each time + # -- before we perform a package deletion. + path = Path(packaged_dir_override).absolute() + if "packages" not in str(path).lower(): + click.secho( + "Error: packages directory path does not contain the word " + f"packages: {str(path)}", + fg="red", + ) + click.secho( + "For safety reasons, if you use the environment variable " + "APIO_PACKAGE_DIR to override\n" + "the packages dir, the new directory must have the word " + "'packages' (case insensitive)\n" + "in its absolute path.", + fg="yellow", + ) + sys.exit(1) + + # -- Override is OK. Use it as the packages dir. + packages_dir = Path(packaged_dir_override) # -- Else, use the default value. else: # -- Ex '/home/obijuan/.apio/packages/tools-oss-cad-suite' - pkg_home_dir = get_home_dir() / "packages" + # -- Guaranteed to be absolute. + packages_dir = get_home_dir() / "packages" - # -- Return the folder if it exists - return pkg_home_dir + # -- Sanity check. If this fails, this is a programming error. + assert "packages" in str(packages_dir).lower(), packages_dir + + # -- All done. + return packages_dir def call(cmd): @@ -564,7 +595,7 @@ def safe_click(text, *args, **kwargs): click.secho(cleaned_text, err=error_flag, *args, **kwargs) -def count(obj: Any, singular: str, plural: str = None) -> str: +def plurality(obj: Any, singular: str, plural: str = None) -> str: """Returns singular or plural based on the size of the object.""" # -- Figure out the size of the object if isinstance(obj, int): From 3bb7c9c3a2e92fe937ecc733a220e8e90daf3a63 Mon Sep 17 00:00:00 2001 From: Zapta Date: Mon, 18 Nov 2024 09:03:35 -0800 Subject: [PATCH 05/22] Simplified the format of the active env options message. From: Active env options ['APIO_HOME_DIR', 'APIO_PLATFORM'] To: Active env options [APIO_HOME_DIR, APIO_PLATFORM] --- apio/apio_context.py | 3 ++- test/commands/test_system.py | 5 +++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/apio/apio_context.py b/apio/apio_context.py index 8d359aba..9f2cbc44 100644 --- a/apio/apio_context.py +++ b/apio/apio_context.py @@ -98,7 +98,8 @@ def __init__( defined_env_options = env_options.get_defined() if defined_env_options: click.secho( - f"Active env options {defined_env_options}.", fg="yellow" + f"Active env options [{', '.join(defined_env_options)}].", + fg="yellow", ) # -- Maps the optional project_dir option to a path. diff --git a/test/commands/test_system.py b/test/commands/test_system.py index 63fb79d2..91de6e39 100644 --- a/test/commands/test_system.py +++ b/test/commands/test_system.py @@ -41,3 +41,8 @@ def test_system(clirunner, configenv): result = clirunner.invoke(cmd_system, ["--info"]) assert result.exit_code == 0, result.output assert "Platform id" in result.output + # -- The these env options are set by the apio text fixture. + assert ( + "Active env options [APIO_HOME_DIR, APIO_PACKAGES_DIR]" + in result.output + ) From 09d4de0354313cdc8a66bc23773670d3c42c7c40 Mon Sep 17 00:00:00 2001 From: Zapta Date: Mon, 18 Nov 2024 09:38:05 -0800 Subject: [PATCH 06/22] Moved the apio_ctx to be the first argument in calles. This is a convention only and should not affect functionality. --- apio/commands/install.py | 10 +++++----- apio/commands/raw.py | 2 +- apio/commands/uninstall.py | 6 +++--- apio/managers/arguments.py | 10 +++++++--- apio/managers/drivers.py | 8 ++++---- apio/managers/examples.py | 8 ++++---- apio/managers/scons.py | 22 +++++++++++----------- apio/managers/system.py | 2 +- apio/pkg_util.py | 10 +++++----- 9 files changed, 41 insertions(+), 37 deletions(-) diff --git a/apio/commands/install.py b/apio/commands/install.py index f0fd680e..bc2da9da 100644 --- a/apio/commands/install.py +++ b/apio/commands/install.py @@ -19,9 +19,9 @@ # R0801: Similar lines in 2 files # pylint: disable=R0801 -def install_packages( - packages: list, +def _install_packages( apio_ctx: ApioContext, + packages: list, force: bool, verbose: bool, ): @@ -103,14 +103,14 @@ def cli( # -- Install the given apio packages if packages: - install_packages(packages, apio_ctx, force, verbose) + _install_packages(apio_ctx, packages, force, verbose) cmd_ctx.exit(0) # -- Install all the available packages (if any) if all_: # -- Install all the available packages for this platform! - install_packages( - apio_ctx.platform_packages.keys(), apio_ctx, force, verbose + _install_packages( + apio_ctx, apio_ctx.platform_packages.keys(), force, verbose ) cmd_ctx.exit(0) diff --git a/apio/commands/raw.py b/apio/commands/raw.py index cbb18172..dd547e57 100644 --- a/apio/commands/raw.py +++ b/apio/commands/raw.py @@ -79,7 +79,7 @@ def cli( if cmd: # -- Make sure that at least the oss-cad-suite is installed. - pkg_util.check_required_packages(["oss-cad-suite"], apio_ctx) + pkg_util.check_required_packages(apio_ctx, ["oss-cad-suite"]) # -- Invoke the command. exit_code = util.call(cmd) diff --git a/apio/commands/uninstall.py b/apio/commands/uninstall.py index 8ad48184..83081929 100644 --- a/apio/commands/uninstall.py +++ b/apio/commands/uninstall.py @@ -19,7 +19,7 @@ # R0801: Similar lines in 2 files # pylint: disable=R0801 -def _uninstall(packages: list, apio_ctx: ApioContext, sayyes, verbose: bool): +def _uninstall(apio_ctx: ApioContext, packages: list, sayyes, verbose: bool): """Uninstall the given list of packages""" # -- Ask the user for confirmation @@ -94,7 +94,7 @@ def cli( # -- Uninstall the given apio packages if packages: - _uninstall(packages, apio_ctx, sayyes, verbose) + _uninstall(apio_ctx, packages, sayyes, verbose) cmd_ctx.exit(0) # -- Uninstall all the packages @@ -102,7 +102,7 @@ def cli( # -- Get all the installed apio packages packages = apio_ctx.profile.packages # -- Uninstall them! - _uninstall(packages, apio_ctx, sayyes, verbose) + _uninstall(apio_ctx, packages, sayyes, verbose) cmd_ctx.exit(0) # -- List all the packages (installed or not) diff --git a/apio/managers/arguments.py b/apio/managers/arguments.py index 33d9d5a2..18b7b0a8 100644 --- a/apio/managers/arguments.py +++ b/apio/managers/arguments.py @@ -84,7 +84,7 @@ def outer(*args): # pylint: disable=R0912 # @debug_params def process_arguments( - config_ini: Dict, apio_ctx: ApioContext, project: Project + apio_ctx: ApioContext, config_ini: Dict, project: Project ) -> Tuple: # noqa """Get the final CONFIGURATION, depending on the board and arguments passed in the command line. @@ -196,7 +196,7 @@ def process_arguments( # -- (The board determine the fpga and the size, but the user has # -- specificied a different size. It is a contradiction!) for item in [ARCH, TYPE, SIZE, PACK, IDCODE]: - update_config_fpga_item(config, item, apio_ctx) + _update_config_fpga_item(apio_ctx, config, item) # -- We already have a final configuration # -- Check that this configuration is ok @@ -271,7 +271,11 @@ def process_arguments( return flags, config[BOARD], config[ARCH] -def update_config_fpga_item(config, item, apio_ctx: ApioContext): +def _update_config_fpga_item( + apio_ctx: ApioContext, + config, + item, +): """Update an item for the current FPGA configuration, if there is no contradiction. It raises an exception in case of contradiction: the current FPGA item diff --git a/apio/managers/drivers.py b/apio/managers/drivers.py index 8cf83a81..f9f2280b 100644 --- a/apio/managers/drivers.py +++ b/apio/managers/drivers.py @@ -446,7 +446,7 @@ def _check_ftdi_driver_darwin(self, driver): # pylint: disable=W0703 def _ftdi_install_windows(self) -> int: # -- Check that the required packages are installed. - pkg_util.check_required_packages(["drivers"], self.apio_ctx) + pkg_util.check_required_packages(self.apio_ctx, ["drivers"]) # -- Get the drivers apio package base folder drivers_base_dir = self.apio_ctx.get_package_dir("drivers") @@ -485,7 +485,7 @@ def _ftdi_install_windows(self) -> int: def _ftdi_uninstall_windows(self) -> int: # -- Check that the required packages exist. - pkg_util.check_required_packages(["drivers"], self.apio_ctx) + pkg_util.check_required_packages(self.apio_ctx, ["drivers"]) click.secho("\nStarting the interactive Device Manager.", fg="green") click.secho(FTDI_UNINSTALL_INSTRUCTIONS_WINDOWS, fg="yellow") @@ -500,7 +500,7 @@ def _ftdi_uninstall_windows(self) -> int: # pylint: disable=W0703 def _serial_install_windows(self) -> int: # -- Check that the required packages exist. - pkg_util.check_required_packages(["drivers"], self.apio_ctx) + pkg_util.check_required_packages(self.apio_ctx, ["drivers"]) drivers_base_dir = self.apio_ctx.get_package_dir("drivers") drivers_bin_dir = drivers_base_dir / "bin" @@ -519,7 +519,7 @@ def _serial_install_windows(self) -> int: def _serial_uninstall_windows(self) -> int: # -- Check that the required packages exist. - pkg_util.check_required_packages(["drivers"], self.apio_ctx) + pkg_util.check_required_packages(self.apio_ctx, ["drivers"]) click.secho("\nStarting the interactive Device Manager.", fg="green") click.secho(SERIAL_UNINSTALL_INSTRUCTIONS_WINDOWS, fg="yellow") diff --git a/apio/managers/examples.py b/apio/managers/examples.py index f94ce410..f003fb98 100644 --- a/apio/managers/examples.py +++ b/apio/managers/examples.py @@ -56,7 +56,7 @@ def get_examples_infos(self) -> Optional[List[ExampleInfo]]: Returns null if an error.""" # -- Check that the example package is installed - pkg_util.check_required_packages(["examples"], self.apio_ctx) + pkg_util.check_required_packages(self.apio_ctx, ["examples"]) # -- Collect the examples home dir each board. boards_dirs: List[PosixPath] = [] @@ -99,7 +99,7 @@ def list_examples(self) -> None: code, 0 if ok, non zero otherwise.""" # -- Check that the examples package is installed. - pkg_util.check_required_packages(["examples"], self.apio_ctx) + pkg_util.check_required_packages(self.apio_ctx, ["examples"]) # -- Get list of examples. examples: List[ExampleInfo] = self.get_examples_infos() @@ -153,7 +153,7 @@ def copy_example_dir(self, example: str, project_dir: Path, sayno: bool): """ # -- Check that the examples package is installed. - pkg_util.check_required_packages(["examples"], self.apio_ctx) + pkg_util.check_required_packages(self.apio_ctx, ["examples"]) # -- Get the working dir (current or given) project_dir = util.get_project_dir(project_dir, create_if_missing=True) @@ -211,7 +211,7 @@ def copy_example_files(self, example: str, project_dir: Path, sayno: bool): """ # -- Check that the examples package is installed. - pkg_util.check_required_packages(["examples"], self.apio_ctx) + pkg_util.check_required_packages(self.apio_ctx, ["examples"]) # -- Get the working dir (current or given) dst_example_path = util.get_project_dir( diff --git a/apio/managers/scons.py b/apio/managers/scons.py index 60d5e18c..165906fd 100644 --- a/apio/managers/scons.py +++ b/apio/managers/scons.py @@ -94,7 +94,7 @@ def clean(self, args) -> int: # -- Split the arguments variables, __, arch = process_arguments( - args, self.apio_ctx, self.project + self.apio_ctx, args, self.project ) # --Clean the project: run scons -c (with aditional arguments) @@ -109,7 +109,7 @@ def verify(self, args) -> int: # -- Split the arguments variables, __, arch = process_arguments( - args, self.apio_ctx, self.project + self.apio_ctx, args, self.project ) # -- Execute scons!!! @@ -128,7 +128,7 @@ def graph(self, args) -> int: # -- Split the arguments variables, _, arch = process_arguments( - args, self.apio_ctx, self.project + self.apio_ctx, args, self.project ) # -- Execute scons!!! @@ -146,7 +146,7 @@ def lint(self, args) -> int: exit code, 0 if ok.""" config = {} - __, __, arch = process_arguments(config, self.apio_ctx, self.project) + __, __, arch = process_arguments(self.apio_ctx, config, self.project) variables = serialize_scons_flags( { "all": args.get("all"), @@ -171,7 +171,7 @@ def sim(self, args) -> int: # -- Split the arguments variables, _, arch = process_arguments( - args, self.apio_ctx, self.project + self.apio_ctx, args, self.project ) return self._run( @@ -188,7 +188,7 @@ def test(self, args) -> int: # -- Split the arguments variables, _, arch = process_arguments( - args, self.apio_ctx, self.project + self.apio_ctx, args, self.project ) return self._run( @@ -205,7 +205,7 @@ def build(self, args) -> int: # -- Split the arguments variables, board, arch = process_arguments( - args, self.apio_ctx, self.project + self.apio_ctx, args, self.project ) # -- Execute scons!!! @@ -224,7 +224,7 @@ def time(self, args) -> int: exit code, 0 if ok.""" variables, board, arch = process_arguments( - args, self.apio_ctx, self.project + self.apio_ctx, args, self.project ) if arch not in ["ice40"]: @@ -249,7 +249,7 @@ def report(self, args) -> int: exit code, 0 if ok.""" variables, board, arch = process_arguments( - args, self.apio_ctx, self.project + self.apio_ctx, args, self.project ) return self._run( @@ -280,7 +280,7 @@ def upload(self, config: dict, prog: dict) -> int: # -- Get important information from the configuration # -- It will raise an exception if it cannot be solved flags, board, arch = process_arguments( - config, self.apio_ctx, self.project + self.apio_ctx, config, self.project ) # -- Information about the FPGA is ok! @@ -933,7 +933,7 @@ def _run( # -- Check that the required packages are installed pkg_util.check_required_packages( - required_packages_names, self.apio_ctx + self.apio_ctx, required_packages_names ) # -- Set env path and vars to use the packages. diff --git a/apio/managers/system.py b/apio/managers/system.py index 07e6fd15..6d2ace21 100644 --- a/apio/managers/system.py +++ b/apio/managers/system.py @@ -152,7 +152,7 @@ def _run_command( """ # -- Check that the required package exists. - pkg_util.check_required_packages(["oss-cad-suite"], self.apio_ctx) + pkg_util.check_required_packages(self.apio_ctx, ["oss-cad-suite"]) # -- Set system env for using the packages. pkg_util.set_env_for_packages(self.apio_ctx) diff --git a/apio/pkg_util.py b/apio/pkg_util.py index 17ed4e63..e7c4b5c7 100644 --- a/apio/pkg_util.py +++ b/apio/pkg_util.py @@ -66,7 +66,7 @@ def _get_env_mutations_for_packages(apio_ctx: ApioContext) -> EnvMutations: def _dump_env_mutations( - mutations: EnvMutations, apio_ctx: ApioContext + apio_ctx: ApioContext, mutations: EnvMutations ) -> None: """For debugging. Delete once stabalizing the new oss-cad-suite on windows.""" @@ -124,7 +124,7 @@ def set_env_for_packages(apio_ctx: ApioContext, verbose: bool = False) -> None: mutations = _get_env_mutations_for_packages(apio_ctx) if verbose: - _dump_env_mutations(mutations, apio_ctx) + _dump_env_mutations(apio_ctx, mutations) # -- If this is the first call, apply the mutations. These mutations are # -- temporary for the lifetime of this process and does not affect the @@ -138,7 +138,7 @@ def set_env_for_packages(apio_ctx: ApioContext, verbose: bool = False) -> None: def check_required_packages( - required_packages_names: List[str], apio_ctx: ApioContext + apio_ctx: ApioContext, required_packages_names: List[str] ) -> None: """Checks that the packages whose names are in 'packages_names' are installed and have a version that meets the requirements. If any error, @@ -169,15 +169,15 @@ def check_required_packages( # -- Check the installed version against the required version. spec_version = spec_packages.get(package_name, "") _check_required_package( - package_name, current_version, spec_version, apio_ctx + apio_ctx, package_name, current_version, spec_version ) def _check_required_package( + apio_ctx: ApioContext, package_name: str, current_version: Optional[str], spec_version: str, - apio_ctx: ApioContext, ) -> None: """Checks that the package with the given packages is installed and has a version that meets the requirements. If any error, it prints an From ca3baa82c41deabb503873e266daf9559cdaccf9 Mon Sep 17 00:00:00 2001 From: Zapta Date: Mon, 18 Nov 2024 09:45:56 -0800 Subject: [PATCH 07/22] Renamed arguments.py to scons_args.py. --- apio/managers/scons.py | 4 ++-- apio/managers/{arguments.py => scons_args.py} | 0 2 files changed, 2 insertions(+), 2 deletions(-) rename apio/managers/{arguments.py => scons_args.py} (100%) diff --git a/apio/managers/scons.py b/apio/managers/scons.py index 165906fd..bef3dead 100644 --- a/apio/managers/scons.py +++ b/apio/managers/scons.py @@ -22,8 +22,8 @@ from apio import util from apio import pkg_util -from apio.managers.arguments import process_arguments -from apio.managers.arguments import serialize_scons_flags +from apio.managers.scons_args import process_arguments +from apio.managers.scons_args import serialize_scons_flags from apio.managers.system import System from apio.apio_context import ApioContext from apio.managers.project import Project diff --git a/apio/managers/arguments.py b/apio/managers/scons_args.py similarity index 100% rename from apio/managers/arguments.py rename to apio/managers/scons_args.py From 5952d8d3da1bd6d1484a9cc1b049e85e9602a9ac Mon Sep 17 00:00:00 2001 From: Zapta Date: Tue, 19 Nov 2024 07:57:15 -0800 Subject: [PATCH 08/22] Flatted the scons verbose_all, verbose_yosys, and verbose_pnr args. Motivation is code simplicity. Before they were created structued and then flatten before passing to scons. Now they are flag from the begining. --- apio/commands/build.py | 8 +++----- apio/commands/clean.py | 4 +--- apio/commands/graph.py | 2 +- apio/commands/report.py | 6 +----- apio/commands/time.py | 8 +++----- apio/commands/upload.py | 8 +++----- apio/commands/verify.py | 2 +- apio/managers/scons_args.py | 32 ++++++++++++-------------------- 8 files changed, 25 insertions(+), 45 deletions(-) diff --git a/apio/commands/build.py b/apio/commands/build.py index 265f5444..8b91dafb 100644 --- a/apio/commands/build.py +++ b/apio/commands/build.py @@ -94,12 +94,10 @@ def cli( "size": size, "type": type_, "pack": pack, - "verbose": { - "all": verbose, - "yosys": verbose_yosys, - "pnr": verbose_pnr, - }, "top-module": top_module, + "verbose_all": verbose, + "verbose_yosys": verbose_yosys, + "verbose_pnr": verbose_pnr, } ) diff --git a/apio/commands/clean.py b/apio/commands/clean.py index 9550a22f..1c81ad29 100644 --- a/apio/commands/clean.py +++ b/apio/commands/clean.py @@ -41,13 +41,11 @@ ) @click.pass_context @options.project_dir_option -@options.verbose_option @options.board_option_gen(deprecated=True) def cli( cmd_ctx: click.core.Context, # Options project_dir: Path, - verbose: bool, # Deprecated options. board: str, ): @@ -62,7 +60,7 @@ def cli( scons = SCons(apio_ctx) # -- Build the project with the given parameters - exit_code = scons.clean({"board": board, "verbose": {"all": verbose}}) + exit_code = scons.clean({"board": board}) # -- Done! cmd_ctx.exit(exit_code) diff --git a/apio/commands/graph.py b/apio/commands/graph.py index 4aa0bf7e..23384617 100644 --- a/apio/commands/graph.py +++ b/apio/commands/graph.py @@ -107,9 +107,9 @@ def cli( # -- Graph the project with the given parameters exit_code = scons.graph( { - "verbose": {"all": verbose, "yosys": False, "pnr": False}, "top-module": top_module, "graph_spec": graph_spec, + "verbose_all": verbose, } ) diff --git a/apio/commands/report.py b/apio/commands/report.py index 49e76432..46d462ca 100644 --- a/apio/commands/report.py +++ b/apio/commands/report.py @@ -79,11 +79,7 @@ def cli( "size": size, "type": type_, "pack": pack, - "verbose": { - "all": False, - "yosys": False, - "pnr": verbose, - }, + "verbose_pnr": verbose, "top-module": top_module, } ) diff --git a/apio/commands/time.py b/apio/commands/time.py index 70a33185..78f76d38 100644 --- a/apio/commands/time.py +++ b/apio/commands/time.py @@ -81,12 +81,10 @@ def cli( "size": size, "type": type_, "pack": pack, - "verbose": { - "all": verbose, - "yosys": verbose_yosys, - "pnr": verbose_pnr, - }, "top-module": top_module, + "verbose_all": verbose, + "verbose_yosys": verbose_yosys, + "verbose_pnr": verbose_pnr, } ) diff --git a/apio/commands/upload.py b/apio/commands/upload.py index ff4bee2d..54ea0c6e 100644 --- a/apio/commands/upload.py +++ b/apio/commands/upload.py @@ -108,12 +108,10 @@ def cli( # -- from the arguments config = { "board": board, - "verbose": { - "all": verbose, - "yosys": verbose_yosys, - "pnr": verbose_pnr, - }, "top-module": top_module, + "verbose_all": verbose, + "verbose_yosys": verbose_yosys, + "verbose_pnr": verbose_pnr, } # -- Construct the programming configuration diff --git a/apio/commands/verify.py b/apio/commands/verify.py index e9509760..29717bab 100644 --- a/apio/commands/verify.py +++ b/apio/commands/verify.py @@ -61,7 +61,7 @@ def cli( exit_code = scons.verify( { "board": board, - "verbose": {"all": verbose, "yosys": False, "pnr": False}, + "verbose_all": verbose, } ) diff --git a/apio/managers/scons_args.py b/apio/managers/scons_args.py index 18b7b0a8..a3075816 100644 --- a/apio/managers/scons_args.py +++ b/apio/managers/scons_args.py @@ -20,10 +20,9 @@ SIZE = "size" # -- Key for the FPGA size PACK = "pack" # -- Key for the FPGA pack IDCODE = "idcode" # -- Key for the FPGA IDCODE -VERBOSE = "verbose" # -- Key for the Verbose flag -ALL = "all" # -- Key for Verbose all -YOSYS = "yosys" # -- Key for Verbose-yosys -PNR = "pnr" # -- Key for Verbose-pnr +VERBOSE_ALL = "verbose_all" # -- Key for the Verbose-all +VERBOSE_YOSYS = "verbose_yosys" # -- Key for Verbose-yosys +VERBOSE_PNR = "verbose_pnr" # -- Key for Verbose-pnr TOP_MODULE = "top-module" # -- Key for top-module TESTBENCH = "testbench" # -- Key for testbench file name GRAPH_SPEC = "graph_spec" # -- Key for graph specification @@ -122,7 +121,9 @@ def process_arguments( SIZE: None, PACK: None, IDCODE: None, - VERBOSE: {ALL: False, "yosys": False, "pnr": False}, + VERBOSE_ALL: False, + VERBOSE_YOSYS: False, + VERBOSE_PNR: False, TOP_MODULE: None, TESTBENCH: None, GRAPH_SPEC: None, @@ -253,14 +254,9 @@ def process_arguments( "fpga_type": config[TYPE], "fpga_pack": config[PACK], "fpga_idcode": config[IDCODE], - "verbose_all": config[VERBOSE][ALL], - # These two flags appear only in some of the commands. - "verbose_yosys": ( - config[VERBOSE][YOSYS] if YOSYS in config[VERBOSE] else False - ), - "verbose_pnr": ( - config[VERBOSE][PNR] if PNR in config[VERBOSE] else False - ), + "verbose_all": config[VERBOSE_ALL], + "verbose_yosys": config[VERBOSE_YOSYS], + "verbose_pnr": config[VERBOSE_PNR], "top_module": config[TOP_MODULE], "testbench": config[TESTBENCH], "graph_spec": config[GRAPH_SPEC], @@ -353,13 +349,9 @@ def print_configuration(config: dict) -> None: print(f" testbench: {config[TESTBENCH]}") print(f" graph_spec: {config[GRAPH_SPEC]}") print(f" platform_id: {config[PLATFORM_ID]}") - print(" verbose:") - print(f" all: {config[VERBOSE][ALL]}") - # These two flags appear only in some of the commands. - if YOSYS in config[VERBOSE]: - print(f" yosys: {config[VERBOSE][YOSYS]}") - if PNR in config[VERBOSE]: - print(f" pnr: {config[VERBOSE][PNR]}") + print(f" verbose_all: {config[VERBOSE_ALL]}") + print(f" verbose_yosys: {config[VERBOSE_YOSYS]}") + print(f" verbose_pnr: {config[VERBOSE_PNR]}") print() From d9a3a25f0c508c495d6c049fbc3ce035b3aa7442 Mon Sep 17 00:00:00 2001 From: Zapta Date: Tue, 19 Nov 2024 16:12:44 -0800 Subject: [PATCH 09/22] Cleaned up the code of scons_args.py. Logic stayed the same. --- apio/managers/scons.py | 4 +- apio/managers/scons_args.py | 377 +++++++++++++++++------------------- test/commands/test_build.py | 35 +++- 3 files changed, 208 insertions(+), 208 deletions(-) diff --git a/apio/managers/scons.py b/apio/managers/scons.py index bef3dead..adc7357e 100644 --- a/apio/managers/scons.py +++ b/apio/managers/scons.py @@ -23,7 +23,7 @@ from apio import util from apio import pkg_util from apio.managers.scons_args import process_arguments -from apio.managers.scons_args import serialize_scons_flags +from apio.managers.scons_args import serialize_scons_variables from apio.managers.system import System from apio.apio_context import ApioContext from apio.managers.project import Project @@ -147,7 +147,7 @@ def lint(self, args) -> int: config = {} __, __, arch = process_arguments(self.apio_ctx, config, self.project) - variables = serialize_scons_flags( + variables = serialize_scons_variables( { "all": args.get("all"), "top_module": args.get("top_module"), diff --git a/apio/managers/scons_args.py b/apio/managers/scons_args.py index a3075816..89d62e13 100644 --- a/apio/managers/scons_args.py +++ b/apio/managers/scons_args.py @@ -5,42 +5,54 @@ # -- Licence GPLv2 """Utilities for procesing the arguments passed to apio commands""" +# pylint: disable=fixme +# TODO: Instead of costructing new_args and then mapping it to +# variables_dict and filtering variables dict, , it would be +# more straight forward to directly constructing the filtered +# variables dict. + from functools import wraps -from typing import Dict, Tuple +from typing import Dict, Tuple, Optional, List import click from apio.managers.project import Project, DEFAULT_TOP_MODULE from apio.apio_context import ApioContext -# ----- Constant for accesing dicctionaries -BOARD = "board" # -- Key for the board name -FPGA = "fpga" # -- Key for the fpga -ARCH = "arch" # -- Key for the FPGA Architecture -TYPE = "type" # -- Key for the FPGA Type -SIZE = "size" # -- Key for the FPGA size -PACK = "pack" # -- Key for the FPGA pack -IDCODE = "idcode" # -- Key for the FPGA IDCODE -VERBOSE_ALL = "verbose_all" # -- Key for the Verbose-all -VERBOSE_YOSYS = "verbose_yosys" # -- Key for Verbose-yosys -VERBOSE_PNR = "verbose_pnr" # -- Key for Verbose-pnr -TOP_MODULE = "top-module" # -- Key for top-module -TESTBENCH = "testbench" # -- Key for testbench file name -GRAPH_SPEC = "graph_spec" # -- Key for graph specification -PLATFORM_ID = "platform_id" # -- Key for the platform id - - -def debug_params(fun): - """DEBUG. Use it as a decorator. It prints the received arguments - and the return value +# -- Names of supported keys in the input process_arguments' args dict. +# -- Unless specified otherwise, all values are strings and optional (that is, +# -- can not exist in 'args' or have a None or empty value). +ARG_BOARD = "board" # Required. +ARG_FPGA_ID = "fpga" # Required, ids per fpgas.json. +ARG_FPGA_ARCH = "arch" +ARG_FPGA_TYPE = "type" +ARG_FPGA_SIZE = "size" +ARG_FPGA_PACK = "pack" +ARG_FPGA_IDCODE = "idcode" +ARG_VERBOSE_ALL = "verbose_all" # Bool. +ARG_VERBOSE_YOSYS = "verbose_yosys" # Bool +ARG_VERBOSE_PNR = "verbose_pnr" # Bool +ARG_TOP_MODULE = "top-module" +ARG_TESTBENCH = "testbench" +ARG_GRAPH_SPEC = "graph_spec" +ARG_PLATFORM_ID = "platform_id" + + +def debug_dump_input_output(process_arguments_func): + """A decorator for debugging. It prints the input and output of + process_arguments(). Comment out the '@debug_params' statement + below to enable, comment to disable.. + INPUTS: * fun: Function to DEBUG """ - @wraps(fun) + @wraps(process_arguments_func) def outer(*args): # -- Print the arguments - print(f"--> DEBUG!. Function {fun.__name__}(). BEGIN") + print( + f"--> DEBUG!. Function {process_arguments_func.__name__}(). BEGIN" + ) print(" * Arguments:") for arg in args: @@ -56,10 +68,10 @@ def outer(*args): print() # -- Call the function - result = fun(*args) + result = process_arguments_func(*args) # -- Print its output - print(f"--> DEBUG!. Function {fun.__name__}(). END") + print(f"--> DEBUG!. Function {process_arguments_func.__name__}(). END") print(" Returns: ") # -- The return object always is a tuple @@ -81,72 +93,58 @@ def outer(*args): # R0912: Too many branches (14/12) # pylint: disable=R0912 -# @debug_params +# +# -- Uncomment the decoracotor below for debugging. +# @debug_dump_input_output def process_arguments( - apio_ctx: ApioContext, config_ini: Dict, project: Project -) -> Tuple: # noqa - """Get the final CONFIGURATION, depending on the board and - arguments passed in the command line. - The config_ini parameter has higher priority. If not specified, - they are read from the Project file (apio.ini) - * INPUTS: - * config_ini: Dictionary with the initial configuration: - { - 'board': str, //-- Board name - 'fpga': str, //-- FPGA name - 'size': str, //-- FPGA size - 'type': str, //-- FPGA type - 'pack': str, //-- FPGA packaging - 'verbose': dict //-- Verbose level - 'top-module`: str //-- Top module name - } - * apio_ctx: Object for accessing the apio configuration and state. - * Project: the contentn of apio.ini, possibly empty if does not exist. + apio_ctx: ApioContext, args: Dict, project: Project +) -> Tuple[List[str], str, Optional[str]]: + """Construct the scons variables list from an ApioContext and user + provided scons args. The list of the valid entires in the args ditct, + see ARG_XX definitions above. + + * apio_ctx: ApioContext of this apio invocation. + * args: a Dictionary with the scons args. + * Project: Optional project information. * OUTPUT: - * Return a tuple (flags, board, arch) - - flags: A list of strings with the flags valures: + * Return a tuple (variables, board, arch) + - variables: A list of strings scons variables. For example ['fpga_arch=ice40', 'fpga_size=8k', 'fpga_type=hx', fpga_pack='tq144:4k']... - board: Board name ('alhambra-ii', 'icezum'...) - arch: FPGA architecture ('ice40', 'ecp5'...) """ - # -- Current configuration - # -- Initially it is the default project configuration - config = { - BOARD: None, - FPGA: None, - ARCH: None, - TYPE: None, - SIZE: None, - PACK: None, - IDCODE: None, - VERBOSE_ALL: False, - VERBOSE_YOSYS: False, - VERBOSE_PNR: False, - TOP_MODULE: None, - TESTBENCH: None, - GRAPH_SPEC: None, - PLATFORM_ID: None, + # -- We will populate a new set of args from input args and other values + # -- we pull from the apio context. + new_args = { + ARG_BOARD: None, + ARG_FPGA_ID: None, + ARG_FPGA_ARCH: None, + ARG_FPGA_TYPE: None, + ARG_FPGA_SIZE: None, + ARG_FPGA_PACK: None, + ARG_FPGA_IDCODE: None, + ARG_VERBOSE_ALL: False, + ARG_VERBOSE_YOSYS: False, + ARG_VERBOSE_PNR: False, + ARG_TOP_MODULE: None, + ARG_TESTBENCH: None, + ARG_GRAPH_SPEC: None, + ARG_PLATFORM_ID: None, } - # -- Merge the initial configuration to the current configuration - config.update(config_ini) - - # -- proj.board: - # -- * None: No apio.ini file - # -- * "name": Board name (str) - - # -- proj.top_module: - # -- * None: Not specified in apio.ini file - # -- * "name": name of the top-module to use - # -- (if not overriden by arguments) + # -- We expect new_args to contain all the supported args and we expect + # -- args to contain only supported args. A failure here indicates a + # -- programming error. + unknown_args = [x for x in args.keys() if x not in new_args] + assert len(unknown_args) == 0, unknown_args - # -- DEBUG: Print both: project board and configuration board - # debug_config_item(config, BOARD, proj.board) + # -- Merge the initial configuration to the current configuration + new_args.update(args) # -- Board name given in the command line - if config[BOARD]: + if new_args[ARG_BOARD]: # -- If there is a project file (apio.ini) the board # -- given by command line overrides it @@ -156,7 +154,7 @@ def process_arguments( # -- As the command line has more priority, and the board # -- given in args is different than the one in the project, # -- inform the user - if config[BOARD] != project.board: + if new_args[ARG_BOARD] != project.board: click.secho( "Info: ignoring board specification from apio.ini.", fg="yellow", @@ -165,60 +163,70 @@ def process_arguments( # -- Board name given in the project file else: # -- ...read it from the apio.ini file - config[BOARD] = project.board + # new_args[ARG_BOARD] = project.board + if project.board: + update_arg(new_args, ARG_BOARD, project.board) # -- The board is given (either by arguments or by project file) - if config[BOARD]: + if new_args[ARG_BOARD]: # -- First, check if the board is valid # -- If not, exit - if config[BOARD] not in apio_ctx.boards: - raise ValueError(f"unknown board: {config[BOARD]}") + if new_args[ARG_BOARD] not in apio_ctx.boards: + raise ValueError(f"unknown board: {new_args[ARG_BOARD]}") # -- Read the FPGA name for the current board - fpga = apio_ctx.boards.get(config[BOARD]).get(FPGA) + fpga = apio_ctx.boards.get(new_args[ARG_BOARD]).get(ARG_FPGA_ID) # -- Add it to the current configuration - update_config_item(config, FPGA, fpga) + if fpga: + update_arg(new_args, ARG_FPGA_ID, fpga) # -- Check that the FPGA was given - if not config[FPGA]: + if not new_args[ARG_FPGA_ID]: perror_insuficient_arguments() raise ValueError("Missing FPGA") # -- Check if the FPGA is valid # -- If not, exit - if config[FPGA] not in apio_ctx.fpgas: - raise ValueError(f"unknown FPGA: {config[FPGA]}") + if new_args[ARG_FPGA_ID] not in apio_ctx.fpgas: + raise ValueError(f"unknown FPGA: {new_args[ARG_FPGA_ID]}") # -- Update the FPGA items according to the current board and fpga # -- Raise an exception in case of a contradiction # -- For example: board = icezum, and size='8k' given by arguments # -- (The board determine the fpga and the size, but the user has # -- specificied a different size. It is a contradiction!) - for item in [ARCH, TYPE, SIZE, PACK, IDCODE]: - _update_config_fpga_item(apio_ctx, config, item) + for arg_name, fpga_property in [ + [ARG_FPGA_ARCH, "arch"], + [ARG_FPGA_TYPE, "type"], + [ARG_FPGA_SIZE, "size"], + [ARG_FPGA_PACK, "pack"], + [ARG_FPGA_IDCODE, "idcode"], + ]: + _update_fpga_property_arg(apio_ctx, new_args, arg_name, fpga_property) # -- We already have a final configuration # -- Check that this configuration is ok # -- At least it should have fpga, type, size and pack # -- Exit if it is not correct - for item in [TYPE, SIZE, PACK]: + for arg_name in [ARG_FPGA_TYPE, ARG_FPGA_SIZE, ARG_FPGA_PACK]: # -- Config item not defined!! it is mandatory! - if not config[item]: + if not new_args[arg_name]: perror_insuficient_arguments() - raise ValueError(f"Missing FPGA {item.upper()}") + raise ValueError(f"Missing FPGA {arg_name.upper()}") # -- Process the top-module # -- Priority 1: Given by arguments in the command line # -- If it has not been set by arguments... - if not config[TOP_MODULE]: + if not new_args[ARG_TOP_MODULE]: # -- Priority 2: Use the top module in the apio.ini file # -- if it exists... if project.top_module: - config[TOP_MODULE] = project.top_module + # new_args[ARG_TOP_MODULE] = project.top_module + update_arg(new_args, ARG_TOP_MODULE, project.top_module) # -- NO top-module specified!! Warn the user else: @@ -235,124 +243,101 @@ def process_arguments( ) # -- Use the default top-level - config[TOP_MODULE] = DEFAULT_TOP_MODULE + # new_args[ARG_TOP_MODULE] = DEFAULT_TOP_MODULE + update_arg(new_args, ARG_TOP_MODULE, DEFAULT_TOP_MODULE) - click.secho("Using the default top-module: `main`", fg="blue") + click.secho( + f"Using the default top-module: `{DEFAULT_TOP_MODULE}`", + fg="blue", + ) # -- Set the platform id. - config[PLATFORM_ID] = apio_ctx.platform_id - - # -- Debug: Print current configuration - # print_configuration(config) - - # -- Build Scons flag list - flags = serialize_scons_flags( - { - "fpga_model": config[FPGA], - "fpga_arch": config[ARCH], - "fpga_size": config[SIZE], - "fpga_type": config[TYPE], - "fpga_pack": config[PACK], - "fpga_idcode": config[IDCODE], - "verbose_all": config[VERBOSE_ALL], - "verbose_yosys": config[VERBOSE_YOSYS], - "verbose_pnr": config[VERBOSE_PNR], - "top_module": config[TOP_MODULE], - "testbench": config[TESTBENCH], - "graph_spec": config[GRAPH_SPEC], - "platform_id": config[PLATFORM_ID], - } - ) + update_arg(new_args, ARG_PLATFORM_ID, apio_ctx.platform_id) + + # -- Build Scons flag list. This is a differnet set of names that may + # -- be different from the args set of names. + variables_dict = { + "fpga_model": new_args[ARG_FPGA_ID], + "fpga_arch": new_args[ARG_FPGA_ARCH], + "fpga_size": new_args[ARG_FPGA_SIZE], + "fpga_type": new_args[ARG_FPGA_TYPE], + "fpga_pack": new_args[ARG_FPGA_PACK], + "fpga_idcode": new_args[ARG_FPGA_IDCODE], + "verbose_all": new_args[ARG_VERBOSE_ALL], + "verbose_yosys": new_args[ARG_VERBOSE_YOSYS], + "verbose_pnr": new_args[ARG_VERBOSE_PNR], + "top_module": new_args[ARG_TOP_MODULE], + "testbench": new_args[ARG_TESTBENCH], + "graph_spec": new_args[ARG_GRAPH_SPEC], + "platform_id": new_args[ARG_PLATFORM_ID], + } - return flags, config[BOARD], config[ARCH] + # -- Convert to a list of 'name=value' strings. Entires with + # -- bool(value) == False are skipped. + variables = serialize_scons_variables(variables_dict) + # -- All done. + return variables, new_args[ARG_BOARD], new_args[ARG_FPGA_ARCH] -def _update_config_fpga_item( - apio_ctx: ApioContext, - config, - item, -): - """Update an item for the current FPGA configuration, if there is no - contradiction. - It raises an exception in case of contradiction: the current FPGA item - in the configuration has already a value, but another has been specified - * INPUTS: - * Config: Current configuration - * item: FPGA item to update: ARCH, TYPE, SIZE, PACK, IDCODE - * value: New valur for the FPGA item, if there is no contradiction - """ - # -- Read the FPGA item from the apio context. - fpga_item = apio_ctx.fpgas.get(config[FPGA]).get(item) +def _update_fpga_property_arg( + apio_ctx: ApioContext, new_args, arg_name, fpga_property_name +): + """Update an fpga property in the given new_args dictionary. - # -- Update the current configuration with that item - # -- and check that there are no contradictions - update_config_item(config, item, fpga_item) + It raises an exception if the there are differnet values in the new_args + dictionary and the fpga property. + It assumes that new_args already contains the FPGA id arg. -def update_config_item(config: dict, item: str, value: str) -> None: - """Update the value of the configuration item, if there is no contradiction - It raises an exception in case of contradiction: the current item in the - configuration has one value (ex. size='1k') but another has been specified * INPUTS: - * Config: Current configuration - * item: Item to update (key): BOARD, ARCH,TYPE, SIZE... - * value: New value for the item, if there are no contradictions + * Apio_ctx: the context of this apio invocation. + * New_args: the args dictionary to populate. + * arg_name: the arg name in the dictionary + * fpga_property: the matching property name in fpgas.json. """ - # -- Debug messages - # debug_config_item(config, item, value) + # -- Get the fpga definition. + fpga_config = apio_ctx.fpgas.get(new_args[ARG_FPGA_ID]) - # -- This item has not been set in the current configuration: ok, set it! - if config[item] is None: - config[item] = value + # -- Get the fpga property value, if exists. + fpga_property_value = fpga_config.get(fpga_property_name, None) - # -- That item already has a value... and another difffernt is being - # -- given.. This is a contradiction!!! - else: - # -- It is a contradiction if their names are different - # -- When the name is the same, it is redundant... - # -- but it is not a problem - if value != config[item]: - raise ValueError(f"contradictory arguments: {value, config[item]}") + # -- Update the arg in new_args, error if the value conflicts. + if fpga_property_value: + update_arg(new_args, arg_name, fpga_property_value) -def debug_config_item(config: dict, item: str, value: str) -> None: - """Print a Debug message related to the project configuration - INPUTS: - * config: Current configuration - * item: item name to print. It is a key of the configuration: - BOARD, ARCH, TYPE, SIZE, PACK, IDCODE.... - * Value: Value of that item specified in the project - """ - print(f"(Debug): {item}, Project: {value}, Argument: {config[item]}") - - -def print_configuration(config: dict) -> None: - """DEBUG function: Print the current configuration on the - console - * INPUTS: - * configuration: Current project configuration +def update_arg(new_args: dict, arg_name: str, new_value: str) -> None: + """Update the a value in the new_args dict. If arg_name doesn't exist + in new_args or if it has a non None value that is different from new_value + than an exception is thrown. + * INPUTS: + * new_args: A dictionary with the args. + * arg_name: The arg name, e.g. ARG_BOARD. + * value: New value for the arg. Can't be None. """ - print() - print("(Debug): Current configuration:") - print(" ITEM: VALUE") - print(" ---- -----") - print(f" board: {config[BOARD]}") - print(f" fpga: {config[FPGA]}") - print(f" arch: {config[ARCH]}") - print(f" type: {config[TYPE]}") - print(f" size: {config[SIZE]}") - print(f" pack: {config[PACK]}") - print(f" idcode: {config[IDCODE]}") - print(f" top-module: {config[TOP_MODULE]}") - print(f" testbench: {config[TESTBENCH]}") - print(f" graph_spec: {config[GRAPH_SPEC]}") - print(f" platform_id: {config[PLATFORM_ID]}") - print(f" verbose_all: {config[VERBOSE_ALL]}") - print(f" verbose_yosys: {config[VERBOSE_YOSYS]}") - print(f" verbose_pnr: {config[VERBOSE_PNR]}") - print() + # -- Sanity checks. These are all programming errors. + assert arg_name in new_args, f"Unknown arg: {arg_name}" + assert new_value is not None, f"Trying to set {arg_name} = None" + + # -- Get the old value. May be None. + old_value = new_args[arg_name] + + # -- If no old value then set the new value. + if old_value is None: + new_args[arg_name] = new_value + return + + # -- If new value is same as old value, do nothing. + if old_value == new_value: + return + + # -- Having conflicting values. + raise ValueError( + f"contradictory argument values: " + f"{arg_name} ({new_value} vs {old_value})" + ) def perror_insuficient_arguments(): @@ -375,7 +360,7 @@ def perror_insuficient_arguments(): ) -def serialize_scons_flags(flags: dict) -> list: +def serialize_scons_variables(flags: dict) -> list: """Format the scons flags as a list of string of the form: 'flag=value' """ diff --git a/test/commands/test_build.py b/test/commands/test_build.py index f5b6d519..de9eaf72 100644 --- a/test/commands/test_build.py +++ b/test/commands/test_build.py @@ -124,7 +124,10 @@ def test_build_complete1(clirunner, configenv): cmd_build, ["--board", "icezum", "--size", "8k"] ) assert result.exit_code != 0, result.output - assert "Error: contradictory arguments: ('1k', '8k')" in result.output + assert ( + "Error: contradictory argument values: ('1k', '8k')" + in result.output + ) # apio build --board icezum --fpga iCE40-HX1K-TQ144 --type lp result = clirunner.invoke( @@ -139,7 +142,10 @@ def test_build_complete1(clirunner, configenv): ], ) assert result.exit_code != 0, result.output - assert "Error: contradictory arguments: ('hx', 'lp')" in result.output + assert ( + "Error: contradictory argument values: ('hx', 'lp')" + in result.output + ) # apio build --board icezum --fpga iCE40-HX1K-VQ100 result = clirunner.invoke( @@ -147,7 +153,7 @@ def test_build_complete1(clirunner, configenv): ) assert result.exit_code != 0, result.output assert ( - "Error: contradictory arguments: ('iCE40-HX1K-TQ144', " + "Error: contradictory argument values: ('iCE40-HX1K-TQ144', " "'iCE40-HX1K-VQ100')" in result.output ) @@ -157,7 +163,10 @@ def test_build_complete1(clirunner, configenv): ["--fpga", "iCE40-HX1K-TQ144", "--type", "lp", "--size", "8k"], ) assert result.exit_code != 0, result.output - assert "Error: contradictory arguments: ('hx', 'lp')" in result.output + assert ( + "Error: contradictory argument values: ('hx', 'lp')" + in result.output + ) # apio build --fpga iCE40-HX1K-TQ144 --pack vq100 result = clirunner.invoke( @@ -165,7 +174,7 @@ def test_build_complete1(clirunner, configenv): ) assert result.exit_code != 0, result.output assert ( - "Error: contradictory arguments: ('tq144', 'vq100')" + "Error: contradictory argument values: ('tq144', 'vq100')" in result.output ) @@ -175,7 +184,7 @@ def test_build_complete1(clirunner, configenv): ) assert result.exit_code != 0, result.output assert ( - "Error: contradictory arguments: ('tq144', 'vq100')" + "Error: contradictory argument values: ('tq144', 'vq100')" in result.output ) @@ -233,7 +242,7 @@ def test_build_complete2(clirunner, configenv): ) assert result.exit_code != 0, result.output assert ( - "Error: contradictory arguments: " + "Error: contradictory argument values: " "('iCE40-HX1K-TQ144', 'iCE40-FAKE')" in result.output ) @@ -268,7 +277,7 @@ def test_build_create(clirunner, configenv): result = clirunner.invoke(cmd_build, ["--fpga", "iCE40-HX1K-VQ100"]) assert result.exit_code != 0, result.output assert ( - "Error: contradictory arguments: " + "Error: contradictory argument values: " "('iCE40-HX1K-TQ144', 'iCE40-HX1K-VQ100')" in result.output ) @@ -277,9 +286,15 @@ def test_build_create(clirunner, configenv): cmd_build, ["--type", "lp", "--size", "8k", "--pack", "cm225:4k"] ) assert result.exit_code != 0, result.output - assert "Error: contradictory arguments: ('hx', 'lp')" in result.output + assert ( + "Error: contradictory argument values: ('hx', 'lp')" + in result.output + ) # apio build --type lp --size 8k result = clirunner.invoke(cmd_build, ["--type", "lp", "--size", "8k"]) assert result.exit_code != 0, result.output - assert "Error: contradictory arguments: ('hx', 'lp')" in result.output + assert ( + "Error: contradictory argument values: ('hx', 'lp')" + in result.output + ) From ea9c6a5595cac3305d606f85adb0d686bccea6f3 Mon Sep 17 00:00:00 2001 From: Zapta Date: Tue, 19 Nov 2024 16:37:43 -0800 Subject: [PATCH 10/22] Renamed variables to reflect association with VERILATOR and Tweaked an error message. --- apio/managers/scons_args.py | 2 +- apio/scons/ecp5/SConstruct | 8 ++++---- apio/scons/gowin/SConstruct | 8 ++++---- apio/scons/ice40/SConstruct | 8 ++++---- test/commands/test_build.py | 25 ++++++++++++------------- 5 files changed, 25 insertions(+), 26 deletions(-) diff --git a/apio/managers/scons_args.py b/apio/managers/scons_args.py index 89d62e13..7b03e3ed 100644 --- a/apio/managers/scons_args.py +++ b/apio/managers/scons_args.py @@ -336,7 +336,7 @@ def update_arg(new_args: dict, arg_name: str, new_value: str) -> None: # -- Having conflicting values. raise ValueError( f"contradictory argument values: " - f"{arg_name} ({new_value} vs {old_value})" + f"'{arg_name}' = ({new_value} vs {old_value})" ) diff --git a/apio/scons/ecp5/SConstruct b/apio/scons/ecp5/SConstruct index 4bda0ba9..47543f24 100644 --- a/apio/scons/ecp5/SConstruct +++ b/apio/scons/ecp5/SConstruct @@ -91,8 +91,8 @@ VERBOSE_PNR = arg_bool(env, "verbose_pnr", False) TESTBENCH = arg_str(env, "testbench", "") VERILATOR_ALL = arg_bool(env, "all", False) VERILATOR_NO_STYLE = arg_bool(env, "nostyle", False) -NOWARNS = arg_str(env, "nowarn", "").split(",") -WARNS = arg_str(env, "warn", "").split(",") +VERILATOR_NOWARNS = arg_str(env, "nowarn", "").split(",") +VERILATOR_WARNS = arg_str(env, "warn", "").split(",") GRAPH_SPEC = arg_str(env, "graph_spec", "") @@ -371,8 +371,8 @@ verilator_builder = Builder( env, warnings_all=VERILATOR_ALL, warnings_no_style=VERILATOR_NO_STYLE, - no_warns=NOWARNS, - warns=WARNS, + no_warns=VERILATOR_NOWARNS, + warns=VERILATOR_WARNS, top_module=TOP_MODULE, lib_dirs=[YOSYS_LIB_DIR], ), diff --git a/apio/scons/gowin/SConstruct b/apio/scons/gowin/SConstruct index 79e1c9c8..21c67241 100644 --- a/apio/scons/gowin/SConstruct +++ b/apio/scons/gowin/SConstruct @@ -91,8 +91,8 @@ VERBOSE_PNR = arg_bool(env, "verbose_pnr", False) TESTBENCH = arg_str(env, "testbench", "") VERILATOR_ALL = arg_bool(env, "all", False) VERILATOR_NO_STYLE = arg_bool(env, "nostyle", False) -NOWARNS = arg_str(env, "nowarn", "").split(",") -WARNS = arg_str(env, "warn", "").split(",") +VERILATOR_NOWARNS = arg_str(env, "nowarn", "").split(",") +VERILATOR_WARNS = arg_str(env, "warn", "").split(",") GRAPH_SPEC = arg_str(env, "graph_spec", "") @@ -362,8 +362,8 @@ verilator_builder = Builder( env, warnings_all=VERILATOR_ALL, warnings_no_style=VERILATOR_NO_STYLE, - no_warns=NOWARNS, - warns=WARNS, + no_warns=VERILATOR_NOWARNS, + warns=VERILATOR_WARNS, top_module=TOP_MODULE, lib_dirs=[YOSYS_LIB_DIR], ), diff --git a/apio/scons/ice40/SConstruct b/apio/scons/ice40/SConstruct index c72d9ec0..07f35935 100644 --- a/apio/scons/ice40/SConstruct +++ b/apio/scons/ice40/SConstruct @@ -90,8 +90,8 @@ VERBOSE_PNR = arg_bool(env, "verbose_pnr", False) TESTBENCH = arg_str(env, "testbench", "") VERILATOR_ALL = arg_bool(env, "all", False) VERILATOR_NO_STYLE = arg_bool(env, "nostyle", False) -NOWARNS = arg_str(env, "nowarn", "").split(",") -WARNS = arg_str(env, "warn", "").split(",") +VERILATOR_NOWARNS = arg_str(env, "nowarn", "").split(",") +VERILATOR_WARNS = arg_str(env, "warn", "").split(",") GRAPH_SPEC = arg_str(env, "graph_spec", "") @@ -387,8 +387,8 @@ verilator_builder = Builder( env, warnings_all=VERILATOR_ALL, warnings_no_style=VERILATOR_NO_STYLE, - no_warns=NOWARNS, - warns=WARNS, + no_warns=VERILATOR_NOWARNS, + warns=VERILATOR_WARNS, top_module=TOP_MODULE, extra_params=["-DNO_ICE40_DEFAULT_ASSIGNMENTS"], lib_files=[YOSYS_LIB_FILE], diff --git a/test/commands/test_build.py b/test/commands/test_build.py index de9eaf72..16e786ce 100644 --- a/test/commands/test_build.py +++ b/test/commands/test_build.py @@ -125,7 +125,7 @@ def test_build_complete1(clirunner, configenv): ) assert result.exit_code != 0, result.output assert ( - "Error: contradictory argument values: ('1k', '8k')" + "Error: contradictory argument values: 'size' = (1k vs 8k)" in result.output ) @@ -143,7 +143,7 @@ def test_build_complete1(clirunner, configenv): ) assert result.exit_code != 0, result.output assert ( - "Error: contradictory argument values: ('hx', 'lp')" + "Error: contradictory argument values: 'type' = (hx vs lp)" in result.output ) @@ -153,8 +153,7 @@ def test_build_complete1(clirunner, configenv): ) assert result.exit_code != 0, result.output assert ( - "Error: contradictory argument values: ('iCE40-HX1K-TQ144', " - "'iCE40-HX1K-VQ100')" in result.output + "Error: contradictory argument values: 'fpga' = ""(iCE40-HX1K-TQ144 vs iCE40-HX1K-VQ100)" in result.output ) # apio build --fpga iCE40-HX1K-TQ144 --type lp --size 8k @@ -164,7 +163,7 @@ def test_build_complete1(clirunner, configenv): ) assert result.exit_code != 0, result.output assert ( - "Error: contradictory argument values: ('hx', 'lp')" + "Error: contradictory argument values: 'type' = (hx vs lp)" in result.output ) @@ -174,7 +173,7 @@ def test_build_complete1(clirunner, configenv): ) assert result.exit_code != 0, result.output assert ( - "Error: contradictory argument values: ('tq144', 'vq100')" + "Error: contradictory argument values: 'pack' = (tq144 vs vq100)" in result.output ) @@ -184,7 +183,7 @@ def test_build_complete1(clirunner, configenv): ) assert result.exit_code != 0, result.output assert ( - "Error: contradictory argument values: ('tq144', 'vq100')" + "Error: contradictory argument values: 'pack' = (tq144 vs vq100)" in result.output ) @@ -242,8 +241,8 @@ def test_build_complete2(clirunner, configenv): ) assert result.exit_code != 0, result.output assert ( - "Error: contradictory argument values: " - "('iCE40-HX1K-TQ144', 'iCE40-FAKE')" in result.output + "Error: contradictory argument values: 'fpga' = " + "(iCE40-HX1K-TQ144 vs iCE40-FAKE)" in result.output ) @@ -277,8 +276,8 @@ def test_build_create(clirunner, configenv): result = clirunner.invoke(cmd_build, ["--fpga", "iCE40-HX1K-VQ100"]) assert result.exit_code != 0, result.output assert ( - "Error: contradictory argument values: " - "('iCE40-HX1K-TQ144', 'iCE40-HX1K-VQ100')" in result.output + "Error: contradictory argument values: 'fpga' = " + "(iCE40-HX1K-TQ144 vs iCE40-HX1K-VQ100)" in result.output ) # apio build --type lp --size 8k --pack cm225:4k @@ -287,7 +286,7 @@ def test_build_create(clirunner, configenv): ) assert result.exit_code != 0, result.output assert ( - "Error: contradictory argument values: ('hx', 'lp')" + "Error: contradictory argument values: 'type' = (hx vs lp)" in result.output ) @@ -295,6 +294,6 @@ def test_build_create(clirunner, configenv): result = clirunner.invoke(cmd_build, ["--type", "lp", "--size", "8k"]) assert result.exit_code != 0, result.output assert ( - "Error: contradictory argument values: ('hx', 'lp')" + "Error: contradictory argument values: 'type' = (hx vs lp)" in result.output ) From 5ef745456aa2d02bc3119ba01ea5102f00f7faf5 Mon Sep 17 00:00:00 2001 From: Zapta Date: Tue, 19 Nov 2024 17:17:11 -0800 Subject: [PATCH 11/22] Migrated the lint command to use the standard scons args processing. --- apio/commands/lint.py | 2 +- apio/managers/scons.py | 14 ++------------ apio/managers/scons_args.py | 29 +++++++++++++++++++++-------- test/commands/test_build.py | 3 ++- 4 files changed, 26 insertions(+), 22 deletions(-) diff --git a/apio/commands/lint.py b/apio/commands/lint.py index 8a596086..5e0594dd 100644 --- a/apio/commands/lint.py +++ b/apio/commands/lint.py @@ -106,7 +106,7 @@ def cli( exit_code = scons.lint( { "all": all_, - "top_module": top_module, + "top-module": top_module, "nostyle": nostyle, "nowarn": nowarn, "warn": warn, diff --git a/apio/managers/scons.py b/apio/managers/scons.py index adc7357e..f31f00ea 100644 --- a/apio/managers/scons.py +++ b/apio/managers/scons.py @@ -23,7 +23,6 @@ from apio import util from apio import pkg_util from apio.managers.scons_args import process_arguments -from apio.managers.scons_args import serialize_scons_variables from apio.managers.system import System from apio.apio_context import ApioContext from apio.managers.project import Project @@ -145,17 +144,8 @@ def lint(self, args) -> int: """Runs a scons subprocess with the 'lint' target. Returns process exit code, 0 if ok.""" - config = {} - __, __, arch = process_arguments(self.apio_ctx, config, self.project) - variables = serialize_scons_variables( - { - "all": args.get("all"), - "top_module": args.get("top_module"), - "nowarn": args.get("nowarn"), - "warn": args.get("warn"), - "nostyle": args.get("nostyle"), - "platform_id": self.apio_ctx.platform_id, - } + variables, __, arch = process_arguments( + self.apio_ctx, args, self.project ) return self._run( "lint", diff --git a/apio/managers/scons_args.py b/apio/managers/scons_args.py index 7b03e3ed..85eae9ea 100644 --- a/apio/managers/scons_args.py +++ b/apio/managers/scons_args.py @@ -35,11 +35,15 @@ ARG_TESTBENCH = "testbench" ARG_GRAPH_SPEC = "graph_spec" ARG_PLATFORM_ID = "platform_id" +ARG_VERILATOR_ALL = "all" +ARG_VERILATOR_NO_STYLE = "nostyle" +ARG_VERILATOR_NO_WARN = "nowarn" +ARG_VERILATOR_WARN = "warn" -def debug_dump_input_output(process_arguments_func): +def debug_dump(process_arguments_func): """A decorator for debugging. It prints the input and output of - process_arguments(). Comment out the '@debug_params' statement + process_arguments(). Comment out the '@debug_dump' statement below to enable, comment to disable.. INPUTS: @@ -95,7 +99,7 @@ def outer(*args): # pylint: disable=R0912 # # -- Uncomment the decoracotor below for debugging. -# @debug_dump_input_output +# @debug_dump def process_arguments( apio_ctx: ApioContext, args: Dict, project: Project ) -> Tuple[List[str], str, Optional[str]]: @@ -125,20 +129,24 @@ def process_arguments( ARG_FPGA_SIZE: None, ARG_FPGA_PACK: None, ARG_FPGA_IDCODE: None, - ARG_VERBOSE_ALL: False, - ARG_VERBOSE_YOSYS: False, - ARG_VERBOSE_PNR: False, + ARG_VERBOSE_ALL: None, + ARG_VERBOSE_YOSYS: None, + ARG_VERBOSE_PNR: None, ARG_TOP_MODULE: None, ARG_TESTBENCH: None, ARG_GRAPH_SPEC: None, ARG_PLATFORM_ID: None, + ARG_VERILATOR_ALL: None, + ARG_VERILATOR_NO_STYLE: None, + ARG_VERILATOR_NO_WARN: None, + ARG_VERILATOR_WARN: None, } # -- We expect new_args to contain all the supported args and we expect # -- args to contain only supported args. A failure here indicates a # -- programming error. unknown_args = [x for x in args.keys() if x not in new_args] - assert len(unknown_args) == 0, unknown_args + assert len(unknown_args) == 0, f"Unexpected sconsargs: {unknown_args}" # -- Merge the initial configuration to the current configuration new_args.update(args) @@ -255,7 +263,8 @@ def process_arguments( update_arg(new_args, ARG_PLATFORM_ID, apio_ctx.platform_id) # -- Build Scons flag list. This is a differnet set of names that may - # -- be different from the args set of names. + # -- be different from the args set of names. These keys should match + # -- the arg keys at the begining of the SConstruct files. variables_dict = { "fpga_model": new_args[ARG_FPGA_ID], "fpga_arch": new_args[ARG_FPGA_ARCH], @@ -270,6 +279,10 @@ def process_arguments( "testbench": new_args[ARG_TESTBENCH], "graph_spec": new_args[ARG_GRAPH_SPEC], "platform_id": new_args[ARG_PLATFORM_ID], + "all": new_args[ARG_VERILATOR_ALL], + "nostyle": new_args[ARG_VERILATOR_NO_STYLE], + "nowarn": new_args[ARG_VERILATOR_NO_WARN], + "warn": new_args[ARG_VERILATOR_WARN], } # -- Convert to a list of 'name=value' strings. Entires with diff --git a/test/commands/test_build.py b/test/commands/test_build.py index 16e786ce..b583d24e 100644 --- a/test/commands/test_build.py +++ b/test/commands/test_build.py @@ -153,7 +153,8 @@ def test_build_complete1(clirunner, configenv): ) assert result.exit_code != 0, result.output assert ( - "Error: contradictory argument values: 'fpga' = ""(iCE40-HX1K-TQ144 vs iCE40-HX1K-VQ100)" in result.output + "Error: contradictory argument values: 'fpga' = " + "(iCE40-HX1K-TQ144 vs iCE40-HX1K-VQ100)" in result.output ) # apio build --fpga iCE40-HX1K-TQ144 --type lp --size 8k From db92e4d9c71b59a437e90625526310056d35975b Mon Sep 17 00:00:00 2001 From: Zapta Date: Tue, 19 Nov 2024 17:47:23 -0800 Subject: [PATCH 12/22] In scons_args.py, renamed 'args' to 'seed_args' and 'new_args' to 'args'. No change in behavior. --- apio/managers/scons_args.py | 117 ++++++++++++++++++------------------ 1 file changed, 57 insertions(+), 60 deletions(-) diff --git a/apio/managers/scons_args.py b/apio/managers/scons_args.py index 85eae9ea..62523269 100644 --- a/apio/managers/scons_args.py +++ b/apio/managers/scons_args.py @@ -6,7 +6,7 @@ """Utilities for procesing the arguments passed to apio commands""" # pylint: disable=fixme -# TODO: Instead of costructing new_args and then mapping it to +# TODO: Instead of costructing args and then mapping it to # variables_dict and filtering variables dict, , it would be # more straight forward to directly constructing the filtered # variables dict. @@ -101,7 +101,7 @@ def outer(*args): # -- Uncomment the decoracotor below for debugging. # @debug_dump def process_arguments( - apio_ctx: ApioContext, args: Dict, project: Project + apio_ctx: ApioContext, seed_args: Dict, project: Project ) -> Tuple[List[str], str, Optional[str]]: """Construct the scons variables list from an ApioContext and user provided scons args. The list of the valid entires in the args ditct, @@ -121,7 +121,7 @@ def process_arguments( # -- We will populate a new set of args from input args and other values # -- we pull from the apio context. - new_args = { + args = { ARG_BOARD: None, ARG_FPGA_ID: None, ARG_FPGA_ARCH: None, @@ -142,17 +142,17 @@ def process_arguments( ARG_VERILATOR_WARN: None, } - # -- We expect new_args to contain all the supported args and we expect - # -- args to contain only supported args. A failure here indicates a + # -- We expect args to contain all the supported args and we expect seed + # -- args to contain a subset of those. A failure here indicates a # -- programming error. - unknown_args = [x for x in args.keys() if x not in new_args] + unknown_args = [x for x in seed_args.keys() if x not in args] assert len(unknown_args) == 0, f"Unexpected sconsargs: {unknown_args}" # -- Merge the initial configuration to the current configuration - new_args.update(args) + args.update(seed_args) # -- Board name given in the command line - if new_args[ARG_BOARD]: + if args[ARG_BOARD]: # -- If there is a project file (apio.ini) the board # -- given by command line overrides it @@ -162,7 +162,7 @@ def process_arguments( # -- As the command line has more priority, and the board # -- given in args is different than the one in the project, # -- inform the user - if new_args[ARG_BOARD] != project.board: + if args[ARG_BOARD] != project.board: click.secho( "Info: ignoring board specification from apio.ini.", fg="yellow", @@ -171,34 +171,33 @@ def process_arguments( # -- Board name given in the project file else: # -- ...read it from the apio.ini file - # new_args[ARG_BOARD] = project.board if project.board: - update_arg(new_args, ARG_BOARD, project.board) + update_arg(args, ARG_BOARD, project.board) # -- The board is given (either by arguments or by project file) - if new_args[ARG_BOARD]: + if args[ARG_BOARD]: # -- First, check if the board is valid # -- If not, exit - if new_args[ARG_BOARD] not in apio_ctx.boards: - raise ValueError(f"unknown board: {new_args[ARG_BOARD]}") + if args[ARG_BOARD] not in apio_ctx.boards: + raise ValueError(f"unknown board: {args[ARG_BOARD]}") # -- Read the FPGA name for the current board - fpga = apio_ctx.boards.get(new_args[ARG_BOARD]).get(ARG_FPGA_ID) + fpga = apio_ctx.boards.get(args[ARG_BOARD]).get(ARG_FPGA_ID) # -- Add it to the current configuration if fpga: - update_arg(new_args, ARG_FPGA_ID, fpga) + update_arg(args, ARG_FPGA_ID, fpga) # -- Check that the FPGA was given - if not new_args[ARG_FPGA_ID]: + if not args[ARG_FPGA_ID]: perror_insuficient_arguments() raise ValueError("Missing FPGA") # -- Check if the FPGA is valid # -- If not, exit - if new_args[ARG_FPGA_ID] not in apio_ctx.fpgas: - raise ValueError(f"unknown FPGA: {new_args[ARG_FPGA_ID]}") + if args[ARG_FPGA_ID] not in apio_ctx.fpgas: + raise ValueError(f"unknown FPGA: {args[ARG_FPGA_ID]}") # -- Update the FPGA items according to the current board and fpga # -- Raise an exception in case of a contradiction @@ -212,7 +211,7 @@ def process_arguments( [ARG_FPGA_PACK, "pack"], [ARG_FPGA_IDCODE, "idcode"], ]: - _update_fpga_property_arg(apio_ctx, new_args, arg_name, fpga_property) + _update_fpga_property_arg(apio_ctx, args, arg_name, fpga_property) # -- We already have a final configuration # -- Check that this configuration is ok @@ -221,20 +220,19 @@ def process_arguments( for arg_name in [ARG_FPGA_TYPE, ARG_FPGA_SIZE, ARG_FPGA_PACK]: # -- Config item not defined!! it is mandatory! - if not new_args[arg_name]: + if not args[arg_name]: perror_insuficient_arguments() raise ValueError(f"Missing FPGA {arg_name.upper()}") # -- Process the top-module # -- Priority 1: Given by arguments in the command line # -- If it has not been set by arguments... - if not new_args[ARG_TOP_MODULE]: + if not args[ARG_TOP_MODULE]: # -- Priority 2: Use the top module in the apio.ini file # -- if it exists... if project.top_module: - # new_args[ARG_TOP_MODULE] = project.top_module - update_arg(new_args, ARG_TOP_MODULE, project.top_module) + update_arg(args, ARG_TOP_MODULE, project.top_module) # -- NO top-module specified!! Warn the user else: @@ -251,8 +249,7 @@ def process_arguments( ) # -- Use the default top-level - # new_args[ARG_TOP_MODULE] = DEFAULT_TOP_MODULE - update_arg(new_args, ARG_TOP_MODULE, DEFAULT_TOP_MODULE) + update_arg(args, ARG_TOP_MODULE, DEFAULT_TOP_MODULE) click.secho( f"Using the default top-module: `{DEFAULT_TOP_MODULE}`", @@ -260,29 +257,29 @@ def process_arguments( ) # -- Set the platform id. - update_arg(new_args, ARG_PLATFORM_ID, apio_ctx.platform_id) + update_arg(args, ARG_PLATFORM_ID, apio_ctx.platform_id) # -- Build Scons flag list. This is a differnet set of names that may # -- be different from the args set of names. These keys should match # -- the arg keys at the begining of the SConstruct files. variables_dict = { - "fpga_model": new_args[ARG_FPGA_ID], - "fpga_arch": new_args[ARG_FPGA_ARCH], - "fpga_size": new_args[ARG_FPGA_SIZE], - "fpga_type": new_args[ARG_FPGA_TYPE], - "fpga_pack": new_args[ARG_FPGA_PACK], - "fpga_idcode": new_args[ARG_FPGA_IDCODE], - "verbose_all": new_args[ARG_VERBOSE_ALL], - "verbose_yosys": new_args[ARG_VERBOSE_YOSYS], - "verbose_pnr": new_args[ARG_VERBOSE_PNR], - "top_module": new_args[ARG_TOP_MODULE], - "testbench": new_args[ARG_TESTBENCH], - "graph_spec": new_args[ARG_GRAPH_SPEC], - "platform_id": new_args[ARG_PLATFORM_ID], - "all": new_args[ARG_VERILATOR_ALL], - "nostyle": new_args[ARG_VERILATOR_NO_STYLE], - "nowarn": new_args[ARG_VERILATOR_NO_WARN], - "warn": new_args[ARG_VERILATOR_WARN], + "fpga_model": args[ARG_FPGA_ID], + "fpga_arch": args[ARG_FPGA_ARCH], + "fpga_size": args[ARG_FPGA_SIZE], + "fpga_type": args[ARG_FPGA_TYPE], + "fpga_pack": args[ARG_FPGA_PACK], + "fpga_idcode": args[ARG_FPGA_IDCODE], + "verbose_all": args[ARG_VERBOSE_ALL], + "verbose_yosys": args[ARG_VERBOSE_YOSYS], + "verbose_pnr": args[ARG_VERBOSE_PNR], + "top_module": args[ARG_TOP_MODULE], + "testbench": args[ARG_TESTBENCH], + "graph_spec": args[ARG_GRAPH_SPEC], + "platform_id": args[ARG_PLATFORM_ID], + "all": args[ARG_VERILATOR_ALL], + "nostyle": args[ARG_VERILATOR_NO_STYLE], + "nowarn": args[ARG_VERILATOR_NO_WARN], + "warn": args[ARG_VERILATOR_WARN], } # -- Convert to a list of 'name=value' strings. Entires with @@ -290,56 +287,56 @@ def process_arguments( variables = serialize_scons_variables(variables_dict) # -- All done. - return variables, new_args[ARG_BOARD], new_args[ARG_FPGA_ARCH] + return variables, args[ARG_BOARD], args[ARG_FPGA_ARCH] def _update_fpga_property_arg( - apio_ctx: ApioContext, new_args, arg_name, fpga_property_name + apio_ctx: ApioContext, args, arg_name, fpga_property_name ): - """Update an fpga property in the given new_args dictionary. + """Update an fpga property in the given args dictionary. - It raises an exception if the there are differnet values in the new_args + It raises an exception if the there are differnet values in the args dictionary and the fpga property. - It assumes that new_args already contains the FPGA id arg. + It assumes that args already contains the FPGA id arg. * INPUTS: * Apio_ctx: the context of this apio invocation. - * New_args: the args dictionary to populate. + * args: the args dictionary to populate. * arg_name: the arg name in the dictionary * fpga_property: the matching property name in fpgas.json. """ # -- Get the fpga definition. - fpga_config = apio_ctx.fpgas.get(new_args[ARG_FPGA_ID]) + fpga_config = apio_ctx.fpgas.get(args[ARG_FPGA_ID]) # -- Get the fpga property value, if exists. fpga_property_value = fpga_config.get(fpga_property_name, None) - # -- Update the arg in new_args, error if the value conflicts. + # -- Update the arg in args, error if the value conflicts. if fpga_property_value: - update_arg(new_args, arg_name, fpga_property_value) + update_arg(args, arg_name, fpga_property_value) -def update_arg(new_args: dict, arg_name: str, new_value: str) -> None: - """Update the a value in the new_args dict. If arg_name doesn't exist - in new_args or if it has a non None value that is different from new_value +def update_arg(args: dict, arg_name: str, new_value: str) -> None: + """Update the a value in the args dict. If arg_name doesn't exist + in args or if it has a non None value that is different from new_value than an exception is thrown. * INPUTS: - * new_args: A dictionary with the args. + * args: A dictionary with the args. * arg_name: The arg name, e.g. ARG_BOARD. * value: New value for the arg. Can't be None. """ # -- Sanity checks. These are all programming errors. - assert arg_name in new_args, f"Unknown arg: {arg_name}" + assert arg_name in args, f"Unknown arg: {arg_name}" assert new_value is not None, f"Trying to set {arg_name} = None" # -- Get the old value. May be None. - old_value = new_args[arg_name] + old_value = args[arg_name] # -- If no old value then set the new value. if old_value is None: - new_args[arg_name] = new_value + args[arg_name] = new_value return # -- If new value is same as old value, do nothing. From a3eb5d03a6fbbe46fdf61b2fb02a1653d30d511b Mon Sep 17 00:00:00 2001 From: Zapta Date: Tue, 19 Nov 2024 20:53:24 -0800 Subject: [PATCH 13/22] Restructured the scons process_arguments() function. It is now implemented using 'smart args' represented by class instances (used to be simple strings). Hopefully this make the code clearer and safer. Will do another pass of cleanup once we make apio.ini mandatory and delete the unnecessary flags. --- apio/managers/scons_args.py | 361 ++++++++++++++++-------------------- 1 file changed, 165 insertions(+), 196 deletions(-) diff --git a/apio/managers/scons_args.py b/apio/managers/scons_args.py index 62523269..1b9c4b70 100644 --- a/apio/managers/scons_args.py +++ b/apio/managers/scons_args.py @@ -5,24 +5,20 @@ # -- Licence GPLv2 """Utilities for procesing the arguments passed to apio commands""" -# pylint: disable=fixme -# TODO: Instead of costructing args and then mapping it to -# variables_dict and filtering variables dict, , it would be -# more straight forward to directly constructing the filtered -# variables dict. +import traceback from functools import wraps -from typing import Dict, Tuple, Optional, List +from typing import Dict, Tuple, Optional, List, Any import click from apio.managers.project import Project, DEFAULT_TOP_MODULE from apio.apio_context import ApioContext -# -- Names of supported keys in the input process_arguments' args dict. -# -- Unless specified otherwise, all values are strings and optional (that is, -# -- can not exist in 'args' or have a None or empty value). -ARG_BOARD = "board" # Required. -ARG_FPGA_ID = "fpga" # Required, ids per fpgas.json. +# -- Names of supported args. Unless specified otherwise, all args are optional +# -- and have a string value. Values such as None, "", or False, which +# -- evaluate to a boolean False are considered 'no value' and are ignored. +ARG_BOARD_ID = "board" +ARG_FPGA_ID = "fpga" ARG_FPGA_ARCH = "arch" ARG_FPGA_TYPE = "type" ARG_FPGA_SIZE = "size" @@ -71,8 +67,13 @@ def outer(*args): print(f" * {arg}") print() - # -- Call the function - result = process_arguments_func(*args) + # -- Call the function, dump exceptions, if any. + try: + result = process_arguments_func(*args) + + except Exception: + print(traceback.format_exc()) + raise # -- Print its output print(f"--> DEBUG!. Function {process_arguments_func.__name__}(). END") @@ -95,6 +96,80 @@ def outer(*args): return outer +class Arg: + """Represent an arg.""" + + def __init__(self, arg_name: str, var_name: str = None): + """If var_name exists, the arg is mapped to scons varialbe with + that name.""" + self._name: str = arg_name + self._var_name: Optional[str] = var_name + self._has_value: bool = False + self._value: Any = None + + def __repr__(self): + """Object representation, for debugging.""" + result = f"Arg '{self._name}'" + if self._has_value: + result += f" = {self._value.__repr__()}" + else: + result += " (no value)" + return result + + @property + def name(self): + """Returns the arg name.""" + return self._name + + @property + def has_value(self): + """True if a value was set.""" + return self._has_value + + def set(self, value: Any): + """Sets a value. Value cannot be None, "", False, or any other value + that is evaluated to a bool False. Value can be set only once, unless + if writing the same value.""" + # -- These are programming errors, not user error. + assert value, f"Setting arg '{self._name}' with a None value." + if not self._has_value: + self._value = value + self._has_value = True + elif value != self._value: + raise ValueError( + f"contradictory argument values: " + f"'{self._name}' = ({value} vs {self._value})" + ) + + @property + def value(self): + """Returns the value. hould be called only if has_value is True.""" + assert self._has_value, f"Arg '{self._name}' has no value to read." + return self._value + + def value_or(self, default): + """Returns value or default if no value.""" + if self._has_value: + return self._value + return default + + @property + def has_var_value(self): + """Does the arg contain a value that should be exported as a scons + variable? To qualify, it needs to has a var name and a value. + To qualify, it needs to""" + return self._var_name and self._has_value + + @property + def var_name(self): + """Returns the name of the scons variable. Should be called only + if the arg is known to have a variable name.""" + assert ( + self._var_name is not None + ), f"Arg '{self._name}' has no var arg." + return self._var_name + + # R0912: Too many branches (14/12) # pylint: disable=R0912 # @@ -119,40 +194,36 @@ def process_arguments( - arch: FPGA architecture ('ice40', 'ecp5'...) """ - # -- We will populate a new set of args from input args and other values - # -- we pull from the apio context. - args = { - ARG_BOARD: None, - ARG_FPGA_ID: None, - ARG_FPGA_ARCH: None, - ARG_FPGA_TYPE: None, - ARG_FPGA_SIZE: None, - ARG_FPGA_PACK: None, - ARG_FPGA_IDCODE: None, - ARG_VERBOSE_ALL: None, - ARG_VERBOSE_YOSYS: None, - ARG_VERBOSE_PNR: None, - ARG_TOP_MODULE: None, - ARG_TESTBENCH: None, - ARG_GRAPH_SPEC: None, - ARG_PLATFORM_ID: None, - ARG_VERILATOR_ALL: None, - ARG_VERILATOR_NO_STYLE: None, - ARG_VERILATOR_NO_WARN: None, - ARG_VERILATOR_WARN: None, + args: Dict[str, Arg] = { + ARG_BOARD_ID: Arg(ARG_BOARD_ID), + ARG_FPGA_ID: Arg(ARG_FPGA_ID, "fpga_model"), + ARG_FPGA_ARCH: Arg(ARG_FPGA_ARCH, "fpga_arch"), + ARG_FPGA_TYPE: Arg(ARG_FPGA_TYPE, "fpga_type"), + ARG_FPGA_SIZE: Arg(ARG_FPGA_SIZE, "fpga_size"), + ARG_FPGA_PACK: Arg(ARG_FPGA_PACK, "fpga_pack"), + ARG_FPGA_IDCODE: Arg(ARG_FPGA_IDCODE, "fpga_idcode"), + ARG_VERBOSE_ALL: Arg(ARG_VERBOSE_ALL, "verbose_all"), + ARG_VERBOSE_YOSYS: Arg(ARG_VERBOSE_YOSYS, "verbose_yosys"), + ARG_VERBOSE_PNR: Arg(ARG_VERBOSE_PNR, "verbose_pnr"), + ARG_TOP_MODULE: Arg(ARG_TOP_MODULE, "top_module"), + ARG_TESTBENCH: Arg(ARG_TESTBENCH, "testbench"), + ARG_GRAPH_SPEC: Arg(ARG_GRAPH_SPEC, "graph_spec"), + ARG_PLATFORM_ID: Arg(ARG_PLATFORM_ID, "platform_id"), + ARG_VERILATOR_ALL: Arg(ARG_VERILATOR_ALL, "all"), + ARG_VERILATOR_NO_STYLE: Arg(ARG_VERILATOR_NO_STYLE, "nostyle"), + ARG_VERILATOR_NO_WARN: Arg(ARG_VERILATOR_NO_WARN, "nowarn"), + ARG_VERILATOR_WARN: Arg(ARG_VERILATOR_WARN, "warn"), } - # -- We expect args to contain all the supported args and we expect seed - # -- args to contain a subset of those. A failure here indicates a - # -- programming error. - unknown_args = [x for x in seed_args.keys() if x not in args] - assert len(unknown_args) == 0, f"Unexpected sconsargs: {unknown_args}" - - # -- Merge the initial configuration to the current configuration - args.update(seed_args) + # -- Populate a subset of the args from the seed. We ignore values such as + # -- None, "", 0, and False which evaluate to boolean False. We expect + # -- those to be the default at the SConstruct arg parsing. + for arg_name, seed_value in seed_args.items(): + if seed_value: + args[arg_name].set(seed_value) # -- Board name given in the command line - if args[ARG_BOARD]: + if args[ARG_BOARD_ID].has_value: # -- If there is a project file (apio.ini) the board # -- given by command line overrides it @@ -162,191 +233,108 @@ def process_arguments( # -- As the command line has more priority, and the board # -- given in args is different than the one in the project, # -- inform the user - if args[ARG_BOARD] != project.board: + if args[ARG_BOARD_ID].value != project.board: click.secho( "Info: ignoring board specification from apio.ini.", fg="yellow", ) - # -- Board name given in the project file + # -- Try getting the board id from the project else: # -- ...read it from the apio.ini file if project.board: - update_arg(args, ARG_BOARD, project.board) + args[ARG_BOARD_ID].set(project.board) + + # update_arg(args, ARG_BOARD, project.board) # -- The board is given (either by arguments or by project file) - if args[ARG_BOARD]: + if args[ARG_BOARD_ID].has_value: - # -- First, check if the board is valid - # -- If not, exit - if args[ARG_BOARD] not in apio_ctx.boards: - raise ValueError(f"unknown board: {args[ARG_BOARD]}") + # -- Check that the board id is valid. + if args[ARG_BOARD_ID].value not in apio_ctx.boards: + raise ValueError(f"unknown board: {args[ARG_BOARD_ID].value}") # -- Read the FPGA name for the current board - fpga = apio_ctx.boards.get(args[ARG_BOARD]).get(ARG_FPGA_ID) + fpga = apio_ctx.boards.get(args[ARG_BOARD_ID].value).get(ARG_FPGA_ID) # -- Add it to the current configuration if fpga: - update_arg(args, ARG_FPGA_ID, fpga) + args[ARG_FPGA_ID].set(fpga) # -- Check that the FPGA was given - if not args[ARG_FPGA_ID]: + if not args[ARG_FPGA_ID].has_value: perror_insuficient_arguments() raise ValueError("Missing FPGA") - # -- Check if the FPGA is valid - # -- If not, exit - if args[ARG_FPGA_ID] not in apio_ctx.fpgas: - raise ValueError(f"unknown FPGA: {args[ARG_FPGA_ID]}") + # -- Check that the FPGA is valid + if args[ARG_FPGA_ID].value not in apio_ctx.fpgas: + raise ValueError(f"unknown FPGA: {args[ARG_FPGA_ID].value}") # -- Update the FPGA items according to the current board and fpga # -- Raise an exception in case of a contradiction # -- For example: board = icezum, and size='8k' given by arguments # -- (The board determine the fpga and the size, but the user has # -- specificied a different size. It is a contradiction!) - for arg_name, fpga_property in [ - [ARG_FPGA_ARCH, "arch"], - [ARG_FPGA_TYPE, "type"], - [ARG_FPGA_SIZE, "size"], - [ARG_FPGA_PACK, "pack"], - [ARG_FPGA_IDCODE, "idcode"], + for arg, fpga_property_name in [ + [args[ARG_FPGA_ARCH], "arch"], + [args[ARG_FPGA_TYPE], "type"], + [args[ARG_FPGA_SIZE], "size"], + [args[ARG_FPGA_PACK], "pack"], + [args[ARG_FPGA_IDCODE], "idcode"], ]: - _update_fpga_property_arg(apio_ctx, args, arg_name, fpga_property) + # -- Get the fpga property, if exits. + fpga_config = apio_ctx.fpgas.get(args[ARG_FPGA_ID].value) + fpga_property = fpga_config.get(fpga_property_name, None) + + if fpga_property: + arg.set(fpga_property) # -- We already have a final configuration # -- Check that this configuration is ok # -- At least it should have fpga, type, size and pack # -- Exit if it is not correct - for arg_name in [ARG_FPGA_TYPE, ARG_FPGA_SIZE, ARG_FPGA_PACK]: + for arg in [args[ARG_FPGA_TYPE], args[ARG_FPGA_SIZE], args[ARG_FPGA_PACK]]: # -- Config item not defined!! it is mandatory! - if not args[arg_name]: + if not arg.has_value: perror_insuficient_arguments() - raise ValueError(f"Missing FPGA {arg_name.upper()}") + raise ValueError(f"Missing FPGA {arg.name.upper()}") - # -- Process the top-module - # -- Priority 1: Given by arguments in the command line - # -- If it has not been set by arguments... - if not args[ARG_TOP_MODULE]: + # -- If top-module not specified by the user, determine what value to use. + if not args[ARG_TOP_MODULE].has_value: - # -- Priority 2: Use the top module in the apio.ini file - # -- if it exists... if project.top_module: - update_arg(args, ARG_TOP_MODULE, project.top_module) + # -- If apio.ini has a top-module value use it. - # -- NO top-module specified!! Warn the user + args[ARG_TOP_MODULE].set(project.top_module) else: - click.secho( - "No top module given\n", - fg="red", - ) - click.secho( - "Option 1: Pass it as a parameter\n" - " `--top-module `\n\n" - "Option 2: Insert in the ini file\n" - " `apio modify --top-module `\n", - fg="yellow", - ) # -- Use the default top-level - update_arg(args, ARG_TOP_MODULE, DEFAULT_TOP_MODULE) + args[ARG_TOP_MODULE].set(DEFAULT_TOP_MODULE) click.secho( - f"Using the default top-module: `{DEFAULT_TOP_MODULE}`", + f"Top-module is missing, assuming: `{DEFAULT_TOP_MODULE}`", fg="blue", ) # -- Set the platform id. - update_arg(args, ARG_PLATFORM_ID, apio_ctx.platform_id) - - # -- Build Scons flag list. This is a differnet set of names that may - # -- be different from the args set of names. These keys should match - # -- the arg keys at the begining of the SConstruct files. - variables_dict = { - "fpga_model": args[ARG_FPGA_ID], - "fpga_arch": args[ARG_FPGA_ARCH], - "fpga_size": args[ARG_FPGA_SIZE], - "fpga_type": args[ARG_FPGA_TYPE], - "fpga_pack": args[ARG_FPGA_PACK], - "fpga_idcode": args[ARG_FPGA_IDCODE], - "verbose_all": args[ARG_VERBOSE_ALL], - "verbose_yosys": args[ARG_VERBOSE_YOSYS], - "verbose_pnr": args[ARG_VERBOSE_PNR], - "top_module": args[ARG_TOP_MODULE], - "testbench": args[ARG_TESTBENCH], - "graph_spec": args[ARG_GRAPH_SPEC], - "platform_id": args[ARG_PLATFORM_ID], - "all": args[ARG_VERILATOR_ALL], - "nostyle": args[ARG_VERILATOR_NO_STYLE], - "nowarn": args[ARG_VERILATOR_NO_WARN], - "warn": args[ARG_VERILATOR_WARN], - } - - # -- Convert to a list of 'name=value' strings. Entires with - # -- bool(value) == False are skipped. - variables = serialize_scons_variables(variables_dict) + assert apio_ctx.platform_id, "Missing platform_id in apio context" + args[ARG_PLATFORM_ID].set(apio_ctx.platform_id) + + # -- Construct the vars list we send to the scons process. + # -- We ignore values such as None, "", 0, and False which evaluate + # -- to boolean False. We expect those to be the default at the + # -- SConstruct arg parsing. + variables = [] + for arg in args.values(): + if arg.has_var_value: + variables.append(f"{arg.var_name}={arg.value}") # -- All done. - return variables, args[ARG_BOARD], args[ARG_FPGA_ARCH] - - -def _update_fpga_property_arg( - apio_ctx: ApioContext, args, arg_name, fpga_property_name -): - """Update an fpga property in the given args dictionary. - - It raises an exception if the there are differnet values in the args - dictionary and the fpga property. - - It assumes that args already contains the FPGA id arg. - - * INPUTS: - * Apio_ctx: the context of this apio invocation. - * args: the args dictionary to populate. - * arg_name: the arg name in the dictionary - * fpga_property: the matching property name in fpgas.json. - """ - - # -- Get the fpga definition. - fpga_config = apio_ctx.fpgas.get(args[ARG_FPGA_ID]) - - # -- Get the fpga property value, if exists. - fpga_property_value = fpga_config.get(fpga_property_name, None) - - # -- Update the arg in args, error if the value conflicts. - if fpga_property_value: - update_arg(args, arg_name, fpga_property_value) - - -def update_arg(args: dict, arg_name: str, new_value: str) -> None: - """Update the a value in the args dict. If arg_name doesn't exist - in args or if it has a non None value that is different from new_value - than an exception is thrown. - * INPUTS: - * args: A dictionary with the args. - * arg_name: The arg name, e.g. ARG_BOARD. - * value: New value for the arg. Can't be None. - """ - # -- Sanity checks. These are all programming errors. - assert arg_name in args, f"Unknown arg: {arg_name}" - assert new_value is not None, f"Trying to set {arg_name} = None" - - # -- Get the old value. May be None. - old_value = args[arg_name] - - # -- If no old value then set the new value. - if old_value is None: - args[arg_name] = new_value - return - - # -- If new value is same as old value, do nothing. - if old_value == new_value: - return - - # -- Having conflicting values. - raise ValueError( - f"contradictory argument values: " - f"'{arg_name}' = ({new_value} vs {old_value})" + return ( + variables, + args[ARG_BOARD_ID].value_or(None), + args[ARG_FPGA_ARCH].value_or(None), ) @@ -368,22 +356,3 @@ def perror_insuficient_arguments(): " `--board `", fg="yellow", ) - - -def serialize_scons_variables(flags: dict) -> list: - """Format the scons flags as a list of string of - the form: 'flag=value' - """ - - # -- Create and empty list - flag_list = [] - - # -- Read all the flags - for key, value in flags.items(): - - # -- Append to the list only the flags with value - if value: - flag_list.append(f"{key}={value}") - - # -- Return the list - return flag_list From f5194ebc5d0c43f969465a58f4f3e657b9dc50b8 Mon Sep 17 00:00:00 2001 From: Zapta Date: Tue, 19 Nov 2024 22:35:13 -0800 Subject: [PATCH 14/22] Now the Project object is also included in the ApiContext. It's loading is conditional on the project_scope arg. Some operations such as 'apio create' do not need to load the project (which may not exist yet). Similar to setup programs such as apio install and apio drivers. --- apio/apio_context.py | 9 ++++++++ apio/commands/create.py | 10 +++++++-- apio/commands/modify.py | 9 ++++++-- apio/managers/project.py | 21 ++++++++++------- apio/managers/scons.py | 45 +++++++++---------------------------- apio/managers/scons_args.py | 11 +++++---- 6 files changed, 54 insertions(+), 51 deletions(-) diff --git a/apio/apio_context.py b/apio/apio_context.py index 9f2cbc44..d8982890 100644 --- a/apio/apio_context.py +++ b/apio/apio_context.py @@ -16,6 +16,7 @@ import click from apio import util, env_options from apio.profile import Profile +from apio.managers.project import Project # pylint: disable=fixme @@ -156,6 +157,14 @@ def __init__( sorted(self.fpgas.items(), key=lambda t: t[0]) ) + # -- If in project scope, load project. + if project_scope: + self.project = Project(self.project_dir) + self.project.read() + + else: + self.project = None + def _load_resource(self, name: str, allow_custom: bool = False) -> dict: """Load the resources from a given json file * INPUTS: diff --git a/apio/commands/create.py b/apio/commands/create.py index d9972496..4fc3fb42 100644 --- a/apio/commands/create.py +++ b/apio/commands/create.py @@ -77,12 +77,18 @@ def cli( # -- Create an apio context. We use project scope in case the project dir # -- already has a custom boards.json file so we validate 'board' # -- against that board list. - apio_ctx = ApioContext(project_dir=project_dir, project_scope=True) + apio_ctx = ApioContext(project_dir=project_dir, project_scope=False) project_dir = util.get_project_dir(project_dir) # Create the apio.ini file - ok = Project.create_ini(apio_ctx, board, top_module, sayyes) + ok = Project.create_ini_file( + apio_ctx.project_dir, + board, + top_module, + apio_ctx.boards, + sayyes, + ) exit_code = 0 if ok else 1 cmd_ctx.exit(exit_code) diff --git a/apio/commands/modify.py b/apio/commands/modify.py index 0e8cbb7f..2434fb22 100644 --- a/apio/commands/modify.py +++ b/apio/commands/modify.py @@ -62,10 +62,15 @@ def cli( cmd_util.check_at_least_one_param(cmd_ctx, nameof(board, top_module)) # Create an apio context. - apio_ctx = ApioContext(project_dir=project_dir, project_scope=True) + apio_ctx = ApioContext(project_dir=project_dir, project_scope=False) # Create the apio.ini file - ok = Project.modify_ini_file(apio_ctx, board, top_module) + ok = Project.modify_ini_file( + apio_ctx.project_dir, + board, + top_module, + apio_ctx.boards, + ) exit_code = 0 if ok else 1 cmd_ctx.exit(exit_code) diff --git a/apio/managers/project.py b/apio/managers/project.py index 7542db7b..8cf09462 100644 --- a/apio/managers/project.py +++ b/apio/managers/project.py @@ -11,11 +11,11 @@ from os.path import isfile from pathlib import Path from configparser import ConfigParser +from typing import Dict from typing import Optional from configobj import ConfigObj import click from apio import util -from apio.apio_context import ApioContext # -- Apio projecto filename PROJECT_FILENAME = "apio.ini" @@ -32,24 +32,27 @@ class Project: """Class for managing apio projects""" def __init__(self, project_dir: Optional[Path]): + """Avoid instantiating this object. Use the instance from + ApioContext when possible. + """ self.project_dir = util.get_project_dir(project_dir) self.board: str = None self.top_module: str = None @staticmethod - def create_ini( - apio_ctx: ApioContext, + def create_ini_file( + project_dir: Path, board: str, top_module: str, + boards: Dict, sayyes: bool = False, ) -> bool: """Creates a new apio project file. Returns True if ok.""" # -- Construct the path - ini_path = apio_ctx.project_dir / PROJECT_FILENAME + ini_path = project_dir / PROJECT_FILENAME # -- Verify that the board id is valid. - boards = apio_ctx.boards if board not in boards.keys(): click.secho(f"Error: no such board '{board}'", fg="red") return False @@ -89,17 +92,19 @@ def create_ini( @staticmethod def modify_ini_file( - apio_ctx: ApioContext, board: Optional[str], top_module: Optional[str] + project_dir: Path, + board: Optional[str], + top_module: Optional[str], + boards: Dict, ) -> bool: """Update the current ini file with the given optional parameters. Returns True if ok.""" # -- construct the file path. - ini_path = apio_ctx.project_dir / PROJECT_FILENAME + ini_path = project_dir / PROJECT_FILENAME # -- Verify that the board id is valid. if board: - boards = apio_ctx.boards if board not in boards.keys(): click.secho( f"Error: no such board '{board}'.\n" diff --git a/apio/managers/scons.py b/apio/managers/scons.py index f31f00ea..b1cd186a 100644 --- a/apio/managers/scons.py +++ b/apio/managers/scons.py @@ -25,7 +25,6 @@ from apio.managers.scons_args import process_arguments from apio.managers.system import System from apio.apio_context import ApioContext -from apio.managers.project import Project from apio.managers.scons_filter import SconsFilter # -- Constant for the dictionary PROG, which contains @@ -79,10 +78,6 @@ def __init__(self, apio_ctx: ApioContext): # -- Cache the apio context. self.apio_ctx = apio_ctx - # -- Read the project file (apio.ini) - self.project = Project(apio_ctx.project_dir) - self.project.read() - # -- Change to the project's folder. os.chdir(apio_ctx.project_dir) @@ -92,9 +87,7 @@ def clean(self, args) -> int: exit code, 0 if ok.""" # -- Split the arguments - variables, __, arch = process_arguments( - self.apio_ctx, args, self.project - ) + variables, __, arch = process_arguments(self.apio_ctx, args) # --Clean the project: run scons -c (with aditional arguments) return self._run( @@ -107,9 +100,7 @@ def verify(self, args) -> int: exit code, 0 if ok.""" # -- Split the arguments - variables, __, arch = process_arguments( - self.apio_ctx, args, self.project - ) + variables, __, arch = process_arguments(self.apio_ctx, args) # -- Execute scons!!! # -- The packages to check are passed @@ -126,9 +117,7 @@ def graph(self, args) -> int: exit code, 0 if ok.""" # -- Split the arguments - variables, _, arch = process_arguments( - self.apio_ctx, args, self.project - ) + variables, _, arch = process_arguments(self.apio_ctx, args) # -- Execute scons!!! # -- The packages to check are passed @@ -144,9 +133,7 @@ def lint(self, args) -> int: """Runs a scons subprocess with the 'lint' target. Returns process exit code, 0 if ok.""" - variables, __, arch = process_arguments( - self.apio_ctx, args, self.project - ) + variables, __, arch = process_arguments(self.apio_ctx, args) return self._run( "lint", variables=variables, @@ -160,9 +147,7 @@ def sim(self, args) -> int: exit code, 0 if ok.""" # -- Split the arguments - variables, _, arch = process_arguments( - self.apio_ctx, args, self.project - ) + variables, _, arch = process_arguments(self.apio_ctx, args) return self._run( "sim", @@ -177,9 +162,7 @@ def test(self, args) -> int: exit code, 0 if ok.""" # -- Split the arguments - variables, _, arch = process_arguments( - self.apio_ctx, args, self.project - ) + variables, _, arch = process_arguments(self.apio_ctx, args) return self._run( "test", @@ -194,9 +177,7 @@ def build(self, args) -> int: exit code, 0 if ok.""" # -- Split the arguments - variables, board, arch = process_arguments( - self.apio_ctx, args, self.project - ) + variables, board, arch = process_arguments(self.apio_ctx, args) # -- Execute scons!!! # -- The packages to check are passed @@ -213,9 +194,7 @@ def time(self, args) -> int: """Runs a scons subprocess with the 'time' target. Returns process exit code, 0 if ok.""" - variables, board, arch = process_arguments( - self.apio_ctx, args, self.project - ) + variables, board, arch = process_arguments(self.apio_ctx, args) if arch not in ["ice40"]: click.secho( @@ -238,9 +217,7 @@ def report(self, args) -> int: """Runs a scons subprocess with the 'report' target. Returns process exit code, 0 if ok.""" - variables, board, arch = process_arguments( - self.apio_ctx, args, self.project - ) + variables, board, arch = process_arguments(self.apio_ctx, args) return self._run( "report", @@ -269,9 +246,7 @@ def upload(self, config: dict, prog: dict) -> int: # -- Get important information from the configuration # -- It will raise an exception if it cannot be solved - flags, board, arch = process_arguments( - self.apio_ctx, config, self.project - ) + flags, board, arch = process_arguments(self.apio_ctx, config) # -- Information about the FPGA is ok! diff --git a/apio/managers/scons_args.py b/apio/managers/scons_args.py index 1b9c4b70..30eb0fa0 100644 --- a/apio/managers/scons_args.py +++ b/apio/managers/scons_args.py @@ -10,7 +10,7 @@ from functools import wraps from typing import Dict, Tuple, Optional, List, Any import click -from apio.managers.project import Project, DEFAULT_TOP_MODULE +from apio.managers.project import DEFAULT_TOP_MODULE from apio.apio_context import ApioContext @@ -176,15 +176,15 @@ def var_name(self): # -- Uncomment the decoracotor below for debugging. # @debug_dump def process_arguments( - apio_ctx: ApioContext, seed_args: Dict, project: Project + apio_ctx: ApioContext, seed_args: Dict ) -> Tuple[List[str], str, Optional[str]]: """Construct the scons variables list from an ApioContext and user provided scons args. The list of the valid entires in the args ditct, see ARG_XX definitions above. - * apio_ctx: ApioContext of this apio invocation. + * apio_ctx: ApioContext of this apio invocation. Assumed to be in + 'project scope' and contain a valid apio_ctx.project. * args: a Dictionary with the scons args. - * Project: Optional project information. * OUTPUT: * Return a tuple (variables, board, arch) - variables: A list of strings scons variables. For example @@ -222,6 +222,9 @@ def process_arguments( if seed_value: args[arg_name].set(seed_value) + # -- Keep a shortcut, for convinience. + project = apio_ctx.project + # -- Board name given in the command line if args[ARG_BOARD_ID].has_value: From e5b6df5ab75a46be8a9654faea1c6ebb7fc79b10 Mon Sep 17 00:00:00 2001 From: Zapta Date: Tue, 19 Nov 2024 23:52:32 -0800 Subject: [PATCH 15/22] Renamed 'project_scope' to 'load_project' and add to ApioContext a property to get the project info after a verification that it's loaded. --- apio/apio_context.py | 55 +++++++++++++++++++++---------------- apio/commands/boards.py | 5 ++-- apio/commands/build.py | 4 +-- apio/commands/clean.py | 2 +- apio/commands/create.py | 6 ++-- apio/commands/drivers.py | 6 ++-- apio/commands/examples.py | 6 ++-- apio/commands/graph.py | 4 +-- apio/commands/install.py | 8 ++---- apio/commands/lint.py | 2 +- apio/commands/modify.py | 4 +-- apio/commands/packages.py | 10 +++---- apio/commands/raw.py | 2 +- apio/commands/report.py | 2 +- apio/commands/sim.py | 2 +- apio/commands/system.py | 2 +- apio/commands/test.py | 2 +- apio/commands/time.py | 2 +- apio/commands/uninstall.py | 2 +- apio/commands/upload.py | 2 +- apio/commands/verify.py | 2 +- apio/managers/scons_args.py | 9 ++++-- 22 files changed, 74 insertions(+), 65 deletions(-) diff --git a/apio/apio_context.py b/apio/apio_context.py index d8982890..b2eabac6 100644 --- a/apio/apio_context.py +++ b/apio/apio_context.py @@ -82,16 +82,15 @@ class ApioContext: def __init__( self, *, - project_scope: bool, + load_project: bool, project_dir: Optional[Path] = None, ): """Initializes the ApioContext object. 'project dir' is an optional path to the project dir, otherwise, the current directory is used. - 'project_scope' indicates if project specfic context such as - boards.json should be loaded, if available' or that the global - default context should be used instead. Some commands such as - 'apio packages' uses the global scope while commands such as - 'apio build' use the project scope. + 'load_project' indicates if project specfic context such as + apio.ini or custom boards.json should be loaded. Some commands operate + on project while other, such as apio drivers and apio packages, + do not require a project. """ # -- Inform as soon as possible about the list of apio env options @@ -103,6 +102,9 @@ def __init__( fg="yellow", ) + # -- Save the load project status. + self._load_project = load_project + # -- Maps the optional project_dir option to a path. self.project_dir: Path = util.get_project_dir(project_dir) @@ -126,19 +128,16 @@ def __init__( self.all_packages, self.platform_id, self.platforms ) - # -- Read the boards information - self.boards = self._load_resource( - BOARDS_JSON, allow_custom=project_scope - ) + # -- Read the boards information. Allow override files in project dir. + self.boards = self._load_resource(BOARDS_JSON, allow_custom=True) - # -- Read the FPGAs information - self.fpgas = self._load_resource( - FPGAS_JSON, allow_custom=project_scope - ) + # -- Read the FPGAs information. Allow override files in project dir. + self.fpgas = self._load_resource(FPGAS_JSON, allow_custom=True) - # -- Read the programmers information + # -- Read the programmers information. Allow override files in project + # -- dir. self.programmers = self._load_resource( - PROGRAMMERS_JSON, allow_custom=project_scope + PROGRAMMERS_JSON, allow_custom=True ) # -- Read the distribution information @@ -157,13 +156,23 @@ def __init__( sorted(self.fpgas.items(), key=lambda t: t[0]) ) - # -- If in project scope, load project. - if project_scope: - self.project = Project(self.project_dir) - self.project.read() - - else: - self.project = None + # -- If requested, load the project's apio.ini + if load_project: + self._project = Project(self.project_dir) + self._project.read() + + def check_project_loaded(self): + """Assert that context was created with project loading..""" + assert ( + self._load_project + ), "Apio context created without project loading." + + @property + def project(self): + """Property to return the project after verification that it was + loaded.""" + self.check_project_loaded() + return self._project def _load_resource(self, name: str, allow_custom: bool = False) -> dict: """Load the resources from a given json file diff --git a/apio/commands/boards.py b/apio/commands/boards.py index 84daf6d7..efeb9b5a 100644 --- a/apio/commands/boards.py +++ b/apio/commands/boards.py @@ -73,9 +73,8 @@ def cli( # Make sure these params are exclusive. cmd_util.check_at_most_one_param(cmd_ctx, nameof(list_, fpgas)) - # -- Create an apio context. We need project scope since the project - # -- may override the list of boards. - apio_ctx = ApioContext(project_dir=project_dir, project_scope=True) + # -- Create the apio context. + apio_ctx = ApioContext(project_dir=project_dir, load_project=False) # -- Option 1: List boards if list_: diff --git a/apio/commands/build.py b/apio/commands/build.py index 8b91dafb..8f9962a7 100644 --- a/apio/commands/build.py +++ b/apio/commands/build.py @@ -78,8 +78,8 @@ def cli( # by means of the scons tool # https://www.scons.org/documentation.html - # -- Create apio context. - apio_ctx = ApioContext(project_dir=project_dir, project_scope=True) + # -- Create the apio context. + apio_ctx = ApioContext(project_dir=project_dir, load_project=True) # -- Create the scons manager. scons = SCons(apio_ctx) diff --git a/apio/commands/clean.py b/apio/commands/clean.py index 1c81ad29..3f1e3a57 100644 --- a/apio/commands/clean.py +++ b/apio/commands/clean.py @@ -54,7 +54,7 @@ def cli( """ # -- Create the apio context. - apio_ctx = ApioContext(project_dir=project_dir, project_scope=True) + apio_ctx = ApioContext(project_dir=project_dir, load_project=True) # -- Create the scons manager. scons = SCons(apio_ctx) diff --git a/apio/commands/create.py b/apio/commands/create.py index 4fc3fb42..fb5aeeec 100644 --- a/apio/commands/create.py +++ b/apio/commands/create.py @@ -74,10 +74,8 @@ def cli( if not top_module: top_module = DEFAULT_TOP_MODULE - # -- Create an apio context. We use project scope in case the project dir - # -- already has a custom boards.json file so we validate 'board' - # -- against that board list. - apio_ctx = ApioContext(project_dir=project_dir, project_scope=False) + # -- Create the apio context. + apio_ctx = ApioContext(project_dir=project_dir, load_project=False) project_dir = util.get_project_dir(project_dir) diff --git a/apio/commands/drivers.py b/apio/commands/drivers.py index 3a9e1970..b1034edb 100644 --- a/apio/commands/drivers.py +++ b/apio/commands/drivers.py @@ -96,8 +96,10 @@ def cli( nameof(ftdi_install, ftdi_uninstall, serial_install, serial_uninstall), ) - # -- Access to the Drivers - apio_ctx = ApioContext(project_scope=False) + # -- Create the apio context. + apio_ctx = ApioContext(load_project=False) + + # -- Create the drivers manager. drivers = Drivers(apio_ctx) # -- FTDI install option diff --git a/apio/commands/examples.py b/apio/commands/examples.py index 9fc9d62b..c091f89c 100644 --- a/apio/commands/examples.py +++ b/apio/commands/examples.py @@ -97,8 +97,10 @@ def cli( cmd_ctx, nameof(list_, fetch_dir, fetch_files) ) - # -- Access to the examples. - apio_ctx = ApioContext(project_scope=False) + # -- Create the apio context. + apio_ctx = ApioContext(load_project=False) + + # -- Create the examples manager. examples = Examples(apio_ctx) # -- Option: Copy the directory diff --git a/apio/commands/graph.py b/apio/commands/graph.py index 23384617..eac924de 100644 --- a/apio/commands/graph.py +++ b/apio/commands/graph.py @@ -98,8 +98,8 @@ def cli( else: graph_spec = "svg" - # -- Create an apio context. - apio_ctx = ApioContext(project_dir=project_dir, project_scope=True) + # -- Create the apio context. + apio_ctx = ApioContext(project_dir=project_dir, load_project=True) # -- Create the scons manager. scons = SCons(apio_ctx) diff --git a/apio/commands/install.py b/apio/commands/install.py index bc2da9da..5748921b 100644 --- a/apio/commands/install.py +++ b/apio/commands/install.py @@ -94,12 +94,8 @@ def cli( # Make sure these params are exclusive. cmd_util.check_at_most_one_param(cmd_ctx, nameof(packages, all_, list_)) - # -- Create an apio context. We don't care about project specific - # -- configuration. - apio_ctx = ApioContext( - project_dir=project_dir, - project_scope=False, - ) + # -- Create the apio context. + apio_ctx = ApioContext(project_dir=project_dir, load_project=False) # -- Install the given apio packages if packages: diff --git a/apio/commands/lint.py b/apio/commands/lint.py index 5e0594dd..acb0a79b 100644 --- a/apio/commands/lint.py +++ b/apio/commands/lint.py @@ -97,7 +97,7 @@ def cli( """Lint the verilog code.""" # -- Create the apio context. - apio_ctx = ApioContext(project_dir=project_dir, project_scope=True) + apio_ctx = ApioContext(project_dir=project_dir, load_project=True) # -- Create the scons manager. scons = SCons(apio_ctx) diff --git a/apio/commands/modify.py b/apio/commands/modify.py index 2434fb22..b19f98cd 100644 --- a/apio/commands/modify.py +++ b/apio/commands/modify.py @@ -61,8 +61,8 @@ def cli( # At least one of these options are required. cmd_util.check_at_least_one_param(cmd_ctx, nameof(board, top_module)) - # Create an apio context. - apio_ctx = ApioContext(project_dir=project_dir, project_scope=False) + # -- Create the apio context. + apio_ctx = ApioContext(project_dir=project_dir, load_project=False) # Create the apio.ini file ok = Project.modify_ini_file( diff --git a/apio/commands/packages.py b/apio/commands/packages.py index e97c444e..de7b6d21 100644 --- a/apio/commands/packages.py +++ b/apio/commands/packages.py @@ -223,13 +223,11 @@ def cli( cmd_util.check_at_most_one_param(cmd_ctx, nameof(list_, packages)) cmd_util.check_at_most_one_param(cmd_ctx, nameof(fix, packages)) - # -- Create the apio context. We don't care about project specific - # -- configuration. - apio_ctx = ApioContext( - project_dir=project_dir, - project_scope=False, - ) + # -- Create the apio context. + + apio_ctx = ApioContext(project_dir=project_dir, load_project=False) + # -- Dispatch the operation. if install: exit_code = _install(apio_ctx, packages, force, verbose) cmd_ctx.exit(exit_code) diff --git a/apio/commands/raw.py b/apio/commands/raw.py index dd547e57..047c29d6 100644 --- a/apio/commands/raw.py +++ b/apio/commands/raw.py @@ -74,7 +74,7 @@ def cli( # -- if --env option is specifies and prepare the env for the command # -- execution below. if cmd or env: - apio_ctx = ApioContext(project_scope=False) + apio_ctx = ApioContext(load_project=False) pkg_util.set_env_for_packages(apio_ctx, verbose=env) if cmd: diff --git a/apio/commands/report.py b/apio/commands/report.py index 46d462ca..b8ae834e 100644 --- a/apio/commands/report.py +++ b/apio/commands/report.py @@ -66,7 +66,7 @@ def cli( """Analyze the design and report timing.""" # -- Create the apio context. - apio_ctx = ApioContext(project_dir=project_dir, project_scope=True) + apio_ctx = ApioContext(project_dir=project_dir, load_project=True) # -- Create the scons manager. scons = SCons(apio_ctx) diff --git a/apio/commands/sim.py b/apio/commands/sim.py index cde7bd26..fc470f62 100644 --- a/apio/commands/sim.py +++ b/apio/commands/sim.py @@ -62,7 +62,7 @@ def cli( """ # -- Create the apio context. - apio_ctx = ApioContext(project_dir=project_dir, project_scope=True) + apio_ctx = ApioContext(project_dir=project_dir, load_project=True) # -- Create the scons manager. scons = SCons(apio_ctx) diff --git a/apio/commands/system.py b/apio/commands/system.py index dbe95cdb..0a46283d 100644 --- a/apio/commands/system.py +++ b/apio/commands/system.py @@ -119,7 +119,7 @@ def cli( ) # Create the apio context. - apio_ctx = ApioContext(project_dir=project_dir, project_scope=False) + apio_ctx = ApioContext(project_dir=project_dir, load_project=False) # -- Create the system object system = System(apio_ctx) diff --git a/apio/commands/test.py b/apio/commands/test.py index b7d31cba..17924f0b 100644 --- a/apio/commands/test.py +++ b/apio/commands/test.py @@ -59,7 +59,7 @@ def cli( """Implements the test command.""" # -- Create the apio context. - apio_ctx = ApioContext(project_dir=project_dir, project_scope=True) + apio_ctx = ApioContext(project_dir=project_dir, load_project=True) # -- Create the scons manager. scons = SCons(apio_ctx) diff --git a/apio/commands/time.py b/apio/commands/time.py index 78f76d38..83f733dc 100644 --- a/apio/commands/time.py +++ b/apio/commands/time.py @@ -68,7 +68,7 @@ def cli( ) # -- Create the apio context. - apio_ctx = ApioContext(project_dir=project_dir, project_scope=True) + apio_ctx = ApioContext(project_dir=project_dir, load_project=True) # -- Create the scons manager. scons = SCons(apio_ctx) diff --git a/apio/commands/uninstall.py b/apio/commands/uninstall.py index 83081929..cc9d2c4f 100644 --- a/apio/commands/uninstall.py +++ b/apio/commands/uninstall.py @@ -90,7 +90,7 @@ def cli( cmd_util.check_at_most_one_param(cmd_ctx, nameof(packages, list_, all_)) # -- Create the apio context. - apio_ctx = ApioContext(project_dir=project_dir, project_scope=False) + apio_ctx = ApioContext(project_dir=project_dir, load_project=False) # -- Uninstall the given apio packages if packages: diff --git a/apio/commands/upload.py b/apio/commands/upload.py index 54ea0c6e..266e592a 100644 --- a/apio/commands/upload.py +++ b/apio/commands/upload.py @@ -92,7 +92,7 @@ def cli( """Implements the upload command.""" # -- Create a apio context. - apio_ctx = ApioContext(project_dir=project_dir, project_scope=True) + apio_ctx = ApioContext(project_dir=project_dir, load_project=True) # -- Create the drivers manager. drivers = Drivers(apio_ctx) diff --git a/apio/commands/verify.py b/apio/commands/verify.py index 29717bab..4106ed04 100644 --- a/apio/commands/verify.py +++ b/apio/commands/verify.py @@ -52,7 +52,7 @@ def cli( ) # -- Crete the apio context. - apio_ctx = ApioContext(project_dir=project_dir, project_scope=True) + apio_ctx = ApioContext(project_dir=project_dir, load_project=True) # -- Create the scons manager. scons = SCons(apio_ctx) diff --git a/apio/managers/scons_args.py b/apio/managers/scons_args.py index 30eb0fa0..9e372896 100644 --- a/apio/managers/scons_args.py +++ b/apio/managers/scons_args.py @@ -182,8 +182,8 @@ def process_arguments( provided scons args. The list of the valid entires in the args ditct, see ARG_XX definitions above. - * apio_ctx: ApioContext of this apio invocation. Assumed to be in - 'project scope' and contain a valid apio_ctx.project. + * apio_ctx: ApioContext of this apio invocation. Should be created with + 'load_project' = True. * args: a Dictionary with the scons args. * OUTPUT: * Return a tuple (variables, board, arch) @@ -194,6 +194,11 @@ def process_arguments( - arch: FPGA architecture ('ice40', 'ecp5'...) """ + # -- We expect that the apio context was created with project loading. + apio_ctx.check_project_loaded() + + # -- Construct the args dictionary with all supported args. Most of the + # -- args also have the name of their exported scons variable. args: Dict[str, Arg] = { ARG_BOARD_ID: Arg(ARG_BOARD_ID), ARG_FPGA_ID: Arg(ARG_FPGA_ID, "fpga_model"), From 683c66df047a294878828b4070c7d1225054c460 Mon Sep 17 00:00:00 2001 From: Zapta Date: Wed, 20 Nov 2024 08:15:17 -0800 Subject: [PATCH 16/22] 'apio system --info' now prints also the current apio version. --- apio/commands/system.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/apio/commands/system.py b/apio/commands/system.py index 0a46283d..a38971b7 100644 --- a/apio/commands/system.py +++ b/apio/commands/system.py @@ -8,6 +8,7 @@ """Implementation of 'apio system' command""" from pathlib import Path +import importlib.metadata from varname import nameof import click from apio import util @@ -141,6 +142,10 @@ def cli( # -- Show system information if info: + # -- Apio version. + click.secho("Apio version ", nl=False) + click.secho(importlib.metadata.version("apio"), fg="cyan") + # -- Print platform id. click.secho("Platform id ", nl=False) click.secho(apio_ctx.platform_id, fg="cyan") From 91f6d7b2b736bb96dc7d3dc84762337600c9fca9 Mon Sep 17 00:00:00 2001 From: Zapta Date: Wed, 20 Nov 2024 09:03:37 -0800 Subject: [PATCH 17/22] Marked the 'apio modify' command as depreated. No change in functionality. --- apio/__main__.py | 2 +- apio/commands/modify.py | 18 +++--------------- 2 files changed, 4 insertions(+), 16 deletions(-) diff --git a/apio/__main__.py b/apio/__main__.py index 5809d993..f802297f 100644 --- a/apio/__main__.py +++ b/apio/__main__.py @@ -34,7 +34,6 @@ ], "Setup commands": [ "create", - "modify", "packages", "drivers", ], @@ -46,6 +45,7 @@ "upgrade", ], "Deprecated commands": [ + "modify", "time", "verify", "install", diff --git a/apio/commands/modify.py b/apio/commands/modify.py index b19f98cd..b764c095 100644 --- a/apio/commands/modify.py +++ b/apio/commands/modify.py @@ -20,20 +20,8 @@ # -- COMMAND # --------------------------- HELP = """ -The modify command modifies selected fields in an existing -apio.ini project file. The commands is typically used in -the root directory of the project that contains the apio.ini file. - -\b -Examples: - apio modify --board icezum - apio modify --board icezum --top-module MyModule - apio create --top-module MyModule - -At least one of the flags --board and --top-module must be specified. - -[Hint] Use the command 'apio examples -l' to see a list of -the supported boards. +The command 'apio modify' has been deprecated. Please edit the 'apio.ini' +file manually with a text editor. """ @@ -41,7 +29,7 @@ # pylint: disable=R0913 @click.command( "modify", - short_help="Modify the apio.ini project file.", + short_help="[Depreciated] Modify the apio.ini project file.", help=HELP, cls=cmd_util.ApioCommand, ) From 1209e2c8014b024ccbf1a75aa997e7012ac70dd3 Mon Sep 17 00:00:00 2001 From: Zapta Date: Wed, 20 Nov 2024 12:13:41 -0800 Subject: [PATCH 18/22] Added to the alhambra ledon example a missing 'top-module' flag in apio.ini --- test-examples/TB/Alhambra-II/icestudio/ledon/apio.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/test-examples/TB/Alhambra-II/icestudio/ledon/apio.ini b/test-examples/TB/Alhambra-II/icestudio/ledon/apio.ini index d9736f52..25c953d2 100644 --- a/test-examples/TB/Alhambra-II/icestudio/ledon/apio.ini +++ b/test-examples/TB/Alhambra-II/icestudio/ledon/apio.ini @@ -1,3 +1,4 @@ [env] board = alhambra-ii +top-module = main From cad2631632fe9dc25116d20b35470b53509ce08c Mon Sep 17 00:00:00 2001 From: Zapta Date: Wed, 20 Nov 2024 13:47:24 -0800 Subject: [PATCH 19/22] All build and other artrifact files are now stored in a subdirectory called _build, under the project dir. This follows platformio's convention of seperating between sources and artifact files and simplifies setting a simple and stable .gitignore file. I tested it on darwin_arm64 but to test it on a windows box I need to submit it first. --- .gitignore | 1 + apio/scons/ecp5/SConstruct | 21 ++++----------- apio/scons/gowin/SConstruct | 21 ++++----------- apio/scons/ice40/SConstruct | 22 ++++------------ apio/scons/scons_util.py | 51 +++++++++++++++++++------------------ 5 files changed, 42 insertions(+), 74 deletions(-) diff --git a/.gitignore b/.gitignore index 01df3227..1d5ba4c2 100644 --- a/.gitignore +++ b/.gitignore @@ -25,6 +25,7 @@ examples/ ice-build/ work/ test-colorlight-v61.ice +_build hardware.json hardware.out hardware.vlt diff --git a/apio/scons/ecp5/SConstruct b/apio/scons/ecp5/SConstruct index 47543f24..35611ff4 100644 --- a/apio/scons/ecp5/SConstruct +++ b/apio/scons/ecp5/SConstruct @@ -317,9 +317,9 @@ graph_target = env.Alias("graph", graphviz_target) # -- (modules).v -> (testbench).out -> (testbench).vcd -> gtkwave if "sim" in COMMAND_LINE_TARGETS: config = get_sim_config(env, TESTBENCH, synth_srcs) - sim_out_target = env.IVerilogTestbench(config.top_module, config.srcs) + sim_out_target = env.IVerilogTestbench(config.output, config.srcs) vcd_file_target = env.VCD(sim_out_target) - waves_target = make_waves_target(env, vcd_file_target, config.top_module) + waves_target = make_waves_target(env, vcd_file_target, config.output) AlwaysBuild(waves_target) @@ -330,12 +330,12 @@ if "test" in COMMAND_LINE_TARGETS: configs = get_tests_configs(env, TESTBENCH, synth_srcs, test_srcs) tests_targets = [] for config in configs: - test_out_target = env.IVerilogTestbench(config.top_module, config.srcs) + test_out_target = env.IVerilogTestbench(config.output, config.srcs) AlwaysBuild(test_out_target) test_vcd_target = env.VCD(test_out_target) AlwaysBuild(test_vcd_target) test_target = env.Alias( - config.top_module, [test_out_target, test_vcd_target] + config.output, [test_out_target, test_vcd_target] ) tests_targets.append(test_target) @@ -394,15 +394,4 @@ AlwaysBuild(lint_target) # -- Handle the cleanu of the artifact files. if GetOption("clean"): - set_up_cleanup( - env, - [ - report_target, - build_target, - synth_target, - verify_target, - pnr_target, - graph_target, - lint_target, - ], - ) + set_up_cleanup(env) diff --git a/apio/scons/gowin/SConstruct b/apio/scons/gowin/SConstruct index 21c67241..d88b2201 100644 --- a/apio/scons/gowin/SConstruct +++ b/apio/scons/gowin/SConstruct @@ -312,9 +312,9 @@ graph_target = env.Alias("graph", graphviz_target) # -- (modules).v -> (testbench).out -> (testbench).vcd -> gtkwave if "sim" in COMMAND_LINE_TARGETS: config = get_sim_config(env, TESTBENCH, synth_srcs) - sim_out_target = env.IVerilogTestbench(config.top_module, config.srcs) + sim_out_target = env.IVerilogTestbench(config.output, config.srcs) vcd_file_target = env.VCD(sim_out_target) - waves_target = make_waves_target(env, vcd_file_target, config.top_module) + waves_target = make_waves_target(env, vcd_file_target, config.output) AlwaysBuild(waves_target) @@ -325,12 +325,12 @@ if "test" in COMMAND_LINE_TARGETS: configs = get_tests_configs(env, TESTBENCH, synth_srcs, test_srcs) tests_targets = [] for config in configs: - test_out_target = env.IVerilogTestbench(config.top_module, config.srcs) + test_out_target = env.IVerilogTestbench(config.output, config.srcs) AlwaysBuild(test_out_target) test_vcd_target = env.VCD(test_out_target) AlwaysBuild(test_vcd_target) test_target = env.Alias( - config.top_module, [test_out_target, test_vcd_target] + config.output, [test_out_target, test_vcd_target] ) tests_targets.append(test_target) @@ -385,15 +385,4 @@ AlwaysBuild(lint_target) # -- Handle the cleanu of the artifact files. if GetOption("clean"): - set_up_cleanup( - env, - [ - report_target, - build_target, - synth_target, - verify_target, - pnr_target, - graph_target, - lint_target, - ], - ) + set_up_cleanup(env) diff --git a/apio/scons/ice40/SConstruct b/apio/scons/ice40/SConstruct index 07f35935..45889cad 100644 --- a/apio/scons/ice40/SConstruct +++ b/apio/scons/ice40/SConstruct @@ -337,9 +337,9 @@ graph_target = env.Alias("graph", graphviz_target) # -- (modules).v -> (testbench).out -> (testbench).vcd -> gtkwave if "sim" in COMMAND_LINE_TARGETS: config = get_sim_config(env, TESTBENCH, synth_srcs) - sim_out_target = env.IVerilogTestbench(config.top_module, config.srcs) + sim_out_target = env.IVerilogTestbench(config.output, config.srcs) vcd_file_target = env.VCD(sim_out_target) - waves_target = make_waves_target(env, vcd_file_target, config.top_module) + waves_target = make_waves_target(env, vcd_file_target, config.output) AlwaysBuild(waves_target) @@ -350,12 +350,12 @@ if "test" in COMMAND_LINE_TARGETS: configs = get_tests_configs(env, TESTBENCH, synth_srcs, test_srcs) tests_targets = [] for config in configs: - test_out_target = env.IVerilogTestbench(config.top_module, config.srcs) + test_out_target = env.IVerilogTestbench(config.output, config.srcs) AlwaysBuild(test_out_target) test_vcd_target = env.VCD(test_out_target) AlwaysBuild(test_vcd_target) test_target = env.Alias( - config.top_module, [test_out_target, test_vcd_target] + config.output, [test_out_target, test_vcd_target] ) tests_targets.append(test_target) @@ -411,16 +411,4 @@ AlwaysBuild(lint_target) # -- Handle the cleanu of the artifact files. if GetOption("clean"): - set_up_cleanup( - env, - [ - report_target, - time_target, - build_target, - synth_target, - verify_target, - pnr_target, - graph_target, - lint_target, - ], - ) + set_up_cleanup(env) diff --git a/apio/scons/scons_util.py b/apio/scons/scons_util.py index 2ea06e24..0cb179b2 100644 --- a/apio/scons/scons_util.py +++ b/apio/scons/scons_util.py @@ -33,9 +33,15 @@ from SCons.Action import FunctionAction, Action from SCons.Builder import Builder +# -- All the build files and other artifcats are created in this this +# -- subdirectory. +BUILD_DIR = "_build" + +# -- A shortcut with '/' or '\' appended to the build dir name. +BUILD_DIR_SEP = BUILD_DIR + os.sep # -- Target name. This is the base file name for various build artifacts. -TARGET = "hardware" +TARGET = BUILD_DIR_SEP + "hardware" SUPPORTED_GRAPH_TYPES = ["svg", "pdf", "png"] @@ -389,8 +395,9 @@ def make_dot_builder( dot_builder = Builder( action=( 'yosys -f verilog -p "show -format dot -colors 1 ' - '-prefix hardware {0}" {1} $SOURCES' + '-prefix {0}hardware {1}" {2} $SOURCES' ).format( + BUILD_DIR_SEP, top_module if top_module else "unknown_top", "" if verbose else "-q", ), @@ -465,7 +472,7 @@ def get_source_files(env: SConsEnvironment) -> Tuple[List[str], List[str]]: class SimulationConfig: """Simulation parameters, for sim and test commands.""" - top_module: str # Top module name of the simulation. + output: str # .out file name, includes build directory.. srcs: List[str] # List of source files to compile. @@ -482,9 +489,9 @@ def get_sim_config( # Construct a SimulationParams with all the synth files + the # testbench file. - top_module = basename(env, testbench) + output = BUILD_DIR_SEP + basename(env, testbench) srcs = synth_srcs + [testbench] - return SimulationConfig(top_module, srcs) + return SimulationConfig(output, srcs) def get_tests_configs( @@ -511,9 +518,9 @@ def get_tests_configs( # Construct a config for each testbench. configs = [] for tb in testbenches: - top_module = basename(env, tb) + output = BUILD_DIR_SEP + basename(env, tb) srcs = synth_srcs + [tb] - configs.append(SimulationConfig(top_module, srcs)) + configs.append(SimulationConfig(output, srcs)) return configs @@ -748,29 +755,23 @@ def wait_for_remote_debugger(env: SConsEnvironment): msg(env, "Remote debugger is attached.", fg="green") -def set_up_cleanup(env: SConsEnvironment, targets) -> None: +def set_up_cleanup(env: SConsEnvironment) -> None: """Should be called only when the "clean" target is specified. Configures - in env the set of targets that should be cleaned up. 'targets' is a list - of top level targets and aliases defined in SConstruct. + in scons env do delete all the files in the build directory. """ # -- Should be called only when the 'clean' target is specified. assert env.GetOption("clean") - # -- Patterns for target files that may not be defined in every invocation - # -- of SConstruct. For example xyz_tb.vcd appears only when simulating - # -- benchmark xyz_tb.vcd. - dynamic_targets = ["*.out", "*.vcd", "zadig.ini"] - - # -- Attach all the existing files that match the dynamic targets to the - # -- first given target (this is an arbitrary choice). - for dynamic_target in dynamic_targets: - for node in env.Glob(dynamic_target): - env.Clean(targets[0], str(node)) + # -- Get the list of all files to clean. Scons adds to the list non + # -- existing files from other targets it encountered. + files_to_clean = env.Glob(f"{BUILD_DIR_SEP}*") + env.Glob("zadig.ini") - # -- Do the same for the apio graph output files. - for graph_type in SUPPORTED_GRAPH_TYPES: - env.Clean(targets[0], TARGET + "." + graph_type) + # -- Create a dummy target. I + dummy_target = env.Command( + "no-such-file-1", "no-such-file-2", "no-such-action" + ) - # -- Tell SCons to cleanup the given targets and all of their dependencies. - env.Default(targets) + # -- Associate all the files with the dummy target. + for file in files_to_clean: + env.Clean(dummy_target, str(file)) From b79551be2cdfeb6830f6fdfeb4457f4fe587382f Mon Sep 17 00:00:00 2001 From: Zapta Date: Wed, 20 Nov 2024 14:31:19 -0800 Subject: [PATCH 20/22] Fixed the path of the .gtkw file file in the gtkwave command. It is a source file and not a build artifact. --- apio/scons/ecp5/SConstruct | 18 ++++++++------ apio/scons/gowin/SConstruct | 18 ++++++++------ apio/scons/ice40/SConstruct | 18 ++++++++------ apio/scons/scons_util.py | 24 +++++++++++-------- .../Alhambra-II/icestudio/ledon/ledon_tb.gtkw | 23 +++++++++++++++--- 5 files changed, 67 insertions(+), 34 deletions(-) diff --git a/apio/scons/ecp5/SConstruct b/apio/scons/ecp5/SConstruct index 35611ff4..b7633c89 100644 --- a/apio/scons/ecp5/SConstruct +++ b/apio/scons/ecp5/SConstruct @@ -316,10 +316,12 @@ graph_target = env.Alias("graph", graphviz_target) # -- Targets. # -- (modules).v -> (testbench).out -> (testbench).vcd -> gtkwave if "sim" in COMMAND_LINE_TARGETS: - config = get_sim_config(env, TESTBENCH, synth_srcs) - sim_out_target = env.IVerilogTestbench(config.output, config.srcs) + sim_config = get_sim_config(env, TESTBENCH, synth_srcs) + sim_out_target = env.IVerilogTestbench( + sim_config.build_testbench_name, sim_config.srcs + ) vcd_file_target = env.VCD(sim_out_target) - waves_target = make_waves_target(env, vcd_file_target, config.output) + waves_target = make_waves_target(env, vcd_file_target, sim_config) AlwaysBuild(waves_target) @@ -327,15 +329,17 @@ if "sim" in COMMAND_LINE_TARGETS: # -- Targets. # -- (modules).v -> (testbenchs).out -> (testbenchs).vcd if "test" in COMMAND_LINE_TARGETS: - configs = get_tests_configs(env, TESTBENCH, synth_srcs, test_srcs) + sim_configs = get_tests_configs(env, TESTBENCH, synth_srcs, test_srcs) tests_targets = [] - for config in configs: - test_out_target = env.IVerilogTestbench(config.output, config.srcs) + for sim_config in sim_configs: + test_out_target = env.IVerilogTestbench( + sim_config.build_testbench_name, sim_config.srcs + ) AlwaysBuild(test_out_target) test_vcd_target = env.VCD(test_out_target) AlwaysBuild(test_vcd_target) test_target = env.Alias( - config.output, [test_out_target, test_vcd_target] + sim_config.build_testbench_name, [test_out_target, test_vcd_target] ) tests_targets.append(test_target) diff --git a/apio/scons/gowin/SConstruct b/apio/scons/gowin/SConstruct index d88b2201..5dc73638 100644 --- a/apio/scons/gowin/SConstruct +++ b/apio/scons/gowin/SConstruct @@ -311,10 +311,12 @@ graph_target = env.Alias("graph", graphviz_target) # -- Targets. # -- (modules).v -> (testbench).out -> (testbench).vcd -> gtkwave if "sim" in COMMAND_LINE_TARGETS: - config = get_sim_config(env, TESTBENCH, synth_srcs) - sim_out_target = env.IVerilogTestbench(config.output, config.srcs) + sim_config = get_sim_config(env, TESTBENCH, synth_srcs) + sim_out_target = env.IVerilogTestbench( + sim_config.build_testbench_name, sim_config.srcs + ) vcd_file_target = env.VCD(sim_out_target) - waves_target = make_waves_target(env, vcd_file_target, config.output) + waves_target = make_waves_target(env, vcd_file_target, sim_config) AlwaysBuild(waves_target) @@ -322,15 +324,17 @@ if "sim" in COMMAND_LINE_TARGETS: # -- Targets. # -- (modules).v -> (testbenchs).out -> (testbenchs).vcd if "test" in COMMAND_LINE_TARGETS: - configs = get_tests_configs(env, TESTBENCH, synth_srcs, test_srcs) + sim_configs = get_tests_configs(env, TESTBENCH, synth_srcs, test_srcs) tests_targets = [] - for config in configs: - test_out_target = env.IVerilogTestbench(config.output, config.srcs) + for sim_config in sim_configs: + test_out_target = env.IVerilogTestbench( + sim_config.build_testbench_name, sim_config.srcs + ) AlwaysBuild(test_out_target) test_vcd_target = env.VCD(test_out_target) AlwaysBuild(test_vcd_target) test_target = env.Alias( - config.output, [test_out_target, test_vcd_target] + sim_config.build_testbench_name, [test_out_target, test_vcd_target] ) tests_targets.append(test_target) diff --git a/apio/scons/ice40/SConstruct b/apio/scons/ice40/SConstruct index 45889cad..205e9bdb 100644 --- a/apio/scons/ice40/SConstruct +++ b/apio/scons/ice40/SConstruct @@ -336,10 +336,12 @@ graph_target = env.Alias("graph", graphviz_target) # -- Targets. # -- (modules).v -> (testbench).out -> (testbench).vcd -> gtkwave if "sim" in COMMAND_LINE_TARGETS: - config = get_sim_config(env, TESTBENCH, synth_srcs) - sim_out_target = env.IVerilogTestbench(config.output, config.srcs) + sim_config = get_sim_config(env, TESTBENCH, synth_srcs) + sim_out_target = env.IVerilogTestbench( + sim_config.build_testbench_name, sim_config.srcs + ) vcd_file_target = env.VCD(sim_out_target) - waves_target = make_waves_target(env, vcd_file_target, config.output) + waves_target = make_waves_target(env, vcd_file_target, sim_config) AlwaysBuild(waves_target) @@ -347,15 +349,17 @@ if "sim" in COMMAND_LINE_TARGETS: # -- Targets. # -- (modules).v -> (testbenchs).out -> (testbenchs).vcd if "test" in COMMAND_LINE_TARGETS: - configs = get_tests_configs(env, TESTBENCH, synth_srcs, test_srcs) + sim_configs = get_tests_configs(env, TESTBENCH, synth_srcs, test_srcs) tests_targets = [] - for config in configs: - test_out_target = env.IVerilogTestbench(config.output, config.srcs) + for sim_config in sim_configs: + test_out_target = env.IVerilogTestbench( + sim_config.build_testbench_name, sim_config.srcs + ) AlwaysBuild(test_out_target) test_vcd_target = env.VCD(test_out_target) AlwaysBuild(test_vcd_target) test_target = env.Alias( - config.output, [test_out_target, test_vcd_target] + sim_config.build_testbench_name, [test_out_target, test_vcd_target] ) tests_targets.append(test_target) diff --git a/apio/scons/scons_util.py b/apio/scons/scons_util.py index 0cb179b2..5951cccd 100644 --- a/apio/scons/scons_util.py +++ b/apio/scons/scons_util.py @@ -472,7 +472,8 @@ def get_source_files(env: SConsEnvironment) -> Tuple[List[str], List[str]]: class SimulationConfig: """Simulation parameters, for sim and test commands.""" - output: str # .out file name, includes build directory.. + testbench_name: str # The testbench name, without the 'v' suffix. + build_testbench_name: str # testbench_name prefixed by build dir. srcs: List[str] # List of source files to compile. @@ -489,9 +490,10 @@ def get_sim_config( # Construct a SimulationParams with all the synth files + the # testbench file. - output = BUILD_DIR_SEP + basename(env, testbench) + testbench_name = basename(env, testbench) + build_testbench_name = BUILD_DIR_SEP + testbench_name srcs = synth_srcs + [testbench] - return SimulationConfig(output, srcs) + return SimulationConfig(testbench_name, build_testbench_name, srcs) def get_tests_configs( @@ -518,9 +520,12 @@ def get_tests_configs( # Construct a config for each testbench. configs = [] for tb in testbenches: - output = BUILD_DIR_SEP + basename(env, tb) + testbench_name = basename(env, tb) + build_testbench_name = BUILD_DIR_SEP + testbench_name srcs = synth_srcs + [tb] - configs.append(SimulationConfig(output, srcs)) + configs.append( + SimulationConfig(testbench_name, build_testbench_name, srcs) + ) return configs @@ -528,12 +533,11 @@ def get_tests_configs( def make_waves_target( env: SConsEnvironment, vcd_file_target: NodeList, - top_module: str, + sim_config: SimulationConfig, ) -> List[Alias]: """Construct a target to launch the QTWave signal viwer. vcd_file_target is - the simulator target that generated the vcd file with the signals. Top - module is to derive the name of the .gtkw which can be used to save the - viewer configuration for future simulations. Returns the new targets. + the simulator target that generated the vcd file with the signals. + Returns the new targets. """ # -- Construct the commands list. @@ -553,7 +557,7 @@ def make_waves_target( "gtkwave {0} {1} {2}.gtkw".format( '--rcvar "splash_disable on" --rcvar "do_initial_zoom_fit 1"', vcd_file_target[0], - top_module, + sim_config.testbench_name, ) ) diff --git a/test-examples/TB/Alhambra-II/icestudio/ledon/ledon_tb.gtkw b/test-examples/TB/Alhambra-II/icestudio/ledon/ledon_tb.gtkw index 868050ff..51d83052 100644 --- a/test-examples/TB/Alhambra-II/icestudio/ledon/ledon_tb.gtkw +++ b/test-examples/TB/Alhambra-II/icestudio/ledon/ledon_tb.gtkw @@ -1,4 +1,21 @@ -[*] Code generated by Icestudio 0.9.2w202204260904 -[*] Thu, 28 Apr 2022 08:36:17 GMT - +[*] +[*] GTKWave Analyzer v3.4.0 (w)1999-2022 BSI +[*] Wed Nov 20 22:19:57 2024 +[*] +[dumpfile] "/Users/user/projects/apio_dev/repo/test-examples/TB/Alhambra-II/icestudio/ledon/_build/ledon_tb.vcd" +[dumpfile_mtime] "Wed Nov 20 22:19:35 2024" +[dumpfile_size] 580 +[savefile] "/Users/user/projects/apio_dev/repo/test-examples/TB/Alhambra-II/icestudio/ledon/ledon_tb.gtkw" +[timestart] 0 +[size] 1000 600 +[pos] -1 -1 +*-14.929331 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 +[sst_width] 253 +[signals_width] 78 +[sst_expanded] 1 +[sst_vpaned_height] 158 +@29 +[color] 1 main_tb.LED +[pattern_trace] 1 +[pattern_trace] 0 From a36cacaa8392343a6500d1c68f14eae68d9c19d4 Mon Sep 17 00:00:00 2001 From: Zapta Date: Wed, 20 Nov 2024 14:55:53 -0800 Subject: [PATCH 21/22] Added in the gtkwave command escaping for windows. --- apio/scons/scons_util.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/apio/scons/scons_util.py b/apio/scons/scons_util.py index 5951cccd..29d59e4b 100644 --- a/apio/scons/scons_util.py +++ b/apio/scons/scons_util.py @@ -598,13 +598,16 @@ def make_iverilog_action( "" if is_windows(env) or not ivl_path else f'-B "{ivl_path}"' ) - # Construct the action string. + # Escaping for windows. '\' -> '\\' + escaped_vcd_output_name = vcd_output_name.replace("\\", "\\\\") + + # -- Construct the action string. action = ( "iverilog {0} {1} -o $TARGET {2} {3} {4} {5} {6} $SOURCES" ).format( ivl_path_param, "-v" if verbose else "", - f"-DVCD_OUTPUT={vcd_output_name}", + f"-DVCD_OUTPUT={escaped_vcd_output_name}", "-DINTERACTIVE_SIM" if is_interactive else "", map_params(env, extra_params, "{}"), map_params(env, lib_dirs, '-I"{}"'), From 076aa8b041ff459e73795ef8d92ac7f845b1a20f Mon Sep 17 00:00:00 2001 From: Zapta Date: Wed, 20 Nov 2024 16:00:40 -0800 Subject: [PATCH 22/22] Split the 'apio boards' command into two commands, 'apio boards' that lists boards and 'apio fpgas' that lists fpgas. Functionality stayed the same. --- apio/__main__.py | 3 +- apio/apio_context.py | 143 --------------------------------- apio/commands/boards.py | 148 ++++++++++++++++++++++++----------- apio/commands/fpgas.py | 109 ++++++++++++++++++++++++++ test/commands/test_boards.py | 11 +-- test/commands/test_fpgas.py | 20 +++++ 6 files changed, 234 insertions(+), 200 deletions(-) create mode 100644 apio/commands/fpgas.py create mode 100644 test/commands/test_fpgas.py diff --git a/apio/__main__.py b/apio/__main__.py index f802297f..f1a8a066 100644 --- a/apio/__main__.py +++ b/apio/__main__.py @@ -39,9 +39,10 @@ ], "Utility commands": [ "boards", + "fpgas", "examples", - "raw", "system", + "raw", "upgrade", ], "Deprecated commands": [ diff --git a/apio/apio_context.py b/apio/apio_context.py index b2eabac6..dcc65365 100644 --- a/apio/apio_context.py +++ b/apio/apio_context.py @@ -19,17 +19,6 @@ from apio.managers.project import Project -# pylint: disable=fixme -# TODO: Rename this file and class to repreent its more general role and the -# main apio data holder. For example ApioContext or ApioEnv. - -# -- Info message -BOARDS_MSG = ( - "\nUse `apio create --board ` to create a new apio " - "project for that board\n" -) - - # ---------- RESOURCES RESOURCES_DIR = "resources" @@ -471,138 +460,6 @@ def get_package_dir(self, package_name: str) -> Path: return package_dir - # R0914: Too many local variables (17/15) - # pylint: disable=R0914 - def list_boards(self): - """Print all the supported boards and the information of - their FPGAs - """ - # Get terminal configuration. It will help us to adapt the format - # to a terminal vs a pipe. - config: util.TerminalConfig = util.get_terminal_config() - - # -- Table title - title = ( - click.style("Board", fg="cyan") + " (FPGA, Arch, Type, Size, Pack)" - ) - - # -- Print the table header for terminal mode. - if config.terminal_mode(): - title = ( - click.style("Board", fg="cyan") - + " (FPGA, Arch, Type, Size, Pack)" - ) - # -- Horizontal line across the terminal. - seperator_line = "─" * config.terminal_width - click.secho(seperator_line) - click.secho(title) - click.secho(seperator_line) - - # -- Sort boards names by case insentive alphabetical order. - board_names = list(self.boards.keys()) - board_names.sort(key=lambda x: x.lower()) - - # -- For a pipe, determine the max example name length. - max_board_name_len = max(len(x) for x in board_names) - - # -- Print all the boards! - for board in board_names: - - # -- Generate the report for a terminal. Color and multi lines - # -- are ok. - - # -- Get board FPGA long name - fpga = self.boards[board]["fpga"] - - # -- Get information about the FPGA - arch = self.fpgas[fpga]["arch"] - type_ = self.fpgas[fpga]["type"] - size = self.fpgas[fpga]["size"] - pack = self.fpgas[fpga]["pack"] - - # -- Print the item with information - # -- Print the Board in a differnt color - - item_fpga = f"(FPGA:{fpga}, {arch}, {type_}, {size}, {pack})" - - if config.terminal_mode(): - # -- Board name with a bullet point and color - board_str = click.style(board, fg="cyan") - item_board = f"• {board_str}" - - # -- Item in one line - one_line_item = f"{item_board} {item_fpga}" - - # -- If there is enough space, print in one line - if len(one_line_item) <= config.terminal_width: - click.secho(one_line_item) - - # -- Not enough space: Print it in two separate lines - else: - two_lines_item = f"{item_board}\n {item_fpga}" - click.secho(two_lines_item) - - else: - # -- Generate the report for a pipe. Single line, no color, no - # -- bullet points. - click.secho(f"{board:<{max_board_name_len}} | {item_fpga}") - - if config.terminal_mode(): - # -- Print the Footer - click.secho(seperator_line) - click.secho(f"Total: {len(self.boards)} boards") - - # -- Help message - click.secho(BOARDS_MSG, fg="green") - - def list_fpgas(self): - """Print all the supported FPGAs""" - - # Get terminal configuration. It will help us to adapt the format - # to a terminal vs a pipe. - config: util.TerminalConfig = util.get_terminal_config() - - if config.terminal_mode(): - # -- Horizontal line across the terminal, - seperator_line = "─" * config.terminal_width - - # -- Table title - fpga_header = click.style(f"{' FPGA':34}", fg="cyan") - title = ( - f"{fpga_header} {'Arch':<10} {'Type':<13}" - f" {'Size':<8} {'Pack'}" - ) - - # -- Print the table header - click.secho(seperator_line) - click.secho(title) - click.secho(seperator_line) - - # -- Print all the fpgas! - for fpga in self.fpgas: - - # -- Get information about the FPGA - arch = self.fpgas[fpga]["arch"] - _type = self.fpgas[fpga]["type"] - size = self.fpgas[fpga]["size"] - pack = self.fpgas[fpga]["pack"] - - # -- Print the item with information - data_str = f"{arch:<10} {_type:<13} {size:<8} {pack}" - if config.terminal_mode(): - # -- For terminal, print the FPGA name in color. - fpga_str = click.style(f"{fpga:32}", fg="cyan") - item = f"• {fpga_str} {data_str}" - click.secho(item) - else: - # -- For pipe, no colors and no bullet point. - click.secho(f"{fpga:32} {data_str}") - - # -- Print the Footer - if config.terminal_mode(): - click.secho(seperator_line) - click.secho(f"Total: {len(self.fpgas)} fpgas\n") - @staticmethod def _determine_platform_id(platforms: Dict[str, Dict]) -> str: """Determines and returns the platform io based on system info and diff --git a/apio/commands/boards.py b/apio/commands/boards.py index efeb9b5a..e572e12d 100644 --- a/apio/commands/boards.py +++ b/apio/commands/boards.py @@ -8,83 +8,137 @@ """Implementation of 'apio boards' command""" from pathlib import Path -from varname import nameof import click from apio.apio_context import ApioContext -from apio import cmd_util +from apio import cmd_util, util from apio.commands import options -# --------------------------- -# -- COMMAND SPECIFIC OPTIONS -# --------------------------- -list_fpgas_option = click.option( - "fpgas", # Var name - "-f", - "--fpga", - is_flag=True, - help="List supported FPGA chips.", - cls=cmd_util.ApioOption, -) + +# ) + + +# R0914: Too many local variables (17/15) +# pylint: disable=R0914 +def list_boards(apio_ctx: ApioContext): + """Prints all the available board definitions.""" + # Get terminal configuration. It will help us to adapt the format + # to a terminal vs a pipe. + config: util.TerminalConfig = util.get_terminal_config() + + # -- Table title + title = click.style("Board", fg="cyan") + " (FPGA, Arch, Type, Size, Pack)" + + # -- Print the table header for terminal mode. + if config.terminal_mode(): + title = ( + click.style("Board", fg="cyan") + " (FPGA, Arch, Type, Size, Pack)" + ) + # -- Horizontal line across the terminal. + seperator_line = "─" * config.terminal_width + click.secho(seperator_line) + click.secho(title) + click.secho(seperator_line) + + # -- Sort boards names by case insentive alphabetical order. + board_names = list(apio_ctx.boards.keys()) + board_names.sort(key=lambda x: x.lower()) + + # -- For a pipe, determine the max example name length. + max_board_name_len = max(len(x) for x in board_names) + + # -- Print all the boards! + for board in board_names: + + # -- Generate the report for a terminal. Color and multi lines + # -- are ok. + + # -- Get board FPGA long name + fpga = apio_ctx.boards[board]["fpga"] + + # -- Get information about the FPGA + arch = apio_ctx.fpgas[fpga]["arch"] + type_ = apio_ctx.fpgas[fpga]["type"] + size = apio_ctx.fpgas[fpga]["size"] + pack = apio_ctx.fpgas[fpga]["pack"] + + # -- Print the item with information + # -- Print the Board in a differnt color + + item_fpga = f"(FPGA:{fpga}, {arch}, {type_}, {size}, {pack})" + + if config.terminal_mode(): + # -- Board name with a bullet point and color + board_str = click.style(board, fg="cyan") + item_board = f"• {board_str}" + + # -- Item in one line + one_line_item = f"{item_board} {item_fpga}" + + # -- If there is enough space, print in one line + if len(one_line_item) <= config.terminal_width: + click.secho(one_line_item) + + # -- Not enough space: Print it in two separate lines + else: + two_lines_item = f"{item_board}\n {item_fpga}" + click.secho(two_lines_item) + + else: + # -- Generate the report for a pipe. Single line, no color, no + # -- bullet points. + click.secho(f"{board:<{max_board_name_len}} | {item_fpga}") + + if config.terminal_mode(): + # -- Print the Footer + click.secho(seperator_line) + click.secho(f"Total: {len(apio_ctx.boards)} boards") + + # -- Help message + # click.secho(BOARDS_MSG, fg="green") # --------------------------- # -- COMMAND # --------------------------- +# R0801: Similar lines in 2 files +# pylint: disable = R0801 HELP = """ -The boards commands lists the FPGA boards and chips that are -supported by apio. +The boards commands lists the FPGA boards that are recongnized by apio. +Custom boards can be defined by placing a custom boards.json file in the +project directory. If such a case, the command +lists the boards from that custom file. + The commands is typically used in the root directory of the project that contains the apio.ini file. \b Examples: - apio boards --list # List boards - apio boards --fpga # List FPGAs - apio boards -l | grep ecp5 # Filter boards results - apio boards -f | grep gowin # Filter FPGA results. - -[Advanced] Boards with wide availability can be added by contacting the -apio team. Custom one-of boards can be added to your project by -placing an alternative boards.json file in your apio project directory. + apio boards # List all boards + apio boards | grep ecp5 # Filter boards results + apio boards --project-dir foo/bar # Use a different + """ @click.command( "boards", - short_help="List supported boards and FPGAs.", + short_help="List available board definitions.", help=HELP, cls=cmd_util.ApioCommand, ) @click.pass_context @options.project_dir_option -@options.list_option_gen(help="List supported FPGA boards.") -@list_fpgas_option def cli( cmd_ctx: click.core.Context, # Options project_dir: Path, - list_: bool, - fpgas: bool, ): - """Implements the 'boards' command which lists supported boards - and FPGAs. - """ + """Implements the 'boards' command which lists available board + definitions.""" - # Make sure these params are exclusive. - cmd_util.check_at_most_one_param(cmd_ctx, nameof(list_, fpgas)) - - # -- Create the apio context. + # -- Create the apio context. If project dir has a boards.json file, + # -- it will be loaded instead of the apio's standard file. apio_ctx = ApioContext(project_dir=project_dir, load_project=False) - # -- Option 1: List boards - if list_: - apio_ctx.list_boards() - cmd_ctx.exit(0) - - # -- Option 2: List fpgas - if fpgas: - apio_ctx.list_fpgas() - cmd_ctx.exit(0) - - # -- No options: show help - click.secho(cmd_ctx.get_help()) + list_boards(apio_ctx) + cmd_ctx.exit(0) diff --git a/apio/commands/fpgas.py b/apio/commands/fpgas.py new file mode 100644 index 00000000..055af503 --- /dev/null +++ b/apio/commands/fpgas.py @@ -0,0 +1,109 @@ +# -*- coding: utf-8 -*- +# -- This file is part of the Apio project +# -- (C) 2016-2024 FPGAwars +# -- Authors +# -- * Jesús Arroyo (2016-2019) +# -- * Juan Gonzalez (obijuan) (2019-2024) +# -- Licence GPLv2 +"""Implementation of 'apio fpgas' command""" + +from pathlib import Path +import click +from apio.apio_context import ApioContext +from apio import cmd_util, util +from apio.commands import options + + +def list_fpgas(apio_ctx: ApioContext): + """Prints all the available FPGA definitions.""" + + # Get terminal configuration. It will help us to adapt the format + # to a terminal vs a pipe. + config: util.TerminalConfig = util.get_terminal_config() + + if config.terminal_mode(): + # -- Horizontal line across the terminal, + seperator_line = "─" * config.terminal_width + + # -- Table title + fpga_header = click.style(f"{' FPGA':34}", fg="cyan") + title = ( + f"{fpga_header} {'Arch':<10} {'Type':<13}" f" {'Size':<8} {'Pack'}" + ) + + # -- Print the table header + click.secho(seperator_line) + click.secho(title) + click.secho(seperator_line) + + # -- Print all the fpgas! + for fpga in apio_ctx.fpgas: + + # -- Get information about the FPGA + arch = apio_ctx.fpgas[fpga]["arch"] + _type = apio_ctx.fpgas[fpga]["type"] + size = apio_ctx.fpgas[fpga]["size"] + pack = apio_ctx.fpgas[fpga]["pack"] + + # -- Print the item with information + data_str = f"{arch:<10} {_type:<13} {size:<8} {pack}" + if config.terminal_mode(): + # -- For terminal, print the FPGA name in color. + fpga_str = click.style(f"{fpga:32}", fg="cyan") + item = f"• {fpga_str} {data_str}" + click.secho(item) + else: + # -- For pipe, no colors and no bullet point. + click.secho(f"{fpga:32} {data_str}") + + # -- Print the Footer + if config.terminal_mode(): + click.secho(seperator_line) + click.secho(f"Total: {len(apio_ctx.fpgas)} fpgas\n") + + +# --------------------------- +# -- COMMAND +# --------------------------- +# R0801: Similar lines in 2 files +# pylint: disable = R0801 +HELP = """ +The fpgas commands lists the FPGA that are recongnized by apio. +Custom FPGAS that are supported by the underlying Yosys tools chain can be +defined by placing a custom fpgas.json file in the +project directory. If such a case, the command +lists the fpgas from that custom file. +The commands is typically used in the root directory +of the project that contains the apio.ini file. + +\b +Examples: + apio fpgas # List all fpgas + apio fpgas | grep gowin # Filter FPGA results. + +""" + + +@click.command( + "fpgas", + short_help="List available FPGA definitions.", + help=HELP, + cls=cmd_util.ApioCommand, +) +@click.pass_context +@options.project_dir_option +def cli( + cmd_ctx: click.core.Context, + # Options + project_dir: Path, +): + """Implements the 'fpgas' command which lists available fpga + definitions. + """ + + # -- Create the apio context. If project dir has a fpgas.json file, + # -- it will be loaded instead of the apio's standard file. + apio_ctx = ApioContext(project_dir=project_dir, load_project=False) + + list_fpgas(apio_ctx) + cmd_ctx.exit(0) diff --git a/test/commands/test_boards.py b/test/commands/test_boards.py index f0dee0d6..1a534afb 100644 --- a/test/commands/test_boards.py +++ b/test/commands/test_boards.py @@ -7,7 +7,7 @@ def test_boards(clirunner, configenv, validate_cliresult): - """Test "apio boards" with different parameters""" + """Test "apio boards" command.""" with clirunner.isolated_filesystem(): @@ -17,11 +17,4 @@ def test_boards(clirunner, configenv, validate_cliresult): # -- Execute "apio boards" result = clirunner.invoke(cmd_boards) validate_cliresult(result) - - # -- Execute "apio boards --list" - result = clirunner.invoke(cmd_boards, ["--list"]) - validate_cliresult(result) - - # -- Execute "apio boards --fpga" - result = clirunner.invoke(cmd_boards, ["--fpga"]) - validate_cliresult(result) + assert "alhambra-ii" in result.output diff --git a/test/commands/test_fpgas.py b/test/commands/test_fpgas.py new file mode 100644 index 00000000..3e838342 --- /dev/null +++ b/test/commands/test_fpgas.py @@ -0,0 +1,20 @@ +""" + Test for the "apio boards" command +""" + +# -- apio fpgas entry point +from apio.commands.fpgas import cli as cmd_fpgas + + +def test_boards(clirunner, configenv, validate_cliresult): + """Test "apio fpgas" command.""" + + with clirunner.isolated_filesystem(): + + # -- Config the environment (conftest.configenv()) + configenv() + + # -- Execute "apio fpgas" + result = clirunner.invoke(cmd_fpgas) + validate_cliresult(result) + assert "iCE40-HX4K-TQ144" in result.output