Skip to content

Commit c36d620

Browse files
authored
Merge pull request #1 from boegel/show_progress
make Rich dependency optional + add easybuild.tools.output module
2 parents 9f5fff0 + d779a61 commit c36d620

File tree

4 files changed

+107
-29
lines changed

4 files changed

+107
-29
lines changed

easybuild/framework/easyblock.py

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,7 @@ def __init__(self, ec):
213213
self.current_step = None
214214

215215
# Create empty progress bar
216-
self.progressbar = None
216+
self.progress_bar = None
217217
self.pbar_task = None
218218

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

307-
def set_progressbar(self, progressbar, task_id):
307+
def set_progress_bar(self, progress_bar, task_id):
308308
"""
309309
Set progress bar, the progress bar is needed when writing messages so
310310
that the progress counter is always at the bottom
311311
"""
312-
self.progressbar = progressbar
312+
self.progress_bar = progress_bar
313313
self.pbar_task = task_id
314314

315315
def advance_progress(self, tick=1.0):
316316
"""
317317
Advance the progress bar forward with `tick`
318318
"""
319-
if self.progressbar and self.pbar_task is not None:
320-
self.progressbar.advance(self.pbar_task, tick)
319+
if self.progress_bar and self.pbar_task is not None:
320+
self.progress_bar.advance(self.pbar_task, tick)
321321

322322
#
323323
# DRY RUN UTILITIES
@@ -3653,7 +3653,7 @@ def print_dry_run_note(loc, silent=True):
36533653
dry_run_msg(msg, silent=silent)
36543654

36553655

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

3704-
# Setup progressbar
3705-
if progressbar and task_id is not None:
3706-
app.set_progressbar(progressbar, task_id)
3707-
_log.info("Updated progressbar instance for easyblock %s" % easyblock)
3704+
# Setup progress bar
3705+
if progress_bar and task_id is not None:
3706+
app.set_progress_bar(progress_bar, task_id)
3707+
_log.info("Updated progress bar instance for easyblock %s", easyblock)
3708+
37083709
# application settings
37093710
stop = build_option('stop')
37103711
if stop is not None:

easybuild/main.py

Lines changed: 19 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -68,12 +68,13 @@
6868
from easybuild.tools.hooks import START, END, load_hooks, run_hook
6969
from easybuild.tools.modules import modules_tool
7070
from easybuild.tools.options import set_up_configuration, use_color
71+
from easybuild.tools.output import create_progress_bar
7172
from easybuild.tools.robot import check_conflicts, dry_run, missing_deps, resolve_dependencies, search_easyconfigs
7273
from easybuild.tools.package.utilities import check_pkg_support
7374
from easybuild.tools.parallelbuild import submit_jobs
7475
from easybuild.tools.repository.repository import init_repository
7576
from easybuild.tools.testing import create_test_report, overall_test_report, regtest, session_state
76-
from rich.progress import Progress, TextColumn, BarColumn, TimeElapsedColumn
77+
7778

7879
_log = None
7980

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

101102

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

