diff options
author | Massimiliano Culpo <massimiliano.culpo@gmail.com> | 2024-03-25 06:59:21 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-03-24 22:59:21 -0700 |
commit | 0c9a53ba3a9c94ee60fa3825160e72ff025fb8b7 (patch) | |
tree | 62162f8c017b87af8b55d0829cd663824b15df87 /var | |
parent | 1fd4353289ec58913a4faf70e4851f66767bb623 (diff) | |
download | spack-0c9a53ba3a9c94ee60fa3825160e72ff025fb8b7.tar.gz spack-0c9a53ba3a9c94ee60fa3825160e72ff025fb8b7.tar.bz2 spack-0c9a53ba3a9c94ee60fa3825160e72ff025fb8b7.tar.xz spack-0c9a53ba3a9c94ee60fa3825160e72ff025fb8b7.zip |
Add `intel-oneapi-runtime`, allow injecting virtual dependencies (#42062)
This PR adds:
- A new runtime for `%oneapi` compilers, called `intel-oneapi-runtime`
- Information to both `gcc-runtime` and `intel-oneapi-runtime`, to ensure
that we don't mix compilers using different soname for either `libgfortran`
or `libifcore`
To do so, the following internal mechanisms have been implemented:
- Possibility to inject virtual dependencies from the `runtime_constraints`
callback on packages
Information has been added to `gcc-runtime` to provide the correct soname
under different conditions on its `%gcc`.
Rules injected into the solver looks like:
```prolog
% Add a dependency on 'gfortran@5' for nodes compiled with gcc@=13.2.0 and using the 'fortran' language
attr("dependency_holds", node(ID, Package), "gfortran", "link") :-
attr("node", node(ID, Package)),
attr("node_compiler", node(ID, Package), "gcc"),
attr("node_compiler_version", node(ID, Package), "gcc", "13.2.0"),
not external(node(ID, Package)),
not runtime(Package),
attr("language", node(ID, Package), "fortran").
attr("virtual_node", node(RuntimeID, "gfortran")) :-
attr("depends_on", node(ID, Package), ProviderNode, "link"),
provider(ProviderNode, node(RuntimeID, "gfortran")),
attr("node", node(ID, Package)),
attr("node_compiler", node(ID, Package), "gcc"),
attr("node_compiler_version", node(ID, Package), "gcc", "13.2.0"),
not external(node(ID, Package)),
not runtime(Package),
attr("language", node(ID, Package), "fortran").
attr("node_version_satisfies", node(RuntimeID, "gfortran"), "5") :-
attr("depends_on", node(ID, Package), ProviderNode, "link"),
provider(ProviderNode, node(RuntimeID, "gfortran")),
attr("node", node(ID, Package)),
attr("node_compiler", node(ID, Package), "gcc"),
attr("node_compiler_version", node(ID, Package), "gcc", "13.2.0"),
not external(node(ID, Package)),
not runtime(Package),
attr("language", node(ID, Package), "fortran").
```
Diffstat (limited to 'var')
6 files changed, 173 insertions, 55 deletions
diff --git a/var/spack/repos/builtin/packages/gcc-runtime/package.py b/var/spack/repos/builtin/packages/gcc-runtime/package.py index 085042b8ce..d883085cc9 100644 --- a/var/spack/repos/builtin/packages/gcc-runtime/package.py +++ b/var/spack/repos/builtin/packages/gcc-runtime/package.py @@ -22,6 +22,9 @@ class GccRuntime(Package): tags = ["runtime"] + # gcc-runtime versions are declared dynamically + skip_version_audit = ["platform=linux", "platform=darwin"] + maintainers("haampie") license("GPL-3.0-or-later WITH GCC-exception-3.1") @@ -44,9 +47,15 @@ class GccRuntime(Package): "ubsan", ] + # libgfortran ABI + provides("fortran-rt", "libgfortran") + provides("libgfortran@3", when="%gcc@:6") + provides("libgfortran@4", when="%gcc@7") + provides("libgfortran@5", when="%gcc@8:") + def install(self, spec, prefix): if spec.platform in ["linux", "cray", "freebsd"]: - libraries = self._get_libraries_elf() + libraries = get_elf_libraries(compiler=self.compiler, libraries=self.LIBRARIES) elif spec.platform == "darwin": libraries = self._get_libraries_macho() else: @@ -61,47 +70,6 @@ class GccRuntime(Package): for path, name in libraries: install(path, os.path.join(prefix.lib, name)) - def _get_libraries_elf(self): - """Get the GCC runtime libraries for ELF binaries""" - cc = Executable(self.compiler.cc) - lib_regex = re.compile(rb"\blib[a-z-_]+\.so\.\d+\b") - path_and_install_name = [] - - for name in self.LIBRARIES: - # Look for the dynamic library that gcc would use to link, - # that is with .so extension and without abi suffix. - path = cc(f"-print-file-name=lib{name}.so", output=str).strip() - - # gcc reports an absolute path on success - if not os.path.isabs(path): - continue - - # Now there are two options: - # 1. the file is an ELF file - # 2. the file is a linker script referencing the actual library - with open(path, "rb") as f: - try: - # Try to parse as an ELF file - soname = parse_elf(f, dynamic_section=True).dt_soname_str.decode("utf-8") - except Exception: - # On failure try to "parse" as ld script; the actual - # library needs to be mentioned by filename. - f.seek(0) - script_matches = lib_regex.findall(f.read()) - if len(script_matches) != 1: - continue - soname = script_matches[0].decode("utf-8") - - # Now locate and install the runtime library - runtime_path = cc(f"-print-file-name={soname}", output=str).strip() - - if not os.path.isabs(runtime_path): - continue - - path_and_install_name.append((runtime_path, soname)) - - return path_and_install_name - def _get_libraries_macho(self): """Same as _get_libraries_elf but for Mach-O binaries""" cc = Executable(self.compiler.cc) @@ -152,3 +120,45 @@ class GccRuntime(Package): @property def headers(self): return HeaderList([]) + + +def get_elf_libraries(compiler, libraries): + """Get the GCC runtime libraries for ELF binaries""" + cc = Executable(compiler.cc) + lib_regex = re.compile(rb"\blib[a-z-_]+\.so\.\d+\b") + path_and_install_name = [] + + for name in libraries: + # Look for the dynamic library that gcc would use to link, + # that is with .so extension and without abi suffix. + path = cc(f"-print-file-name=lib{name}.so", output=str).strip() + + # gcc reports an absolute path on success + if not os.path.isabs(path): + continue + + # Now there are two options: + # 1. the file is an ELF file + # 2. the file is a linker script referencing the actual library + with open(path, "rb") as f: + try: + # Try to parse as an ELF file + soname = parse_elf(f, dynamic_section=True).dt_soname_str.decode("utf-8") + except Exception: + # On failure try to "parse" as ld script; the actual + # library needs to be mentioned by filename. + f.seek(0) + script_matches = lib_regex.findall(f.read()) + if len(script_matches) != 1: + continue + soname = script_matches[0].decode("utf-8") + + # Now locate and install the runtime library + runtime_path = cc(f"-print-file-name={soname}", output=str).strip() + + if not os.path.isabs(runtime_path): + continue + + path_and_install_name.append((runtime_path, soname)) + + return path_and_install_name diff --git a/var/spack/repos/builtin/packages/gcc/package.py b/var/spack/repos/builtin/packages/gcc/package.py index ea11cba15a..f9ff5b9fbe 100644 --- a/var/spack/repos/builtin/packages/gcc/package.py +++ b/var/spack/repos/builtin/packages/gcc/package.py @@ -1144,7 +1144,7 @@ class Gcc(AutotoolsPackage, GNUMirrorPackage): ) @classmethod - def runtime_constraints(cls, *, compiler, pkg): + def runtime_constraints(cls, *, spec, pkg): """Callback function to inject runtime-related rules into the solver. Rule-injection is obtained through method calls of the ``pkg`` argument. @@ -1153,7 +1153,7 @@ class Gcc(AutotoolsPackage, GNUMirrorPackage): we'll document the behavior at https://spack.readthedocs.io/en/latest/ Args: - compiler: compiler object (node attribute) currently considered + spec: spec that will inject runtime dependencies pkg: object used to forward information to the solver """ pkg("*").depends_on( @@ -1163,11 +1163,27 @@ class Gcc(AutotoolsPackage, GNUMirrorPackage): description="If any package uses %gcc, it depends on gcc-runtime", ) pkg("*").depends_on( - f"gcc-runtime@{str(compiler.version)}:", - when=f"%{str(compiler.spec)}", + f"gcc-runtime@{str(spec.version)}:", + when=f"%{str(spec)}", type="link", - description=f"If any package uses %{str(compiler.spec)}, " - f"it depends on gcc-runtime@{str(compiler.version)}:", + description=f"If any package uses %{str(spec)}, " + f"it depends on gcc-runtime@{str(spec.version)}:", ) + + gfortran_str = "libgfortran@5" + if spec.satisfies("gcc@:6"): + gfortran_str = "libgfortran@3" + elif spec.satisfies("gcc@7"): + gfortran_str = "libgfortran@4" + + for fortran_virtual in ("fortran-rt", gfortran_str): + pkg("*").depends_on( + fortran_virtual, + when=f"%{str(spec)}", + languages=["fortran"], + type="link", + description=f"Add a dependency on '{gfortran_str}' for nodes compiled with " + f"{str(spec)} and using the 'fortran' language", + ) # The version of gcc-runtime is the same as the %gcc used to "compile" it - pkg("gcc-runtime").requires(f"@={str(compiler.version)}", when=f"%{str(compiler.spec)}") + pkg("gcc-runtime").requires(f"@={str(spec.version)}", when=f"%{str(spec)}") diff --git a/var/spack/repos/builtin/packages/hdf5/package.py b/var/spack/repos/builtin/packages/hdf5/package.py index a7f267d810..87a53995e4 100644 --- a/var/spack/repos/builtin/packages/hdf5/package.py +++ b/var/spack/repos/builtin/packages/hdf5/package.py @@ -33,6 +33,9 @@ class Hdf5(CMakePackage): license("custom") + depends_on("cxx", type="build", when="+cxx") + depends_on("fortran", type="build", when="+fortran") + # The 'develop' version is renamed so that we could uninstall (or patch) it # without affecting other develop version. version("develop-1.15", branch="develop") diff --git a/var/spack/repos/builtin/packages/intel-oneapi-compilers/package.py b/var/spack/repos/builtin/packages/intel-oneapi-compilers/package.py index 44a52b8d28..ffa6e6bb08 100644 --- a/var/spack/repos/builtin/packages/intel-oneapi-compilers/package.py +++ b/var/spack/repos/builtin/packages/intel-oneapi-compilers/package.py @@ -388,3 +388,31 @@ class IntelOneapiCompilers(IntelOneApiPackage): p = join_path(self.component_prefix.linux, d) if find(p, "*." + dso_suffix, recursive=False): yield p + + @classmethod + def runtime_constraints(cls, *, spec, pkg): + pkg("*").depends_on( + "intel-oneapi-runtime", + when="%oneapi", + type="link", + description="If any package uses %oneapi, it depends on intel-oneapi-runtime", + ) + pkg("*").depends_on( + f"intel-oneapi-runtime@{str(spec.version)}:", + when=f"%{str(spec)}", + type="link", + description=f"If any package uses %{str(spec)}, " + f"it depends on intel-oneapi-runtime@{str(spec.version)}:", + ) + + for fortran_virtual in ("fortran-rt", "libifcore@5"): + pkg("*").depends_on( + fortran_virtual, + when=f"%{str(spec)}", + languages=["fortran"], + type="link", + description=f"Add a dependency on 'libifcore' for nodes compiled with " + f"{str(spec)} and using the 'fortran' language", + ) + # The version of gcc-runtime is the same as the %gcc used to "compile" it + pkg("intel-oneapi-runtime").requires(f"@={str(spec.version)}", when=f"%{str(spec)}") diff --git a/var/spack/repos/builtin/packages/intel-oneapi-runtime/package.py b/var/spack/repos/builtin/packages/intel-oneapi-runtime/package.py new file mode 100644 index 0000000000..575d51444e --- /dev/null +++ b/var/spack/repos/builtin/packages/intel-oneapi-runtime/package.py @@ -0,0 +1,61 @@ +# Copyright 2013-2024 Lawrence Livermore National Security, LLC and other +# Spack Project Developers. See the top-level COPYRIGHT file for details. +# +# SPDX-License-Identifier: (Apache-2.0 OR MIT) +import os + +from llnl.util import tty + +from spack.package import * +from spack.pkg.builtin.gcc_runtime import get_elf_libraries + + +class IntelOneapiRuntime(Package): + """Package for OneAPI compiler runtime libraries""" + + homepage = "https://software.intel.com/content/www/us/en/develop/tools/oneapi.html" + has_code = False + + tags = ["runtime"] + + requires("%oneapi") + + depends_on("gcc-runtime", type="link") + + LIBRARIES = [ + "imf", + "intlc", + "irng", + "svml", + "ifcore", # Fortran + "ifcoremt", # Fortran + "ifport", # Fortran + "iomp5", + "sycl", + ] + + # libifcore ABI + provides("fortran-rt", "libifcore@5", when="%oneapi@2021:") + provides("sycl") + + conflicts("platform=windows", msg="IntelOneAPI can only be installed on Linux, and FreeBSD") + conflicts("platform=darwin", msg="IntelOneAPI can only be installed on Linux, and FreeBSD") + + def install(self, spec, prefix): + libraries = get_elf_libraries(compiler=self.compiler, libraries=self.LIBRARIES) + mkdir(prefix.lib) + + if not libraries: + tty.warn("Could not detect any shared OneAPI runtime libraries") + return + + for path, name in libraries: + install(path, os.path.join(prefix.lib, name)) + + @property + def libs(self): + return LibraryList([]) + + @property + def headers(self): + return HeaderList([]) diff --git a/var/spack/repos/compiler_runtime.test/packages/gcc/package.py b/var/spack/repos/compiler_runtime.test/packages/gcc/package.py index c70ff4faff..ef28e411d3 100644 --- a/var/spack/repos/compiler_runtime.test/packages/gcc/package.py +++ b/var/spack/repos/compiler_runtime.test/packages/gcc/package.py @@ -14,7 +14,7 @@ class Gcc(Package): version("12.3.0") @classmethod - def runtime_constraints(cls, *, compiler, pkg): + def runtime_constraints(cls, *, spec, pkg): pkg("*").depends_on( "gcc-runtime", when="%gcc", @@ -22,11 +22,11 @@ class Gcc(Package): description="If any package uses %gcc, it depends on gcc-runtime", ) pkg("*").depends_on( - f"gcc-runtime@{str(compiler.version)}:", - when=f"%{str(compiler.spec)}", + f"gcc-runtime@{str(spec.version)}:", + when=f"%{str(spec)}", type="link", - description=f"If any package uses %{str(compiler.spec)}, " - f"it depends on gcc-runtime@{str(compiler.version)}:", + description=f"If any package uses %{str(spec)}, " + f"it depends on gcc-runtime@{str(spec.version)}:", ) # The version of gcc-runtime is the same as the %gcc used to "compile" it - pkg("gcc-runtime").requires(f"@={str(compiler.version)}", when=f"%{str(compiler.spec)}") + pkg("gcc-runtime").requires(f"@={str(spec.version)}", when=f"%{str(spec)}") |