diff --git a/lib/spack/spack/build_systems/cached_cmake.py b/lib/spack/spack/build_systems/cached_cmake.py index 0dc0375c5da..bd439f2cc35 100644 --- a/lib/spack/spack/build_systems/cached_cmake.py +++ b/lib/spack/spack/build_systems/cached_cmake.py @@ -10,6 +10,7 @@ import llnl.util.tty as tty import spack.phase_callbacks +from spack.directives import depends_on from .cmake import CMakeBuilder, CMakePackage @@ -357,6 +358,10 @@ class CachedCMakePackage(CMakePackage): CMakeBuilder = CachedCMakeBuilder + # These dependencies are assumed in the builder + depends_on("c", type="build") + depends_on("cxx", type="build") + def flag_handler(self, name, flags): if name in ("cflags", "cxxflags", "cppflags", "fflags"): return None, None, None # handled in the cmake cache diff --git a/lib/spack/spack/compilers/adaptor.py b/lib/spack/spack/compilers/adaptor.py new file mode 100644 index 00000000000..ad4a9d75819 --- /dev/null +++ b/lib/spack/spack/compilers/adaptor.py @@ -0,0 +1,211 @@ +# Copyright Spack Project Developers. See COPYRIGHT file for details. +# +# SPDX-License-Identifier: (Apache-2.0 OR MIT) +import enum +import typing +from typing import Dict, List + +from llnl.util import lang + +from .libraries import CompilerPropertyDetector + +if typing.TYPE_CHECKING: + import spack.spec + + +class Languages(enum.Enum): + C = "c" + CXX = "cxx" + FORTRAN = "fortran" + + +class CompilerAdaptor: + def __init__( + self, compiled_spec: "spack.spec.Spec", compilers: Dict[Languages, "spack.spec.Spec"] + ) -> None: + if not compilers: + raise AttributeError(f"{compiled_spec} has no 'compiler' attribute") + + self.compilers = compilers + self.compiled_spec = compiled_spec + + def _lang_exists_or_raise(self, name: str, *, lang: Languages) -> None: + if lang not in self.compilers: + raise AttributeError( + f"'{self.compiled_spec}' has no {lang.value} compiler, so the " + f"'{name}' property cannot be retrieved" + ) + + def _maybe_return_attribute(self, name: str, *, lang: Languages) -> str: + self._lang_exists_or_raise(name, lang=lang) + return getattr(self.compilers[lang].package, name) + + @property + def cc_rpath_arg(self) -> str: + self._lang_exists_or_raise("cc_rpath_arg", lang=Languages.C) + return self.compilers[Languages.C].package.rpath_arg + + @property + def cxx_rpath_arg(self) -> str: + self._lang_exists_or_raise("cxx_rpath_arg", lang=Languages.CXX) + return self.compilers[Languages.CXX].package.rpath_arg + + @property + def fc_rpath_arg(self) -> str: + self._lang_exists_or_raise("fc_rpath_arg", lang=Languages.FORTRAN) + return self.compilers[Languages.FORTRAN].package.rpath_arg + + @property + def f77_rpath_arg(self) -> str: + self._lang_exists_or_raise("f77_rpath_arg", lang=Languages.FORTRAN) + return self.compilers[Languages.FORTRAN].package.rpath_arg + + @property + def linker_arg(self) -> str: + return self._maybe_return_attribute("linker_arg", lang=Languages.C) + + @property + def name(self): + return next(iter(self.compilers.values())).name + + @property + def version(self): + return next(iter(self.compilers.values())).version + + def implicit_rpaths(self) -> List[str]: + result, seen = [], set() + for compiler in self.compilers.values(): + if compiler in seen: + continue + seen.add(compiler) + result.extend(CompilerPropertyDetector(compiler).implicit_rpaths()) + return result + + @property + def openmp_flag(self) -> str: + return next(iter(self.compilers.values())).package.openmp_flag + + @property + def cxx98_flag(self) -> str: + return self.compilers[Languages.CXX].package.standard_flag( + language=Languages.CXX.value, standard="98" + ) + + @property + def cxx11_flag(self) -> str: + return self.compilers[Languages.CXX].package.standard_flag( + language=Languages.CXX.value, standard="11" + ) + + @property + def cxx14_flag(self) -> str: + return self.compilers[Languages.CXX].package.standard_flag( + language=Languages.CXX.value, standard="14" + ) + + @property + def cxx17_flag(self) -> str: + return self.compilers[Languages.CXX].package.standard_flag( + language=Languages.CXX.value, standard="17" + ) + + @property + def cxx20_flag(self) -> str: + return self.compilers[Languages.CXX].package.standard_flag( + language=Languages.CXX.value, standard="20" + ) + + @property + def cxx23_flag(self) -> str: + return self.compilers[Languages.CXX].package.standard_flag( + language=Languages.CXX.value, standard="23" + ) + + @property + def c99_flag(self) -> str: + return self.compilers[Languages.C].package.standard_flag( + language=Languages.C.value, standard="99" + ) + + @property + def c11_flag(self) -> str: + return self.compilers[Languages.C].package.standard_flag( + language=Languages.C.value, standard="11" + ) + + @property + def c17_flag(self) -> str: + return self.compilers[Languages.C].package.standard_flag( + language=Languages.C.value, standard="17" + ) + + @property + def c23_flag(self) -> str: + return self.compilers[Languages.C].package.standard_flag( + language=Languages.C.value, standard="17" + ) + + @property + def cc_pic_flag(self) -> str: + self._lang_exists_or_raise("cc_pic_flag", lang=Languages.C) + return self.compilers[Languages.C].package.pic_flag + + @property + def cxx_pic_flag(self) -> str: + self._lang_exists_or_raise("cxx_pic_flag", lang=Languages.CXX) + return self.compilers[Languages.CXX].package.pic_flag + + @property + def fc_pic_flag(self) -> str: + self._lang_exists_or_raise("fc_pic_flag", lang=Languages.FORTRAN) + return self.compilers[Languages.FORTRAN].package.pic_flag + + @property + def f77_pic_flag(self) -> str: + self._lang_exists_or_raise("f77_pic_flag", lang=Languages.FORTRAN) + return self.compilers[Languages.FORTRAN].package.pic_flag + + @property + def prefix(self) -> str: + return next(iter(self.compilers.values())).prefix + + @property + def extra_rpaths(self) -> List[str]: + compiler = next(iter(self.compilers.values())) + return getattr(compiler, "extra_attributes", {}).get("extra_rpaths", []) + + @property + def cc(self): + return self._maybe_return_attribute("cc", lang=Languages.C) + + @property + def cxx(self): + return self._maybe_return_attribute("cxx", lang=Languages.CXX) + + @property + def fc(self): + self._lang_exists_or_raise("fc", lang=Languages.FORTRAN) + return self.compilers[Languages.FORTRAN].package.fortran + + @property + def f77(self): + self._lang_exists_or_raise("f77", lang=Languages.FORTRAN) + return self.compilers[Languages.FORTRAN].package.fortran + + +class DeprecatedCompiler(lang.DeprecatedProperty): + def __init__(self) -> None: + super().__init__(name="compiler") + + def factory(self, instance, owner) -> CompilerAdaptor: + spec = instance.spec + if not spec.concrete: + raise ValueError("Can only get a compiler for a concrete package.") + + compilers = {} + for language in Languages: + deps = spec.dependencies(virtuals=[language.value]) + if deps: + compilers[language] = deps[0] + + return CompilerAdaptor(instance, compilers) diff --git a/lib/spack/spack/environment/depfile.py b/lib/spack/spack/environment/depfile.py index 956a980c5ca..f69bc15e4e1 100644 --- a/lib/spack/spack/environment/depfile.py +++ b/lib/spack/spack/environment/depfile.py @@ -165,9 +165,7 @@ def __init__( item.target.safe_name(), " ".join(self._install_target(s.safe_name()) for s in item.prereqs), item.target.spec_hash(), - item.target.unsafe_format( - "{name}{@version}{%compiler}{variants}{arch=architecture}" - ), + item.target.unsafe_format("{name}{@version}{variants}{ arch=architecture}"), item.buildcache_flag, ) for item in adjacency_list diff --git a/lib/spack/spack/mixins.py b/lib/spack/spack/mixins.py index d5467726f4e..6d49220967e 100644 --- a/lib/spack/spack/mixins.py +++ b/lib/spack/spack/mixins.py @@ -70,12 +70,16 @@ def _filter_compiler_wrappers_impl(pkg_or_builder): x = llnl.util.filesystem.FileFilter(*abs_files) - compiler_vars = [ - ("CC", pkg.compiler.cc), - ("CXX", pkg.compiler.cxx), - ("F77", pkg.compiler.f77), - ("FC", pkg.compiler.fc), - ] + compiler_vars = [] + if "c" in pkg.spec: + compiler_vars.append(("CC", pkg.spec["c"].package.cc)) + + if "cxx" in pkg.spec: + compiler_vars.append(("CXX", pkg.spec["cxx"].package.cxx)) + + if "fortran" in pkg.spec: + compiler_vars.append(("FC", pkg.spec["fortran"].package.fortran)) + compiler_vars.append(("F77", pkg.spec["fortran"].package.fortran)) # Some paths to the compiler wrappers might be substrings of the others. # For example: @@ -103,7 +107,11 @@ def _filter_compiler_wrappers_impl(pkg_or_builder): x.filter(wrapper_path, compiler_path, **filter_kwargs) # Remove this linking flag if present (it turns RPATH into RUNPATH) - x.filter("{0}--enable-new-dtags".format(pkg.compiler.linker_arg), "", **filter_kwargs) + for compiler_lang in ("c", "cxx", "fortran"): + if compiler_lang not in pkg.spec: + continue + compiler_pkg = pkg.spec[compiler_lang].package + x.filter(f"{compiler_pkg.linker_arg}--enable-new-dtags", "", **filter_kwargs) # NAG compiler is usually mixed with GCC, which has a different # prefix for linker arguments. diff --git a/lib/spack/spack/package_base.py b/lib/spack/spack/package_base.py index d2edde1c5d0..6f0e6112e79 100644 --- a/lib/spack/spack/package_base.py +++ b/lib/spack/spack/package_base.py @@ -53,6 +53,7 @@ import spack.util.path import spack.util.web import spack.variant +from spack.compilers.adaptor import DeprecatedCompiler from spack.error import InstallError, NoURLError, PackageError from spack.filesystem_view import YamlFilesystemView from spack.resource import Resource @@ -67,10 +68,9 @@ ] FLAG_HANDLER_TYPE = Callable[[str, Iterable[str]], FLAG_HANDLER_RETURN_TYPE] -"""Allowed URL schemes for spack packages.""" +#: Allowed URL schemes for spack packages _ALLOWED_URL_SCHEMES = ["http", "https", "ftp", "file", "git"] - #: Filename for the Spack build/install log. _spack_build_logfile = "spack-build-out.txt" @@ -592,6 +592,8 @@ class PackageBase(WindowsRPath, PackageViewMixin, metaclass=PackageMeta): """ + compiler = DeprecatedCompiler() + # # These are default values for instance variables. # @@ -1489,14 +1491,6 @@ def prefix(self): def home(self): return self.prefix - @property # type: ignore[misc] - @memoized - def compiler(self): - """Get the spack.compiler.Compiler object used to build this package""" - if not self.spec.concrete: - raise ValueError("Can only get a compiler for a concrete package.") - raise NotImplementedError("Wrapper to old API still to be implemented") - def url_version(self, version): """ Given a version, this returns a string that should be substituted diff --git a/lib/spack/spack/spec.py b/lib/spack/spack/spec.py index d6461752573..d915eb8cce9 100644 --- a/lib/spack/spack/spec.py +++ b/lib/spack/spack/spec.py @@ -175,15 +175,13 @@ #: Default format for Spec.format(). This format can be round-tripped, so that: #: Spec(Spec("string").format()) == Spec("string)" DEFAULT_FORMAT = ( - "{name}{@versions}" - "{%compiler.name}{@compiler.versions}{compiler_flags}" + "{name}{@versions}{compiler_flags}" "{variants}{ namespace=namespace_if_anonymous}{ arch=architecture}{/abstract_hash}" ) #: Display format, which eliminates extra `@=` in the output, for readability. DISPLAY_FORMAT = ( - "{name}{@version}" - "{%compiler.name}{@compiler.version}{compiler_flags}" + "{name}{@version}{compiler_flags}" "{variants}{ namespace=namespace_if_anonymous}{ arch=architecture}{/abstract_hash}" ) @@ -620,10 +618,69 @@ def complete_with_defaults(self) -> None: self.target = default_architecture.target -# FIXME (compiler as nodes): remove this class class CompilerSpec: - def __init__(self, *args): - raise SystemExit("CompilerSpec is being removed") + """Adaptor to the old compiler spec interface. Exposes just a few attributes""" + + def __init__(self, spec): + self.spec = spec + + @property + def name(self): + return self.spec.name + + @property + def version(self): + return self.spec.version + + @property + def versions(self): + return self.spec.versions + + @property + def display_str(self): + """Equivalent to {compiler.name}{@compiler.version} for Specs, without extra + @= for readability.""" + if self.spec.concrete: + return f"{self.name}@{self.version}" + elif self.versions != vn.any_version: + return f"{self.name}@{self.versions}" + return self.name + + def __lt__(self, other): + if not isinstance(other, CompilerSpec): + return self.spec < other + return self.spec < other.spec + + def __eq__(self, other): + if not isinstance(other, CompilerSpec): + return self.spec == other + return self.spec == other.spec + + def __hash__(self): + return hash(self.spec) + + def __str__(self): + return str(self.spec) + + def _cmp_iter(self): + return self.spec._cmp_iter() + + def __bool__(self): + if self.spec == Spec(): + return False + return bool(self.spec) + + +class DeprecatedCompilerSpec(lang.DeprecatedProperty): + def __init__(self): + super().__init__(name="compiler") + + def factory(self, instance, owner): + for language in ("c", "cxx", "fortran"): + deps = instance.dependencies(virtuals=language) + if deps: + return CompilerSpec(deps[0]) + return CompilerSpec(Spec()) @lang.lazy_lexicographic_ordering @@ -1342,7 +1399,7 @@ def tree( @lang.lazy_lexicographic_ordering(set_hash=False) class Spec: - compiler = lang.Const(None) + compiler = DeprecatedCompilerSpec() @staticmethod def default_arch(): @@ -1414,7 +1471,7 @@ def __init__(self, spec_like=None, *, external_path=None, external_modules=None) spack.spec_parser.parse_one_or_raise(spec_like, self) elif spec_like is not None: - raise TypeError("Can't make spec out of %s" % type(spec_like)) + raise TypeError(f"Can't make spec out of {type(spec_like)}") @staticmethod def _format_module_list(modules): @@ -2150,10 +2207,6 @@ def to_node_dict(self, hash=ht.dag_hash): 'platform_os': 'mojave', 'target': 'x86_64', }, - 'compiler': { - 'name': 'apple-clang', - 'version': '10.0.0', - }, 'namespace': 'builtin', 'parameters': { 'fts': 'true',