Skip to content

Commit

Permalink
Merge pull request #441 from zapta/develop
Browse files Browse the repository at this point in the history
Updated the developement status of apio from Alpha to Production/Stable
  • Loading branch information
Obijuan authored Oct 14, 2024
2 parents da8903a + 3938ccf commit 5c47620
Show file tree
Hide file tree
Showing 5 changed files with 111 additions and 40 deletions.
4 changes: 4 additions & 0 deletions apio/managers/scons.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,10 @@ def wrapper(*args, **kwargs):
try:
return function(*args, **kwargs)
except Exception as exc:
# For debugging. Uncomment to print the exception's stack.
# import traceback
# traceback.print_tb(exc.__traceback__)

if str(exc):
click.secho("Error: " + str(exc), fg="red")
return exit_code
Expand Down
132 changes: 98 additions & 34 deletions apio/managers/scons_filter.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,15 +42,15 @@ class RangeEvents(Enum):
END_AFTER = 4 # Range ends, after the current line.


class SectionDetector:
"""Base classifier of a range of lines within the sequence of stdout/err
class RangeDetector:
"""Base detector of a range of lines within the sequence of stdout/err
lines recieves from the scons subprocess."""

def __init__(self):
self._in_range = False

def update(self, pipe_id: PipeId, line: str) -> bool:
"""Updates the section classifier with the next stdout/err line.
"""Updates the range detector with the next stdout/err line.
return True iff detector classified this line to be within a range."""

