Skip to content

Commit

Permalink
pw_watch: Remove deps to enable running watch in other environments
Browse files Browse the repository at this point in the history
- Remove pw_build dependency from the pw_watch library. pw_build
  transitively depends on pigweed.json, which means pw_watch cannot run
  unless projects have a pigweed.json integrated into their build.
  Remove pw_build and other deps from core pw_watch functionality.
- Move UI code into pw_watch/run.py to simplify things and remove a
  dependency on pw_presubmit, which transitively depends on pw_build.

Change-Id: Ie4a2728fac546500905973bd496d47c9fad1729c
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/259693
Docs-Not-Needed: Wyatt Hepler <[email protected]>
Presubmit-Verified: CQ Bot Account <[email protected]>
Commit-Queue: Auto-Submit <[email protected]>
Reviewed-by: Dave Roth <[email protected]>
Pigweed-Auto-Submit: Wyatt Hepler <[email protected]>
Lint: Lint 🤖 <[email protected]>
  • Loading branch information
255 authored and CQ Bot Account committed Jan 11, 2025
1 parent dfc7184 commit 6465286
Show file tree
Hide file tree
Showing 5 changed files with 68 additions and 51 deletions.
24 changes: 10 additions & 14 deletions pw_presubmit/py/pw_presubmit/presubmit.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ def run_the_build():

_LEFT = 7
_RIGHT = 11
BOX_CENTER_WIDTH = WIDTH - _LEFT - _RIGHT - 4
_CENTER = WIDTH - _LEFT - _RIGHT - 4


def _title(msg, style=_SUMMARY_BOX) -> str:
Expand All @@ -120,7 +120,7 @@ def _box(
section1=left + ('' if left.endswith(' ') else ' '),
width1=_LEFT,
section2=' ' + middle,
width2=BOX_CENTER_WIDTH,
width2=_CENTER,
section3=right + ' ',
width3=_RIGHT,
)
Expand All @@ -147,12 +147,14 @@ def color(value):
return padding + color(self.value) + padding


def step_header(left: str, middle: str, right: str) -> str:
return _box(_CHECK_UPPER, left, middle, right)
def _step_header(count: int, total: int, name: str, num_paths: int) -> str:
return _box(
_CHECK_UPPER, f'{count}/{total}', name, plural(num_paths, "file")
)


def step_footer(left: str, middle: str, right: str) -> str:
return _box(_CHECK_LOWER, left, middle, right)
def _step_footer(result: PresubmitResult, name: str, timestamp: str) -> str:
return _box(_CHECK_LOWER, result.colorized(_LEFT), name, timestamp)


class Program(collections.abc.Sequence):
Expand Down Expand Up @@ -921,13 +923,7 @@ def run(
) -> PresubmitResult:
"""Runs the presubmit check on the provided paths."""

_print_ui(
step_header(
f'{count}/{total}',
self.name,
plural(ctx.paths, "file"),
)
)
_print_ui(_step_header(count, total, self.name, len(ctx.paths)))

