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:
		 Harmen Stoppels
					Harmen Stoppels
				
			
				
					committed by
					
						 Harmen Stoppels
						Harmen Stoppels
					
				
			
			
				
	
			
			
			 Harmen Stoppels
						Harmen Stoppels
					
				
			
						parent
						
							e8c41cdbcb
						
					
				
				
					commit
					209a3bf302
				
			| @@ -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") | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user