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:
		
				
					committed by
					
						
						Harmen Stoppels
					
				
			
			
				
	
			
			
			
						parent
						
							e8c41cdbcb
						
					
				
				
					commit
					209a3bf302
				
			@@ -8,9 +8,11 @@
 | 
			
		||||
import os
 | 
			
		||||
import platform
 | 
			
		||||
import re
 | 
			
		||||
import shlex
 | 
			
		||||
import shutil
 | 
			
		||||
import sys
 | 
			
		||||
import tempfile
 | 
			
		||||
from subprocess import PIPE, run
 | 
			
		||||
from typing import List, Optional, Sequence
 | 
			
		||||
 | 
			
		||||
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))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
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):
 | 
			
		||||
    system_dirs = [
 | 
			
		||||
        "/lib/",
 | 
			
		||||
@@ -417,17 +526,33 @@ def real_version(self):
 | 
			
		||||
                self._real_version = self.version
 | 
			
		||||
        return self._real_version
 | 
			
		||||
 | 
			
		||||
    def implicit_rpaths(self):
 | 
			
		||||
    def implicit_rpaths(self) -> List[str]:
 | 
			
		||||
        if self.enable_implicit_rpaths is False:
 | 
			
		||||
            return []
 | 
			
		||||
 | 
			
		||||
        # Put CXX first since it has the most linking issues
 | 
			
		||||
        # And because it has flags that affect linking
 | 
			
		||||
        link_dirs = self._get_compiler_link_paths()
 | 
			
		||||
        output = self.compiler_verbose_output
 | 
			
		||||
 | 
			
		||||
        if not output:
 | 
			
		||||
            return []
 | 
			
		||||
 | 
			
		||||
        link_dirs = _parse_non_system_link_dirs(output)
 | 
			
		||||
 | 
			
		||||
        all_required_libs = list(self.required_libs) + Compiler._all_compiler_rpath_libraries
 | 
			
		||||
        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
 | 
			
		||||
    def required_libs(self):
 | 
			
		||||
        """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
 | 
			
		||||
        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
 | 
			
		||||
        if not cc or not self.verbose_flag:
 | 
			
		||||
            # Cannot determine implicit link paths without a compiler / verbose flag
 | 
			
		||||
            return []
 | 
			
		||||
 | 
			
		||||
        # What flag types apply to first_compiler, in what order
 | 
			
		||||
        if cc == self.cc:
 | 
			
		||||
            flags = ["cflags", "cppflags", "ldflags"]
 | 
			
		||||
        else:
 | 
			
		||||
            flags = ["cxxflags", "cppflags", "ldflags"]
 | 
			
		||||
            return None
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            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"
 | 
			
		||||
                )
 | 
			
		||||
            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, []))
 | 
			
		||||
 | 
			
		||||
            with self.compiler_environment():
 | 
			
		||||
                output = cc_exe(self.verbose_flag, fin, "-o", fout, output=str, error=str)
 | 
			
		||||
            return _parse_non_system_link_dirs(output)
 | 
			
		||||
                return cc_exe(self.verbose_flag, fin, "-o", fout, output=str, error=str)
 | 
			
		||||
        except spack.util.executable.ProcessError as pe:
 | 
			
		||||
            tty.debug("ProcessError: Command exited with non-zero status: " + pe.long_message)
 | 
			
		||||
            return []
 | 
			
		||||
            return None
 | 
			
		||||
        finally:
 | 
			
		||||
            shutil.rmtree(tmpdir, ignore_errors=True)
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def verbose_flag(self):
 | 
			
		||||
    def verbose_flag(self) -> Optional[str]:
 | 
			
		||||
        """
 | 
			
		||||
        This property should be overridden in the compiler subclass if a
 | 
			
		||||
        verbose flag is available.
 | 
			
		||||
 
 | 
			
		||||
@@ -64,7 +64,7 @@ def verbose_flag(self):
 | 
			
		||||
        #
 | 
			
		||||
        # This way, we at least enable the implicit rpath detection, which is
 | 
			
		||||
        # 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.
 | 
			
		||||
        # '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'
 | 
			
		||||
 
 | 
			
		||||
@@ -566,6 +566,23 @@ def _spec_with_default_name(spec_str, name):
 | 
			
		||||
    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:
 | 
			
		||||
    def __init__(self, model):
 | 
			
		||||
        self.model = model
 | 
			
		||||
@@ -1554,12 +1571,8 @@ def emit_facts_from_requirement_rules(self, rules: List[RequirementRule]):
 | 
			
		||||
                requirement_weight += 1
 | 
			
		||||
 | 
			
		||||
    def external_packages(self):
 | 
			
		||||
        """Facts on external packages, as read from packages.yaml"""
 | 
			
		||||
        # Read packages.yaml and normalize it, so that it
 | 
			
		||||
        # will not contain entries referring to virtual
 | 
			
		||||
        # packages.
 | 
			
		||||
        packages_yaml = spack.config.get("packages")
 | 
			
		||||
        packages_yaml = _normalize_packages_yaml(packages_yaml)
 | 
			
		||||
        """Facts on external packages, from packages.yaml and implicit externals."""
 | 
			
		||||
        packages_yaml = _external_config_with_implictit_externals()
 | 
			
		||||
 | 
			
		||||
        self.gen.h1("External packages")
 | 
			
		||||
        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] = []
 | 
			
		||||
 | 
			
		||||
    def external_spec_selected(self, node, idx):
 | 
			
		||||
        """This means that the external spec and index idx
 | 
			
		||||
        has been selected for this package.
 | 
			
		||||
        """
 | 
			
		||||
 | 
			
		||||
        packages_yaml = spack.config.get("packages")
 | 
			
		||||
        packages_yaml = _normalize_packages_yaml(packages_yaml)
 | 
			
		||||
        """This means that the external spec and index idx has been selected for this package."""
 | 
			
		||||
        packages_yaml = _external_config_with_implictit_externals()
 | 
			
		||||
        spec_info = packages_yaml[node.pkg]["externals"][int(idx)]
 | 
			
		||||
        self._specs[node].external_path = spec_info.get("prefix", None)
 | 
			
		||||
        self._specs[node].external_modules = spack.spec.Spec._format_module_list(
 | 
			
		||||
 
 | 
			
		||||
@@ -12,16 +12,3 @@
 | 
			
		||||
% macOS
 | 
			
		||||
os_compatible("monterey", "bigsur").
 | 
			
		||||
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.spec
 | 
			
		||||
import spack.util.environment
 | 
			
		||||
import spack.util.module_cmd
 | 
			
		||||
from spack.compiler import Compiler
 | 
			
		||||
from spack.util.executable import Executable, ProcessError
 | 
			
		||||
 | 
			
		||||
@@ -137,14 +138,6 @@ def __init__(self):
 | 
			
		||||
            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
 | 
			
		||||
    def name(self):
 | 
			
		||||
        return "mockcompiler"
 | 
			
		||||
@@ -162,34 +155,25 @@ def verbose_flag(self):
 | 
			
		||||
    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
 | 
			
		||||
 | 
			
		||||
    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._compile_c_source_output = "ld " + " ".join(f"-L{d}" for d in all_dirs)
 | 
			
		||||
    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"]
 | 
			
		||||
no_flag_output = "ld -L%s -L%s" % tuple(no_flag_dirs)
 | 
			
		||||
 | 
			
		||||
flag_dirs = ["/path/to/first/with/flag/lib", "/path/to/second/lib64"]
 | 
			
		||||
flag_output = "ld -L%s -L%s" % tuple(flag_dirs)
 | 
			
		||||
without_flag_output = "ld -L/path/to/first/lib -L/path/to/second/lib64"
 | 
			
		||||
with_flag_output = "ld -L/path/to/first/with/flag/lib -L/path/to/second/lib64"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def call_compiler(exe, *args, **kwargs):
 | 
			
		||||
    # This method can replace Executable.__call__ to emulate a compiler that
 | 
			
		||||
    # changes libraries depending on a flag.
 | 
			
		||||
    if "--correct-flag" in exe.exe:
 | 
			
		||||
        return flag_output
 | 
			
		||||
    return no_flag_output
 | 
			
		||||
        return with_flag_output
 | 
			
		||||
    return without_flag_output
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@pytest.mark.not_on_windows("Not supported on Windows (yet)")
 | 
			
		||||
@@ -203,8 +187,8 @@ def call_compiler(exe, *args, **kwargs):
 | 
			
		||||
        ("cc", "cppflags"),
 | 
			
		||||
    ],
 | 
			
		||||
)
 | 
			
		||||
