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

make: print spinning icon while flashing/resetting #15970

Merged
merged 5 commits into from
Feb 25, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
7 changes: 5 additions & 2 deletions Makefile.include
Original file line number Diff line number Diff line change
Expand Up @@ -709,10 +709,13 @@ distclean:
-@for i in $(USEPKG) ; do "$(MAKE)" -C $(RIOTPKG)/$$i distclean ; done
-@rm -rf $(BINDIRBASE)

# Include PROGRAMMER_FLASH/PROGRAMMER_RESET variables
include $(RIOTMAKE)/tools/programmer.inc.mk
aabadie marked this conversation as resolved.
Show resolved Hide resolved

# Define flash-recipe with a default value
define default-flash-recipe
$(call check_cmd,$(FLASHER),Flash program)
$(FLASHER) $(FFLAGS)
$(PROGRAMMER_FLASH)
endef
flash-recipe ?= $(default-flash-recipe)

Expand Down Expand Up @@ -774,7 +777,7 @@ endif

reset:
$(call check_cmd,$(RESET),Reset program)
$(RESET) $(RESET_FLAGS)
$(PROGRAMMER_RESET)

# tests related targets and variables
include $(RIOTMAKE)/tests/tests.inc.mk
Expand Down
13 changes: 5 additions & 8 deletions dist/testbed-support/makefile.iotlab.single.inc.mk
Original file line number Diff line number Diff line change
Expand Up @@ -172,17 +172,14 @@ info-iotlab-node:

# Configure FLASHER, RESET, TERMPROG depending on BOARD and if on frontend

# Command to check if 'stdin' is 0. Cannot use 'cmp - <(echo 0)' without bash shell
_STDIN_EQ_0 = grep 0

ifneq (iotlab-a8-m3,$(BOARD))

# M3 nodes
FLASHER = iotlab-node
RESET = iotlab-node
_NODE_FMT = --jmespath='keys(@)[0]' --format='int'
FFLAGS = $(_NODE_FMT) $(_IOTLAB_EXP_ID) $(_IOTLAB_NODELIST) $(_NODES_FLASH_OPTION) $(FLASHFILE) | $(_STDIN_EQ_0)
RESET_FLAGS = $(_NODE_FMT) $(_IOTLAB_EXP_ID) $(_IOTLAB_NODELIST) --reset | $(_STDIN_EQ_0)
_NODE_FMT = --jmespath='keys(@)[0]' --format='lambda ret: exit(int(ret))'
FFLAGS = $(_NODE_FMT) $(_IOTLAB_EXP_ID) $(_IOTLAB_NODELIST) $(_NODES_FLASH_OPTION) $(FLASHFILE)
RESET_FLAGS = $(_NODE_FMT) $(_IOTLAB_EXP_ID) $(_IOTLAB_NODELIST) --reset

ifeq (,$(_IOTLAB_ON_FRONTEND))
TERMPROG = ssh
Expand All @@ -198,8 +195,8 @@ else
FLASHER = iotlab-ssh
RESET = iotlab-ssh
_NODE_FMT = --jmespath='keys(values(@)[0])[0]' --fmt='int'
FFLAGS = $(_NODE_FMT) $(_IOTLAB_EXP_ID) flash-m3 $(_IOTLAB_NODELIST) $(FLASHFILE) | $(_STDIN_EQ_0)
RESET_FLAGS = $(_NODE_FMT) $(_IOTLAB_EXP_ID) reset-m3 $(_IOTLAB_NODELIST) | $(_STDIN_EQ_0)
FFLAGS = $(_NODE_FMT) $(_IOTLAB_EXP_ID) flash-m3 $(_IOTLAB_NODELIST) $(FLASHFILE)
RESET_FLAGS = $(_NODE_FMT) $(_IOTLAB_EXP_ID) reset-m3 $(_IOTLAB_NODELIST)

TERMPROG = ssh
ifeq (,$(_IOTLAB_ON_FRONTEND))
Expand Down
115 changes: 115 additions & 0 deletions dist/tools/programmer/programmer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
#!/usr/bin/env python3

# Copyright (C) 2021 Inria
#
# This file is subject to the terms and conditions of the GNU Lesser
# General Public License v2.1. See the file LICENSE in the top level
# directory for more details.

import sys
import time
import shlex
import subprocess
import argparse

from contextlib import contextmanager


SUCCESS = "\033[32;1m✓\033[0m"
FAILED = "\033[31;1m×\033[0m"
SPIN = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]


class Programmer:

@contextmanager
def spawn_process(self):
"""Yield a subprocess running in background."""
kwargs = {} if self.verbose else {
"stdout": subprocess.PIPE,
"stderr": subprocess.STDOUT
}
yield subprocess.Popen(shlex.split(self.cmd), **kwargs)

def spin(self, process):
"""Print a spinning icon while programmer process is running."""
while process.poll() is None:
for index in range(len(SPIN)):
sys.stdout.write(
"\r \033[36;1m{}\033[0m {} in progress "
"(programmer: '{}')"
.format(SPIN[index], self.action, self.programmer)
)
sys.stdout.flush()
time.sleep(0.1)

