Features/compiler config consistency (#2999)

* default scope for config command is made consistent with cmd/__init__ default

* dont specify a scope when looking for compilers with a matching spec (since compiler concretization is scope-independent)

* config edit should default to platform-specific file only for compilers

* when duplicate compiler specs are detected, the exception raised now points the user to the files where the duplicates appear

* updated error message to emphasize that a spec is duplicated (since multiple specs can reference the same compiler)

* 'spack compilers' is now also broken down into sections by os and target

* Added tests for new compiler methods
This commit is contained in:
scheibelp 2017-03-10 13:58:48 -08:00 committed by becker33
parent 68f5b9adf8
commit e727f56d89
6 changed files with 181 additions and 70 deletions

View file

@ -96,8 +96,7 @@ def compiler_find(args):
for c in compilers:
arch_spec = ArchSpec(None, c.operating_system, c.target)
same_specs = spack.compilers.compilers_for_spec(c.spec,
arch_spec,
args.scope)
arch_spec)
if not same_specs:
new_compilers.append(c)
@ -165,14 +164,18 @@ def compiler_info(args):
def compiler_list(args):
tty.msg("Available compilers")
index = index_by(spack.compilers.all_compilers(scope=args.scope), 'name')
for i, (name, compilers) in enumerate(index.items()):
index = index_by(spack.compilers.all_compilers(scope=args.scope),
lambda c: (c.spec.name, c.operating_system, c.target))
for i, (key, compilers) in enumerate(index.items()):
if i >= 1:
print
cname = "%s{%s}" % (spack.spec.compiler_color, name)
name, os, target = key
os_str = os
if target:
os_str += "-%s" % target
cname = "%s{%s} %s" % (spack.spec.compiler_color, name, os_str)
tty.hline(colorize(cname), char='-')
colify(reversed(sorted(compilers)))
colify(reversed(sorted(c.spec for c in compilers)))
def compiler(parser, args):

View file

@ -55,7 +55,10 @@ def config_get(args):
def config_edit(args):
if not args.scope:
args.scope = 'user'
if args.section == 'compilers':
args.scope = spack.cmd.default_modify_scope
else:
args.scope = 'user'
if not args.section:
args.section = None
config_file = spack.config.get_config_filename(args.scope, args.section)

View file