@pytest.mark.enable_compiler_link_paths
 | 
			
		||||
def test_get_compiler_link_paths(monkeypatch, exe, flagname):
 | 
			
		||||
@pytest.mark.enable_compiler_execution
 | 
			
		||||
def test_compile_dummy_c_source_adds_flags(monkeypatch, exe, flagname):
 | 
			
		||||
    # create fake compiler that emits mock verbose output
 | 
			
		||||
    compiler = MockCompiler()
 | 
			
		||||
    monkeypatch.setattr(Executable, "__call__", call_compiler)
 | 
			
		||||
@@ -221,40 +205,38 @@ def test_get_compiler_link_paths(monkeypatch, exe, flagname):
 | 
			
		||||
        assert False
 | 
			
		||||
 | 
			
		||||
    # Test without flags
 | 
			
		||||
    assert compiler._get_compiler_link_paths() == no_flag_dirs
 | 
			
		||||
    assert compiler._compile_dummy_c_source() == without_flag_output
 | 
			
		||||
 | 
			
		||||
    if flagname:
 | 
			
		||||
        # set flags and test
 | 
			
		||||
        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.cc = None
 | 
			
		||||
    compiler.cxx = None
 | 
			
		||||
    compiler.f77 = None
 | 
			
		||||
    compiler.fc = None
 | 
			
		||||
    assert compiler._get_compiler_link_paths() == []
 | 
			
		||||
    assert compiler._compile_dummy_c_source() is None
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
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._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.enable_compiler_link_paths
 | 
			
		||||
