Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ApiContext, scons arguments, Modify command, and _build directory. #469

Merged
merged 22 commits into from
Nov 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
ab86e79
Renamed the click context variables from 'ctx' to 'cmd_ctx' as a prep…
zapta Nov 18, 2024
1d58a9d
Renamed Resources to ApiContext and resources to api_context. Will
zapta Nov 18, 2024
38ac5f7
Renamed resources.py to apio_context.py. No behavior change.
zapta Nov 18, 2024
a27b645
Now requiring the directory set by the environment variable APIO_PACK…
zapta Nov 18, 2024
3bb7c9c
Simplified the format of the active env options message.
zapta Nov 18, 2024
09d4de0
Moved the apio_ctx to be the first argument in calles.
zapta Nov 18, 2024
ca3baa8
Renamed arguments.py to scons_args.py.
zapta Nov 18, 2024
5952d8d
Flatted the scons verbose_all, verbose_yosys, and verbose_pnr args.
zapta Nov 19, 2024
d9a3a25
Cleaned up the code of scons_args.py. Logic stayed the same.
zapta Nov 20, 2024
ea9c6a5
Renamed variables to reflect association with VERILATOR and Tweaked an
zapta Nov 20, 2024
5ef7454
Migrated the lint command to use the standard scons args processing.
zapta Nov 20, 2024
db92e4d
In scons_args.py, renamed 'args' to 'seed_args' and 'new_args' to 'ar…
zapta Nov 20, 2024
a3eb5d0
Restructured the scons process_arguments() function. It is now implem…
zapta Nov 20, 2024
f5194eb
Now the Project object is also included in the ApiContext. It's loading
zapta Nov 20, 2024
e5b6df5
Renamed 'project_scope' to 'load_project' and add to ApioContext
zapta Nov 20, 2024
683c66d
'apio system --info' now prints also the current apio version.
zapta Nov 20, 2024
91f6d7b
Marked the 'apio modify' command as depreated. No change in functiona…
zapta Nov 20, 2024
1209e2c
Added to the alhambra ledon example a missing 'top-module' flag in ap…
zapta Nov 20, 2024
cad2631
All build and other artrifact files are now stored in a subdirectory
zapta Nov 20, 2024
b79551b
Fixed the path of the .gtkw file file in the gtkwave command. It is
zapta Nov 20, 2024
a36caca
Added in the gtkwave command escaping for windows.
zapta Nov 20, 2024
076aa8b
Split the 'apio boards' command into two commands, 'apio boards' that
zapta Nov 21, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ examples/
ice-build/
work/
test-colorlight-v61.ice
_build
hardware.json
hardware.out
hardware.vlt
Expand Down
22 changes: 10 additions & 12 deletions apio/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
import string
import re
from typing import List
from click.core import Context
import click
from apio import util

Expand All @@ -35,18 +34,19 @@
],
"Setup commands": [
"create",
"modify",
"packages",
"drivers",
],
"Utility commands": [
"boards",
"fpgas",
"examples",
"raw",
"system",
"raw",
"upgrade",
],
"Deprecated commands": [
"modify",
"time",
"verify",
"install",
Expand Down Expand Up @@ -95,8 +95,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")

Expand Down Expand Up @@ -155,7 +153,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
Expand All @@ -178,7 +176,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
Expand All @@ -200,7 +198,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.
Expand Down Expand Up @@ -248,15 +246,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}")
214 changes: 45 additions & 169 deletions apio/resources.py → apio/apio_context.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""Resources module"""
"""The apio context."""

# -*- coding: utf-8 -*-
# -- This file is part of the Apio project
Expand All @@ -16,17 +16,7 @@
import click
from apio import util, env_options
from apio.profile import Profile


# 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 <boardname>` to create a new apio "
"project for that board\n"
)
from apio.managers.project import Project


# ---------- RESOURCES
Expand Down Expand Up @@ -75,32 +65,35 @@


# 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,
*,
project_scope: bool,
load_project: 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
boards.json should be loaded, if available' or that the global
default resources should be used instead. Some commands such as
'apio packages' uses the global scope while commands such as
'apio build' use the project scope.
"""Initializes the ApioContext object. 'project dir' is an optional
path to the project dir, otherwise, the current directory is used.
'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
# -- that modify its default behavior.
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",
)

# -- 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)

Expand All @@ -117,26 +110,23 @@ 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(
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
Expand All @@ -155,6 +145,24 @@ def __init__(
sorted(self.fpgas.items(), key=lambda t: t[0])
)

# -- 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
* INPUTS:
Expand Down Expand Up @@ -292,14 +300,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
)

Expand Down Expand Up @@ -452,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
Expand All @@ -593,7 +469,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.
Expand Down
Loading