substep_part = f'.{substep}' if substep else ''
_LOG.debug(
Expand All @@ -951,7 +947,7 @@ def run(
if ctx.dry_run:
log_check_traces(ctx)

_print_ui(step_footer(result.colorized(_LEFT), self.name, time_str))
_print_ui(_step_footer(result, self.name, time_str))
_LOG.debug('%s duration:%s', self.name, time_str)

return result
Expand Down
19 changes: 11 additions & 8 deletions pw_watch/py/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -27,24 +27,27 @@ py_library(
"pw_watch/common.py",
"pw_watch/debounce.py",
"pw_watch/run.py",
"pw_watch/watch.py",
"pw_watch/watch_app.py",
],
imports = ["."],
deps = [
"//pw_build/py:pw_project_builder",
"//pw_cli/py:pw_cli",
"//pw_config_loader/py:pw_config_loader",
"//pw_console/py:pw_console",
"@python_packages//prompt_toolkit",
"@python_packages//watchdog",
],
)

pw_py_binary(
name = "watch",
srcs = ["pw_watch/watch.py"],
deps = [":pw_watch"],
srcs = [
"pw_watch/watch.py",
"pw_watch/watch_app.py",
],
deps = [
":pw_watch",
"//pw_build/py:pw_project_builder",
"//pw_config_loader/py:pw_config_loader",
"//pw_console/py:pw_console",
"@python_packages//prompt_toolkit",
],
)

pw_py_binary(
Expand Down
32 changes: 16 additions & 16 deletions pw_watch/py/pw_watch/debounce.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,9 @@
import enum
import logging
import threading
from typing import Any, Callable
from abc import ABC, abstractmethod

from pw_build.project_builder_context import get_project_builder_context

BUILDER_CONTEXT = get_project_builder_context()

_LOG = logging.getLogger('pw_build.watch')


Expand Down Expand Up @@ -60,12 +57,25 @@ class State(enum.Enum):
RERUN = 6 # ------- Transistions to: IDLE (but triggers a press)


def _flush_and_log(event_description: str) -> None:
# Surround the error message with newlines to make it stand out.
print('\n', flush=True)
_LOG.warning('Event while running: %s', event_description)
print('', flush=True)


class Debouncer:
"""Run an interruptable, cancellable function with debouncing"""

def __init__(self, function: DebouncedFunction) -> None:
def __init__(
self,
function: DebouncedFunction,
*,
log_event: Callable[[str], Any] = _flush_and_log,
) -> None:
super().__init__()
self.function = function
self._log_event = log_event

self.state = State.IDLE

Expand Down Expand Up @@ -101,17 +111,7 @@ def _press_unlocked(self, event_description: str) -> None:
# When the function is already running but we get an incoming
# event, go into the INTERRUPTED state to signal that we should
# re-try running afterwards.
error_message = ['Event while running: %s', event_description]
if BUILDER_CONTEXT.using_progress_bars():
_LOG.warning(*error_message)
else:
# Push an empty line to flush ongoing I/O in subprocess.
print('')

# Surround the error message with newlines to make it stand out.
print('')
_LOG.warning(*error_message)
print('')
self._log_event(event_description)

self.function.cancel()
self._transition(State.INTERRUPTED)
Expand Down
33 changes: 21 additions & 12 deletions pw_watch/py/pw_watch/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
.. code-block:: sh
# Run a `cowsay`, then `cowthink`, when files change.
# Run `cowsay` then `cowthink` when watched files change.
run.py cowsay "Hey, how are you?" , cowthink Not in moood to talk
The Bazel build's ``//pw_watch/py:bazel`` watch entrypoint invokes
Expand All @@ -53,14 +53,25 @@
import pw_cli.env
import pw_cli.log
from pw_cli.plural import plural
from pw_presubmit import presubmit

from pw_watch import common
from pw_watch.debounce import DebouncedFunction, Debouncer

_LOG = logging.getLogger('pw_watch')
_COLOR = pw_cli.color.colors()

_WIDTH = 80
_STEP_START = '━' * (_WIDTH - 1) + '┓'
_STEP_FINISH = '━' * (_WIDTH - 1) + '┛'


def _format_time(time_s: float) -> str:
minutes, seconds = divmod(time_s, 60)
if minutes < 60:
return f' {int(minutes)}:{seconds:04.1f}'
hours, minutes = divmod(minutes, 60)
return f'{int(hours):d}:{int(minutes):02}:{int(seconds):02}'


class Watcher(FileSystemEventHandler, DebouncedFunction):
"""Process filesystem events and run commands on changes."""
Expand Down Expand Up @@ -114,9 +125,9 @@ def run(self) -> None:
print('\033c', end='', flush=True) # clear the screen

for i, command in enumerate(self.commands, 1):
count = f'{i}/{len(self.commands)}'
count = f' {i}/{len(self.commands)} '
print(
presubmit.step_header(count, shlex.join(command), ''),
f'{_STEP_START}\n{count}{shlex.join(command)}\n',
flush=True,
)
start = time.time()
Expand All @@ -125,19 +136,17 @@ def run(self) -> None:

if code == 0:
result = _COLOR.bold_green('PASSED')
msg = ''
else:
result = f'{_COLOR.bold_red("FAILED")} with exit code {code}'
result = _COLOR.bold_red('FAILED')
msg = f' with exit code {code}'

remaining_width = _WIDTH - len(count) - 6 - len(msg) - 2
timestamp = _format_time(total_time).rjust(remaining_width)
print(
presubmit.step_footer(
count,
# Pad the result to account for the ANSI escape codes.
result.ljust(presubmit.BOX_CENTER_WIDTH + 13),
presubmit.format_time(total_time),
),
f'\n{count}{result}{msg}{timestamp}\n{_STEP_FINISH}\n',
flush=True,
)
print(flush=True)

if code and not self.keep_going and i < len(self.commands):
_LOG.info(
Expand Down
11 changes: 10 additions & 1 deletion pw_watch/py/pw_watch/watch.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,15 @@
BUILDER_CONTEXT = get_project_builder_context()


def _log_event(event_description: str) -> None:
if BUILDER_CONTEXT.using_progress_bars():
_LOG.warning('Event while running: %s', event_description)
else:
print('\n')
_LOG.warning('Event while running: %s', event_description)
print('')


class PigweedBuildWatcher(FileSystemEventHandler, DebouncedFunction):
"""Process filesystem events and launch builds if necessary."""

Expand Down Expand Up @@ -125,7 +134,7 @@ def __init__( # pylint: disable=too-many-arguments
if self.parallel_workers > 1:
self.separate_logfiles = True

self.debouncer = Debouncer(self)
self.debouncer = Debouncer(self, log_event=_log_event)

# Track state of a build. These need to be members instead of locals
# due to the split between dispatch(), run(), and on_complete().
Expand Down

0 comments on commit 6465286

Please sign in to comment.