@ -164,7 +164,7 @@ def all_compilers_config(scope=None, init_config=True):
return _cache_config_file
def all_compilers(scope=None, init_config=True):
def all_compiler_specs(scope=None, init_config=True):
# Return compiler specs from the merged config.
return [spack.spec.CompilerSpec(s['compiler']['spec'])
for s in all_compilers_config(scope, init_config)]
@ -203,72 +203,93 @@ def supported(compiler_spec):
def find(compiler_spec, scope=None):
"""Return specs of available compilers that match the supplied
compiler spec. Return an empty list if nothing found."""
return [c for c in all_compilers(scope) if c.satisfies(compiler_spec)]
return [c for c in all_compiler_specs(scope) if c.satisfies(compiler_spec)]
def all_compilers(scope=None):
config = get_compiler_config(scope)
compilers = list()
for items in config:
items = items['compiler']
compilers.append(compiler_from_config_entry(items))
return compilers
@_auto_compiler_spec
def compilers_for_spec(compiler_spec, arch_spec=None, scope=None):
def compilers_for_spec(compiler_spec, arch_spec=None, scope=None,
use_cache=True):
"""This gets all compilers that satisfy the supplied CompilerSpec.
Returns an empty list if none are found.
"""
config = all_compilers_config(scope)
def get_compilers(cspec):
compilers = []
for items in config:
items = items['compiler']
if items['spec'] != str(cspec):
continue
# If an arch spec is given, confirm that this compiler
# is for the given operating system
os = items.get('operating_system', None)
if arch_spec and os != arch_spec.platform_os:
continue
# If an arch spec is given, confirm that this compiler
# is for the given target. If the target is 'any', match
# any given arch spec. If the compiler has no assigned
# target this is an old compiler config file, skip this logic.
target = items.get('target', None)
if arch_spec and target and (target != arch_spec.target and
target != 'any'):
continue
if not ('paths' in items and
all(n in items['paths'] for n in _path_instance_vars)):
raise InvalidCompilerConfigurationError(cspec)
cls = class_for_compiler_name(cspec.name)
compiler_paths = []
for c in _path_instance_vars:
compiler_path = items['paths'][c]
if compiler_path != 'None':
compiler_paths.append(compiler_path)
else:
compiler_paths.append(None)
mods = items.get('modules')
if mods == 'None':
mods = []
alias = items.get('alias', None)
compiler_flags = items.get('flags', {})
environment = items.get('environment', {})
extra_rpaths = items.get('extra_rpaths', [])
compilers.append(
cls(cspec, os, target, compiler_paths, mods, alias,
environment, extra_rpaths, **compiler_flags))
return compilers
if use_cache:
config = all_compilers_config(scope)
else:
config = get_compiler_config(scope)
matches = set(find(compiler_spec, scope))
compilers = []
for cspec in matches:
compilers.extend(get_compilers(cspec))
compilers.extend(get_compilers(cspec, config, arch_spec))
return compilers
def compiler_from_config_entry(items):
cspec = spack.spec.CompilerSpec(items['spec'])
os = items.get('operating_system', None)
target = items.get('target', None)
if not ('paths' in items and
all(n in items['paths'] for n in _path_instance_vars)):
raise InvalidCompilerConfigurationError(cspec)
cls = class_for_compiler_name(cspec.name)
compiler_paths = []
for c in _path_instance_vars:
compiler_path = items['paths'][c]
if compiler_path != 'None':
compiler_paths.append(compiler_path)
else:
compiler_paths.append(None)
mods = items.get('modules')
if mods == 'None':
mods = []
alias = items.get('alias', None)
compiler_flags = items.get('flags', {})
environment = items.get('environment', {})
extra_rpaths = items.get('extra_rpaths', [])
return cls(cspec, os, target, compiler_paths, mods, alias,
environment, extra_rpaths, **compiler_flags)
def get_compilers(cspec, config, arch_spec=None):
compilers = []
for items in config:
items = items['compiler']
if items['spec'] != str(cspec):
continue
# If an arch spec is given, confirm that this compiler
# is for the given operating system
os = items.get('operating_system', None)
if arch_spec and os != arch_spec.platform_os:
continue
# If an arch spec is given, confirm that this compiler
# is for the given target. If the target is 'any', match
# any given arch spec. If the compiler has no assigned
# target this is an old compiler config file, skip this logic.
target = items.get('target', None)
if arch_spec and target and (target != arch_spec.target and
target != 'any'):
continue
compilers.append(compiler_from_config_entry(items))
return compilers
@ -283,10 +304,28 @@ def compiler_for_spec(compiler_spec, arch_spec):
if len(compilers) < 1:
raise NoCompilerForSpecError(compiler_spec, arch_spec.platform_os)
if len(compilers) > 1:
raise CompilerSpecInsufficientlySpecificError(compiler_spec)
raise CompilerDuplicateError(compiler_spec, arch_spec)
return compilers[0]
@_auto_compiler_spec
def get_compiler_duplicates(compiler_spec, arch_spec):
config_scopes = spack.config.config_scopes
scope_to_compilers = dict()
for scope in config_scopes:
compilers = compilers_for_spec(compiler_spec, arch_spec=arch_spec,
scope=scope, use_cache=False)
if compilers:
scope_to_compilers[scope] = compilers
cfg_file_to_duplicates = dict()
for scope, compilers in scope_to_compilers.iteritems():
config_file = config_scopes[scope].get_section_filename('compilers')
cfg_file_to_duplicates[config_file] = compilers
return cfg_file_to_duplicates
def class_for_compiler_name(compiler_name):
"""Given a compiler module name, get the corresponding Compiler class."""
assert(supported(compiler_name))
@ -341,6 +380,24 @@ def __init__(self, compiler_spec, target):
% (target, compiler_spec))
class CompilerDuplicateError(spack.error.SpackError):
def __init__(self, compiler_spec, arch_spec):
config_file_to_duplicates = get_compiler_duplicates(
compiler_spec, arch_spec)
duplicate_table = list(
(x, len(y)) for x, y in config_file_to_duplicates.iteritems())
descriptor = lambda num: 'time' if num == 1 else 'times'
duplicate_msg = (
lambda cfgfile, count: "{0}: {1} {2}".format(
cfgfile, str(count), descriptor(count)))
msg = (
"Compiler configuration contains entries with duplicate" +
" specification ({0}, {1})".format(compiler_spec, arch_spec) +
" in the following files:\n\t" +
'\n\t'.join(duplicate_msg(x, y) for x, y in duplicate_table))
super(CompilerDuplicateError, self).__init__(msg)
class CompilerSpecInsufficientlySpecificError(spack.error.SpackError):
def __init__(self, compiler_spec):
super(CompilerSpecInsufficientlySpecificError, self).__init__(

View file

@ -315,7 +315,7 @@ def concretize_compiler(self, spec):
def _proper_compiler_style(cspec, aspec):
return spack.compilers.compilers_for_spec(cspec, arch_spec=aspec)
all_compilers = spack.compilers.all_compilers()
all_compilers = spack.compilers.all_compiler_specs()
if (spec.compiler and
spec.compiler.concrete and

View file

@ -71,12 +71,12 @@ def test_compiler_remove(self):
all=True, compiler_spec='gcc@4.5.0', add_paths=[], scope=None
)
spack.cmd.compiler.compiler_remove(args)
compilers = spack.compilers.all_compilers()
compilers = spack.compilers.all_compiler_specs()
assert spack.spec.CompilerSpec("gcc@4.5.0") not in compilers
def test_compiler_add(self, mock_compiler_dir):
# Compilers available by default.
old_compilers = set(spack.compilers.all_compilers())
old_compilers = set(spack.compilers.all_compiler_specs())
args = spack.util.pattern.Bunch(
all=None,
@ -87,7 +87,7 @@ def test_compiler_add(self, mock_compiler_dir):
spack.cmd.compiler.compiler_find(args)
# Ensure new compiler is in there
new_compilers = set(spack.compilers.all_compilers())
new_compilers = set(spack.compilers.all_compiler_specs())
new_compiler = new_compilers - old_compilers
assert new_compiler
c = new_compiler.pop()

View file

@ -0,0 +1,48 @@
##############################################################################
# Copyright (c) 2013-2016, Lawrence Livermore National Security, LLC.
# Produced at the Lawrence Livermore National Laboratory.
#
# This file is part of Spack.
# Created by Todd Gamblin, tgamblin@llnl.gov, All rights reserved.
# LLNL-CODE-647188
#
# For details, see https://github.com/llnl/spack
# Please also see the LICENSE file for our notice and the LGPL.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License (as
# published by the Free Software Foundation) version 2.1, February 1999.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the IMPLIED WARRANTY OF
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the terms and
# conditions of the GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
##############################################################################
import pytest
import spack.spec
import spack.compilers as compilers
@pytest.mark.usefixtures('config')
class TestCompilers(object):
def test_get_compiler_duplicates(self):
# In this case there is only one instance of the specified compiler in
# the test configuration (so it is not actually a duplicate), but the
# method behaves the same.
cfg_file_to_duplicates = compilers.get_compiler_duplicates(
'gcc@4.5.0', spack.spec.ArchSpec('cray-CNL-xeon'))
assert len(cfg_file_to_duplicates) == 1
cfg_file, duplicates = cfg_file_to_duplicates.iteritems().next()
assert len(duplicates) == 1
def test_all_compilers(self):
all_compilers = compilers.all_compilers()
filtered = list(x for x in all_compilers if str(x.spec) == 'clang@3.3')
filtered = list(x for x in filtered if x.operating_system == 'SuSE11')
assert len(filtered) == 1