Install: Add use-buildcache option to install (#32537)
Install: Add use-buildcache option to install * Allow differentiating between top level packages and dependencies when determining whether to install from the cache or not. * Add unit test for --use-buildcache * Use metavar to display use-buildcache options. * Update spack-completion
This commit is contained in:
		@@ -5,6 +5,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import argparse
 | 
					import argparse
 | 
				
			||||||
import os
 | 
					import os
 | 
				
			||||||
 | 
					import re
 | 
				
			||||||
import shutil
 | 
					import shutil
 | 
				
			||||||
import sys
 | 
					import sys
 | 
				
			||||||
import textwrap
 | 
					import textwrap
 | 
				
			||||||
@@ -31,10 +32,50 @@
 | 
				
			|||||||
level = "short"
 | 
					level = "short"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Pass in the value string passed to use-buildcache and get back
 | 
				
			||||||
 | 
					# the package and dependencies values.
 | 
				
			||||||
 | 
					def parse_use_buildcache(opt):
 | 
				
			||||||
 | 
					    bc_keys = ["package:", "dependencies:", ""]
 | 
				
			||||||
 | 
					    bc_values = ["only", "never", "auto"]
 | 
				
			||||||
 | 
					    kv_list = re.findall("([a-z]+:)?([a-z]+)", opt)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Verify keys and values
 | 
				
			||||||
 | 
					    bc_map = {k: v for k, v in kv_list if k in bc_keys and v in bc_values}
 | 
				
			||||||
 | 
					    if not len(kv_list) == len(bc_map):
 | 
				
			||||||
 | 
					        tty.error("Unrecognized arguments passed to use-buildcache")
 | 
				
			||||||
 | 
					        tty.error(
 | 
				
			||||||
 | 
					            "Expected: --use-buildcache "
 | 
				
			||||||
 | 
					            "[[auto|only|never],[package:[auto|only|never]],[dependencies:[auto|only|never]]]"
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        exit(1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for _group in ["package:", "dependencies:"]:
 | 
				
			||||||
 | 
					        if _group not in bc_map:
 | 
				
			||||||
 | 
					            if "" in bc_map:
 | 
				
			||||||
 | 
					                bc_map[_group] = bc_map[""]
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                bc_map[_group] = "auto"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return bc_map["package:"], bc_map["dependencies:"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Determine value of cache flag
 | 
				
			||||||
 | 
					def cache_opt(default_opt, use_buildcache):
 | 
				
			||||||
 | 
					    if use_buildcache == "auto":
 | 
				
			||||||
 | 
					        return default_opt
 | 
				
			||||||
 | 
					    elif use_buildcache == "only":
 | 
				
			||||||
 | 
					        return True
 | 
				
			||||||
 | 
					    elif use_buildcache == "never":
 | 
				
			||||||
 | 
					        return False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def install_kwargs_from_args(args):
 | 
					def install_kwargs_from_args(args):
 | 
				
			||||||
    """Translate command line arguments into a dictionary that will be passed
 | 
					    """Translate command line arguments into a dictionary that will be passed
 | 
				
			||||||
    to the package installer.
 | 
					    to the package installer.
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pkg_use_bc, dep_use_bc = parse_use_buildcache(args.use_buildcache)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return {
 | 
					    return {
 | 
				
			||||||
        "fail_fast": args.fail_fast,
 | 
					        "fail_fast": args.fail_fast,
 | 
				
			||||||
        "keep_prefix": args.keep_prefix,
 | 
					        "keep_prefix": args.keep_prefix,
 | 
				
			||||||
@@ -44,8 +85,10 @@ def install_kwargs_from_args(args):
 | 
				
			|||||||
        "verbose": args.verbose or args.install_verbose,
 | 
					        "verbose": args.verbose or args.install_verbose,
 | 
				
			||||||
        "fake": args.fake,
 | 
					        "fake": args.fake,
 | 
				
			||||||
        "dirty": args.dirty,
 | 
					        "dirty": args.dirty,
 | 
				
			||||||
        "use_cache": args.use_cache,
 | 
					        "package_use_cache": cache_opt(args.use_cache, pkg_use_bc),
 | 
				
			||||||
        "cache_only": args.cache_only,
 | 
					        "package_cache_only": cache_opt(args.cache_only, pkg_use_bc),
 | 
				
			||||||
 | 
					        "dependencies_use_cache": cache_opt(args.use_cache, dep_use_bc),
 | 
				
			||||||
 | 
					        "dependencies_cache_only": cache_opt(args.cache_only, dep_use_bc),
 | 
				
			||||||
        "include_build_deps": args.include_build_deps,
 | 
					        "include_build_deps": args.include_build_deps,
 | 
				
			||||||
        "explicit": True,  # Use true as a default for install command
 | 
					        "explicit": True,  # Use true as a default for install command
 | 
				
			||||||
        "stop_at": args.until,
 | 
					        "stop_at": args.until,
 | 
				
			||||||
@@ -123,6 +166,18 @@ def setup_parser(subparser):
 | 
				
			|||||||
        default=False,
 | 
					        default=False,
 | 
				
			||||||
        help="only install package from binary mirrors",
 | 
					        help="only install package from binary mirrors",
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
 | 
					    cache_group.add_argument(
 | 
				
			||||||
 | 
					        "--use-buildcache",
 | 
				
			||||||
 | 
					        dest="use_buildcache",
 | 
				
			||||||
 | 
					        default="package:auto,dependencies:auto",
 | 
				
			||||||
 | 
					        metavar="[{auto,only,never},][package:{auto,only,never},][dependencies:{auto,only,never}]",
 | 
				
			||||||
 | 
					        help="""select the mode of buildcache for the 'package' and 'dependencies'.
 | 
				
			||||||
 | 
					Default: package:auto,dependencies:auto
 | 
				
			||||||
 | 
					- `auto` behaves like --use-cache
 | 
				
			||||||
 | 
					- `only` behaves like --cache-only
 | 
				
			||||||
 | 
					- `never` behaves like --no-cache
 | 
				
			||||||
 | 
					""",
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    subparser.add_argument(
 | 
					    subparser.add_argument(
 | 
				
			||||||
        "--include-build-deps",
 | 
					        "--include-build-deps",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1182,12 +1182,12 @@ def _install_task(self, task):
 | 
				
			|||||||
        Args:
 | 
					        Args:
 | 
				
			||||||
            task (BuildTask): the installation build task for a package"""
 | 
					            task (BuildTask): the installation build task for a package"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        install_args = task.request.install_args
 | 
					 | 
				
			||||||
        cache_only = install_args.get("cache_only")
 | 
					 | 
				
			||||||
        explicit = task.explicit
 | 
					        explicit = task.explicit
 | 
				
			||||||
 | 
					        install_args = task.request.install_args
 | 
				
			||||||
 | 
					        cache_only = task.cache_only
 | 
				
			||||||
 | 
					        use_cache = task.use_cache
 | 
				
			||||||
        tests = install_args.get("tests")
 | 
					        tests = install_args.get("tests")
 | 
				
			||||||
        unsigned = install_args.get("unsigned")
 | 
					        unsigned = install_args.get("unsigned")
 | 
				
			||||||
        use_cache = install_args.get("use_cache")
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        pkg, pkg_id = task.pkg, task.pkg_id
 | 
					        pkg, pkg_id = task.pkg, task.pkg_id
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -2220,7 +2220,29 @@ def flag_installed(self, installed):
 | 
				
			|||||||
    @property
 | 
					    @property
 | 
				
			||||||
    def explicit(self):
 | 
					    def explicit(self):
 | 
				
			||||||
        """The package was explicitly requested by the user."""
 | 
					        """The package was explicitly requested by the user."""
 | 
				
			||||||
        return self.pkg == self.request.pkg and self.request.install_args.get("explicit", True)
 | 
					        return self.is_root and self.request.install_args.get("explicit", True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @property
 | 
				
			||||||
 | 
					    def is_root(self):
 | 
				
			||||||
 | 
					        """The package was requested directly, but may or may not be explicit
 | 
				
			||||||
 | 
					        in an environment."""
 | 
				
			||||||
 | 
					        return self.pkg == self.request.pkg
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @property
 | 
				
			||||||
 | 
					    def use_cache(self):
 | 
				
			||||||
 | 
					        _use_cache = True
 | 
				
			||||||
 | 
					        if self.is_root:
 | 
				
			||||||
 | 
					            return self.request.install_args.get("package_use_cache", _use_cache)
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            return self.request.install_args.get("dependencies_use_cache", _use_cache)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @property
 | 
				
			||||||
 | 
					    def cache_only(self):
 | 
				
			||||||
 | 
					        _cache_only = False
 | 
				
			||||||
 | 
					        if self.is_root:
 | 
				
			||||||
 | 
					            return self.request.install_args.get("package_cache_only", _cache_only)
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            return self.request.install_args.get("dependencies_cache_only", _cache_only)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @property
 | 
					    @property
 | 
				
			||||||
    def key(self):
 | 
					    def key(self):
 | 
				
			||||||
@@ -2302,21 +2324,23 @@ def __str__(self):
 | 
				
			|||||||
    def _add_default_args(self):
 | 
					    def _add_default_args(self):
 | 
				
			||||||
        """Ensure standard install options are set to at least the default."""
 | 
					        """Ensure standard install options are set to at least the default."""
 | 
				
			||||||
        for arg, default in [
 | 
					        for arg, default in [
 | 
				
			||||||
            ("cache_only", False),
 | 
					 | 
				
			||||||
            ("context", "build"),  # installs *always* build
 | 
					            ("context", "build"),  # installs *always* build
 | 
				
			||||||
 | 
					            ("dependencies_cache_only", False),
 | 
				
			||||||
 | 
					            ("dependencies_use_cache", True),
 | 
				
			||||||
            ("dirty", False),
 | 
					            ("dirty", False),
 | 
				
			||||||
            ("fail_fast", False),
 | 
					            ("fail_fast", False),
 | 
				
			||||||
            ("fake", False),
 | 
					            ("fake", False),
 | 
				
			||||||
            ("install_deps", True),
 | 
					            ("install_deps", True),
 | 
				
			||||||
            ("install_package", True),
 | 
					            ("install_package", True),
 | 
				
			||||||
            ("install_source", False),
 | 
					            ("install_source", False),
 | 
				
			||||||
 | 
					            ("package_cache_only", False),
 | 
				
			||||||
 | 
					            ("package_use_cache", True),
 | 
				
			||||||
            ("keep_prefix", False),
 | 
					            ("keep_prefix", False),
 | 
				
			||||||
            ("keep_stage", False),
 | 
					            ("keep_stage", False),
 | 
				
			||||||
            ("restage", False),
 | 
					            ("restage", False),
 | 
				
			||||||
            ("skip_patch", False),
 | 
					            ("skip_patch", False),
 | 
				
			||||||
            ("tests", False),
 | 
					            ("tests", False),
 | 
				
			||||||
            ("unsigned", False),
 | 
					            ("unsigned", False),
 | 
				
			||||||
            ("use_cache", True),
 | 
					 | 
				
			||||||
            ("verbose", False),
 | 
					            ("verbose", False),
 | 
				
			||||||
        ]:
 | 
					        ]:
 | 
				
			||||||
            _ = self.install_args.setdefault(arg, default)
 | 
					            _ = self.install_args.setdefault(arg, default)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,6 +5,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import argparse
 | 
					import argparse
 | 
				
			||||||
import filecmp
 | 
					import filecmp
 | 
				
			||||||
 | 
					import itertools
 | 
				
			||||||
import os
 | 
					import os
 | 
				
			||||||
import re
 | 
					import re
 | 
				
			||||||
import sys
 | 
					import sys
 | 
				
			||||||
@@ -14,6 +15,7 @@
 | 
				
			|||||||
from six.moves import builtins
 | 
					from six.moves import builtins
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import llnl.util.filesystem as fs
 | 
					import llnl.util.filesystem as fs
 | 
				
			||||||
 | 
					import llnl.util.tty as tty
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import spack.cmd.install
 | 
					import spack.cmd.install
 | 
				
			||||||
import spack.compilers as compilers
 | 
					import spack.compilers as compilers
 | 
				
			||||||
@@ -1090,3 +1092,80 @@ def test_install_callbacks_fail(install_mockery, mock_fetch, name, method):
 | 
				
			|||||||
    assert output.count(method) == 2
 | 
					    assert output.count(method) == 2
 | 
				
			||||||
    assert output.count("method not implemented") == 1
 | 
					    assert output.count("method not implemented") == 1
 | 
				
			||||||
    assert output.count("TestFailure: 1 tests failed") == 1
 | 
					    assert output.count("TestFailure: 1 tests failed") == 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def test_install_use_buildcache(
 | 
				
			||||||
 | 
					    capsys,
 | 
				
			||||||
 | 
					    mock_packages,
 | 
				
			||||||
 | 
					    mock_fetch,
 | 
				
			||||||
 | 
					    mock_archive,
 | 
				
			||||||
 | 
					    mock_binary_index,
 | 
				
			||||||
 | 
					    tmpdir,
 | 
				
			||||||
 | 
					    install_mockery_mutable_config,
 | 
				
			||||||
 | 
					):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Make sure installing with use-buildcache behaves correctly.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    package_name = "dependent-install"
 | 
				
			||||||
 | 
					    dependency_name = "dependency-install"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def validate(mode, out, pkg):
 | 
				
			||||||
 | 
					        def assert_auto(pkg, out):
 | 
				
			||||||
 | 
					            assert "==> Extracting {0}".format(pkg) in out
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        def assert_only(pkg, out):
 | 
				
			||||||
 | 
					            assert "==> Extracting {0}".format(pkg) in out
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        def assert_never(pkg, out):
 | 
				
			||||||
 | 
					            assert "==> {0}: Executing phase: 'install'".format(pkg) in out
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if mode == "auto":
 | 
				
			||||||
 | 
					            assert_auto(pkg, out)
 | 
				
			||||||
 | 
					        elif mode == "only":
 | 
				
			||||||
 | 
					            assert_only(pkg, out)
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            assert_never(pkg, out)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def install_use_buildcache(opt):
 | 
				
			||||||
 | 
					        out = install(
 | 
				
			||||||
 | 
					            "--no-check-signature", "--use-buildcache", opt, package_name, fail_on_error=True
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        pkg_opt, dep_opt = spack.cmd.install.parse_use_buildcache(opt)
 | 
				
			||||||
 | 
					        validate(dep_opt, out, dependency_name)
 | 
				
			||||||
 | 
					        validate(pkg_opt, out, package_name)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Clean up installed packages
 | 
				
			||||||
 | 
					        uninstall("-y", "-a")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Setup the mirror
 | 
				
			||||||
 | 
					    # Create a temp mirror directory for buildcache usage
 | 
				
			||||||
 | 
					    mirror_dir = tmpdir.join("mirror_dir")
 | 
				
			||||||
 | 
					    mirror_url = "file://{0}".format(mirror_dir.strpath)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Populate the buildcache
 | 
				
			||||||
 | 
					    install(package_name)
 | 
				
			||||||
 | 
					    buildcache("create", "-u", "-a", "-f", "-d", mirror_dir.strpath, package_name, dependency_name)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Uninstall the all of the packages for clean slate
 | 
				
			||||||
 | 
					    uninstall("-y", "-a")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Configure the mirror where we put that buildcache w/ the compiler
 | 
				
			||||||
 | 
					    mirror("add", "test-mirror", mirror_url)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    with capsys.disabled():
 | 
				
			||||||
 | 
					        # Install using the matrix of possible combinations with --use-buildcache
 | 
				
			||||||
 | 
					        for pkg, deps in itertools.product(["auto", "only", "never"], repeat=2):
 | 
				
			||||||
 | 
					            tty.debug(
 | 
				
			||||||
 | 
					                "Testing `spack install --use-buildcache package:{0},dependencies:{1}`".format(
 | 
				
			||||||
 | 
					                    pkg, deps
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            install_use_buildcache("package:{0},dependencies:{1}".format(pkg, deps))
 | 
				
			||||||
 | 
					            install_use_buildcache("dependencies:{0},package:{1}".format(deps, pkg))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Install using a default override option
 | 
				
			||||||
 | 
					        # Alternative to --cache-only (always) or --no-cache (never)
 | 
				
			||||||
 | 
					        for opt in ["auto", "only", "never"]:
 | 
				
			||||||
 | 
					            install_use_buildcache(opt)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1188,7 +1188,7 @@ _spack_info() {
 | 
				
			|||||||
_spack_install() {
 | 
					_spack_install() {
 | 
				
			||||||
    if $list_options
 | 
					    if $list_options
 | 
				
			||||||
    then
 | 
					    then
 | 
				
			||||||
        SPACK_COMPREPLY="-h --help --only -u --until -j --jobs --overwrite --fail-fast --keep-prefix --keep-stage --dont-restage --use-cache --no-cache --cache-only --include-build-deps --no-check-signature --show-log-on-error --source -n --no-checksum --deprecated -v --verbose --fake --only-concrete --no-add -f --file --clean --dirty --test --log-format --log-file --help-cdash --cdash-upload-url --cdash-build --cdash-site --cdash-track --cdash-buildstamp -y --yes-to-all -U --fresh --reuse"
 | 
					        SPACK_COMPREPLY="-h --help --only -u --until -j --jobs --overwrite --fail-fast --keep-prefix --keep-stage --dont-restage --use-cache --no-cache --cache-only --use-buildcache --include-build-deps --no-check-signature --show-log-on-error --source -n --no-checksum --deprecated -v --verbose --fake --only-concrete --no-add -f --file --clean --dirty --test --log-format --log-file --help-cdash --cdash-upload-url --cdash-build --cdash-site --cdash-track --cdash-buildstamp -y --yes-to-all -U --fresh --reuse"
 | 
				
			||||||
    else
 | 
					    else
 | 
				
			||||||
        _all_packages
 | 
					        _all_packages
 | 
				
			||||||
    fi
 | 
					    fi
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user