Source code for spack.compilers.config

# Copyright Spack Project Developers. See COPYRIGHT file for details.
#
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
"""This module contains functions related to finding compilers on the system,
and configuring Spack to use multiple compilers.
"""

import sys
from typing import Any, Dict, List, Optional, Tuple

import spack.detection
import spack.error
import spack.platforms
import spack.repo
import spack.spec
import spack.util.filesystem as fs
import spack.util.lang
from spack.config import Configuration
from spack.externals import ExternalSpecsParser, external_spec, extract_dicts_from_configuration
from spack.operating_systems import windows_os
from spack.util import tty
from spack.util.environment import get_path

#: Tag used to identify packages providing a compiler
COMPILER_TAG = "compiler"


[docs] def compiler_config_files(config: Configuration, *, repo: spack.repo.RepoPath) -> List[str]: """Returns the writable configuration files that define at least one compiler. Args: config: configuration to be queried repo: package repository used to enumerate compiler packages """ config_files = [] for scope in config.writable_scopes: name = scope.name if all_compilers_from(config, scope=name, repo=repo): config_files.append(config.get_config_filename(name, "packages")) return config_files
[docs] def add_compiler_to_config( new_compilers: List[spack.spec.Spec], *, config: Configuration, scope: Optional[str] = None ) -> None: """Add compiler specs to the configuration, at the required scope.""" by_name: Dict[str, List[spack.spec.Spec]] = {} for x in new_compilers: by_name.setdefault(x.name, []).append(x) spack.detection.update_configuration(by_name, config=config, buildable=True, scope=scope)
[docs] def find_compilers( path_hints: Optional[List[str]] = None, *, config: Configuration, repo: spack.repo.RepoPath, scope: Optional[str] = None, max_workers: Optional[int] = None, ) -> List[spack.spec.Spec]: """Searches for compiler in the paths given as argument. If any new compiler is found, the configuration is updated, and the list of new compiler objects is returned. Args: path_hints: list of path hints where to look for. A sensible default based on the ``PATH`` environment variable will be used if the value is None config: configuration to be updated with the new compilers repo: package repository used to detect compilers scope: configuration scope to modify max_workers: number of processes used to search for compilers """ if path_hints is None: path_hints = get_path("PATH") default_paths = fs.search_paths_for_executables(*path_hints) if sys.platform == "win32": default_paths.extend(windows_os.WindowsOs().compiler_search_paths) compiler_pkgs = repo.packages_with_tags(COMPILER_TAG, full=True) detected_packages = spack.detection.by_path( compiler_pkgs, repo=repo, path_hints=default_paths, max_workers=max_workers ) new_compilers = spack.detection.update_configuration( detected_packages, config=config, buildable=True, scope=scope ) return new_compilers
[docs] def select_new_compilers( candidates: List[spack.spec.Spec], *, config: Configuration, repo: spack.repo.RepoPath, scope: Optional[str] = None, ) -> List[spack.spec.Spec]: """Given a list of compilers, remove those that are already defined in the configuration. """ compilers_in_config = all_compilers_from(config, scope=scope, repo=repo) return [c for c in candidates if c not in compilers_in_config]
[docs] def supported_compilers(*, repo: spack.repo.RepoPath) -> List[str]: """Returns all the currently supported compiler packages. Args: repo: package repository to query """ return sorted(repo.packages_with_tags(COMPILER_TAG))
[docs] def all_compilers( config: Configuration, *, repo: spack.repo.RepoPath, scope: Optional[str] = None, init_config: bool = True, ) -> List[spack.spec.Spec]: """Returns all the compilers from the given configuration. Args: config: configuration to be queried, and updated if ``init_config`` is True repo: package repository used to enumerate and detect compiler packages scope: configuration scope from which to extract the compilers. If None, the merged configuration is used. init_config: if True, search for compilers if none is found in configuration. """ compilers = all_compilers_from(config, scope=scope, repo=repo) if not compilers and init_config: _init_packages_yaml(config, repo=repo, scope=scope) compilers = all_compilers_from(config, scope=scope, repo=repo) return compilers
def _init_packages_yaml( config: Configuration, *, repo: spack.repo.RepoPath, scope: Optional[str] ) -> None: new_compilers = find_compilers(config=config, repo=repo, scope=scope) if new_compilers: tty.info("Compilers have been configured automatically from PATH inspection")
[docs] def all_compilers_from( configuration: Configuration, scope: Optional[str] = None, *, repo: spack.repo.RepoPath ) -> List[spack.spec.Spec]: """Returns all the compilers from the given configuration. Args: configuration: configuration to be queried scope: configuration scope from which to extract the compilers. If None, the merged configuration is used. repo: package repository used to enumerate compiler packages. """ compiler_package_names = supported_compilers(repo=repo) packages_yaml = configuration.deepcopy_as_builtin("packages", scope=scope) init_external_dicts = extract_dicts_from_configuration(packages_yaml) external_parser = ExternalSpecsParser(init_external_dicts, repo=repo) valid_compiler_specs = [] for name, external_specs_and_config in external_parser.specs_by_name.items(): if name not in compiler_package_names: continue for spec_with_config in external_specs_and_config: if _EXTRA_ATTRIBUTES_KEY not in spec_with_config.config: header = ( f"The external spec '{spec_with_config.config['spec']}'" " cannot be used as a compiler" ) tty.debug(f"[{__file__}] {header}: missing the '{_EXTRA_ATTRIBUTES_KEY}' key") continue valid_compiler_specs.append(spec_with_config.spec) return valid_compiler_specs
[docs] class CompilerRemover: """Removes compiler from configuration.""" def __init__(self, config: Configuration, *, repo: spack.repo.RepoPath) -> None: self.configuration = config self.repo = repo self.marked_packages_yaml: List[Tuple[str, Any]] = []
[docs] def mark_compilers(self, *, match: str, scope: Optional[str] = None) -> List[spack.spec.Spec]: """Marks compilers to be removed in configuration, and returns a corresponding list of specs. Args: match: constraint that the compiler must match to be removed. scope: scope where to remove the compiler. If None, all writeable scopes are checked. """ self.marked_packages_yaml = [] candidate_scopes = [scope] if scope is None: candidate_scopes = [x.name for x in self.configuration.writable_scopes] return self._mark_in_packages_yaml(match, candidate_scopes)
def _mark_in_packages_yaml(self, match, candidate_scopes): compiler_package_names = supported_compilers(repo=self.repo) all_removals = [] for current_scope in candidate_scopes: packages_yaml = self.configuration.get("packages", scope=current_scope) if not packages_yaml: continue removed_from_scope = [] for name, entry in packages_yaml.items(): if name not in compiler_package_names: continue externals_config = entry.get("externals", None) if not externals_config: continue def _partition_match(external_yaml): return not external_spec(external_yaml, repo=self.repo).satisfies(match) to_keep, to_remove = spack.util.lang.stable_partition( externals_config, _partition_match ) if not to_remove: continue removed_from_scope.extend(to_remove) entry["externals"] = to_keep if not removed_from_scope: continue self.marked_packages_yaml.append((current_scope, packages_yaml)) all_removals.extend([external_spec(x, repo=self.repo) for x in removed_from_scope]) return all_removals
[docs] def flush(self): """Removes from configuration the specs that have been marked by the previous call of ``remove_compilers``. """ for scope, packages_yaml in self.marked_packages_yaml: self.configuration.set("packages", packages_yaml, scope=scope)
[docs] def compilers_for_arch( arch_spec: spack.spec.ArchSpec, *, config: Configuration, repo: spack.repo.RepoPath, scope: Optional[str] = None, ) -> List[spack.spec.Spec]: """Returns the compilers that can be used on the input architecture""" compilers = all_compilers_from(config, scope=scope, repo=repo) query = f"platform={arch_spec.platform} target=:{arch_spec.target}" return [x for x in compilers if x.satisfies(query)]
_EXTRA_ATTRIBUTES_KEY = "extra_attributes"
[docs] def name_os_target(spec: spack.spec.Spec) -> Tuple[str, str, str]: if not spec.architecture: host_platform = spack.platforms.host() operating_system = host_platform.operating_system("default_os") target = host_platform.target("default_target") else: target = spec.architecture.target if not target: target = spack.platforms.host().target("default_target") target = target.family operating_system = spec.os if not operating_system: host_platform = spack.platforms.host() operating_system = host_platform.operating_system("default_os") return spec.name, str(operating_system), str(target)
[docs] class UnknownCompilerError(spack.error.SpackError): def __init__(self, compiler_name): super().__init__(f"Spack doesn't support the requested compiler: {compiler_name}")
[docs] class NoAvailableCompilerError(spack.error.SpackError): pass