def test_get_compiler_link_paths_load_env(working_env, monkeypatch, tmpdir):
 | 
			
		||||
@pytest.mark.enable_compiler_execution
 | 
			
		||||
def test_compile_dummy_c_source_load_env(working_env, monkeypatch, tmpdir):
 | 
			
		||||
    gcc = str(tmpdir.join("gcc"))
 | 
			
		||||
    with open(gcc, "w") as f:
 | 
			
		||||
        f.write(
 | 
			
		||||
            """#!/bin/sh
 | 
			
		||||
            f"""#!/bin/sh
 | 
			
		||||
if [ "$ENV_SET" = "1" ] && [ "$MODULE_LOADED" = "1" ]; then
 | 
			
		||||
  echo '"""
 | 
			
		||||
            + no_flag_output
 | 
			
		||||
            + """'
 | 
			
		||||
  printf '{without_flag_output}'
 | 
			
		||||
fi
 | 
			
		||||
"""
 | 
			
		||||
        )
 | 
			
		||||
@@ -274,7 +256,7 @@ def module(*args):
 | 
			
		||||
    compiler.environment = {"set": {"ENV_SET": "1"}}
 | 
			
		||||
    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.
 | 
			
		||||
 
 | 
			
		||||
@@ -34,6 +34,7 @@
 | 
			
		||||
import spack.binary_distribution
 | 
			
		||||
import spack.caches
 | 
			
		||||
import spack.cmd.buildcache
 | 
			
		||||
import spack.compiler
 | 
			
		||||
import spack.compilers
 | 
			
		||||
import spack.config
 | 
			
		||||
import spack.database
 | 
			
		||||
@@ -269,10 +270,6 @@ def clean_test_environment():
 | 
			
		||||
    ev.deactivate()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _verify_executables_noop(*args):
 | 
			
		||||
    return None
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _host():
 | 
			
		||||
    """Mock archspec host so there is no inconsistency on the Windows platform
 | 
			
		||||
    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 "enable_compiler_verification" not in request.keywords:
 | 
			
		||||
        monkeypatch.setattr(
 | 
			
		||||
            spack.compiler.Compiler, "verify_executables", _verify_executables_noop
 | 
			
		||||
        )
 | 
			
		||||
        monkeypatch.setattr(spack.compiler.Compiler, "verify_executables", _return_none)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# 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
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _compiler_link_paths_noop(*args):
 | 
			
		||||
    return []
 | 
			
		||||
def _return_none(*args):
 | 
			
		||||
    return None
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@pytest.fixture(scope="function", autouse=True)
 | 
			
		||||
def disable_compiler_execution(monkeypatch, request):
 | 
			
		||||
    """
 | 
			
		||||
    This fixture can be disabled for tests of the compiler link path
 | 
			
		||||
    functionality by::
 | 
			
		||||
 | 
			
		||||
        @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
 | 
			
		||||
        )
 | 
			
		||||
    """Disable compiler execution to determine implicit link paths and libc flavor and version.
 | 
			
		||||
    To re-enable use `@pytest.mark.enable_compiler_execution`"""
 | 
			
		||||
    if "enable_compiler_execution" not in request.keywords:
 | 
			
		||||
        monkeypatch.setattr(spack.compiler.Compiler, "_compile_dummy_c_source", _return_none)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@pytest.fixture(scope="function")
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user