summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMichael Kuhn <michael.kuhn@ovgu.de>2023-07-12 08:54:45 +0200
committerGitHub <noreply@github.com>2023-07-12 08:54:45 +0200
commitd5d0b8821cd9789b8404e06a40dfeabce09a3480 (patch)
treef09861f4bb756ba54a47cdd87663429de9e51e74
parentd3704130b63fa177bd5786659b3a64fb338c4b00 (diff)
downloadspack-d5d0b8821cd9789b8404e06a40dfeabce09a3480.tar.gz
spack-d5d0b8821cd9789b8404e06a40dfeabce09a3480.tar.bz2
spack-d5d0b8821cd9789b8404e06a40dfeabce09a3480.tar.xz
spack-d5d0b8821cd9789b8404e06a40dfeabce09a3480.zip
installer: Improve status reporting (#37903)
Refactor `TermTitle` into `InstallStatus` and use it to show progress information both in the terminal title as well as inline. This also turns on the terminal title status by default. The inline output will look like the following after this change: ``` ==> Installing m4-1.4.19-w2fxrpuz64zdq63woprqfxxzc3tzu7p3 [4/4] ```
-rw-r--r--etc/spack/defaults/config.yaml9
-rw-r--r--lib/spack/docs/config_yaml.rst9
-rw-r--r--lib/spack/spack/installer.py60
-rw-r--r--lib/spack/spack/schema/config.py7
-rw-r--r--lib/spack/spack/test/installer.py36
-rw-r--r--share/spack/templates/depfile/Makefile2
6 files changed, 70 insertions, 53 deletions
diff --git a/etc/spack/defaults/config.yaml b/etc/spack/defaults/config.yaml
index 43f8a98dff..b4d81f69da 100644
--- a/etc/spack/defaults/config.yaml
+++ b/etc/spack/defaults/config.yaml
@@ -216,10 +216,11 @@ config:
# manipulation by unprivileged user (e.g. AFS)
allow_sgid: true
- # Whether to set the terminal title to display status information during
- # building and installing packages. This gives information about Spack's
- # current progress as well as the current and total number of packages.
- terminal_title: false
+ # Whether to show status information during building and installing packages.
+ # This gives information about Spack's current progress as well as the current
+ # and total number of packages. Information is shown both in the terminal
+ # title and inline.
+ install_status: true
# Number of seconds a buildcache's index.json is cached locally before probing
# for updates, within a single Spack invocation. Defaults to 10 minutes.
diff --git a/lib/spack/docs/config_yaml.rst b/lib/spack/docs/config_yaml.rst
index b1e7a1d249..294f7c3436 100644
--- a/lib/spack/docs/config_yaml.rst
+++ b/lib/spack/docs/config_yaml.rst
@@ -292,12 +292,13 @@ It is also worth noting that:
non_bindable_shared_objects = ["libinterface.so"]
----------------------
-``terminal_title``
+``install_status``
----------------------
-By setting this option to ``true``, Spack will update the terminal's title to
-provide information about its current progress as well as the current and
-total package numbers.
+When set to ``true``, Spack will show information about its current progress
+as well as the current and total package numbers. Progress is shown both
+in the terminal title and inline. Setting it to ``false`` will not show any
+progress information.
To work properly, this requires your terminal to reset its title after
Spack has finished its work, otherwise Spack's status information will
diff --git a/lib/spack/spack/installer.py b/lib/spack/spack/installer.py
index bc3e17d41d..3b632fdbcc 100644
--- a/lib/spack/spack/installer.py
+++ b/lib/spack/spack/installer.py
@@ -536,7 +536,7 @@ def get_dependent_ids(spec):
return [package_id(d.package) for d in spec.dependents()]
-def install_msg(name, pid):
+def install_msg(name, pid, install_status):
"""
Colorize the name/id of the package being installed
@@ -548,7 +548,12 @@ def install_msg(name, pid):
str: Colorized installing message
"""
pre = "{0}: ".format(pid) if tty.show_pid() else ""
- return pre + colorize("@*{Installing} @*g{%s}" % name)
+ post = (
+ " @*{%s}" % install_status.get_progress()
+ if install_status and spack.config.get("config:install_status", True)
+ else ""
+ )
+ return pre + colorize("@*{Installing} @*g{%s}%s" % (name, post))
def archive_install_logs(pkg, phase_log_dir):
@@ -657,9 +662,9 @@ def package_id(pkg):
return "{0}-{1}-{2}".format(pkg.name, pkg.version, pkg.spec.dag_hash())
-class TermTitle:
+class InstallStatus:
def __init__(self, pkg_count):
- # Counters used for showing status information in the terminal title
+ # Counters used for showing status information
self.pkg_num = 0
self.pkg_count = pkg_count
self.pkg_ids = set()
@@ -671,17 +676,20 @@ class TermTitle:
self.pkg_num += 1
self.pkg_ids.add(pkg_id)
- def set(self, text):
- if not spack.config.get("config:terminal_title", False):
+ def set_term_title(self, text):
+ if not spack.config.get("config:install_status", True):
return
if not sys.stdout.isatty():
return
- status = "{0} [{1}/{2}]".format(text, self.pkg_num, self.pkg_count)
+ status = "{0} {1}".format(text, self.get_progress())
sys.stdout.write("\033]0;Spack: {0}\007".format(status))
sys.stdout.flush()
+ def get_progress(self):
+ return "[{0}/{1}]".format(self.pkg_num, self.pkg_count)
+
class TermStatusLine:
"""
@@ -1240,7 +1248,7 @@ class PackageInstaller:
spack.compilers.find_compilers([compiler_search_prefix])
)
- def _install_task(self, task):
+ def _install_task(self, task, install_status):
"""
Perform the installation of the requested spec and/or dependency
represented by the build task.
@@ -1257,7 +1265,7 @@ class PackageInstaller:
pkg, pkg_id = task.pkg, task.pkg_id
- tty.msg(install_msg(pkg_id, self.pid))
+ tty.msg(install_msg(pkg_id, self.pid, install_status))
task.start = task.start or time.time()
task.status = STATUS_INSTALLING
@@ -1406,7 +1414,7 @@ class PackageInstaller:
else:
return None
- def _requeue_task(self, task):
+ def _requeue_task(self, task, install_status):
"""
Requeues a task that appears to be in progress by another process.
@@ -1416,7 +1424,8 @@ class PackageInstaller:
if task.status not in [STATUS_INSTALLED, STATUS_INSTALLING]:
tty.debug(
"{0} {1}".format(
- install_msg(task.pkg_id, self.pid), "in progress by another process"
+ install_msg(task.pkg_id, self.pid, install_status),
+ "in progress by another process",
)
)
@@ -1595,7 +1604,7 @@ class PackageInstaller:
single_explicit_spec = len(self.build_requests) == 1
failed_explicits = []
- term_title = TermTitle(len(self.build_pq))
+ install_status = InstallStatus(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.
@@ -1611,8 +1620,8 @@ class PackageInstaller:
keep_prefix = install_args.get("keep_prefix")
pkg, pkg_id, spec = task.pkg, task.pkg_id, task.pkg.spec
- term_title.next_pkg(pkg)
- term_title.set("Processing {0}".format(pkg.name))
+ install_status.next_pkg(pkg)
+ install_status.set_term_title("Processing {0}".format(pkg.name))
tty.debug("Processing {0}: task={1}".format(pkg_id, task))
# Ensure that the current spec has NO uninstalled dependencies,
# which is assumed to be reflected directly in its priority.
@@ -1676,7 +1685,7 @@ class PackageInstaller:
# another process is likely (un)installing the spec or has
# determined the spec has already been installed (though the
# other process may be hung).
- term_title.set("Acquiring lock for {0}".format(pkg.name))
+ install_status.set_term_title("Acquiring lock for {0}".format(pkg.name))
term_status.add(pkg_id)
ltype, lock = self._ensure_locked("write", pkg)
if lock is None:
@@ -1689,7 +1698,7 @@ class PackageInstaller:
# can check the status presumably established by another process
# -- failed, installed, or uninstalled -- on the next pass.
if lock is None:
- self._requeue_task(task)
+ self._requeue_task(task, install_status)
continue
term_status.clear()
@@ -1700,7 +1709,7 @@ class PackageInstaller:
task.request.overwrite_time = time.time()
# Determine state of installation artifacts and adjust accordingly.
- term_title.set("Preparing {0}".format(pkg.name))
+ install_status.set_term_title("Preparing {0}".format(pkg.name))
self._prepare_for_install(task)
# Flag an already installed package
@@ -1728,7 +1737,7 @@ class PackageInstaller:
# established by the other process -- failed, installed,
# or uninstalled -- on the next pass.
self.installed.remove(pkg_id)
- self._requeue_task(task)
+ self._requeue_task(task, install_status)
continue
# Having a read lock on an uninstalled pkg may mean another
@@ -1741,19 +1750,19 @@ class PackageInstaller:
# uninstalled -- on the next pass.
if ltype == "read":
lock.release_read()
- self._requeue_task(task)
+ self._requeue_task(task, install_status)
continue
# Proceed with the installation since we have an exclusive write
# lock on the package.
- term_title.set("Installing {0}".format(pkg.name))
+ install_status.set_term_title("Installing {0}".format(pkg.name))
try:
action = self._install_action(task)
if action == InstallAction.INSTALL:
- self._install_task(task)
+ self._install_task(task, install_status)
elif action == InstallAction.OVERWRITE:
- OverwriteInstall(self, spack.store.db, task).install()
+ OverwriteInstall(self, spack.store.db, task, install_status).install()
self._update_installed(task)
@@ -1779,7 +1788,7 @@ class PackageInstaller:
err += " Requeueing to install from source."
tty.error(err.format(pkg.name, str(exc)))
task.use_cache = False
- self._requeue_task(task)
+ self._requeue_task(task, install_status)
continue
except (Exception, SystemExit) as exc:
@@ -2092,10 +2101,11 @@ def build_process(pkg, install_args):
class OverwriteInstall:
- def __init__(self, installer, database, task):
+ def __init__(self, installer, database, task, install_status):
self.installer = installer
self.database = database
self.task = task
+ self.install_status = install_status
def install(self):
"""
@@ -2106,7 +2116,7 @@ class OverwriteInstall:
"""
try:
with fs.replace_directory_transaction(self.task.pkg.prefix):
- self.installer._install_task(self.task)
+ self.installer._install_task(self.task, self.install_status)
except fs.CouldNotRestoreDirectoryBackup as e:
self.database.remove(self.task.pkg.spec)
tty.error(
diff --git a/lib/spack/spack/schema/config.py b/lib/spack/spack/schema/config.py
index 106e45ce7a..6c30f0aab9 100644
--- a/lib/spack/spack/schema/config.py
+++ b/lib/spack/spack/schema/config.py
@@ -87,15 +87,16 @@ properties = {
"anyOf": [{"type": "integer", "minimum": 1}, {"type": "null"}]
},
"allow_sgid": {"type": "boolean"},
+ "install_status": {"type": "boolean"},
"binary_index_root": {"type": "string"},
"url_fetch_method": {"type": "string", "enum": ["urllib", "curl"]},
"additional_external_search_paths": {"type": "array", "items": {"type": "string"}},
"binary_index_ttl": {"type": "integer", "minimum": 0},
},
"deprecatedProperties": {
- "properties": ["module_roots"],
- "message": "config:module_roots has been replaced by "
- "modules:[module set]:roots and is ignored",
+ "properties": ["terminal_title"],
+ "message": "config:terminal_title has been replaced by "
+ "install_status and is ignored",
"error": False,
},
}
diff --git a/lib/spack/spack/test/installer.py b/lib/spack/spack/test/installer.py
index 7b9c2b5184..a562fafbb0 100644
--- a/lib/spack/spack/test/installer.py
+++ b/lib/spack/spack/test/installer.py
@@ -148,15 +148,19 @@ def test_install_msg(monkeypatch):
install_msg = "Installing {0}".format(name)
monkeypatch.setattr(tty, "_debug", 0)
- assert inst.install_msg(name, pid) == install_msg
+ assert inst.install_msg(name, pid, None) == install_msg
+
+ install_status = inst.InstallStatus(1)
+ expected = "{0} [0/1]".format(install_msg)
+ assert inst.install_msg(name, pid, install_status) == expected
monkeypatch.setattr(tty, "_debug", 1)
- assert inst.install_msg(name, pid) == install_msg
+ assert inst.install_msg(name, pid, None) == install_msg
# Expect the PID to be added at debug level 2
monkeypatch.setattr(tty, "_debug", 2)
expected = "{0}: {1}".format(pid, install_msg)
- assert inst.install_msg(name, pid) == expected
+ assert inst.install_msg(name, pid, None) == expected
def test_install_from_cache_errors(install_mockery, capsys):
@@ -795,7 +799,7 @@ def test_install_task_use_cache(install_mockery, monkeypatch):
task = create_build_task(request.pkg)
monkeypatch.setattr(inst, "_install_from_cache", _true)
- installer._install_task(task)
+ installer._install_task(task, None)
assert request.pkg_id in installer.installed
@@ -817,7 +821,7 @@ def test_install_task_add_compiler(install_mockery, monkeypatch, capfd):
monkeypatch.setattr(spack.database.Database, "add", _noop)
monkeypatch.setattr(spack.compilers, "add_compilers_to_config", _add)
- installer._install_task(task)
+ installer._install_task(task, None)
out = capfd.readouterr()[0]
assert config_msg in out
@@ -868,7 +872,7 @@ def test_requeue_task(install_mockery, capfd):
# temporarily set tty debug messages on so we can test output
current_debug_level = tty.debug_level()
tty.set_debug(1)
- installer._requeue_task(task)
+ installer._requeue_task(task, None)
tty.set_debug(current_debug_level)
ids = list(installer.build_tasks)
@@ -1031,7 +1035,7 @@ def test_install_fail_on_interrupt(install_mockery, monkeypatch):
spec_name = "a"
err_msg = "mock keyboard interrupt for {0}".format(spec_name)
- def _interrupt(installer, task, **kwargs):
+ def _interrupt(installer, task, install_status, **kwargs):
if task.pkg.name == spec_name:
raise KeyboardInterrupt(err_msg)
else:
@@ -1058,7 +1062,7 @@ def test_install_fail_single(install_mockery, monkeypatch):
class MyBuildException(Exception):
pass
- def _install(installer, task, **kwargs):
+ def _install(installer, task, install_status, **kwargs):
if task.pkg.name == spec_name:
raise MyBuildException(err_msg)
else:
@@ -1085,7 +1089,7 @@ def test_install_fail_multi(install_mockery, monkeypatch):
class MyBuildException(Exception):
pass
- def _install(installer, task, **kwargs):
+ def _install(installer, task, install_status, **kwargs):
if task.pkg.name == spec_name:
raise MyBuildException(err_msg)
else:
@@ -1157,7 +1161,7 @@ def test_install_fail_fast_on_except(install_mockery, monkeypatch, capsys):
def test_install_lock_failures(install_mockery, monkeypatch, capfd):
"""Cover basic install lock failure handling in a single pass."""
- def _requeued(installer, task):
+ def _requeued(installer, task, install_status):
tty.msg("requeued {0}".format(task.pkg.spec.name))
const_arg = installer_args(["b"], {})
@@ -1192,7 +1196,7 @@ def test_install_lock_installed_requeue(install_mockery, monkeypatch, capfd):
# also do not allow the package to be locked again
monkeypatch.setattr(inst.PackageInstaller, "_ensure_locked", _not_locked)
- def _requeued(installer, task):
+ def _requeued(installer, task, install_status):
tty.msg("requeued {0}".format(inst.package_id(task.pkg)))
# Flag the package as installed
@@ -1224,7 +1228,7 @@ def test_install_read_locked_requeue(install_mockery, monkeypatch, capfd):
tty.msg("preparing {0}".format(task.pkg.spec.name))
assert task.pkg.spec.name not in installer.installed
- def _requeued(installer, task):
+ def _requeued(installer, task, install_status):
tty.msg("requeued {0}".format(task.pkg.spec.name))
# Force a read lock
@@ -1289,7 +1293,7 @@ def test_overwrite_install_backup_success(temporary_store, config, mock_packages
fs.touchp(installed_file)
class InstallerThatWipesThePrefixDir:
- def _install_task(self, task):
+ def _install_task(self, task, install_status):
shutil.rmtree(task.pkg.prefix, ignore_errors=True)
fs.mkdirp(task.pkg.prefix)
raise Exception("Some fatal install error")
@@ -1302,7 +1306,7 @@ def test_overwrite_install_backup_success(temporary_store, config, mock_packages
fake_installer = InstallerThatWipesThePrefixDir()
fake_db = FakeDatabase()
- overwrite_install = inst.OverwriteInstall(fake_installer, fake_db, task)
+ overwrite_install = inst.OverwriteInstall(fake_installer, fake_db, task, None)
# Installation should throw the installation exception, not the backup
# failure.
@@ -1323,7 +1327,7 @@ def test_overwrite_install_backup_failure(temporary_store, config, mock_packages
"""
class InstallerThatAccidentallyDeletesTheBackupDir:
- def _install_task(self, task):
+ def _install_task(self, task, install_status):
# Remove the backup directory, which is at the same level as the prefix,
# starting with .backup
backup_glob = os.path.join(
@@ -1351,7 +1355,7 @@ def test_overwrite_install_backup_failure(temporary_store, config, mock_packages
fake_installer = InstallerThatAccidentallyDeletesTheBackupDir()
fake_db = FakeDatabase()
- overwrite_install = inst.OverwriteInstall(fake_installer, fake_db, task)
+ overwrite_install = inst.OverwriteInstall(fake_installer, fake_db, task, None)
# Installation should throw the installation exception, not the backup
# failure.
diff --git a/share/spack/templates/depfile/Makefile b/share/spack/templates/depfile/Makefile
index a50304a8be..dde42cf7d5 100644
--- a/share/spack/templates/depfile/Makefile
+++ b/share/spack/templates/depfile/Makefile
@@ -1,4 +1,4 @@
-SPACK ?= spack
+SPACK ?= spack -c config:install_status:false
SPACK_INSTALL_FLAGS ?=
# This variable can be used to add post install hooks