compiler flags: add cxx98 standard support (#7601)

The following improvements are made to cxx standard support
(e.g. compiler.cxxNN_flag functions) in compilers:

* Add cxx98_flag property
* Add support for throwing an exception when a flag is not supported (previously
  if a flag was not supported the application was terminated with tty.die)
* The name of the flag associated with e.g. c++14 standard support changes for
  different compiler versions (e.g. c++1y vs c++14). This makes a few corrections
  on what flag to return for which version.
* Added tests to confirm that versions report expected flags for various c++
  standards (or raise an exception for versions that don't provide a given cxx
  standard)

Note that if a given cxx standard is the default, the associated flag property will
return ""; cxx98 is assumed to be the default standard so this is the behavior for
the associated property in the base compiler class.

Package changes:

* Improvements to the boost spec to take advantage of the improved standard
  flag facility.
* Update the clingo spec to catch the new exception rather than look for an
  empty flag to indicate non-support (which is not part of the compiler flag API)
This commit is contained in:
Chris Green 2018-06-08 15:49:31 -05:00 committed by scheibelp
parent ceb2790f30
commit 15c98fa57c
10 changed files with 328 additions and 52 deletions

View File

@ -2654,8 +2654,8 @@ In rare circumstances such as compiling and running small unit tests, a
package developer may need to know what are the appropriate compiler
flags to enable features like ``OpenMP``, ``c++11``, ``c++14`` and
alike. To that end the compiler classes in ``spack`` implement the
following **properties**: ``openmp_flag``, ``cxx11_flag``,
``cxx14_flag``, which can be accessed in a package by
following **properties**: ``openmp_flag``, ``cxx98_flag``, ``cxx11_flag``,
``cxx14_flag``, and ``cxx17_flag``, which can be accessed in a package by
``self.compiler.cxx11_flag`` and alike. Note that the implementation is
such that if a given compiler version does not support this feature, an
error will be produced. Therefore package developers can also use these

View File

@ -178,40 +178,40 @@ def version(self):
@property
def openmp_flag(self):
# If it is not overridden, assume it is not supported and warn the user
tty.die(
"The compiler you have chosen does not currently support OpenMP.",
"If you think it should, please edit the compiler subclass and",
"submit a pull request or issue.")
raise UnsupportedCompilerFlag(self, "OpenMP", "openmp_flag")
# This property should be overridden in the compiler subclass if
# C++98 is not the default standard for that compiler
@property
def cxx98_flag(self):
return ""
# This property should be overridden in the compiler subclass if
# C++11 is supported by that compiler
@property
def cxx11_flag(self):
# If it is not overridden, assume it is not supported and warn the user
tty.die(
"The compiler you have chosen does not currently support C++11.",
"If you think it should, please edit the compiler subclass and",
"submit a pull request or issue.")
raise UnsupportedCompilerFlag(self,
"the C++11 standard",
"cxx11_flag")
# This property should be overridden in the compiler subclass if
# C++14 is supported by that compiler
@property
def cxx14_flag(self):
# If it is not overridden, assume it is not supported and warn the user
tty.die(
"The compiler you have chosen does not currently support C++14.",
"If you think it should, please edit the compiler subclass and",
"submit a pull request or issue.")
raise UnsupportedCompilerFlag(self,
"the C++14 standard",
"cxx14_flag")
# This property should be overridden in the compiler subclass if
# C++17 is supported by that compiler
@property
def cxx17_flag(self):
# If it is not overridden, assume it is not supported and warn the user
tty.die(
"The compiler you have chosen does not currently support C++17.",
"If you think it should, please edit the compiler subclass and",
"submit a pull request or issue.")
raise UnsupportedCompilerFlag(self,
"the C++17 standard",
"cxx17_flag")
#
# Compiler classes have methods for querying the version of
@ -339,3 +339,19 @@ class InvalidCompilerError(spack.error.SpackError):
def __init__(self):
super(InvalidCompilerError, self).__init__(
"Compiler has no executables.")
class UnsupportedCompilerFlag(spack.error.SpackError):
def __init__(self, compiler, feature, flag_name, ver_string=None):
super(UnsupportedCompilerFlag, self).__init__(
"{0} ({1}) does not support {2} (as compiler.{3})."
.format(compiler.name,
ver_string if ver_string else compiler.version,
feature,
flag_name),
"If you think it should, please edit the compiler.{0} subclass to"
.format(compiler.name) +
" implement the {0} property and submit a pull request or issue."
.format(flag_name)
)

View File

@ -30,7 +30,7 @@
import llnl.util.tty as tty
import spack.paths
from spack.compiler import Compiler, _version_cache
from spack.compiler import Compiler, _version_cache, UnsupportedCompilerFlag
from spack.util.executable import Executable
from spack.version import ver
@ -69,7 +69,10 @@ def is_apple(self):
@property
def openmp_flag(self):
if self.is_apple:
tty.die("Clang from Apple does not support Openmp yet.")
raise UnsupportedCompilerFlag(self,
"OpenMP",
"openmp_flag",
"Xcode {0}".format(self.version))
else:
return "-fopenmp"
@ -77,14 +80,20 @@ def openmp_flag(self):
def cxx11_flag(self):
if self.is_apple:
# Adapted from CMake's AppleClang-CXX rules
# Spack's AppleClang detection only valid form Xcode >= 4.6
# Spack's AppleClang detection only valid from Xcode >= 4.6
if self.version < ver('4.0.0'):
tty.die("Only Apple LLVM 4.0 and above support c++11")
raise UnsupportedCompilerFlag(self,
"the C++11 standard",
"cxx11_flag",
"Xcode < 4.0.0")
else:
return "-std=c++11"
else:
if self.version < ver('3.3'):
tty.die("Only Clang 3.3 and above support c++11.")
raise UnsupportedCompilerFlag(self,
"the C++11 standard",
"cxx11_flag",
"< 3.3")
else:
return "-std=c++11"
@ -93,14 +102,20 @@ def cxx14_flag(self):
if self.is_apple:
# Adapted from CMake's rules for AppleClang
if self.version < ver('5.1.0'):
tty.die("Only Apple LLVM 5.1 and above support c++14.")
raise UnsupportedCompilerFlag(self,
"the C++14 standard",
"cxx14_flag",
"Xcode < 5.1.0")
elif self.version < ver('6.1.0'):
return "-std=c++1y"
else:
return "-std=c++14"
else:
if self.version < ver('3.4'):
tty.die("Only Clang 3.4 and above support c++14.")
raise UnsupportedCompilerFlag(self,
"the C++14 standard",
"cxx14_flag",
"< 3.5")
elif self.version < ver('3.5'):
return "-std=c++1y"
else:
@ -111,14 +126,22 @@ def cxx17_flag(self):
if self.is_apple:
# Adapted from CMake's rules for AppleClang
if self.version < ver('6.1.0'):
tty.die("Only Apple LLVM 6.1 and above support c++17.")
raise UnsupportedCompilerFlag(self,
"the C++17 standard",
"cxx17_flag",
"Xcode < 6.1.0")
else:
return "-std=c++1z"
else:
if self.version < ver('3.5'):
tty.die("Only Clang 3.5 and above support c++17.")
else:
raise UnsupportedCompilerFlag(self,
"the C++17 standard",
"cxx17_flag",
"< 5.0")
elif self.version < ver('5.0'):
return "-std=c++1z"
else:
return "-std=c++17"
@property
def pic_flag(self):

View File

@ -22,10 +22,9 @@
# 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 llnl.util.tty as tty
import spack.compilers.clang
from spack.compiler import Compiler, get_compiler_version
from spack.compiler import \
Compiler, get_compiler_version, UnsupportedCompilerFlag
from spack.version import ver
@ -60,10 +59,20 @@ class Gcc(Compiler):
def openmp_flag(self):
return "-fopenmp"
@property
def cxx98_flag(self):
if self.version < ver('6.0'):
return ""
else:
return "-std=c++98"
@property
def cxx11_flag(self):
if self.version < ver('4.3'):
tty.die("Only gcc 4.3 and above support c++11.")
raise UnsupportedCompilerFlag(self,
"the C++11 standard",
"cxx11_flag",
" < 4.3")
elif self.version < ver('4.7'):
return "-std=c++0x"
else:
@ -72,18 +81,28 @@ def cxx11_flag(self):
@property
def cxx14_flag(self):
if self.version < ver('4.8'):
tty.die("Only gcc 4.8 and above support c++14.")
raise UnsupportedCompilerFlag(self,
"the C++14 standard",
"cxx14_flag",
"< 4.8")
elif self.version < ver('4.9'):
return "-std=c++1y"
else:
elif self.version < ver('6.0'):
return "-std=c++14"
else:
return ""
@property
def cxx17_flag(self):
if self.version < ver('5.0'):
tty.die("Only gcc 5.0 and above support c++17.")
else:
raise UnsupportedCompilerFlag(self,
"the C++17 standard",
"cxx17_flag",
"< 5.0")
elif self.version < ver('6.0'):
return "-std=c++1z"
else:
return "-std=c++17"
@property
def pic_flag(self):

View File

@ -22,9 +22,8 @@
# 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 llnl.util.tty as tty
from spack.compiler import Compiler, get_compiler_version
from spack.compiler import \
Compiler, get_compiler_version, UnsupportedCompilerFlag
from spack.version import ver
@ -60,7 +59,11 @@ def openmp_flag(self):
@property
def cxx11_flag(self):
if self.version < ver('11.1'):
tty.die("Only intel 11.1 and above support c++11.")
raise UnsupportedCompilerFlag(self,
"the C++11 standard",
"cxx11_flag",
"< 11.1")
elif self.version < ver('13'):
return "-std=c++0x"
else:
@ -70,7 +73,10 @@ def cxx11_flag(self):
def cxx14_flag(self):
# Adapted from CMake's Intel-CXX rules.
if self.version < ver('15'):
tty.die("Only intel 15.0 and above support c++14.")
raise UnsupportedCompilerFlag(self,
"the C++14 standard",
"cxx14_flag",
"< 15")
elif self.version < ver('15.0.2'):
return "-std=c++1y"
else:

View File

@ -22,9 +22,8 @@
# 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 llnl.util.tty as tty
from spack.compiler import Compiler, get_compiler_version
from spack.compiler import \
Compiler, get_compiler_version, UnsupportedCompilerFlag
from spack.version import ver
@ -54,7 +53,10 @@ def openmp_flag(self):
@property
def cxx11_flag(self):
if self.version < ver('13.1'):
tty.die("Only xlC 13.1 and above have some c++11 support.")
raise UnsupportedCompilerFlag(self,
"the C++11 standard",
"cxx11_flag",
"< 13.1")
else:
return "-qlanglvl=extended0x"

View File

@ -23,9 +23,8 @@
# 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 llnl.util.tty as tty
from spack.compiler import Compiler, get_compiler_version
from spack.compiler import \
Compiler, get_compiler_version, UnsupportedCompilerFlag
from spack.version import ver
@ -55,7 +54,10 @@ def openmp_flag(self):
@property
def cxx11_flag(self):
if self.version < ver('13.1'):
tty.die("Only xlC 13.1 and above have some c++11 support.")
raise UnsupportedCompilerFlag(self,
"the C++11 standard",
"cxx11_flag",
"< 13.1")
else:
return "-qlanglvl=extended0x"

View File

@ -22,11 +22,12 @@
# License along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
##############################################################################
from copy import copy
from six import iteritems
import spack.spec
import spack.compilers as compilers
from spack.compiler import _get_versioned_tuple
from spack.compiler import _get_versioned_tuple, Compiler
def test_get_compiler_duplicates(config):
@ -79,3 +80,169 @@ def test_compiler_flags_from_config_are_grouped():
compiler = compilers.compiler_from_config_entry(compiler_entry)
assert any(x == '-foo-flag foo-val' for x in compiler.flags['cflags'])
# Test behavior of flags and UnsupportedCompilerFlag.
# Utility function to test most flags.
default_compiler_entry = {
'spec': 'clang@2.0.0-apple',
'operating_system': 'foo-os',
'paths': {
'cc': 'cc-path',
'cxx': 'cxx-path',
'fc': None,
'f77': None
},
'flags': {},
'modules': None
}
# Fake up a mock compiler where everything is defaulted.
class MockCompiler(Compiler):
def __init__(self):
super(MockCompiler, self).__init__(
"badcompiler@1.0.0",
default_compiler_entry['operating_system'],
None,
[default_compiler_entry['paths']['cc'],
default_compiler_entry['paths']['cxx'],
default_compiler_entry['paths']['fc'],
default_compiler_entry['paths']['f77']])
@property
def name(self):
return "mockcompiler"
@property
def version(self):
return "1.0.0"
# Get the desired flag from the specified compiler spec.
def flag_value(flag, spec):
compiler = None
if spec is None:
compiler = MockCompiler()
else:
compiler_entry = copy(default_compiler_entry)
compiler_entry['spec'] = spec
# Disable faulty id()-based cache (issue #7647).
compilers._compiler_cache = {}
compiler = compilers.compiler_from_config_entry(compiler_entry)
return getattr(compiler, flag)
# Utility function to verify that the expected exception is thrown for
# an unsupported flag.
def unsupported_flag_test(flag, spec=None):
caught_exception = None
try:
flag_value(flag, spec)
except spack.compiler.UnsupportedCompilerFlag:
caught_exception = True
assert(caught_exception and "Expected exception not thrown.")
# Verify the expected flag value for the give compiler spec.
def supported_flag_test(flag, flag_value_ref, spec=None):
assert(flag_value(flag, spec) == flag_value_ref)
# Tests for UnsupportedCompilerFlag exceptions from default
# implementations of flags.
def test_default_flags():
unsupported_flag_test("openmp_flag")
unsupported_flag_test("cxx11_flag")
unsupported_flag_test("cxx14_flag")
unsupported_flag_test("cxx17_flag")
supported_flag_test("cxx98_flag", "")
# Verify behavior of particular compiler definitions.
def test_clang_flags():
# Common
supported_flag_test("pic_flag", "-fPIC", "gcc@4.0")
# Apple Clang.
unsupported_flag_test("openmp_flag", "clang@2.0.0-apple")
unsupported_flag_test("cxx11_flag", "clang@2.0.0-apple")
supported_flag_test("cxx11_flag", "-std=c++11", "clang@4.0.0-apple")
unsupported_flag_test("cxx14_flag", "clang@5.0.0-apple")
supported_flag_test("cxx14_flag", "-std=c++1y", "clang@5.1.0-apple")
supported_flag_test("cxx14_flag", "-std=c++14", "clang@6.1.0-apple")
unsupported_flag_test("cxx17_flag", "clang@6.0.0-apple")
supported_flag_test("cxx17_flag", "-std=c++1z", "clang@6.1.0-apple")
# non-Apple Clang.
supported_flag_test("openmp_flag", "-fopenmp", "clang@3.3")
unsupported_flag_test("cxx11_flag", "clang@3.2")
supported_flag_test("cxx11_flag", "-std=c++11", "clang@3.3")
unsupported_flag_test("cxx14_flag", "clang@3.3")
supported_flag_test("cxx14_flag", "-std=c++1y", "clang@3.4")
supported_flag_test("cxx14_flag", "-std=c++14", "clang@3.5")
unsupported_flag_test("cxx17_flag", "clang@3.4")
supported_flag_test("cxx17_flag", "-std=c++1z", "clang@3.5")
supported_flag_test("cxx17_flag", "-std=c++17", "clang@5.0")
def test_cce_flags():
supported_flag_test("openmp_flag", "-h omp", "cce@1.0")
supported_flag_test("cxx11_flag", "-h std=c++11", "cce@1.0")
supported_flag_test("pic_flag", "-h PIC", "cce@1.0")
def test_gcc_flags():
supported_flag_test("openmp_flag", "-fopenmp", "gcc@4.1")
supported_flag_test("cxx98_flag", "", "gcc@5.2")
supported_flag_test("cxx98_flag", "-std=c++98", "gcc@6.0")
unsupported_flag_test("cxx11_flag", "gcc@4.2")
supported_flag_test("cxx11_flag", "-std=c++0x", "gcc@4.3")
supported_flag_test("cxx11_flag", "-std=c++11", "gcc@4.7")
unsupported_flag_test("cxx14_flag", "gcc@4.7")
supported_flag_test("cxx14_flag", "-std=c++1y", "gcc@4.8")
supported_flag_test("cxx14_flag", "-std=c++14", "gcc@4.9")
supported_flag_test("cxx14_flag", "", "gcc@6.0")
unsupported_flag_test("cxx17_flag", "gcc@4.9")
supported_flag_test("pic_flag", "-fPIC", "gcc@4.0")
def test_intel_flags():
supported_flag_test("openmp_flag", "-openmp", "intel@15.0")
supported_flag_test("openmp_flag", "-qopenmp", "intel@16.0")
unsupported_flag_test("cxx11_flag", "intel@11.0")
supported_flag_test("cxx11_flag", "-std=c++0x", "intel@12.0")
supported_flag_test("cxx11_flag", "-std=c++11", "intel@13")
unsupported_flag_test("cxx14_flag", "intel@14.0")
supported_flag_test("cxx14_flag", "-std=c++1y", "intel@15.0")
supported_flag_test("cxx14_flag", "-std=c++14", "intel@15.0.2")
supported_flag_test("pic_flag", "-fPIC", "intel@1.0")
def test_nag_flags():
supported_flag_test("openmp_flag", "-openmp", "nag@1.0")
supported_flag_test("cxx11_flag", "-std=c++11", "nag@1.0")
supported_flag_test("pic_flag", "-PIC", "nag@1.0")
def test_pgi_flags():
supported_flag_test("openmp_flag", "-mp", "pgi@1.0")
supported_flag_test("cxx11_flag", "-std=c++11", "pgi@1.0")
supported_flag_test("pic_flag", "-fpic", "pgi@1.0")
def test_xl_flags():
supported_flag_test("openmp_flag", "-qsmp=omp", "xl@1.0")
unsupported_flag_test("cxx11_flag", "xl@13.0")
supported_flag_test("cxx11_flag", "-qlanglvl=extended0x", "xl@13.1")
supported_flag_test("pic_flag", "-qpic", "xl@1.0")
def test_xl_r_flags():
supported_flag_test("openmp_flag", "-qsmp=omp", "xl_r@1.0")
unsupported_flag_test("cxx11_flag", "xl_r@13.0")
supported_flag_test("cxx11_flag", "-qlanglvl=extended0x", "xl_r@13.1")
supported_flag_test("pic_flag", "-qpic", "xl_r@1.0")

View File

@ -126,6 +126,11 @@ class Boost(Package):
variant(lib, default=(lib not in default_noinstall_libs),
description="Compile with {0} library".format(lib))
variant('cxxstd',
default='default',
values=('default', '98', '11', '14', '17'),
multi=False,
description='Use the specified C++ standard when building.')
variant('debug', default=False,
description='Switch to the debug version of Boost')
variant('shared', default=True,
@ -250,6 +255,26 @@ def determine_bootstrap_options(self, spec, withLibs, options):
if '+python' in spec:
f.write(self.bjam_python_line(spec))
def cxxstd_to_flag(self, std):
flag = ''
if self.spec.variants['cxxstd'].value == '98':
flag = self.compiler.cxx98_flag
elif self.spec.variants['cxxstd'].value == '11':
flag = self.compiler.cxx11_flag
elif self.spec.variants['cxxstd'].value == '14':
flag = self.compiler.cxx14_flag
elif self.spec.variants['cxxstd'].value == '17':
flag = self.compiler.cxx17_flag
elif self.spec.variants['cxxstd'].value == 'default':
# Let the compiler do what it usually does.
pass
else:
# The user has selected a (new?) legal value that we've
# forgotten to deal with here.
tty.die("INTERNAL ERROR: cannot accommodate unexpected variant ",
"cxxstd={0}".format(spec.variants['cxxstd'].value))
return flag
def determine_b2_options(self, spec, options):
if '+debug' in spec:
options.append('variant=debug')
@ -299,6 +324,17 @@ def determine_b2_options(self, spec, options):
'toolset=%s' % self.determine_toolset(spec)
])
# Other C++ flags.
cxxflags = []
# Deal with C++ standard.
if spec.satisfies('@1.66:'):
options.append('cxxstd={0}'.format(spec.variants['cxxstd'].value))
else: # Add to cxxflags for older Boost.
flag = self.cxxstd_to_flag(spec.variants['cxxstd'].value)
if flag:
cxxflags.append(flag)
# clang is not officially supported for pre-compiled headers
# and at least in clang 3.9 still fails to build
# http://www.boost.org/build/doc/html/bbv2/reference/precompiled_headers.html
@ -306,10 +342,13 @@ def determine_b2_options(self, spec, options):
if spec.satisfies('%clang'):
options.extend(['pch=off'])
if '+clanglibcpp' in spec:
cxxflags.append('-stdlib=libc++')
options.extend(['toolset=clang',
'cxxflags="-stdlib=libc++"',
'linkflags="-stdlib=libc++"'])
if cxxflags:
options.append('cxxflags="{0}"'.format(' '.join(cxxflags)))
return threadingOpts
def add_buildopt_symlinks(self, prefix):

View File

@ -44,7 +44,9 @@ class Clingo(CMakePackage):
depends_on('python')
def cmake_args(self):
if not self.compiler.cxx14_flag:
try:
self.compiler.cxx14_flag
except UnsupportedCompilerFlag:
InstallError('clingo requires a C++14-compliant C++ compiler')
args = ['-DCLINGO_BUILD_WITH_PYTHON=ON',