From e3d62b2f7be10bddc3c93c074fdbd3e22436f7f2 Mon Sep 17 00:00:00 2001 From: Harmen Stoppels Date: Wed, 26 Jan 2022 10:42:08 +0100 Subject: Print 'Waiting for another process to install x, y, z' in distributed builds (#28535) Co-authored-by: Massimiliano Culpo --- lib/spack/spack/installer.py | 53 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) (limited to 'lib') diff --git a/lib/spack/spack/installer.py b/lib/spack/spack/installer.py index c8fdd4e87a..dec5bab626 100644 --- a/lib/spack/spack/installer.py +++ b/lib/spack/spack/installer.py @@ -648,6 +648,49 @@ class TermTitle(object): sys.stdout.flush() +class TermStatusLine(object): + """ + This class is used in distributed builds to inform the user that other packages are + being installed by another process. + """ + def __init__(self, enabled): + self.enabled = enabled + self.pkg_set = set() + self.pkg_list = [] + + def add(self, pkg_id): + """ + Add a package to the waiting list, and if it is new, update the status line. + """ + if not self.enabled or pkg_id in self.pkg_set: + return + + self.pkg_set.add(pkg_id) + self.pkg_list.append(pkg_id) + tty.msg(colorize('@*{Waiting for} @*g{%s}' % pkg_id)) + sys.stdout.flush() + + def clear(self): + """ + Clear the status line. + """ + if not self.enabled: + return + + lines = len(self.pkg_list) + + if lines == 0: + return + + self.pkg_set.clear() + self.pkg_list.clear() + + # Move the cursor to the beginning of the first "Waiting for" message and clear + # everything after it. + sys.stdout.write('\x1b[%sF\x1b[J' % lines) + sys.stdout.flush() + + class PackageInstaller(object): ''' Class for managing the install process for a Spack instance based on a @@ -1500,6 +1543,10 @@ class PackageInstaller(object): term_title = TermTitle(len(self.build_pq)) + # Only enable the terminal status line when we're in a tty without debug info + # enabled, so that the output does not get cluttered. + term_status = TermStatusLine(enabled=sys.stdout.isatty() and not tty.is_debug()) + while self.build_pq: term_title.next_pkg() @@ -1523,6 +1570,7 @@ class PackageInstaller(object): # all subsequent tasks will have non-zero priorities or may be # dependencies of this task. if task.priority != 0: + term_status.clear() tty.error('Detected uninstalled dependencies for {0}: {1}' .format(pkg_id, task.uninstalled_deps)) left = [dep_id for dep_id in task.uninstalled_deps if @@ -1545,12 +1593,14 @@ class PackageInstaller(object): # some package likely depends on it. if not task.explicit: if _handle_external_and_upstream(pkg, False): + term_status.clear() self._flag_installed(pkg, task.dependents) continue # Flag a failed spec. Do not need an (install) prefix lock since # assume using a separate (failed) prefix lock file. if pkg_id in self.failed or spack.store.db.prefix_failed(spec): + term_status.clear() tty.warn('{0} failed to install'.format(pkg_id)) self._update_failed(task) @@ -1569,6 +1619,7 @@ class PackageInstaller(object): # determined the spec has already been installed (though the # other process may be hung). term_title.set('Acquiring lock for {0}'.format(pkg.name)) + term_status.add(pkg_id) ltype, lock = self._ensure_locked('write', pkg) if lock is None: # Attempt to get a read lock instead. If this fails then @@ -1583,6 +1634,8 @@ class PackageInstaller(object): self._requeue_task(task) continue + term_status.clear() + # Take a timestamp with the overwrite argument to allow checking # whether another process has already overridden the package. if task.request.overwrite and task.explicit: -- cgit v1.2.3-70-g09d2