Compiler.default_libc
Some logic to detect what libc the c / cxx compilers use by default, based on `-dynamic-linker`. The function `compiler.default_libc()` returns a `Spec` of the form `glibc@x.y` or `musl@x.y` with the `external_path` property set. The idea is this can be injected as a dependency. If we can't run the dynamic linker directly, fall back to `ldd` relative to the prefix computed from `ld.so.`
This commit is contained in:
parent
e8c41cdbcb
commit
209a3bf302
11 changed files with 212 additions and 110 deletions
|
@ -35,6 +35,7 @@ packages:
|
||||||
java: [openjdk, jdk, ibm-java]
|
java: [openjdk, jdk, ibm-java]
|
||||||
jpeg: [libjpeg-turbo, libjpeg]
|
jpeg: [libjpeg-turbo, libjpeg]
|
||||||
lapack: [openblas, amdlibflame]
|
lapack: [openblas, amdlibflame]
|
||||||
|
libc: [glibc, musl]
|
||||||
libgfortran: [ gcc-runtime ]
|
libgfortran: [ gcc-runtime ]
|
||||||
libglx: [mesa+glx, mesa18+glx]
|
libglx: [mesa+glx, mesa18+glx]
|
||||||
libifcore: [ intel-oneapi-runtime ]
|
libifcore: [ intel-oneapi-runtime ]
|
||||||
|
|
|
@ -8,9 +8,11 @@
|
||||||
import os
|
import os
|
||||||
import platform
|
import platform
|
||||||
import re
|
import re
|
||||||
|
import shlex
|
||||||
import shutil
|
import shutil
|
||||||
import sys
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
|
from subprocess import PIPE, run
|
||||||
from typing import List, Optional, Sequence
|
from typing import List, Optional, Sequence
|
||||||
|
|
||||||
import llnl.path
|
import llnl.path
|
||||||
|
@ -184,6 +186,113 @@ def _parse_non_system_link_dirs(string: str) -> List[str]:
|
||||||
return list(p for p in link_dirs if not in_system_subdirectory(p))
|
return list(p for p in link_dirs if not in_system_subdirectory(p))
|
||||||
|
|
||||||
|
|
||||||
|
def _parse_dynamic_linker(output: str):
|
||||||
|
"""Parse -dynamic-linker /path/to/ld.so from compiler output"""
|
||||||
|
for line in reversed(output.splitlines()):
|
||||||
|
if "-dynamic-linker" not in line:
|
||||||
|
continue
|
||||||
|
args = shlex.split(line)
|
||||||
|
|
||||||
|
for idx in reversed(range(1, len(args))):
|
||||||
|
arg = args[idx]
|
||||||
|
if arg == "-dynamic-linker" or args == "--dynamic-linker":
|
||||||
|
return args[idx + 1]
|
||||||
|
elif arg.startswith("--dynamic-linker=") or arg.startswith("-dynamic-linker="):
|
||||||
|
return arg.split("=", 1)[1]
|
||||||
|
|
||||||
|
|
||||||
|
def _libc_from_ldd(ldd: str) -> Optional["spack.spec.Spec"]:
|
||||||
|
try:
|
||||||
|
result = run([ldd, "--version"], stdout=PIPE, stderr=PIPE, check=False)
|
||||||
|
stdout = result.stdout.decode("utf-8")
|
||||||
|
except Exception:
|
||||||
|
return None
|
||||||
|
|
||||||
|
if not re.search("gnu|glibc", stdout, re.IGNORECASE):
|
||||||
|
return None
|
||||||
|
|
||||||
|
version_str = re.match(r".+\(.+\) (.+)", stdout)
|
||||||
|
if not version_str:
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
return spack.spec.Spec(f"glibc@={version_str.group(1)}")
|
||||||
|
except Exception:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _libc_from_dynamic_linker(dynamic_linker: str) -> Optional["spack.spec.Spec"]:
|
||||||
|
if not os.path.exists(dynamic_linker):
|
||||||
|
return None
|
||||||
|
|
||||||
|
# The dynamic linker is usually installed in the same /lib(64)?/ld-*.so path across all
|
||||||
|
# distros. The rest of libc is elsewhere, e.g. /usr. Typically the dynamic linker is then
|
||||||
|
# a symlink into /usr/lib, which we use to for determining the actual install prefix of
|
||||||
|
# libc.
|
||||||
|
realpath = os.path.realpath(dynamic_linker)
|
||||||
|
|
||||||
|
prefix = os.path.dirname(realpath)
|
||||||
|
# Remove the multiarch suffix if it exists
|
||||||
|
if os.path.basename(prefix) not in ("lib", "lib64"):
|
||||||
|
prefix = os.path.dirname(prefix)
|
||||||
|
|
||||||
|
# Non-standard install layout -- just bail.
|
||||||
|
if os.path.basename(prefix) not in ("lib", "lib64"):
|
||||||
|
return None
|
||||||
|
|
||||||
|
prefix = os.path.dirname(prefix)
|
||||||
|
|
||||||
|
# Now try to figure out if glibc or musl, which is the only ones we support.
|
||||||
|
# In recent glibc we can simply execute the dynamic loader. In musl that's always the case.
|
||||||
|
try:
|
||||||
|
result = run([dynamic_linker, "--version"], stdout=PIPE, stderr=PIPE, check=False)
|
||||||
|
stdout = result.stdout.decode("utf-8")
|
||||||
|
stderr = result.stderr.decode("utf-8")
|
||||||
|
except Exception:
|
||||||
|
return None
|
||||||
|
|
||||||
|
# musl prints to stderr
|
||||||
|
if stderr.startswith("musl libc"):
|
||||||
|
version_str = re.search(r"^Version (.+)$", stderr, re.MULTILINE)
|
||||||
|
if not version_str:
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
spec = spack.spec.Spec(f"musl@={version_str.group(1)}")
|
||||||
|
spec.external_path = prefix
|
||||||
|
return spec
|
||||||
|
except Exception:
|
||||||
|
return None
|
||||||
|
elif re.search("gnu|glibc", stdout, re.IGNORECASE):
|
||||||
|
# output is like "ld.so (...) stable release version 2.33." write a regex for it
|
||||||
|
match = re.search(r"version (\d+\.\d+(?:\.\d+)?)", stdout)
|
||||||
|
if not match:
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
version = match.group(1)
|
||||||
|
spec = spack.spec.Spec(f"glibc@={version}")
|
||||||
|
spec.external_path = prefix
|
||||||
|
return spec
|
||||||
|
except Exception:
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
# Could not get the version by running the dynamic linker directly. Instead locate `ldd`
|
||||||
|
# relative to the dynamic linker.
|
||||||
|
ldd = os.path.join(prefix, "bin", "ldd")
|
||||||
|
if not os.path.exists(ldd):
|
||||||
|
# If `/lib64/ld.so` was not a symlink to `/usr/lib/ld.so` we can try to use /usr as
|
||||||
|
# prefix. This is the case on ubuntu 18.04 where /lib != /usr/lib.
|
||||||
|
if prefix != "/":
|
||||||
|
return None
|
||||||
|
prefix = "/usr"
|
||||||
|
ldd = os.path.join(prefix, "bin", "ldd")
|
||||||
|
if not os.path.exists(ldd):
|
||||||
|
return None
|
||||||
|
maybe_spec = _libc_from_ldd(ldd)
|
||||||
|
if not maybe_spec:
|
||||||
|
return None
|
||||||
|
maybe_spec.external_path = prefix
|
||||||
|
return maybe_spec
|
||||||
|
|
||||||
|
|
||||||
def in_system_subdirectory(path):
|
def in_system_subdirectory(path):
|
||||||
system_dirs = [
|
system_dirs = [
|
||||||
"/lib/",
|
"/lib/",
|
||||||
|
@ -417,17 +526,33 @@ def real_version(self):
|
||||||
self._real_version = self.version
|
self._real_version = self.version
|
||||||
return self._real_version
|
return self._real_version
|
||||||
|
|
||||||
def implicit_rpaths(self):
|
def implicit_rpaths(self) -> List[str]:
|
||||||
if self.enable_implicit_rpaths is False:
|
if self.enable_implicit_rpaths is False:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
# Put CXX first since it has the most linking issues
|
output = self.compiler_verbose_output
|
||||||
# And because it has flags that affect linking
|
|
||||||
link_dirs = self._get_compiler_link_paths()
|
if not output:
|
||||||
|
return []
|
||||||
|
|
||||||
|
link_dirs = _parse_non_system_link_dirs(output)
|
||||||
|
|
||||||
all_required_libs = list(self.required_libs) + Compiler._all_compiler_rpath_libraries
|
all_required_libs = list(self.required_libs) + Compiler._all_compiler_rpath_libraries
|
||||||
return list(paths_containing_libs(link_dirs, all_required_libs))
|
return list(paths_containing_libs(link_dirs, all_required_libs))
|
||||||
|
|
||||||
|
def default_libc(self) -> Optional["spack.spec.Spec"]:
|
||||||
|
output = self.compiler_verbose_output
|
||||||
|
|
||||||
|
if not output:
|
||||||
|
return None
|
||||||
|
|
||||||
|
dynamic_linker = _parse_dynamic_linker(output)
|
||||||
|
|
||||||
|
if not dynamic_linker:
|
||||||
|
return None
|
||||||
|
|
||||||
|
return _libc_from_dynamic_linker(dynamic_linker)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def required_libs(self):
|
def required_libs(self):
|
||||||
"""For executables created with this compiler, the compiler libraries
|
"""For executables created with this compiler, the compiler libraries
|
||||||
|
@ -436,17 +561,17 @@ def required_libs(self):
|
||||||
# By default every compiler returns the empty list
|
# By default every compiler returns the empty list
|
||||||
return []
|
return []
|
||||||
|
|
||||||
def _get_compiler_link_paths(self):
|
@property
|
||||||
|
def compiler_verbose_output(self) -> Optional[str]:
|
||||||
|
"""Verbose output from compiling a dummy C source file. Output is cached."""
|
||||||
|
if not hasattr(self, "_compile_c_source_output"):
|
||||||
|
self._compile_c_source_output = self._compile_dummy_c_source()
|
||||||
|
return self._compile_c_source_output
|
||||||
|
|
||||||
|
def _compile_dummy_c_source(self) -> Optional[str]:
|
||||||
cc = self.cc if self.cc else self.cxx
|
cc = self.cc if self.cc else self.cxx
|
||||||
if not cc or not self.verbose_flag:
|
if not cc or not self.verbose_flag:
|
||||||
# Cannot determine implicit link paths without a compiler / verbose flag
|
return None
|
||||||
return []
|
|
||||||
|
|
||||||
# What flag types apply to first_compiler, in what order
|
|
||||||
if cc == self.cc:
|
|
||||||
flags = ["cflags", "cppflags", "ldflags"]
|
|
||||||
else:
|
|
||||||
flags = ["cxxflags", "cppflags", "ldflags"]
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
tmpdir = tempfile.mkdtemp(prefix="spack-implicit-link-info")
|
tmpdir = tempfile.mkdtemp(prefix="spack-implicit-link-info")
|
||||||
|
@ -458,20 +583,19 @@ def _get_compiler_link_paths(self):
|
||||||
"int main(int argc, char* argv[]) { (void)argc; (void)argv; return 0; }\n"
|
"int main(int argc, char* argv[]) { (void)argc; (void)argv; return 0; }\n"
|
||||||
)
|
)
|
||||||
cc_exe = spack.util.executable.Executable(cc)
|
cc_exe = spack.util.executable.Executable(cc)
|
||||||
for flag_type in flags:
|
for flag_type in ["cflags" if cc == self.cc else "cxxflags", "cppflags", "ldflags"]:
|
||||||
cc_exe.add_default_arg(*self.flags.get(flag_type, []))
|
cc_exe.add_default_arg(*self.flags.get(flag_type, []))
|
||||||
|
|
||||||
with self.compiler_environment():
|
with self.compiler_environment():
|
||||||
output = cc_exe(self.verbose_flag, fin, "-o", fout, output=str, error=str)
|
return cc_exe(self.verbose_flag, fin, "-o", fout, output=str, error=str)
|
||||||
return _parse_non_system_link_dirs(output)
|
|
||||||
except spack.util.executable.ProcessError as pe:
|
except spack.util.executable.ProcessError as pe:
|
||||||
tty.debug("ProcessError: Command exited with non-zero status: " + pe.long_message)
|
tty.debug("ProcessError: Command exited with non-zero status: " + pe.long_message)
|
||||||
return []
|
return None
|
||||||
finally:
|
finally:
|
||||||
shutil.rmtree(tmpdir, ignore_errors=True)
|
shutil.rmtree(tmpdir, ignore_errors=True)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def verbose_flag(self):
|
def verbose_flag(self) -> Optional[str]:
|
||||||
"""
|
"""
|
||||||
This property should be overridden in the compiler subclass if a
|
This property should be overridden in the compiler subclass if a
|
||||||
verbose flag is available.
|
verbose flag is available.
|
||||||
|
|
|
@ -64,7 +64,7 @@ def verbose_flag(self):
|
||||||
#
|
#
|
||||||
# This way, we at least enable the implicit rpath detection, which is
|
# This way, we at least enable the implicit rpath detection, which is
|
||||||
# based on compilation of a C file (see method
|
# based on compilation of a C file (see method
|
||||||
# spack.compiler._get_compiler_link_paths): in the case of a mixed
|
# spack.compiler._compile_dummy_c_source): in the case of a mixed
|
||||||
# NAG/GCC toolchain, the flag will be passed to g++ (e.g.
|
# NAG/GCC toolchain, the flag will be passed to g++ (e.g.
|
||||||
# 'g++ -Wl,-v ./main.c'), otherwise, the flag will be passed to nagfor
|
# 'g++ -Wl,-v ./main.c'), otherwise, the flag will be passed to nagfor
|
||||||
# (e.g. 'nagfor -Wl,-v ./main.c' - note that nagfor recognizes '.c'
|
# (e.g. 'nagfor -Wl,-v ./main.c' - note that nagfor recognizes '.c'
|
||||||
|
|
|
@ -566,6 +566,23 @@ def _spec_with_default_name(spec_str, name):
|
||||||
return spec
|
return spec
|
||||||
|
|
||||||
|
|
||||||
|
def _external_config_with_implictit_externals():
|
||||||
|
# Read packages.yaml and normalize it, so that it will not contain entries referring to
|
||||||
|
# virtual packages.
|
||||||
|
packages_yaml = _normalize_packages_yaml(spack.config.get("packages"))
|
||||||
|
|
||||||
|
# Add externals for libc from compilers on Linux
|
||||||
|
if spack.platforms.host().name != "linux":
|
||||||
|
return packages_yaml
|
||||||
|
|
||||||
|
for compiler in all_compilers_in_config():
|
||||||
|
libc = compiler.default_libc()
|
||||||
|
if libc:
|
||||||
|
entry = {"spec": f"{libc} %{compiler.spec}", "prefix": libc.external_path}
|
||||||
|
packages_yaml.setdefault(libc.name, {}).setdefault("externals", []).append(entry)
|
||||||
|
return packages_yaml
|
||||||
|
|
||||||
|
|
||||||
class ErrorHandler:
|
class ErrorHandler:
|
||||||
def __init__(self, model):
|
def __init__(self, model):
|
||||||
self.model = model
|
self.model = model
|
||||||
|
@ -1554,12 +1571,8 @@ def emit_facts_from_requirement_rules(self, rules: List[RequirementRule]):
|
||||||
requirement_weight += 1
|
requirement_weight += 1
|
||||||
|
|
||||||
def external_packages(self):
|
def external_packages(self):
|
||||||
"""Facts on external packages, as read from packages.yaml"""
|
"""Facts on external packages, from packages.yaml and implicit externals."""
|
||||||
# Read packages.yaml and normalize it, so that it
|
packages_yaml = _external_config_with_implictit_externals()
|
||||||
# will not contain entries referring to virtual
|
|
||||||
# packages.
|
|
||||||
packages_yaml = spack.config.get("packages")
|
|
||||||
packages_yaml = _normalize_packages_yaml(packages_yaml)
|
|
||||||
|
|
||||||
self.gen.h1("External packages")
|
self.gen.h1("External packages")
|
||||||
for pkg_name, data in packages_yaml.items():
|
for pkg_name, data in packages_yaml.items():
|
||||||
|
@ -3185,12 +3198,8 @@ def no_flags(self, node, flag_type):
|
||||||
self._specs[node].compiler_flags[flag_type] = []
|
self._specs[node].compiler_flags[flag_type] = []
|
||||||
|
|
||||||
def external_spec_selected(self, node, idx):
|
def external_spec_selected(self, node, idx):
|
||||||
"""This means that the external spec and index idx
|
"""This means that the external spec and index idx has been selected for this package."""
|
||||||
has been selected for this package.
|
packages_yaml = _external_config_with_implictit_externals()
|
||||||
"""
|
|
||||||
|
|
||||||
packages_yaml = spack.config.get("packages")
|
|
||||||
packages_yaml = _normalize_packages_yaml(packages_yaml)
|
|
||||||
spec_info = packages_yaml[node.pkg]["externals"][int(idx)]
|
spec_info = packages_yaml[node.pkg]["externals"][int(idx)]
|
||||||
self._specs[node].external_path = spec_info.get("prefix", None)
|
self._specs[node].external_path = spec_info.get("prefix", None)
|
||||||
self._specs[node].external_modules = spack.spec.Spec._format_module_list(
|
self._specs[node].external_modules = spack.spec.Spec._format_module_list(
|
||||||
|
|
|
@ -12,16 +12,3 @@
|
||||||
% macOS
|
% macOS
|
||||||
os_compatible("monterey", "bigsur").
|
os_compatible("monterey", "bigsur").
|
||||||
os_compatible("bigsur", "catalina").
|
os_compatible("bigsur", "catalina").
|
||||||
|
|
||||||
% Ubuntu
|
|
||||||
os_compatible("ubuntu22.04", "ubuntu21.10").
|
|
||||||
os_compatible("ubuntu21.10", "ubuntu21.04").
|
|
||||||
os_compatible("ubuntu21.04", "ubuntu20.10").
|
|
||||||
os_compatible("ubuntu20.10", "ubuntu20.04").
|
|
||||||
os_compatible("ubuntu20.04", "ubuntu19.10").
|
|
||||||
os_compatible("ubuntu19.10", "ubuntu19.04").
|
|
||||||
os_compatible("ubuntu19.04", "ubuntu18.10").
|
|
||||||
os_compatible("ubuntu18.10", "ubuntu18.04").
|
|
||||||
|
|
||||||
%EL8
|
|
||||||
os_compatible("rhel8", "rocky8").
|
|
||||||
|
|
|
@ -14,6 +14,7 @@
|
||||||
import spack.compilers
|
import spack.compilers
|
||||||
import spack.spec
|
import spack.spec
|
||||||
import spack.util.environment
|
import spack.util.environment
|
||||||
|
import spack.util.module_cmd
|
||||||
from spack.compiler import Compiler
|
from spack.compiler import Compiler
|
||||||
from spack.util.executable import Executable, ProcessError
|
from spack.util.executable import Executable, ProcessError
|
||||||
|
|
||||||
|
@ -137,14 +138,6 @@ def __init__(self):
|
||||||
environment={},
|
environment={},
|
||||||
)
|
)
|
||||||
|
|
||||||
def _get_compiler_link_paths(self):
|
|
||||||
# Mock os.path.isdir so the link paths don't have to exist
|
|
||||||
old_isdir = os.path.isdir
|
|
||||||
os.path.isdir = lambda x: True
|
|
||||||
ret = super()._get_compiler_link_paths()
|
|
||||||
os.path.isdir = old_isdir
|
|
||||||
return ret
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self):
|
def name(self):
|
||||||
return "mockcompiler"
|
return "mockcompiler"
|
||||||
|
@ -162,34 +155,25 @@ def verbose_flag(self):
|
||||||
required_libs = ["libgfortran"]
|
required_libs = ["libgfortran"]
|
||||||
|
|
||||||
|
|
||||||
def test_implicit_rpaths(dirs_with_libfiles, monkeypatch):
|
@pytest.mark.not_on_windows("Not supported on Windows (yet)")
|
||||||
|
def test_implicit_rpaths(dirs_with_libfiles):
|
||||||
lib_to_dirs, all_dirs = dirs_with_libfiles
|
lib_to_dirs, all_dirs = dirs_with_libfiles
|
||||||
|
|
||||||
def try_all_dirs(*args):
|
|
||||||
return all_dirs
|
|
||||||
|
|
||||||
monkeypatch.setattr(MockCompiler, "_get_compiler_link_paths", try_all_dirs)
|
|
||||||
|
|
||||||
expected_rpaths = set(lib_to_dirs["libstdc++"] + lib_to_dirs["libgfortran"])
|
|
||||||
|
|
||||||
compiler = MockCompiler()
|
compiler = MockCompiler()
|
||||||
|
compiler._compile_c_source_output = "ld " + " ".join(f"-L{d}" for d in all_dirs)
|
||||||
retrieved_rpaths = compiler.implicit_rpaths()
|
retrieved_rpaths = compiler.implicit_rpaths()
|
||||||
assert set(retrieved_rpaths) == expected_rpaths
|
assert set(retrieved_rpaths) == set(lib_to_dirs["libstdc++"] + lib_to_dirs["libgfortran"])
|
||||||
|
|
||||||
|
|
||||||
no_flag_dirs = ["/path/to/first/lib", "/path/to/second/lib64"]
|
without_flag_output = "ld -L/path/to/first/lib -L/path/to/second/lib64"
|
||||||
no_flag_output = "ld -L%s -L%s" % tuple(no_flag_dirs)
|
with_flag_output = "ld -L/path/to/first/with/flag/lib -L/path/to/second/lib64"
|
||||||
|
|
||||||
flag_dirs = ["/path/to/first/with/flag/lib", "/path/to/second/lib64"]
|
|
||||||
flag_output = "ld -L%s -L%s" % tuple(flag_dirs)
|
|
||||||
|
|
||||||
|
|
||||||
def call_compiler(exe, *args, **kwargs):
|
def call_compiler(exe, *args, **kwargs):
|
||||||
# This method can replace Executable.__call__ to emulate a compiler that
|
# This method can replace Executable.__call__ to emulate a compiler that
|
||||||
# changes libraries depending on a flag.
|
# changes libraries depending on a flag.
|
||||||
if "--correct-flag" in exe.exe:
|
if "--correct-flag" in exe.exe:
|
||||||
return flag_output
|
return with_flag_output
|
||||||
return no_flag_output
|
return without_flag_output
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.not_on_windows("Not supported on Windows (yet)")
|
@pytest.mark.not_on_windows("Not supported on Windows (yet)")
|
||||||
|
@ -203,8 +187,8 @@ def call_compiler(exe, *args, **kwargs):
|
||||||
("cc", "cppflags"),
|
("cc", "cppflags"),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
@pytest.mark.enable_compiler_link_paths
|
@pytest.mark.enable_compiler_execution
|
||||||
def test_get_compiler_link_paths(monkeypatch, exe, flagname):
|
def test_compile_dummy_c_source_adds_flags(monkeypatch, exe, flagname):
|
||||||
# create fake compiler that emits mock verbose output
|
# create fake compiler that emits mock verbose output
|
||||||
compiler = MockCompiler()
|
compiler = MockCompiler()
|
||||||
monkeypatch.setattr(Executable, "__call__", call_compiler)
|
monkeypatch.setattr(Executable, "__call__", call_compiler)
|
||||||
|
@ -221,40 +205,38 @@ def test_get_compiler_link_paths(monkeypatch, exe, flagname):
|
||||||
assert False
|
assert False
|
||||||
|
|
||||||
# Test without flags
|
# Test without flags
|
||||||
assert compiler._get_compiler_link_paths() == no_flag_dirs
|
assert compiler._compile_dummy_c_source() == without_flag_output
|
||||||
|
|
||||||
if flagname:
|
if flagname:
|
||||||
# set flags and test
|
# set flags and test
|
||||||
compiler.flags = {flagname: ["--correct-flag"]}
|
compiler.flags = {flagname: ["--correct-flag"]}
|
||||||
assert compiler._get_compiler_link_paths() == flag_dirs
|
assert compiler._compile_dummy_c_source() == with_flag_output
|
||||||
|
|
||||||
|
|
||||||
def test_get_compiler_link_paths_no_path():
|
@pytest.mark.enable_compiler_execution
|
||||||
|
def test_compile_dummy_c_source_no_path():
|
||||||
compiler = MockCompiler()
|
compiler = MockCompiler()
|
||||||
compiler.cc = None
|
compiler.cc = None
|
||||||
compiler.cxx = None
|
compiler.cxx = None
|
||||||
compiler.f77 = None
|
assert compiler._compile_dummy_c_source() is None
|
||||||
compiler.fc = None
|
|
||||||
assert compiler._get_compiler_link_paths() == []
|
|
||||||
|
|
||||||
|
|
||||||
def test_get_compiler_link_paths_no_verbose_flag():
|
@pytest.mark.enable_compiler_execution
|
||||||
|
def test_compile_dummy_c_source_no_verbose_flag():
|
||||||
compiler = MockCompiler()
|
compiler = MockCompiler()
|
||||||
compiler._verbose_flag = None
|
compiler._verbose_flag = None
|
||||||
assert compiler._get_compiler_link_paths() == []
|
assert compiler._compile_dummy_c_source() is None
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.not_on_windows("Not supported on Windows (yet)")
|
@pytest.mark.not_on_windows("Not supported on Windows (yet)")
|
||||||
@pytest.mark.enable_compiler_link_paths
|
@pytest.mark.enable_compiler_execution
|
||||||
def test_get_compiler_link_paths_load_env(working_env, monkeypatch, tmpdir):
|
def test_compile_dummy_c_source_load_env(working_env, monkeypatch, tmpdir):
|
||||||
gcc = str(tmpdir.join("gcc"))
|
gcc = str(tmpdir.join("gcc"))
|
||||||
with open(gcc, "w") as f:
|
with open(gcc, "w") as f:
|
||||||
f.write(
|
f.write(
|
||||||
"""#!/bin/sh
|
f"""#!/bin/sh
|
||||||
if [ "$ENV_SET" = "1" ] && [ "$MODULE_LOADED" = "1" ]; then
|
if [ "$ENV_SET" = "1" ] && [ "$MODULE_LOADED" = "1" ]; then
|
||||||
echo '"""
|
printf '{without_flag_output}'
|
||||||
+ no_flag_output
|
|
||||||
+ """'
|
|
||||||
fi
|
fi
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
|
@ -274,7 +256,7 @@ def module(*args):
|
||||||
compiler.environment = {"set": {"ENV_SET": "1"}}
|
compiler.environment = {"set": {"ENV_SET": "1"}}
|
||||||
compiler.modules = ["turn_on"]
|
compiler.modules = ["turn_on"]
|
||||||
|
|
||||||
assert compiler._get_compiler_link_paths() == no_flag_dirs
|
assert compiler._compile_dummy_c_source() == without_flag_output
|
||||||
|
|
||||||
|
|
||||||
# Get the desired flag from the specified compiler spec.
|
# Get the desired flag from the specified compiler spec.
|
||||||
|
|
|
@ -34,6 +34,7 @@
|
||||||
import spack.binary_distribution
|
import spack.binary_distribution
|
||||||
import spack.caches
|
import spack.caches
|
||||||
import spack.cmd.buildcache
|
import spack.cmd.buildcache
|
||||||
|
import spack.compiler
|
||||||
import spack.compilers
|
import spack.compilers
|
||||||
import spack.config
|
import spack.config
|
||||||
import spack.database
|
import spack.database
|
||||||
|
@ -269,10 +270,6 @@ def clean_test_environment():
|
||||||
ev.deactivate()
|
ev.deactivate()
|
||||||
|
|
||||||
|
|
||||||
def _verify_executables_noop(*args):
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def _host():
|
def _host():
|
||||||
"""Mock archspec host so there is no inconsistency on the Windows platform
|
"""Mock archspec host so there is no inconsistency on the Windows platform
|
||||||
This function cannot be local as it needs to be pickleable"""
|
This function cannot be local as it needs to be pickleable"""
|
||||||
|
@ -298,9 +295,7 @@ def mock_compiler_executable_verification(request, monkeypatch):
|
||||||
|
|
||||||
If a test is marked in that way this is a no-op."""
|
If a test is marked in that way this is a no-op."""
|
||||||
if "enable_compiler_verification" not in request.keywords:
|
if "enable_compiler_verification" not in request.keywords:
|
||||||
monkeypatch.setattr(
|
monkeypatch.setattr(spack.compiler.Compiler, "verify_executables", _return_none)
|
||||||
spack.compiler.Compiler, "verify_executables", _verify_executables_noop
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
# Hooks to add command line options or set other custom behaviors.
|
# Hooks to add command line options or set other custom behaviors.
|
||||||
|
@ -934,26 +929,16 @@ def dirs_with_libfiles(tmpdir_factory):
|
||||||
yield lib_to_dirs, all_dirs
|
yield lib_to_dirs, all_dirs
|
||||||
|
|
||||||
|
|
||||||
def _compiler_link_paths_noop(*args):
|
def _return_none(*args):
|
||||||
return []
|
return None
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="function", autouse=True)
|
@pytest.fixture(scope="function", autouse=True)
|
||||||
def disable_compiler_execution(monkeypatch, request):
|
def disable_compiler_execution(monkeypatch, request):
|
||||||
"""
|
"""Disable compiler execution to determine implicit link paths and libc flavor and version.
|
||||||
This fixture can be disabled for tests of the compiler link path
|
To re-enable use `@pytest.mark.enable_compiler_execution`"""
|
||||||
functionality by::
|
if "enable_compiler_execution" not in request.keywords:
|
||||||
|
monkeypatch.setattr(spack.compiler.Compiler, "_compile_dummy_c_source", _return_none)
|
||||||
@pytest.mark.enable_compiler_link_paths
|
|
||||||
|
|
||||||
If a test is marked in that way this is a no-op."""
|
|
||||||
if "enable_compiler_link_paths" not in request.keywords:
|
|
||||||
# Compiler.determine_implicit_rpaths actually runs the compiler. So
|
|
||||||
# replace that function with a noop that simulates finding no implicit
|
|
||||||
# RPATHs
|
|
||||||
monkeypatch.setattr(
|
|
||||||
spack.compiler.Compiler, "_get_compiler_link_paths", _compiler_link_paths_noop
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="function")
|
@pytest.fixture(scope="function")
|
||||||
|
|
|
@ -12,7 +12,7 @@ markers =
|
||||||
requires_executables: tests that requires certain executables in PATH to run
|
requires_executables: tests that requires certain executables in PATH to run
|
||||||
nomockstage: use a stage area specifically created for this test, instead of relying on a common mock stage
|
nomockstage: use a stage area specifically created for this test, instead of relying on a common mock stage
|
||||||
enable_compiler_verification: enable compiler verification within unit tests
|
enable_compiler_verification: enable compiler verification within unit tests
|
||||||
enable_compiler_link_paths: verifies compiler link paths within unit tests
|
enable_compiler_execution: enable compiler execution to detect link paths and libc
|
||||||
disable_clean_stage_check: avoid failing tests if there are leftover files in the stage area
|
disable_clean_stage_check: avoid failing tests if there are leftover files in the stage area
|
||||||
only_clingo: mark unit tests that run only with clingo
|
only_clingo: mark unit tests that run only with clingo
|
||||||
only_original: mark unit tests that are specific to the original concretizer
|
only_original: mark unit tests that are specific to the original concretizer
|
||||||
|
|
|
@ -1185,5 +1185,13 @@ def runtime_constraints(cls, *, spec, pkg):
|
||||||
description=f"Add a dependency on '{gfortran_str}' for nodes compiled with "
|
description=f"Add a dependency on '{gfortran_str}' for nodes compiled with "
|
||||||
f"{str(spec)} and using the 'fortran' language",
|
f"{str(spec)} and using the 'fortran' language",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
libc = compiler.default_libc()
|
||||||
|
|
||||||
|
if libc:
|
||||||
|
pkg("*").depends_on(
|
||||||
|
str(libc), when=f"%{str(compiler.spec)}", type="link", description="Add libc"
|
||||||
|
)
|
||||||
|
|
||||||
# The version of gcc-runtime is the same as the %gcc used to "compile" it
|
# The version of gcc-runtime is the same as the %gcc used to "compile" it
|
||||||
pkg("gcc-runtime").requires(f"@={str(spec.version)}", when=f"%{str(spec)}")
|
pkg("gcc-runtime").requires(f"@={str(spec.version)}", when=f"%{str(spec)}")
|
||||||
|
|
|
@ -20,9 +20,12 @@ class Glibc(AutotoolsPackage, GNUMirrorPackage):
|
||||||
maintainers("haampie")
|
maintainers("haampie")
|
||||||
|
|
||||||
build_directory = "build"
|
build_directory = "build"
|
||||||
|
tags = ["runtime"]
|
||||||
|
|
||||||
license("LGPL-2.1-or-later")
|
license("LGPL-2.1-or-later")
|
||||||
|
|
||||||
|
provides("libc")
|
||||||
|
|
||||||
version("master", branch="master")
|
version("master", branch="master")
|
||||||
version("2.39", sha256="97f84f3b7588cd54093a6f6389b0c1a81e70d99708d74963a2e3eab7c7dc942d")
|
version("2.39", sha256="97f84f3b7588cd54093a6f6389b0c1a81e70d99708d74963a2e3eab7c7dc942d")
|
||||||
version("2.38", sha256="16e51e0455e288f03380b436e41d5927c60945abd86d0c9852b84be57dd6ed5e")
|
version("2.38", sha256="16e51e0455e288f03380b436e41d5927c60945abd86d0c9852b84be57dd6ed5e")
|
||||||
|
|
|
@ -25,9 +25,12 @@ class Musl(MakefilePackage):
|
||||||
|
|
||||||
homepage = "https://www.musl-libc.org"
|
homepage = "https://www.musl-libc.org"
|
||||||
url = "https://www.musl-libc.org/releases/musl-1.1.23.tar.gz"
|
url = "https://www.musl-libc.org/releases/musl-1.1.23.tar.gz"
|
||||||
|
tags = ["runtime"]
|
||||||
|
|
||||||
license("MIT")
|
license("MIT")
|
||||||
|
|
||||||
|
provides("libc")
|
||||||
|
|
||||||
version("1.2.4", sha256="7a35eae33d5372a7c0da1188de798726f68825513b7ae3ebe97aaaa52114f039")
|
version("1.2.4", sha256="7a35eae33d5372a7c0da1188de798726f68825513b7ae3ebe97aaaa52114f039")
|
||||||
version("1.2.3", sha256="7d5b0b6062521e4627e099e4c9dc8248d32a30285e959b7eecaa780cf8cfd4a4")
|
version("1.2.3", sha256="7d5b0b6062521e4627e099e4c9dc8248d32a30285e959b7eecaa780cf8cfd4a4")
|
||||||
version("1.2.2", sha256="9b969322012d796dc23dda27a35866034fa67d8fb67e0e2c45c913c3d43219dd")
|
version("1.2.2", sha256="9b969322012d796dc23dda27a35866034fa67d8fb67e0e2c45c913c3d43219dd")
|
||||||
|
|
Loading…
Reference in a new issue