Skip to content

Commit

Permalink
Merge pull request #1 from boegel/show_progress
Browse files Browse the repository at this point in the history
make Rich dependency optional + add easybuild.tools.output module
  • Loading branch information
nordmoen authored Sep 11, 2021
2 parents 9f5fff0 + d779a61 commit c36d620
Show file tree
Hide file tree
Showing 4 changed files with 107 additions and 29 deletions.
21 changes: 11 additions & 10 deletions easybuild/framework/easyblock.py
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ def __init__(self, ec):
self.current_step = None

# Create empty progress bar
self.progressbar = None
self.progress_bar = None
self.pbar_task = None

# list of loaded modules
Expand Down Expand Up @@ -304,20 +304,20 @@ def close_log(self):
self.log.info("Closing log for application name %s version %s" % (self.name, self.version))
fancylogger.logToFile(self.logfile, enable=False)

def set_progressbar(self, progressbar, task_id):
def set_progress_bar(self, progress_bar, task_id):
"""
Set progress bar, the progress bar is needed when writing messages so
that the progress counter is always at the bottom
"""
self.progressbar = progressbar
self.progress_bar = progress_bar
self.pbar_task = task_id

def advance_progress(self, tick=1.0):
"""
Advance the progress bar forward with `tick`
"""
if self.progressbar and self.pbar_task is not None:
self.progressbar.advance(self.pbar_task, tick)
if self.progress_bar and self.pbar_task is not None:
self.progress_bar.advance(self.pbar_task, tick)

#
# DRY RUN UTILITIES
Expand Down Expand Up @@ -3653,7 +3653,7 @@ def print_dry_run_note(loc, silent=True):
dry_run_msg(msg, silent=silent)