prev_state = self._in_range
Expand Down Expand Up @@ -85,11 +85,12 @@ def classify_line(
raise NotImplementedError("Should be implemented by a subclass")


class PnrSectionDetector(SectionDetector):
"""Implements a RangeDetector for the nextpnr command verbose log lines."""
class PnrRangeDetector(RangeDetector):
"""Implements a RangeDetector for the nextpnr command verbose
log lines."""

def classify_line(self, pipe_id: PipeId, line: str) -> RangeEvents:
# -- Brek line into words.
# -- Break line into words.
tokens = line.split()

# -- Range start: A nextpnr command on stdout without
Expand All @@ -108,14 +109,38 @@ def classify_line(self, pipe_id: PipeId, line: str) -> RangeEvents:
return None


class IceProgRangeDetector(RangeDetector):
"""Implements a RangeDetector for the iceprog command output."""

def __init__(self):
super().__init__()
# -- Indicates if the last line should be erased before printing the
# -- next one. This happens with interactive progress meters.
self.pending_erasure = False

def classify_line(self, pipe_id: PipeId, line: str) -> RangeEvents:
# -- Range start: A nextpnr command on stdout without
# -- the -q (quiet) flag.
if pipe_id == PipeId.STDOUT and line.startswith("iceprog"):
self.pending_erasure = False
return RangeEvents.START_AFTER

# Range end: The end message of nextnpr.
if pipe_id == PipeId.STDERR and line.startswith("Bye."):
return RangeEvents.END_AFTER

return None


class SconsFilter:
"""Implements the filtering and printing of the stdout/err streams of the
scons subprocess. Accepts a line one at a time, detects lines ranges of
intereset, mutates and colors the lines where applicable, and print to
stdout."""

def __init__(self):
self._pnr_detector = PnrSectionDetector()
self._pnr_detector = PnrRangeDetector()
self._iceprog_detector = IceProgRangeDetector()

def on_stdout_line(self, line: str) -> None:
"""Stdout pipe calls this on each line."""
Expand Down Expand Up @@ -150,8 +175,9 @@ def on_line(self, pipe_id: PipeId, line: str) -> None:
from other programs. See the PNR detector for an example.
"""

# -- Update the classifiers
# -- Update the range detectors.
in_pnr_verbose_range = self._pnr_detector.update(pipe_id, line)
in_iceprog_range = self._iceprog_detector.update(pipe_id, line)

# -- Handle the line while in the nextpnr verbose log range.
if pipe_id == PipeId.STDERR and in_pnr_verbose_range:
Expand All @@ -164,22 +190,75 @@ def on_line(self, pipe_id: PipeId, line: str) -> None:
# -- Assign line color.
line_color = self._assign_line_color(
line.lower(),
{
[
(r"^warning:", "yellow"),
(r"^error:", "red"),
},
],
)
click.secho(f"{line}", fg=line_color)
return

# -- Special handling for iceprog line range.
if pipe_id == PipeId.STDERR and in_iceprog_range:
# -- Iceprog prints blank likes that are used as line erasers.
# -- We don't need them here.
if len(line) == 0:
return

# -- If the last iceprog line was a to-be-erased line, erase it
# -- now and clear the flag.
if self._iceprog_detector.pending_erasure:
print(
CURSOR_UP + ERASE_LINE,
end="",
flush=True,
)
self._iceprog_detector.pending_erasure = False

# -- Determine if the current line should be erased before we will
# -- print the next line.
# --
# -- Match outputs like these "addr 0x001400 3%"
# -- Regular expression remainder:
# -- ^ --> Match the begining of the line
# -- \s --> Match one blank space
# -- [0-9A-F]+ one or more hexadecimal digit
# -- \d{1,2} one or two decimal digits
pattern = r"^addr\s0x[0-9A-F]+\s+\d{1,2}%"

# -- Calculate if there is a match!
match = re.search(pattern, line)

# -- If the line is to be erased set the flag.
if match:
self._iceprog_detector.pending_erasure = True

# -- Determine line color by its content and print it.
line_color = self._assign_line_color(
line,
[
(r"^done.", "green"),
(r"^VERIFY OK", "green"),
],
)
click.secho(line, fg=line_color)
return

# -- Special handling for Fumo lines.
if pipe_id == PipeId.STDOUT:
pattern_fomu = r"^Download\s*\[=*"
match = re.search(pattern_fomu, line)
if match:
# -- Delete the previous line
#
# -- NOTE: If the progress line will scroll instead of
# -- overwriting each other, try to add erasure of a second
# -- line. This is due to the commit below which restored
# -- empty lines.
# - Commit 93fc9bc4f3bfd21568e2d66f11976831467e3b97.
#
print(CURSOR_UP + ERASE_LINE, end="", flush=True)
click.secho(f"{line}", fg="green")
click.secho(line, fg="green")
return

# -- Special handling for tinyprog lines.
Expand All @@ -198,29 +277,14 @@ def on_line(self, pipe_id: PipeId, line: str) -> None:
# -- Match all the progress bar lines except the
# -- initial one (when it is 0%)
if match_tinyprog and " 0%|" not in line:
# -- Delete the previous line
print(CURSOR_UP + ERASE_LINE, end="", flush=True)
click.secho(f"{line}")
return

# -- Special handling for iceprog lines.
if pipe_id == PipeId.STDERR:
# -- Match outputs like these "addr 0x001400 3%"
# -- Regular expression remainder:
# -- ^ --> Match the begining of the line
# -- \s --> Match one blank space
# -- [0-9A-F]+ one or more hexadecimal digit
# -- \d{1,2} one or two decimal digits
pattern = r"^addr\s0x[0-9A-F]+\s+\d{1,2}%"

# -- Calculate if there is a match!
match = re.search(pattern, line)

# -- It is a match! (iceprog is running!)
# -- (or if it is the end of the writing!)
# -- (or if it is the end of verifying!)
if match or "done." in line or "VERIFY OK" in line:
# -- Delete the previous line
# -- Delete the previous line.
#
# -- NOTE: If the progress line will scroll instead of
# -- overwriting each other, try to add erasure of a second
# -- line. This is due to the commit below which restored
# -- empty lines.
# - Commit 93fc9bc4f3bfd21568e2d66f11976831467e3b97.
#
print(CURSOR_UP + ERASE_LINE, end="", flush=True)
click.secho(line)
return
Expand Down
4 changes: 2 additions & 2 deletions apio/managers/system.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ def get_usb_devices(self) -> list:
# -- Get the list of the usb devices. It is read
# -- from the command stdout
# -- Ex: [{'hwid':'1d6b:0003'}, {'hwid':'04f2:b68b'}...]
usb_devices = self._parse_usb_devices(result["out"])
usb_devices = self._parse_usb_devices(result.out_text)

# -- Return the devices
return usb_devices
Expand Down Expand Up @@ -130,7 +130,7 @@ def get_ftdi_devices(self) -> list:
# -- from the command stdout
# -- Ex: [{'index': '0', 'manufacturer': 'AlhambraBits',
# -- 'description': 'Alhambra II v1.0A - B07-095'}]
ftdi_devices = self._parse_ftdi_devices(result["out"])
ftdi_devices = self._parse_ftdi_devices(result.out_text)

# -- Return the devices
return ftdi_devices
Expand Down
4 changes: 2 additions & 2 deletions apio/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -686,14 +686,14 @@ def exec_command(*args, **kwargs) -> CommandResult:
if isinstance(pipe, AsyncPipe):
lines = pipe.get_buffer()
text = "\n".join(lines)
out_text = text.strip()
out_text = text

# -- If stderr pipe is an AsyncPipe, extract its text.
pipe = flags["stderr"]
if isinstance(pipe, AsyncPipe):
lines = pipe.get_buffer()
text = "\n".join(lines)
err_text = text.strip()
err_text = text

# -- All done.
result = CommandResult(out_text, err_text, exit_code)
Expand Down
7 changes: 5 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,14 @@ author = "Jesus Arroyo"
author-email = "[email protected] "
home-page = "https://github.com/FPGAwars/apio"
classifiers=[
'Development Status :: 3 - Alpha',
'Development Status :: 5 - Production/Stable',
'Environment :: Console',
'Intended Audience :: Developers',
'License :: OSI Approved :: GNU General Public License v2 (GPLv2)',
'Programming Language :: Python']
'Programming Language :: Python',
'Natural Language :: English',
'Topic :: Scientific/Engineering :: Electronic Design Automation (EDA)',
]
description-file = "README.md"
requires-python = ">=3.9"
requires = [
Expand Down

0 comments on commit 5c47620

Please sign in to comment.