115117
# Initialize progress bar with overall installation task
116-
if progress:
117-
task_id = progress.add_task("", total=len(ecs))
118+
if progress_bar:
119+
task_id = progress_bar.add_task("", total=len(ecs))
120+
else:
121+
task_id = None
122+
118123
res = []
119124
for ec in ecs:
120-
if progress:
121-
progress.update(task_id, description=ec['short_mod_name'])
125+
126+
if progress_bar:
127+
progress_bar.update(task_id, description=ec['short_mod_name'])
128+
122129
ec_res = {}
123130
try:
124-
(ec_res['success'], app_log, err) = build_and_install_one(ec, init_env, progressbar=progress,
131+
(ec_res['success'], app_log, err) = build_and_install_one(ec, init_env, progress_bar=progress_bar,
125132
task_id=task_id)
126133
ec_res['log_file'] = app_log
127134
if not ec_res['success']:
@@ -527,18 +534,12 @@ def main(args=None, logfile=None, do_build=None, testing=False, modtool=None):
527534
# build software, will exit when errors occurs (except when testing)
528535
if not testing or (testing and do_build):
529536
exit_on_failure = not (options.dump_test_report or options.upload_test_report)
530-
# Create progressbar around software to install
531-
progress_bar = Progress(
532-
TextColumn("[bold blue]Installing {task.description} ({task.completed:.0f}/{task.total})"),
533-
BarColumn(),
534-
"[progress.percentage]{task.percentage:>3.1f}%",
535-
"•",
536-
TimeElapsedColumn()
537-
)
537+
538+
progress_bar = create_progress_bar()
538539
with progress_bar:
539-
ecs_with_res = build_and_install_software(
540-
ordered_ecs, init_session_state, exit_on_failure=exit_on_failure,
541-
progress=progress_bar)
540+
ecs_with_res = build_and_install_software(ordered_ecs, init_session_state,
541+
exit_on_failure=exit_on_failure,
542+
progress_bar=progress_bar)
542543
else:
543544
ecs_with_res = [(ec, {}) for ec in ordered_ecs]
544545

easybuild/tools/output.py

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
# -*- coding: utf-8 -*-
2+
# #
3+
# Copyright 2021-2021 Ghent University
4+
#
5+
# This file is part of EasyBuild,
6+
# originally created by the HPC team of Ghent University (http://ugent.be/hpc/en),
7+
# with support of Ghent University (http://ugent.be/hpc),
8+
# the Flemish Supercomputer Centre (VSC) (https://www.vscentrum.be),
9+
# Flemish Research Foundation (FWO) (http://www.fwo.be/en)
10+
# and the Department of Economy, Science and Innovation (EWI) (http://www.ewi-vlaanderen.be/en).
11+
#
12+
# https://github.com/easybuilders/easybuild
13+
#
14+
# EasyBuild is free software: you can redistribute it and/or modify
15+
# it under the terms of the GNU General Public License as published by
16+
# the Free Software Foundation v2.
17+
#
18+
# EasyBuild is distributed in the hope that it will be useful,
19+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
20+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21+
# GNU General Public License for more details.
22+
#
23+
# You should have received a copy of the GNU General Public License
24+
# along with EasyBuild. If not, see <http://www.gnu.org/licenses/>.
25+
# #
26+
"""
27+
Tools for controlling output to terminal produced by EasyBuild.
28+
29+
:author: Kenneth Hoste (Ghent University)
30+
:author: Jørgen Nordmoen (University of Oslo)
31+
"""
32+
try:
33+
from rich.progress import Progress, TextColumn, BarColumn, TimeElapsedColumn
34+
HAVE_RICH = True
35+
except ImportError:
36+
HAVE_RICH = False
37+
38+
39+
class DummyProgress(object):
40+
"""Shim for Rich's Progress class."""
41+
42+
# __enter__ and __exit__ must be implemented to allow use as context manager
43+
def __enter__(self, *args, **kwargs):
44+
pass
45+
46+
def __exit__(self, *args, **kwargs):
47+
pass
48+
49+
# dummy implementations for methods supported by rich.progress.Progress class
50+
def add_task(self, *args, **kwargs):
51+
pass
52+
53+
def update(self, *args, **kwargs):
54+
pass
55+
56+
57+
def create_progress_bar():
58+
"""
59+
Create progress bar to display overall progress.
60+
61+
Returns rich.progress.Progress instance if the Rich Python package is available,
62+
or a shim DummyProgress instance otherwise.
63+
"""
64+
if HAVE_RICH:
65+
progress_bar = Progress(
66+
TextColumn("[bold blue]Installing {task.description} ({task.completed:.0f}/{task.total})"),
67+
BarColumn(),
68+
"[progress.percentage]{task.percentage:>3.1f}%",
69+
"•",
70+
TimeElapsedColumn()
71+
)
72+
else:
73+
progress_bar = DummyProgress()
74+
75+
return progress_bar

requirements.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,4 +63,5 @@ archspec; python_version >= '2.7'
6363
cryptography==3.3.2; python_version == '2.7'
6464
cryptography; python_version >= '3.5'
6565

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

0 commit comments

Comments
 (0)