def build_and_install_one(ecdict, init_env, progressbar=None, task_id=None):
def build_and_install_one(ecdict, init_env, progress_bar=None, task_id=None):
"""
Build the software
:param ecdict: dictionary contaning parsed easyconfig + metadata
Expand Down Expand Up @@ -3701,10 +3701,11 @@ def build_and_install_one(ecdict, init_env, progressbar=None, task_id=None):
print_error("Failed to get application instance for %s (easyblock: %s): %s" % (name, easyblock, err.msg),
silent=silent)

# Setup progressbar
if progressbar and task_id is not None:
app.set_progressbar(progressbar, task_id)
_log.info("Updated progressbar instance for easyblock %s" % easyblock)
# Setup progress bar
if progress_bar and task_id is not None:
app.set_progress_bar(progress_bar, task_id)
_log.info("Updated progress bar instance for easyblock %s", easyblock)

# application settings
stop = build_option('stop')
if stop is not None:
Expand Down
37 changes: 19 additions & 18 deletions easybuild/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,12 +68,13 @@
from easybuild.tools.hooks import START, END, load_hooks, run_hook
from easybuild.tools.modules import modules_tool
from easybuild.tools.options import set_up_configuration, use_color
from easybuild.tools.output import create_progress_bar
from easybuild.tools.robot import check_conflicts, dry_run, missing_deps, resolve_dependencies, search_easyconfigs
from easybuild.tools.package.utilities import check_pkg_support
from easybuild.tools.parallelbuild import submit_jobs
from easybuild.tools.repository.repository import init_repository
from easybuild.tools.testing import create_test_report, overall_test_report, regtest, session_state
from rich.progress import Progress, TextColumn, BarColumn, TimeElapsedColumn


_log = None

Expand All @@ -99,29 +100,35 @@ def find_easyconfigs_by_specs(build_specs, robot_path, try_to_generate, testing=
return [(ec_file, generated)]


def build_and_install_software(ecs, init_session_state, exit_on_failure=True, progress=None):
def build_and_install_software(ecs, init_session_state, exit_on_failure=True, progress_bar=None):
"""
Build and install software for all provided parsed easyconfig files.
:param ecs: easyconfig files to install software with
:param init_session_state: initial session state, to use in test reports
:param exit_on_failure: whether or not to exit on installation failure
:param progress_bar: progress bar to use to report progress
"""
# obtain a copy of the starting environment so each build can start afresh
# we shouldn't use the environment from init_session_state, since relevant env vars might have been set since
# e.g. via easyconfig.handle_allowed_system_deps
init_env = copy.deepcopy(os.environ)

# Initialize progress bar with overall installation task
if progress:
task_id = progress.add_task("", total=len(ecs))
if progress_bar:
task_id = progress_bar.add_task("", total=len(ecs))
else:
task_id = None

res = []
for ec in ecs:
if progress:
progress.update(task_id, description=ec['short_mod_name'])

if progress_bar:
progress_bar.update(task_id, description=ec['short_mod_name'])

ec_res = {}
try:
(ec_res['success'], app_log, err) = build_and_install_one(ec, init_env, progressbar=progress,
(ec_res['success'], app_log, err) = build_and_install_one(ec, init_env, progress_bar=progress_bar,
task_id=task_id)
ec_res['log_file'] = app_log
if not ec_res['success']:
Expand Down Expand Up @@ -527,18 +534,12 @@ def main(args=None, logfile=None, do_build=None, testing=False, modtool=None):
# build software, will exit when errors occurs (except when testing)
if not testing or (testing and do_build):
exit_on_failure = not (options.dump_test_report or options.upload_test_report)
# Create progressbar around software to install
progress_bar = Progress(
TextColumn("[bold blue]Installing {task.description} ({task.completed:.0f}/{task.total})"),
BarColumn(),
"[progress.percentage]{task.percentage:>3.1f}%",
"•",
TimeElapsedColumn()
)

progress_bar = create_progress_bar()
with progress_bar:
ecs_with_res = build_and_install_software(
ordered_ecs, init_session_state, exit_on_failure=exit_on_failure,
progress=progress_bar)
ecs_with_res = build_and_install_software(ordered_ecs, init_session_state,
exit_on_failure=exit_on_failure,
progress_bar=progress_bar)
else:
ecs_with_res = [(ec, {}) for ec in ordered_ecs]

Expand Down
75 changes: 75 additions & 0 deletions easybuild/tools/output.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# -*- coding: utf-8 -*-
# #
# Copyright 2021-2021 Ghent University
#
# This file is part of EasyBuild,
# originally created by the HPC team of Ghent University (http://ugent.be/hpc/en),
# with support of Ghent University (http://ugent.be/hpc),
# the Flemish Supercomputer Centre (VSC) (https://www.vscentrum.be),
# Flemish Research Foundation (FWO) (http://www.fwo.be/en)
# and the Department of Economy, Science and Innovation (EWI) (http://www.ewi-vlaanderen.be/en).
#
# https://github.com/easybuilders/easybuild
#
# EasyBuild is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation v2.
#
# EasyBuild is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with EasyBuild. If not, see <http://www.gnu.org/licenses/>.
# #
"""
Tools for controlling output to terminal produced by EasyBuild.
:author: Kenneth Hoste (Ghent University)
:author: Jørgen Nordmoen (University of Oslo)
"""
try:
from rich.progress import Progress, TextColumn, BarColumn, TimeElapsedColumn
HAVE_RICH = True
except ImportError:
HAVE_RICH = False


class DummyProgress(object):
"""Shim for Rich's Progress class."""

# __enter__ and __exit__ must be implemented to allow use as context manager
def __enter__(self, *args, **kwargs):
pass

def __exit__(self, *args, **kwargs):
pass

# dummy implementations for methods supported by rich.progress.Progress class
def add_task(self, *args, **kwargs):
pass

def update(self, *args, **kwargs):
pass


def create_progress_bar():
"""
Create progress bar to display overall progress.
Returns rich.progress.Progress instance if the Rich Python package is available,
or a shim DummyProgress instance otherwise.
"""
if HAVE_RICH:
progress_bar = Progress(
TextColumn("[bold blue]Installing {task.description} ({task.completed:.0f}/{task.total})"),
BarColumn(),
"[progress.percentage]{task.percentage:>3.1f}%",
"•",
TimeElapsedColumn()
)
else:
progress_bar = DummyProgress()

return progress_bar
3 changes: 2 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -63,4 +63,5 @@ archspec; python_version >= '2.7'
cryptography==3.3.2; python_version == '2.7'
cryptography; python_version >= '3.5'

rich; python_version >= '2.7'
# rich is only supported for Python 3.6+
rich; python_version >= '3.6'

0 comments on commit c36d620

Please sign in to comment.