diff options
Diffstat (limited to 'lib')
-rw-r--r-- | lib/spack/spack/test/bindist.py | 509 |
1 files changed, 156 insertions, 353 deletions
diff --git a/lib/spack/spack/test/bindist.py b/lib/spack/spack/test/bindist.py index e9edb2e751..45dd931e8c 100644 --- a/lib/spack/spack/test/bindist.py +++ b/lib/spack/spack/test/bindist.py @@ -2,95 +2,61 @@ # Spack Project Developers. See the top-level COPYRIGHT file for details. # # SPDX-License-Identifier: (Apache-2.0 OR MIT) - -""" -This test checks creating and install buildcaches -""" import os import sys +import platform + import py import pytest -import argparse -import platform -import spack.repo -import spack.store + import spack.binary_distribution as bindist -import spack.cmd.buildcache as buildcache -import spack.cmd.install as install -import spack.cmd.uninstall as uninstall -import spack.cmd.mirror as mirror +import spack.config import spack.hooks.sbang as sbang -from spack.main import SpackCommand +import spack.main import spack.mirror +import spack.repo +import spack.store import spack.util.gpg import spack.util.web as web_util + from spack.directory_layout import YamlDirectoryLayout from spack.spec import Spec - -def_install_path_scheme = '${ARCHITECTURE}/${COMPILERNAME}-${COMPILERVER}/${PACKAGE}-${VERSION}-${HASH}' # noqa: E501 -ndef_install_path_scheme = '${PACKAGE}/${VERSION}/${ARCHITECTURE}-${COMPILERNAME}-${COMPILERVER}-${HASH}' # noqa: E501 - -mirror_path_def = None -mirror_path_rel = None - -mirror_cmd = SpackCommand('mirror') -install_cmd = SpackCommand('install') -uninstall_cmd = SpackCommand('uninstall') -buildcache_cmd = SpackCommand('buildcache') +mirror_cmd = spack.main.SpackCommand('mirror') +install_cmd = spack.main.SpackCommand('install') +uninstall_cmd = spack.main.SpackCommand('uninstall') +buildcache_cmd = spack.main.SpackCommand('buildcache') @pytest.fixture(scope='function') def cache_directory(tmpdir): - old_cache_path = spack.caches.fetch_cache - tmpdir.ensure('fetch_cache', dir=True) - fsc = spack.fetch_strategy.FsCache(str(tmpdir.join('fetch_cache'))) - spack.config.caches = fsc - yield spack.config.caches - tmpdir.join('fetch_cache').remove() - spack.config.caches = old_cache_path - - -@pytest.fixture(scope='session') -def session_mirror_def(tmpdir_factory): - dir = tmpdir_factory.mktemp('mirror') - global mirror_path_rel - mirror_path_rel = dir - dir.ensure('build_cache', dir=True) - yield dir - dir.join('build_cache').remove() + fetch_cache_dir = tmpdir.ensure('fetch_cache', dir=True) + fsc = spack.fetch_strategy.FsCache(str(fetch_cache_dir)) + spack.config.caches, old_cache_path = fsc, spack.caches.fetch_cache + yield spack.config.caches -@pytest.fixture(scope='function') -def mirror_directory_def(session_mirror_def): - yield str(session_mirror_def) + fetch_cache_dir.remove() + spack.config.caches = old_cache_path -@pytest.fixture(scope='session') -def session_mirror_rel(tmpdir_factory): +@pytest.fixture(scope='module') +def mirror_dir(tmpdir_factory): dir = tmpdir_factory.mktemp('mirror') - global mirror_path_rel - mirror_path_rel = dir dir.ensure('build_cache', dir=True) - yield dir + yield str(dir) dir.join('build_cache').remove() @pytest.fixture(scope='function') -def mirror_directory_rel(session_mirror_rel): - yield(session_mirror_rel) - - -@pytest.fixture(scope='function') -def function_mirror(tmpdir): - mirror_dir = str(tmpdir.join('mirror')) - mirror_cmd('add', '--scope', 'site', 'test-mirror-func', - 'file://%s' % mirror_dir) +def test_mirror(mirror_dir): + mirror_url = 'file://%s' % mirror_dir + mirror_cmd('add', '--scope', 'site', 'test-mirror-func', mirror_url) yield mirror_dir mirror_cmd('rm', '--scope=site', 'test-mirror-func') -@pytest.fixture(scope='session') +@pytest.fixture(scope='module') def config_directory(tmpdir_factory): tmpdir = tmpdir_factory.mktemp('test_configs') # restore some sane defaults for packages and config @@ -116,8 +82,14 @@ def config_directory(tmpdir_factory): @pytest.fixture(scope='function') -def default_config(tmpdir_factory, config_directory, monkeypatch): - +def default_config( + tmpdir_factory, config_directory, monkeypatch, + install_mockery_mutable_config +): + # This fixture depends on install_mockery_mutable_config to ensure + # there is a clear order of initialization. The substitution of the + # config scopes here is done on top of the substitution that comes with + # install_mockery_mutable_config mutable_dir = tmpdir_factory.mktemp('mutable_config').join('tmp') config_directory.copy(mutable_dir) @@ -126,7 +98,7 @@ def default_config(tmpdir_factory, config_directory, monkeypatch): for name in ['site/%s' % platform.system().lower(), 'site', 'user']]) - monkeypatch.setattr(spack.config, 'config', cfg) + spack.config.config, old_config = cfg, spack.config.config # This is essential, otherwise the cache will create weird side effects # that will compromise subsequent tests if compilers.yaml is modified @@ -148,18 +120,25 @@ def default_config(tmpdir_factory, config_directory, monkeypatch): timeout = spack.config.get('config:connect_timeout') if not timeout: spack.config.set('config:connect_timeout', 10, scope='user') + yield spack.config.config + + spack.config.config = old_config mutable_dir.remove() @pytest.fixture(scope='function') def install_dir_default_layout(tmpdir): """Hooks a fake install directory with a default layout""" - real_store = spack.store.store - real_layout = spack.store.layout - spack.store.store = spack.store.Store(str(tmpdir.join('opt'))) - spack.store.layout = YamlDirectoryLayout(str(tmpdir.join('opt')), - path_scheme=def_install_path_scheme) # noqa: E501 + scheme = os.path.join( + '${architecture}', + '${compiler.name}-${compiler.version}', + '${name}-${version}-${hash}' + ) + real_store, real_layout = spack.store.store, spack.store.layout + opt_dir = tmpdir.join('opt') + spack.store.store = spack.store.Store(str(opt_dir)) + spack.store.layout = YamlDirectoryLayout(str(opt_dir), path_scheme=scheme) try: yield spack.store finally: @@ -170,11 +149,14 @@ def install_dir_default_layout(tmpdir): @pytest.fixture(scope='function') def install_dir_non_default_layout(tmpdir): """Hooks a fake install directory with a non-default layout""" - real_store = spack.store.store - real_layout = spack.store.layout - spack.store.store = spack.store.Store(str(tmpdir.join('opt'))) - spack.store.layout = YamlDirectoryLayout(str(tmpdir.join('opt')), - path_scheme=ndef_install_path_scheme) # noqa: E501 + scheme = os.path.join( + '${name}', '${version}', + '${architecture}-${compiler.name}-${compiler.version}-${hash}' + ) + real_store, real_layout = spack.store.store, spack.store.layout + opt_dir = tmpdir.join('opt') + spack.store.store = spack.store.Store(str(opt_dir)) + spack.store.layout = YamlDirectoryLayout(str(opt_dir), path_scheme=scheme) try: yield spack.store finally: @@ -190,308 +172,149 @@ else: @pytest.mark.requires_executables(*args) -@pytest.mark.disable_clean_stage_check @pytest.mark.maybeslow -@pytest.mark.usefixtures('default_config', 'cache_directory', - 'install_dir_default_layout') -def test_default_rpaths_create_install_default_layout(tmpdir, - mirror_directory_def, - install_mockery): +@pytest.mark.usefixtures( + 'default_config', 'cache_directory', 'install_dir_default_layout', + 'test_mirror' +) +def test_default_rpaths_create_install_default_layout(mirror_dir): """ Test the creation and installation of buildcaches with default rpaths into the default directory layout scheme. """ + gspec, cspec = Spec('garply').concretized(), Spec('corge').concretized() - gspec = Spec('garply') - gspec.concretize() - cspec = Spec('corge') - cspec.concretize() - - iparser = argparse.ArgumentParser() - install.setup_parser(iparser) - # Install some packages with dependent packages - iargs = iparser.parse_args(['--no-cache', cspec.name]) - install.install(iparser, iargs) - - global mirror_path_def - mirror_path_def = mirror_directory_def - mparser = argparse.ArgumentParser() - mirror.setup_parser(mparser) - margs = mparser.parse_args( - ['add', '--scope', 'site', 'test-mirror-def', 'file://%s' % mirror_path_def]) - mirror.mirror(mparser, margs) - margs = mparser.parse_args(['list']) - mirror.mirror(mparser, margs) - - # setup argument parser - parser = argparse.ArgumentParser() - buildcache.setup_parser(parser) - - # Set default buildcache args - create_args = ['create', '-a', '-u', '-d', str(mirror_path_def), - cspec.name] - install_args = ['install', '-a', '-u', cspec.name] + # Install 'corge' without using a cache + install_cmd('--no-cache', cspec.name) # Create a buildache - args = parser.parse_args(create_args) - buildcache.buildcache(parser, args) - # Test force overwrite create buildcache - create_args.insert(create_args.index('-a'), '-f') - args = parser.parse_args(create_args) - buildcache.buildcache(parser, args) - # create mirror index - args = parser.parse_args(['update-index', '-d', 'file://%s' % str(mirror_path_def)]) - buildcache.buildcache(parser, args) - # list the buildcaches in the mirror - args = parser.parse_args(['list', '-a', '-l', '-v']) - buildcache.buildcache(parser, args) + buildcache_cmd('create', '-au', '-d', mirror_dir, cspec.name) + # Test force overwrite create buildcache (-f option) + buildcache_cmd('create', '-auf', '-d', mirror_dir, cspec.name) + + # Create mirror index + mirror_url = 'file://{0}'.format(mirror_dir) + buildcache_cmd('update-index', '-d', mirror_url) + # List the buildcaches in the mirror + buildcache_cmd('list', '-alv') # Uninstall the package and deps - uparser = argparse.ArgumentParser() - uninstall.setup_parser(uparser) - uargs = uparser.parse_args(['-y', '--dependents', gspec.name]) - uninstall.uninstall(uparser, uargs) + uninstall_cmd('-y', '--dependents', gspec.name) - # test install - args = parser.parse_args(install_args) - buildcache.buildcache(parser, args) + # Test installing from build caches + buildcache_cmd('install', '-au', cspec.name) # This gives warning that spec is already installed - buildcache.buildcache(parser, args) - - # test overwrite install - install_args.insert(install_args.index('-a'), '-f') - args = parser.parse_args(install_args) - buildcache.buildcache(parser, args) + buildcache_cmd('install', '-au', cspec.name) - args = parser.parse_args(['keys', '-f']) - buildcache.buildcache(parser, args) + # Test overwrite install + buildcache_cmd('install', '-afu', cspec.name) - args = parser.parse_args(['list']) - buildcache.buildcache(parser, args) + buildcache_cmd('keys', '-f') + buildcache_cmd('list') - args = parser.parse_args(['list', '-a']) - buildcache.buildcache(parser, args) - - args = parser.parse_args(['list', '-l', '-v']) - buildcache.buildcache(parser, args) - bindist.clear_spec_cache() - spack.stage.purge() - margs = mparser.parse_args( - ['rm', '--scope', 'site', 'test-mirror-def']) - mirror.mirror(mparser, margs) + buildcache_cmd('list', '-a') + buildcache_cmd('list', '-l', '-v') @pytest.mark.requires_executables(*args) -@pytest.mark.disable_clean_stage_check @pytest.mark.maybeslow @pytest.mark.nomockstage -@pytest.mark.usefixtures('default_config', 'cache_directory', - 'install_dir_non_default_layout') -def test_default_rpaths_install_nondefault_layout(tmpdir, - install_mockery): +@pytest.mark.usefixtures( + 'default_config', 'cache_directory', 'install_dir_non_default_layout', + 'test_mirror' +) +def test_default_rpaths_install_nondefault_layout(mirror_dir): """ Test the creation and installation of buildcaches with default rpaths into the non-default directory layout scheme. """ - - gspec = Spec('garply') - gspec.concretize() - cspec = Spec('corge') - cspec.concretize() - - global mirror_path_def - mparser = argparse.ArgumentParser() - mirror.setup_parser(mparser) - margs = mparser.parse_args( - ['add', '--scope', 'site', 'test-mirror-def', 'file://%s' % mirror_path_def]) - mirror.mirror(mparser, margs) - - # setup argument parser - parser = argparse.ArgumentParser() - buildcache.setup_parser(parser) - - # Set default buildcache args - install_args = ['install', '-a', '-u', '%s' % cspec.name] + cspec = Spec('corge').concretized() # Install some packages with dependent packages # test install in non-default install path scheme - args = parser.parse_args(install_args) - buildcache.buildcache(parser, args) - # test force install in non-default install path scheme - install_args.insert(install_args.index('-a'), '-f') - args = parser.parse_args(install_args) - buildcache.buildcache(parser, args) + buildcache_cmd('install', '-au', cspec.name) - bindist.clear_spec_cache() - spack.stage.purge() - margs = mparser.parse_args( - ['rm', '--scope', 'site', 'test-mirror-def']) - mirror.mirror(mparser, margs) + # Test force install in non-default install path scheme + buildcache_cmd('install', '-auf', cspec.name) @pytest.mark.requires_executables(*args) -@pytest.mark.disable_clean_stage_check @pytest.mark.maybeslow @pytest.mark.nomockstage -@pytest.mark.usefixtures('default_config', 'cache_directory', - 'install_dir_default_layout') -def test_relative_rpaths_create_default_layout(tmpdir, - mirror_directory_rel, - install_mockery): +@pytest.mark.usefixtures( + 'default_config', 'cache_directory', 'install_dir_default_layout' +) +def test_relative_rpaths_create_default_layout(mirror_dir): """ Test the creation and installation of buildcaches with relative rpaths into the default directory layout scheme. """ - gspec = Spec('garply') - gspec.concretize() - cspec = Spec('corge') - cspec.concretize() + gspec, cspec = Spec('garply').concretized(), Spec('corge').concretized() - global mirror_path_rel - mirror_path_rel = mirror_directory_rel - # Install patchelf needed for relocate in linux test environment - iparser = argparse.ArgumentParser() - install.setup_parser(iparser) - # Install some packages with dependent packages - iargs = iparser.parse_args(['--no-cache', cspec.name]) - install.install(iparser, iargs) - - # setup argument parser - parser = argparse.ArgumentParser() - buildcache.setup_parser(parser) - - # set default buildcache args - create_args = ['create', '-a', '-u', '-r', '-d', - str(mirror_path_rel), - cspec.name] - - # create build cache with relatived rpaths - args = parser.parse_args(create_args) - buildcache.buildcache(parser, args) - # create mirror index - args = parser.parse_args(['update-index', '-d', 'file://%s' % str(mirror_path_rel)]) - buildcache.buildcache(parser, args) - # Uninstall the package and deps - uparser = argparse.ArgumentParser() - uninstall.setup_parser(uparser) - uargs = uparser.parse_args(['-y', '--dependents', gspec.name]) - uninstall.uninstall(uparser, uargs) + # Install 'corge' without using a cache + install_cmd('--no-cache', cspec.name) - bindist.clear_spec_cache() - spack.stage.purge() + # Create build cache with relative rpaths + buildcache_cmd( + 'create', '-aur', '-d', mirror_dir, cspec.name + ) + + # Create mirror index + mirror_url = 'file://%s' % mirror_dir + buildcache_cmd('update-index', '-d', mirror_url) + + # Uninstall the package and deps + uninstall_cmd('-y', '--dependents', gspec.name) @pytest.mark.requires_executables(*args) -@pytest.mark.disable_clean_stage_check @pytest.mark.maybeslow @pytest.mark.nomockstage -@pytest.mark.usefixtures('default_config', 'cache_directory', - 'install_dir_default_layout') -def test_relative_rpaths_install_default_layout(tmpdir, - install_mockery): +@pytest.mark.usefixtures( + 'default_config', 'cache_directory', 'install_dir_default_layout', + 'test_mirror' +) +def test_relative_rpaths_install_default_layout(mirror_dir): """ Test the creation and installation of buildcaches with relative rpaths into the default directory layout scheme. """ + gspec, cspec = Spec('garply').concretized(), Spec('corge').concretized() - gspec = Spec('garply') - gspec.concretize() - cspec = Spec('corge') - cspec.concretize() - - global mirror_path_rel - mparser = argparse.ArgumentParser() - mirror.setup_parser(mparser) - margs = mparser.parse_args( - ['add', '--scope', 'site', 'test-mirror-rel', 'file://%s' % mirror_path_rel]) - mirror.mirror(mparser, margs) - - iparser = argparse.ArgumentParser() - install.setup_parser(iparser) - - # setup argument parser - parser = argparse.ArgumentParser() - buildcache.setup_parser(parser) - - # set default buildcache args - install_args = ['install', '-a', '-u', '-f', - cspec.name] - - # install buildcache created with relativized rpaths - args = parser.parse_args(install_args) - buildcache.buildcache(parser, args) + # Install buildcache created with relativized rpaths + buildcache_cmd('install', '-auf', cspec.name) # This gives warning that spec is already installed - buildcache.buildcache(parser, args) + buildcache_cmd('install', '-auf', cspec.name) # Uninstall the package and deps - uparser = argparse.ArgumentParser() - uninstall.setup_parser(uparser) - uargs = uparser.parse_args(['-y', '--dependents', gspec.name]) - uninstall.uninstall(uparser, uargs) + uninstall_cmd('-y', '--dependents', gspec.name) - # install build cache - buildcache.buildcache(parser, args) + # Install build cache + buildcache_cmd('install', '-auf', cspec.name) - # test overwrite install - install_args.insert(install_args.index('-a'), '-f') - args = parser.parse_args(install_args) - buildcache.buildcache(parser, args) - - bindist.clear_spec_cache() - spack.stage.purge() - margs = mparser.parse_args( - ['rm', '--scope', 'site', 'test-mirror-rel']) - mirror.mirror(mparser, margs) + # Test overwrite install + buildcache_cmd('install', '-auf', cspec.name) @pytest.mark.requires_executables(*args) -@pytest.mark.disable_clean_stage_check @pytest.mark.maybeslow @pytest.mark.nomockstage -@pytest.mark.usefixtures('default_config', 'cache_directory', - 'install_dir_non_default_layout') -def test_relative_rpaths_install_nondefault(tmpdir, - install_mockery): +@pytest.mark.usefixtures( + 'default_config', 'cache_directory', 'install_dir_non_default_layout', + 'test_mirror' +) +def test_relative_rpaths_install_nondefault(mirror_dir): """ Test the installation of buildcaches with relativized rpaths into the non-default directory layout scheme. """ + cspec = Spec('corge').concretized() - gspec = Spec('garply') - gspec.concretize() - cspec = Spec('corge') - cspec.concretize() - - global mirror_path_rel - - mparser = argparse.ArgumentParser() - mirror.setup_parser(mparser) - margs = mparser.parse_args( - ['add', '--scope', 'site', 'test-mirror-rel', 'file://%s' % mirror_path_rel]) - mirror.mirror(mparser, margs) - - iparser = argparse.ArgumentParser() - install.setup_parser(iparser) - - # setup argument parser - parser = argparse.ArgumentParser() - buildcache.setup_parser(parser) - - # Set default buildcache args - install_args = ['install', '-a', '-u', '-f', '%s' % cspec.name] - - # test install in non-default install path scheme and relative path - args = parser.parse_args(install_args) - buildcache.buildcache(parser, args) - - bindist.clear_spec_cache() - spack.stage.purge() - margs = mparser.parse_args( - ['rm', '--scope', 'site', 'test-mirror-rel']) - mirror.mirror(mparser, margs) + # Test install in non-default install path scheme and relative path + buildcache_cmd('install', '-auf', cspec.name) @pytest.mark.skipif(not spack.util.gpg.has_gpg(), @@ -534,38 +357,20 @@ def test_push_and_fetch_keys(mock_gnupghome): @pytest.mark.requires_executables(*args) -@pytest.mark.disable_clean_stage_check @pytest.mark.maybeslow @pytest.mark.nomockstage -@pytest.mark.usefixtures('default_config', 'cache_directory', - 'install_dir_non_default_layout') -def test_built_spec_cache(tmpdir, - install_mockery): +@pytest.mark.usefixtures( + 'default_config', 'cache_directory', 'install_dir_non_default_layout', + 'test_mirror' +) +def test_built_spec_cache(mirror_dir): """ Because the buildcache list command fetches the buildcache index and uses it to populate the binary_distribution built spec cache, when this test calls get_mirrors_for_spec, it is testing the popluation of that cache from a buildcache index. """ - global mirror_path_rel - - mparser = argparse.ArgumentParser() - mirror.setup_parser(mparser) - margs = mparser.parse_args( - ['add', '--scope', 'site', 'test-mirror-rel', 'file://%s' % mirror_path_rel]) - mirror.mirror(mparser, margs) - - # setup argument parser - parser = argparse.ArgumentParser() - buildcache.setup_parser(parser) + buildcache_cmd('list', '-a', '-l') - list_args = ['list', '-a', '-l'] - args = parser.parse_args(list_args) - buildcache.buildcache(parser, args) - - gspec = Spec('garply') - gspec.concretize() - - cspec = Spec('corge') - cspec.concretize() + gspec, cspec = Spec('garply').concretized(), Spec('corge').concretized() full_hash_map = { 'garply': gspec.full_hash(), @@ -590,12 +395,6 @@ def test_built_spec_cache(tmpdir, assert(result['mirror_url'] not in cspec_mirrors) cspec_mirrors[result['mirror_url']] = True - bindist.clear_spec_cache() - - margs = mparser.parse_args( - ['rm', '--scope', 'site', 'test-mirror-rel']) - mirror.mirror(mparser, margs) - def fake_full_hash(spec): # Generate an arbitrary hash that is intended to be different than @@ -604,8 +403,11 @@ def fake_full_hash(spec): return 'tal4c7h4z0gqmixb1eqa92mjoybxn5l6' -def test_spec_needs_rebuild(install_mockery_mutable_config, mock_packages, - mock_fetch, monkeypatch, tmpdir): +@pytest.mark.usefixtures( + 'install_mockery_mutable_config', 'mock_packages', 'mock_fetch', + 'test_mirror' +) +def test_spec_needs_rebuild(monkeypatch, tmpdir): """Make sure needs_rebuild properly compares remote full_hash against locally computed one, avoiding unnecessary rebuilds""" @@ -613,8 +415,6 @@ def test_spec_needs_rebuild(install_mockery_mutable_config, mock_packages, mirror_dir = tmpdir.join('mirror_dir') mirror_url = 'file://{0}'.format(mirror_dir.strpath) - mirror_cmd('add', 'test-mirror', mirror_url) - s = Spec('libdwarf').concretized() # Install a package @@ -683,24 +483,25 @@ def test_generate_indices_exception(monkeypatch, capfd): assert expect in err -@pytest.mark.usefixtures('mock_fetch') -def test_update_sbang(tmpdir, install_mockery, function_mirror): - """ - Test the creation and installation of buildcaches with default rpaths +@pytest.mark.usefixtures('mock_fetch', 'install_mockery') +def test_update_sbang(tmpdir, test_mirror): + """Test the creation and installation of buildcaches with default rpaths into the non-default directory layout scheme, triggering an update of the sbang. """ - + scheme = os.path.join( + '${name}', '${version}', + '${architecture}-${compiler.name}-${compiler.version}-${hash}' + ) # Save the original store and layout before we touch ANYTHING. - real_store = spack.store.store - real_layout = spack.store.layout + real_store, real_layout = spack.store.store, spack.store.layout # Concretize a package with some old-fashioned sbang lines. sspec = Spec('old-sbang') sspec.concretize() # Need a fake mirror with *function* scope. - mirror_dir = function_mirror + mirror_dir = test_mirror # Assumes all commands will concretize sspec the same way. install_cmd('--no-cache', sspec.name) @@ -718,9 +519,11 @@ def test_update_sbang(tmpdir, install_mockery, function_mirror): try: # New install tree locations... # Too fine-grained to do be done in a fixture - spack.store.store = spack.store.Store(str(tmpdir.join('newtree'))) - spack.store.layout = YamlDirectoryLayout(str(tmpdir.join('newtree')), - path_scheme=ndef_install_path_scheme) # noqa: E501 + newtree_dir = tmpdir.join('newtree') + spack.store.store = spack.store.Store(str(newtree_dir)) + spack.store.layout = YamlDirectoryLayout( + str(newtree_dir), path_scheme=scheme + ) # Install package from buildcache buildcache_cmd('install', '-a', '-u', '-f', sspec.name) |