spack.compiler
/spack.util.libc
: add caching (#47213)
* spack.compiler: cache output * compute libc from the dynamic linker at most once per spack process * wrap compiler cache entry in class, add type hints * test compiler caching * ensure tests do not populate user cache, and fix 2 tests * avoid recursion: cache lookup -> compute key -> cflags -> real_version -> cache lookup * allow compiler execution in test that depends on get_real_version
This commit is contained in:
parent
4322cf56b1
commit
c6997e11a7
@ -221,6 +221,7 @@ def setup(sphinx):
|
|||||||
("py:class", "spack.filesystem_view.SimpleFilesystemView"),
|
("py:class", "spack.filesystem_view.SimpleFilesystemView"),
|
||||||
("py:class", "spack.traverse.EdgeAndDepth"),
|
("py:class", "spack.traverse.EdgeAndDepth"),
|
||||||
("py:class", "archspec.cpu.microarchitecture.Microarchitecture"),
|
("py:class", "archspec.cpu.microarchitecture.Microarchitecture"),
|
||||||
|
("py:class", "spack.compiler.CompilerCache"),
|
||||||
# TypeVar that is not handled correctly
|
# TypeVar that is not handled correctly
|
||||||
("py:class", "llnl.util.lang.T"),
|
("py:class", "llnl.util.lang.T"),
|
||||||
]
|
]
|
||||||
|
@ -4,20 +4,23 @@
|
|||||||
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||||
|
|
||||||
import contextlib
|
import contextlib
|
||||||
|
import hashlib
|
||||||
import itertools
|
import itertools
|
||||||
|
import json
|
||||||
import os
|
import os
|
||||||
import platform
|
import platform
|
||||||
import re
|
import re
|
||||||
import shutil
|
import shutil
|
||||||
import sys
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
from typing import List, Optional, Sequence
|
from typing import Dict, List, Optional, Sequence
|
||||||
|
|
||||||
import llnl.path
|
import llnl.path
|
||||||
import llnl.util.lang
|
import llnl.util.lang
|
||||||
import llnl.util.tty as tty
|
import llnl.util.tty as tty
|
||||||
from llnl.util.filesystem import path_contains_subdirectory, paths_containing_libs
|
from llnl.util.filesystem import path_contains_subdirectory, paths_containing_libs
|
||||||
|
|
||||||
|
import spack.caches
|
||||||
import spack.error
|
import spack.error
|
||||||
import spack.schema.environment
|
import spack.schema.environment
|
||||||
import spack.spec
|
import spack.spec
|
||||||
@ -34,7 +37,7 @@
|
|||||||
|
|
||||||
|
|
||||||
@llnl.util.lang.memoized
|
@llnl.util.lang.memoized
|
||||||
def _get_compiler_version_output(compiler_path, version_arg, ignore_errors=()):
|
def _get_compiler_version_output(compiler_path, version_arg, ignore_errors=()) -> str:
|
||||||
"""Invokes the compiler at a given path passing a single
|
"""Invokes the compiler at a given path passing a single
|
||||||
version argument and returns the output.
|
version argument and returns the output.
|
||||||
|
|
||||||
@ -57,7 +60,7 @@ def _get_compiler_version_output(compiler_path, version_arg, ignore_errors=()):
|
|||||||
return output
|
return output
|
||||||
|
|
||||||
|
|
||||||
def get_compiler_version_output(compiler_path, *args, **kwargs):
|
def get_compiler_version_output(compiler_path, *args, **kwargs) -> str:
|
||||||
"""Wrapper for _get_compiler_version_output()."""
|
"""Wrapper for _get_compiler_version_output()."""
|
||||||
# This ensures that we memoize compiler output by *absolute path*,
|
# This ensures that we memoize compiler output by *absolute path*,
|
||||||
# not just executable name. If we don't do this, and the path changes
|
# not just executable name. If we don't do this, and the path changes
|
||||||
@ -290,6 +293,7 @@ def __init__(
|
|||||||
self.environment = environment or {}
|
self.environment = environment or {}
|
||||||
self.extra_rpaths = extra_rpaths or []
|
self.extra_rpaths = extra_rpaths or []
|
||||||
self.enable_implicit_rpaths = enable_implicit_rpaths
|
self.enable_implicit_rpaths = enable_implicit_rpaths
|
||||||
|
self.cache = COMPILER_CACHE
|
||||||
|
|
||||||
self.cc = paths[0]
|
self.cc = paths[0]
|
||||||
self.cxx = paths[1]
|
self.cxx = paths[1]
|
||||||
@ -390,15 +394,11 @@ def real_version(self):
|
|||||||
|
|
||||||
E.g. C++11 flag checks.
|
E.g. C++11 flag checks.
|
||||||
"""
|
"""
|
||||||
if not self._real_version:
|
real_version_str = self.cache.get(self).real_version
|
||||||
try:
|
if not real_version_str or real_version_str == "unknown":
|
||||||
real_version = spack.version.Version(self.get_real_version())
|
return self.version
|
||||||
if real_version == spack.version.Version("unknown"):
|
|
||||||
return self.version
|
return spack.version.StandardVersion.from_string(real_version_str)
|
||||||
self._real_version = real_version
|
|
||||||
except spack.util.executable.ProcessError:
|
|
||||||
self._real_version = self.version
|
|
||||||
return self._real_version
|
|
||||||
|
|
||||||
def implicit_rpaths(self) -> List[str]:
|
def implicit_rpaths(self) -> List[str]:
|
||||||
if self.enable_implicit_rpaths is False:
|
if self.enable_implicit_rpaths is False:
|
||||||
@ -445,9 +445,7 @@ def required_libs(self):
|
|||||||
@property
|
@property
|
||||||
def compiler_verbose_output(self) -> Optional[str]:
|
def compiler_verbose_output(self) -> Optional[str]:
|
||||||
"""Verbose output from compiling a dummy C source file. Output is cached."""
|
"""Verbose output from compiling a dummy C source file. Output is cached."""
|
||||||
if not hasattr(self, "_compile_c_source_output"):
|
return self.cache.get(self).c_compiler_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]:
|
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
|
||||||
@ -559,7 +557,7 @@ def fc_pic_flag(self):
|
|||||||
# Note: This is not a class method. The class methods are used to detect
|
# Note: This is not a class method. The class methods are used to detect
|
||||||
# compilers on PATH based systems, and do not set up the run environment of
|
# compilers on PATH based systems, and do not set up the run environment of
|
||||||
# the compiler. This method can be called on `module` based systems as well
|
# the compiler. This method can be called on `module` based systems as well
|
||||||
def get_real_version(self):
|
def get_real_version(self) -> str:
|
||||||
"""Query the compiler for its version.
|
"""Query the compiler for its version.
|
||||||
|
|
||||||
This is the "real" compiler version, regardless of what is in the
|
This is the "real" compiler version, regardless of what is in the
|
||||||
@ -569,14 +567,17 @@ def get_real_version(self):
|
|||||||
modifications) to enable the compiler to run properly on any platform.
|
modifications) to enable the compiler to run properly on any platform.
|
||||||
"""
|
"""
|
||||||
cc = spack.util.executable.Executable(self.cc)
|
cc = spack.util.executable.Executable(self.cc)
|
||||||
with self.compiler_environment():
|
try:
|
||||||
output = cc(
|
with self.compiler_environment():
|
||||||
self.version_argument,
|
output = cc(
|
||||||
output=str,
|
self.version_argument,
|
||||||
error=str,
|
output=str,
|
||||||
ignore_errors=tuple(self.ignore_version_errors),
|
error=str,
|
||||||
)
|
ignore_errors=tuple(self.ignore_version_errors),
|
||||||
return self.extract_version_from_output(output)
|
)
|
||||||
|
return self.extract_version_from_output(output)
|
||||||
|
except spack.util.executable.ProcessError:
|
||||||
|
return "unknown"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def prefix(self):
|
def prefix(self):
|
||||||
@ -603,7 +604,7 @@ def default_version(cls, cc):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@llnl.util.lang.memoized
|
@llnl.util.lang.memoized
|
||||||
def extract_version_from_output(cls, output):
|
def extract_version_from_output(cls, output: str) -> str:
|
||||||
"""Extracts the version from compiler's output."""
|
"""Extracts the version from compiler's output."""
|
||||||
match = re.search(cls.version_regex, output)
|
match = re.search(cls.version_regex, output)
|
||||||
return match.group(1) if match else "unknown"
|
return match.group(1) if match else "unknown"
|
||||||
@ -732,3 +733,106 @@ def __init__(self, compiler, feature, flag_name, ver_string=None):
|
|||||||
)
|
)
|
||||||
+ " implement the {0} property and submit a pull request or issue.".format(flag_name),
|
+ " implement the {0} property and submit a pull request or issue.".format(flag_name),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class CompilerCacheEntry:
|
||||||
|
"""Deserialized cache entry for a compiler"""
|
||||||
|
|
||||||
|
__slots__ = ["c_compiler_output", "real_version"]
|
||||||
|
|
||||||
|
def __init__(self, c_compiler_output: Optional[str], real_version: str):
|
||||||
|
self.c_compiler_output = c_compiler_output
|
||||||
|
self.real_version = real_version
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_dict(cls, data: Dict[str, Optional[str]]):
|
||||||
|
if not isinstance(data, dict):
|
||||||
|
raise ValueError(f"Invalid {cls.__name__} data")
|
||||||
|
c_compiler_output = data.get("c_compiler_output")
|
||||||
|
real_version = data.get("real_version")
|
||||||
|
if not isinstance(real_version, str) or not isinstance(
|
||||||
|
c_compiler_output, (str, type(None))
|
||||||
|
):
|
||||||
|
raise ValueError(f"Invalid {cls.__name__} data")
|
||||||
|
return cls(c_compiler_output, real_version)
|
||||||
|
|
||||||
|
|
||||||
|
class CompilerCache:
|
||||||
|
"""Base class for compiler output cache. Default implementation does not cache anything."""
|
||||||
|
|
||||||
|
def value(self, compiler: Compiler) -> Dict[str, Optional[str]]:
|
||||||
|
return {
|
||||||
|
"c_compiler_output": compiler._compile_dummy_c_source(),
|
||||||
|
"real_version": compiler.get_real_version(),
|
||||||
|
}
|
||||||
|
|
||||||
|
def get(self, compiler: Compiler) -> CompilerCacheEntry:
|
||||||
|
return CompilerCacheEntry.from_dict(self.value(compiler))
|
||||||
|
|
||||||
|
|
||||||
|
class FileCompilerCache(CompilerCache):
|
||||||
|
"""Cache for compiler output, which is used to determine implicit link paths, the default libc
|
||||||
|
version, and the compiler version."""
|
||||||
|
|
||||||
|
name = os.path.join("compilers", "compilers.json")
|
||||||
|
|
||||||
|
def __init__(self, cache: "spack.caches.FileCacheType") -> None:
|
||||||
|
self.cache = cache
|
||||||
|
self.cache.init_entry(self.name)
|
||||||
|
self._data: Dict[str, Dict[str, Optional[str]]] = {}
|
||||||
|
|
||||||
|
def _get_entry(self, key: str) -> Optional[CompilerCacheEntry]:
|
||||||
|
try:
|
||||||
|
return CompilerCacheEntry.from_dict(self._data[key])
|
||||||
|
except ValueError:
|
||||||
|
del self._data[key]
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get(self, compiler: Compiler) -> CompilerCacheEntry:
|
||||||
|
# Cache hit
|
||||||
|
try:
|
||||||
|
with self.cache.read_transaction(self.name) as f:
|
||||||
|
assert f is not None
|
||||||
|
self._data = json.loads(f.read())
|
||||||
|
assert isinstance(self._data, dict)
|
||||||
|
except (json.JSONDecodeError, AssertionError):
|
||||||
|
self._data = {}
|
||||||
|
|
||||||
|
key = self._key(compiler)
|
||||||
|
value = self._get_entry(key)
|
||||||
|
if value is not None:
|
||||||
|
return value
|
||||||
|
|
||||||
|
# Cache miss
|
||||||
|
with self.cache.write_transaction(self.name) as (old, new):
|
||||||
|
try:
|
||||||
|
assert old is not None
|
||||||
|
self._data = json.loads(old.read())
|
||||||
|
assert isinstance(self._data, dict)
|
||||||
|
except (json.JSONDecodeError, AssertionError):
|
||||||
|
self._data = {}
|
||||||
|
|
||||||
|
# Use cache entry that may have been created by another process in the meantime.
|
||||||
|
entry = self._get_entry(key)
|
||||||
|
|
||||||
|
# Finally compute the cache entry
|
||||||
|
if entry is None:
|
||||||
|
self._data[key] = self.value(compiler)
|
||||||
|
entry = CompilerCacheEntry.from_dict(self._data[key])
|
||||||
|
|
||||||
|
new.write(json.dumps(self._data, separators=(",", ":")))
|
||||||
|
|
||||||
|
return entry
|
||||||
|
|
||||||
|
def _key(self, compiler: Compiler) -> str:
|
||||||
|
as_bytes = json.dumps(compiler.to_dict(), separators=(",", ":")).encode("utf-8")
|
||||||
|
return hashlib.sha256(as_bytes).hexdigest()
|
||||||
|
|
||||||
|
|
||||||
|
def _make_compiler_cache():
|
||||||
|
return FileCompilerCache(spack.caches.MISC_CACHE)
|
||||||
|
|
||||||
|
|
||||||
|
COMPILER_CACHE: CompilerCache = llnl.util.lang.Singleton(_make_compiler_cache) # type: ignore
|
||||||
|
@ -116,5 +116,5 @@ def fflags(self):
|
|||||||
def _handle_default_flag_addtions(self):
|
def _handle_default_flag_addtions(self):
|
||||||
# This is a known issue for AOCC 3.0 see:
|
# This is a known issue for AOCC 3.0 see:
|
||||||
# https://developer.amd.com/wp-content/resources/AOCC-3.0-Install-Guide.pdf
|
# https://developer.amd.com/wp-content/resources/AOCC-3.0-Install-Guide.pdf
|
||||||
if self.real_version.satisfies(ver("3.0.0")):
|
if self.version.satisfies(ver("3.0.0")):
|
||||||
return "-Wno-unused-command-line-argument " "-mllvm -eliminate-similar-expr=false"
|
return "-Wno-unused-command-line-argument " "-mllvm -eliminate-similar-expr=false"
|
||||||
|
@ -3,8 +3,10 @@
|
|||||||
#
|
#
|
||||||
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||||
"""Test basic behavior of compilers in Spack"""
|
"""Test basic behavior of compilers in Spack"""
|
||||||
|
import json
|
||||||
import os
|
import os
|
||||||
from copy import copy
|
from copy import copy
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
@ -17,6 +19,7 @@
|
|||||||
import spack.util.module_cmd
|
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
|
||||||
|
from spack.util.file_cache import FileCache
|
||||||
|
|
||||||
|
|
||||||
def test_multiple_conflicting_compiler_definitions(mutable_config):
|
def test_multiple_conflicting_compiler_definitions(mutable_config):
|
||||||
@ -101,11 +104,14 @@ def verbose_flag(self):
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.not_on_windows("Not supported on Windows (yet)")
|
@pytest.mark.not_on_windows("Not supported on Windows (yet)")
|
||||||
def test_implicit_rpaths(dirs_with_libfiles):
|
def test_implicit_rpaths(dirs_with_libfiles, monkeypatch):
|
||||||
lib_to_dirs, all_dirs = dirs_with_libfiles
|
lib_to_dirs, all_dirs = dirs_with_libfiles
|
||||||
compiler = MockCompiler()
|
monkeypatch.setattr(
|
||||||
compiler._compile_c_source_output = "ld " + " ".join(f"-L{d}" for d in all_dirs)
|
MockCompiler,
|
||||||
retrieved_rpaths = compiler.implicit_rpaths()
|
"_compile_dummy_c_source",
|
||||||
|
lambda self: "ld " + " ".join(f"-L{d}" for d in all_dirs),
|
||||||
|
)
|
||||||
|
retrieved_rpaths = MockCompiler().implicit_rpaths()
|
||||||
assert set(retrieved_rpaths) == set(lib_to_dirs["libstdc++"] + lib_to_dirs["libgfortran"])
|
assert set(retrieved_rpaths) == set(lib_to_dirs["libstdc++"] + lib_to_dirs["libgfortran"])
|
||||||
|
|
||||||
|
|
||||||
@ -647,6 +653,7 @@ def test_raising_if_compiler_target_is_over_specific(config):
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.not_on_windows("Not supported on Windows (yet)")
|
@pytest.mark.not_on_windows("Not supported on Windows (yet)")
|
||||||
|
@pytest.mark.enable_compiler_execution
|
||||||
def test_compiler_get_real_version(working_env, monkeypatch, tmpdir):
|
def test_compiler_get_real_version(working_env, monkeypatch, tmpdir):
|
||||||
# Test variables
|
# Test variables
|
||||||
test_version = "2.2.2"
|
test_version = "2.2.2"
|
||||||
@ -736,6 +743,7 @@ def test_get_compilers(config):
|
|||||||
) == [spack.compilers._compiler_from_config_entry(without_suffix)]
|
) == [spack.compilers._compiler_from_config_entry(without_suffix)]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.enable_compiler_execution
|
||||||
def test_compiler_get_real_version_fails(working_env, monkeypatch, tmpdir):
|
def test_compiler_get_real_version_fails(working_env, monkeypatch, tmpdir):
|
||||||
# Test variables
|
# Test variables
|
||||||
test_version = "2.2.2"
|
test_version = "2.2.2"
|
||||||
@ -784,15 +792,13 @@ def _call(*args, **kwargs):
|
|||||||
compilers = spack.compilers.get_compilers([compiler_dict])
|
compilers = spack.compilers.get_compilers([compiler_dict])
|
||||||
assert len(compilers) == 1
|
assert len(compilers) == 1
|
||||||
compiler = compilers[0]
|
compiler = compilers[0]
|
||||||
try:
|
assert compiler.get_real_version() == "unknown"
|
||||||
_ = compiler.get_real_version()
|
# Confirm environment does not change after failed call
|
||||||
assert False
|
assert "SPACK_TEST_CMP_ON" not in os.environ
|
||||||
except ProcessError:
|
|
||||||
# Confirm environment does not change after failed call
|
|
||||||
assert "SPACK_TEST_CMP_ON" not in os.environ
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.not_on_windows("Bash scripting unsupported on Windows (for now)")
|
@pytest.mark.not_on_windows("Bash scripting unsupported on Windows (for now)")
|
||||||
|
@pytest.mark.enable_compiler_execution
|
||||||
def test_compiler_flags_use_real_version(working_env, monkeypatch, tmpdir):
|
def test_compiler_flags_use_real_version(working_env, monkeypatch, tmpdir):
|
||||||
# Create compiler
|
# Create compiler
|
||||||
gcc = str(tmpdir.join("gcc"))
|
gcc = str(tmpdir.join("gcc"))
|
||||||
@ -895,3 +901,57 @@ def test_compiler_environment(working_env):
|
|||||||
)
|
)
|
||||||
with compiler.compiler_environment():
|
with compiler.compiler_environment():
|
||||||
assert os.environ["TEST"] == "yes"
|
assert os.environ["TEST"] == "yes"
|
||||||
|
|
||||||
|
|
||||||
|
class MockCompilerWithoutExecutables(MockCompiler):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
self._compile_dummy_c_source_count = 0
|
||||||
|
self._get_real_version_count = 0
|
||||||
|
|
||||||
|
def _compile_dummy_c_source(self) -> Optional[str]:
|
||||||
|
self._compile_dummy_c_source_count += 1
|
||||||
|
return "gcc helloworld.c -o helloworld"
|
||||||
|
|
||||||
|
def get_real_version(self) -> str:
|
||||||
|
self._get_real_version_count += 1
|
||||||
|
return "1.0.0"
|
||||||
|
|
||||||
|
|
||||||
|
def test_compiler_output_caching(tmp_path):
|
||||||
|
"""Test that compiler output is cached on the filesystem."""
|
||||||
|
# The first call should trigger the cache to updated.
|
||||||
|
a = MockCompilerWithoutExecutables()
|
||||||
|
cache = spack.compiler.FileCompilerCache(FileCache(str(tmp_path)))
|
||||||
|
assert cache.get(a).c_compiler_output == "gcc helloworld.c -o helloworld"
|
||||||
|
assert cache.get(a).real_version == "1.0.0"
|
||||||
|
assert a._compile_dummy_c_source_count == 1
|
||||||
|
assert a._get_real_version_count == 1
|
||||||
|
|
||||||
|
# The second call on an equivalent but distinct object should not trigger compiler calls.
|
||||||
|
b = MockCompilerWithoutExecutables()
|
||||||
|
cache = spack.compiler.FileCompilerCache(FileCache(str(tmp_path)))
|
||||||
|
assert cache.get(b).c_compiler_output == "gcc helloworld.c -o helloworld"
|
||||||
|
assert cache.get(b).real_version == "1.0.0"
|
||||||
|
assert b._compile_dummy_c_source_count == 0
|
||||||
|
assert b._get_real_version_count == 0
|
||||||
|
|
||||||
|
# Cache schema change should be handled gracefully.
|
||||||
|
with open(cache.cache.cache_path(cache.name), "w") as f:
|
||||||
|
for k in cache._data:
|
||||||
|
cache._data[k] = "corrupted entry"
|
||||||
|
f.write(json.dumps(cache._data))
|
||||||
|
|
||||||
|
c = MockCompilerWithoutExecutables()
|
||||||
|
cache = spack.compiler.FileCompilerCache(FileCache(str(tmp_path)))
|
||||||
|
assert cache.get(c).c_compiler_output == "gcc helloworld.c -o helloworld"
|
||||||
|
assert cache.get(c).real_version == "1.0.0"
|
||||||
|
|
||||||
|
# Cache corruption should be handled gracefully.
|
||||||
|
with open(cache.cache.cache_path(cache.name), "w") as f:
|
||||||
|
f.write("corrupted cache")
|
||||||
|
|
||||||
|
d = MockCompilerWithoutExecutables()
|
||||||
|
cache = spack.compiler.FileCompilerCache(FileCache(str(tmp_path)))
|
||||||
|
assert cache.get(d).c_compiler_output == "gcc helloworld.c -o helloworld"
|
||||||
|
assert cache.get(d).real_version == "1.0.0"
|
||||||
|
@ -2316,6 +2316,7 @@ def test_compiler_match_constraints_when_selected(self):
|
|||||||
|
|
||||||
@pytest.mark.regression("36339")
|
@pytest.mark.regression("36339")
|
||||||
@pytest.mark.not_on_windows("Not supported on Windows")
|
@pytest.mark.not_on_windows("Not supported on Windows")
|
||||||
|
@pytest.mark.enable_compiler_execution
|
||||||
def test_compiler_with_custom_non_numeric_version(self, mock_executable):
|
def test_compiler_with_custom_non_numeric_version(self, mock_executable):
|
||||||
"""Test that, when a compiler has a completely made up version, we can use its
|
"""Test that, when a compiler has a completely made up version, we can use its
|
||||||
'real version' to detect targets and don't raise during concretization.
|
'real version' to detect targets and don't raise during concretization.
|
||||||
|
@ -973,12 +973,26 @@ def _return_none(*args):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _compiler_output(self):
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
|
def _get_real_version(self):
|
||||||
|
return str(self.version)
|
||||||
|
|
||||||
|
|
||||||
@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.
|
"""Disable compiler execution to determine implicit link paths and libc flavor and version.
|
||||||
To re-enable use `@pytest.mark.enable_compiler_execution`"""
|
To re-enable use `@pytest.mark.enable_compiler_execution`"""
|
||||||
if "enable_compiler_execution" not in request.keywords:
|
if "enable_compiler_execution" not in request.keywords:
|
||||||
monkeypatch.setattr(spack.compiler.Compiler, "_compile_dummy_c_source", _return_none)
|
monkeypatch.setattr(spack.compiler.Compiler, "_compile_dummy_c_source", _compiler_output)
|
||||||
|
monkeypatch.setattr(spack.compiler.Compiler, "get_real_version", _get_real_version)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(autouse=True)
|
||||||
|
def disable_compiler_output_cache(monkeypatch):
|
||||||
|
monkeypatch.setattr(spack.compiler, "COMPILER_CACHE", spack.compiler.CompilerCache())
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="function")
|
@pytest.fixture(scope="function")
|
||||||
|
@ -11,6 +11,8 @@
|
|||||||
from subprocess import PIPE, run
|
from subprocess import PIPE, run
|
||||||
from typing import Dict, List, Optional
|
from typing import Dict, List, Optional
|
||||||
|
|
||||||
|
from llnl.util.lang import memoized
|
||||||
|
|
||||||
import spack.spec
|
import spack.spec
|
||||||
import spack.util.elf
|
import spack.util.elf
|
||||||
|
|
||||||
@ -61,6 +63,14 @@ def default_search_paths_from_dynamic_linker(dynamic_linker: str) -> List[str]:
|
|||||||
|
|
||||||
|
|
||||||
def libc_from_dynamic_linker(dynamic_linker: str) -> Optional["spack.spec.Spec"]:
|
def libc_from_dynamic_linker(dynamic_linker: str) -> Optional["spack.spec.Spec"]:
|
||||||
|
maybe_spec = _libc_from_dynamic_linker(dynamic_linker)
|
||||||
|
if maybe_spec:
|
||||||
|
return maybe_spec.copy()
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
@memoized
|
||||||
|
def _libc_from_dynamic_linker(dynamic_linker: str) -> Optional["spack.spec.Spec"]:
|
||||||
if not os.path.exists(dynamic_linker):
|
if not os.path.exists(dynamic_linker):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user