diff options
-rw-r--r-- | etc/spack/defaults/config.yaml | 9 | ||||
-rw-r--r-- | lib/spack/docs/config_yaml.rst | 9 | ||||
-rw-r--r-- | lib/spack/spack/installer.py | 60 | ||||
-rw-r--r-- | lib/spack/spack/schema/config.py | 7 | ||||
-rw-r--r-- | lib/spack/spack/test/installer.py | 36 | ||||
-rw-r--r-- | share/spack/templates/depfile/Makefile | 2 |
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 |