def print_status(self, process, elapsed):
"""Print status of background programmer process."""
print(
"\r \u001b[2K{} {} {} (programmer: '{}' - duration: {:0.2f}s)"
benpicco marked this conversation as resolved.
Show resolved Hide resolved
.format(
FAILED if process.returncode != 0 else SUCCESS,
self.action,
"failed!" if process.returncode != 0 else "done!",
self.programmer,
elapsed
)
)
# Print content of stdout (which also contain stderr) when the
# subprocess failed
if process.returncode != 0:
print(process.stdout.read().decode())
else:
print(
"(for full programmer output add PROGRAMMER_QUIET=0 or "
"QUIET=0 to the make command line)"
)

def run(self):
"""Run the programmer in a background process."""
if not self.cmd.strip():
# Do nothing if programmer command is empty
return 0

if self.verbose:
print(self.cmd)
start = time.time()
with self.spawn_process() as proc:
try:
if self.verbose:
proc.communicate()
else:
self.spin(proc)
except KeyboardInterrupt:
proc.terminate()
proc.kill()
elapsed = time.time() - start
if not self.verbose:
# When using the spinning icon, print the programmer status
self.print_status(proc, elapsed)

return proc.returncode


def main(parser):
"""Main function."""
programmer = Programmer()
parser.parse_args(namespace=programmer)
# Return with same return code as subprocess
sys.exit(programmer.run())


def parser():
"""Return an argument parser."""
parser = argparse.ArgumentParser()
parser.add_argument("--action", help="Programmer action")
parser.add_argument("--cmd", help="Programmer command")
parser.add_argument("--programmer", help="Programmer")
parser.add_argument(
"--verbose", action='store_true', default=False, help="Verbose output"
)
return parser


if __name__ == "__main__":
main(parser())
2 changes: 1 addition & 1 deletion makefiles/boot/riotboot.mk
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ riotboot/bootloader/%: $(BUILDDEPS)
QUIET=$(QUIET) PATH="$(PATH)"\
EXTERNAL_BOARD_DIRS="$(EXTERNAL_BOARD_DIRS)" BOARD=$(BOARD)\
DEBUG_ADAPTER_ID=$(DEBUG_ADAPTER_ID) \
PROGRAMMER=$(PROGRAMMER) \
PROGRAMMER=$(PROGRAMMER) PROGRAMMER_QUIET=$(PROGRAMMER_QUIET) \
$(MAKE) --no-print-directory -C $(RIOTBOOT_DIR) $*

# Generate a binary file from the bootloader which fills all the
Expand Down
26 changes: 26 additions & 0 deletions makefiles/tools/programmer.inc.mk
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Configure the programmer related variables

PROGRAMMER_QUIET ?= $(QUIET)
ifeq (0,$(PROGRAMMER_QUIET))
PROGRAMMER_VERBOSE_OPT ?= --verbose
endif

# Don't use the programmer wrapper for the CI (where speed and verbose output
# are important)
ifneq (1,$(RIOT_CI_BUILD))
USE_PROGRAMMER_WRAPPER_SCRIPT ?= 1
else
USE_PROGRAMMER_WRAPPER_SCRIPT ?= 0
endif

ifeq (1,$(USE_PROGRAMMER_WRAPPER_SCRIPT))
PROGRAMMER_FLASH ?= @$(RIOTTOOLS)/programmer/programmer.py \
--action Flashing --cmd "$(FLASHER) $(FFLAGS)" \
--programmer "$(PROGRAMMER)" $(PROGRAMMER_VERBOSE_OPT)
PROGRAMMER_RESET ?= @$(RIOTTOOLS)/programmer/programmer.py \
--action Resetting --cmd "$(RESET) $(RESET_FLAGS)" \
--programmer "$(PROGRAMMER)" $(PROGRAMMER_VERBOSE_OPT)
else
PROGRAMMER_FLASH ?= $(FLASHER) $(FFLAGS)
PROGRAMMER_RESET ?= $(RESET) $(RESET_FLAGS)
endif
4 changes: 4 additions & 0 deletions makefiles/vars.inc.mk
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,10 @@ export HEXFILE # The 'intel hex' stripped result of the compilatio
# RESET_FLAGS # The parameters to supply to RESET.
# PROGRAMMER # The programmer to use when flashing, resetting or debugging
# PROGRAMMERS_SUPPORTED # The list of programmers supported by a board
# PROGRAMMER_QUIET # Change verbosity of programmer output (only used with flash and reset targets).
# Default is 1, not verbose. Use 0 to get normal programmer output.
# USE_PROGRAMMER_WRAPPER_SCRIPT # Use the programmer wrapper Python script. Default is 1 (0 if RIOT_CI_BUILD is set).

aabadie marked this conversation as resolved.
Show resolved Hide resolved

export DLCACHE # directory used to cache http downloads
export DOWNLOAD_TO_FILE # Use `$(DOWNLOAD_TO_FILE) $(DESTINATION) $(URL)` to download `$(URL)` to `$(DESTINATION)`.
Expand Down