diff --git a/lib/spack/spack/cmd/install.py b/lib/spack/spack/cmd/install.py index e4fd74f6b5..12a33a0370 100644 --- a/lib/spack/spack/cmd/install.py +++ b/lib/spack/spack/cmd/install.py @@ -92,7 +92,8 @@ def setup_parser(subparser): '--fake', action='store_true', help="fake install for debug purposes.") subparser.add_argument( - '-f', '--file', action='store_true', + '-f', '--file', action='append', default=[], + dest='specfiles', metavar='SPEC_YAML_FILE', help="install from file. Read specs to install from .yaml files") cd_group = subparser.add_mutually_exclusive_group() @@ -377,8 +378,8 @@ def install_spec(cli_args, kwargs, spec): def install(parser, args, **kwargs): - if not args.package: - tty.die("install requires at least one package argument") + if not args.package and not args.specfiles: + tty.die("install requires at least one package argument or yaml file") if args.jobs is not None: if args.jobs <= 0: @@ -405,6 +406,7 @@ def install(parser, args, **kwargs): if args.run_tests: tty.warn("Deprecated option: --run-tests: use --test=all instead") + # 1. Abstract specs from cli specs = spack.cmd.parse_specs(args.package) if args.test == 'all' or args.run_tests: spack.package_testing.test_all() @@ -412,23 +414,21 @@ def install(parser, args, **kwargs): for spec in specs: spack.package_testing.test(spec.name) - # Spec from cli - specs = [] - if args.file: - for file in args.package: - with open(file, 'r') as f: - s = spack.spec.Spec.from_yaml(f) + specs = spack.cmd.parse_specs(args.package, concretize=True) - if s.concretized().dag_hash() != s.dag_hash(): - msg = 'skipped invalid file "{0}". ' - msg += 'The file does not contain a concrete spec.' - tty.warn(msg.format(file)) - continue + # 2. Concrete specs from yaml files + for file in args.specfiles: + with open(file, 'r') as f: + s = spack.spec.Spec.from_yaml(f) - specs.append(s.concretized()) + if s.concretized().dag_hash() != s.dag_hash(): + msg = 'skipped invalid file "{0}". ' + msg += 'The file does not contain a concrete spec.' + tty.warn(msg.format(file)) + continue + + specs.append(s.concretized()) - else: - specs = spack.cmd.parse_specs(args.package, concretize=True) if len(specs) == 0: tty.die('The `spack install` command requires a spec to install.') diff --git a/lib/spack/spack/test/cmd/install.py b/lib/spack/spack/test/cmd/install.py index 8e5502049d..384a9ec3d0 100644 --- a/lib/spack/spack/test/cmd/install.py +++ b/lib/spack/spack/test/cmd/install.py @@ -259,10 +259,41 @@ def test_install_from_file(spec, concretize, error_code, tmpdir): if concretize: spec.concretize() - with fs.working_dir(str(tmpdir)): - # A non-concrete spec will fail to be installed - with open('spec.yaml', 'w') as f: - spec.to_yaml(f) - install('-f', 'spec.yaml', fail_on_error=False) + specfile = tmpdir.join('spec.yaml') + with specfile.open('w') as f: + spec.to_yaml(f) + + # Relative path to specfile (regression for #6906) + with fs.working_dir(specfile.dirname): + # A non-concrete spec will fail to be installed + install('-f', specfile.basename, fail_on_error=False) assert install.returncode == error_code + + # Absolute path to specfile (regression for #6983) + install('-f', str(specfile), fail_on_error=False) + assert install.returncode == error_code + + +@pytest.mark.usefixtures('noop_install', 'config') +@pytest.mark.parametrize('clispecs,filespecs', [ + [[], ['mpi']], + [[], ['mpi', 'boost']], + [['cmake'], ['mpi']], + [['cmake', 'libelf'], []], + [['cmake', 'libelf'], ['mpi', 'boost']], +]) +def test_install_mix_cli_and_files(clispecs, filespecs, tmpdir): + + args = clispecs + + for spec in filespecs: + filepath = tmpdir.join(spec + '.yaml') + args = ['-f', str(filepath)] + args + s = Spec(spec) + s.concretize() + with filepath.open('w') as f: + s.to_yaml(f) + + install(*args, fail_on_error=False) + assert install.returncode == 0