Add type-hints to spack.bootstrap (#36491)

This commit is contained in:
Massimiliano Culpo 2023-03-30 22:12:18 +02:00 committed by GitHub
parent f5624f096c
commit e1a104e3a2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 117 additions and 78 deletions

View File

@ -9,6 +9,7 @@
import sys import sys
import sysconfig import sysconfig
import warnings import warnings
from typing import Dict, Optional, Sequence, Union
import archspec.cpu import archspec.cpu
@ -21,8 +22,10 @@
from .config import spec_for_current_python from .config import spec_for_current_python
QueryInfo = Dict[str, "spack.spec.Spec"]
def _python_import(module):
def _python_import(module: str) -> bool:
try: try:
__import__(module) __import__(module)
except ImportError: except ImportError:
@ -30,7 +33,9 @@ def _python_import(module):
return True return True
def _try_import_from_store(module, query_spec, query_info=None): def _try_import_from_store(
module: str, query_spec: Union[str, "spack.spec.Spec"], query_info: Optional[QueryInfo] = None
) -> bool:
"""Return True if the module can be imported from an already """Return True if the module can be imported from an already
installed spec, False otherwise. installed spec, False otherwise.
@ -52,7 +57,7 @@ def _try_import_from_store(module, query_spec, query_info=None):
module_paths = [ module_paths = [
os.path.join(candidate_spec.prefix, pkg.purelib), os.path.join(candidate_spec.prefix, pkg.purelib),
os.path.join(candidate_spec.prefix, pkg.platlib), os.path.join(candidate_spec.prefix, pkg.platlib),
] # type: list[str] ]
path_before = list(sys.path) path_before = list(sys.path)
# NOTE: try module_paths first and last, last allows an existing version in path # NOTE: try module_paths first and last, last allows an existing version in path
@ -89,7 +94,7 @@ def _try_import_from_store(module, query_spec, query_info=None):
return False return False
def _fix_ext_suffix(candidate_spec): def _fix_ext_suffix(candidate_spec: "spack.spec.Spec"):
"""Fix the external suffixes of Python extensions on the fly for """Fix the external suffixes of Python extensions on the fly for
platforms that may need it platforms that may need it
@ -157,7 +162,11 @@ def _fix_ext_suffix(candidate_spec):
os.symlink(abs_path, link_name) os.symlink(abs_path, link_name)
def _executables_in_store(executables, query_spec, query_info=None): def _executables_in_store(
executables: Sequence[str],
query_spec: Union["spack.spec.Spec", str],
query_info: Optional[QueryInfo] = None,
) -> bool:
"""Return True if at least one of the executables can be retrieved from """Return True if at least one of the executables can be retrieved from
a spec in store, False otherwise. a spec in store, False otherwise.
@ -193,7 +202,7 @@ def _executables_in_store(executables, query_spec, query_info=None):
return False return False
def _root_spec(spec_str): def _root_spec(spec_str: str) -> str:
"""Add a proper compiler and target to a spec used during bootstrapping. """Add a proper compiler and target to a spec used during bootstrapping.
Args: Args:

View File

@ -7,6 +7,7 @@
import contextlib import contextlib
import os.path import os.path
import sys import sys
from typing import Any, Dict, Generator, MutableSequence, Sequence
from llnl.util import tty from llnl.util import tty
@ -24,12 +25,12 @@
_REF_COUNT = 0 _REF_COUNT = 0
def is_bootstrapping(): def is_bootstrapping() -> bool:
"""Return True if we are in a bootstrapping context, False otherwise.""" """Return True if we are in a bootstrapping context, False otherwise."""
return _REF_COUNT > 0 return _REF_COUNT > 0
def spec_for_current_python(): def spec_for_current_python() -> str:
"""For bootstrapping purposes we are just interested in the Python """For bootstrapping purposes we are just interested in the Python
minor version (all patches are ABI compatible with the same minor). minor version (all patches are ABI compatible with the same minor).
@ -41,14 +42,14 @@ def spec_for_current_python():
return f"python@{version_str}" return f"python@{version_str}"
def root_path(): def root_path() -> str:
"""Root of all the bootstrap related folders""" """Root of all the bootstrap related folders"""
return spack.util.path.canonicalize_path( return spack.util.path.canonicalize_path(
spack.config.get("bootstrap:root", spack.paths.default_user_bootstrap_path) spack.config.get("bootstrap:root", spack.paths.default_user_bootstrap_path)
) )
def store_path(): def store_path() -> str:
"""Path to the store used for bootstrapped software""" """Path to the store used for bootstrapped software"""
enabled = spack.config.get("bootstrap:enable", True) enabled = spack.config.get("bootstrap:enable", True)
if not enabled: if not enabled:
@ -59,7 +60,7 @@ def store_path():
@contextlib.contextmanager @contextlib.contextmanager
def spack_python_interpreter(): def spack_python_interpreter() -> Generator:
"""Override the current configuration to set the interpreter under """Override the current configuration to set the interpreter under
which Spack is currently running as the only Python external spec which Spack is currently running as the only Python external spec
available. available.
@ -76,18 +77,18 @@ def spack_python_interpreter():
yield yield
def _store_path(): def _store_path() -> str:
bootstrap_root_path = root_path() bootstrap_root_path = root_path()
return spack.util.path.canonicalize_path(os.path.join(bootstrap_root_path, "store")) return spack.util.path.canonicalize_path(os.path.join(bootstrap_root_path, "store"))
def _config_path(): def _config_path() -> str:
bootstrap_root_path = root_path() bootstrap_root_path = root_path()
return spack.util.path.canonicalize_path(os.path.join(bootstrap_root_path, "config")) return spack.util.path.canonicalize_path(os.path.join(bootstrap_root_path, "config"))
@contextlib.contextmanager @contextlib.contextmanager
def ensure_bootstrap_configuration(): def ensure_bootstrap_configuration() -> Generator:
"""Swap the current configuration for the one used to bootstrap Spack. """Swap the current configuration for the one used to bootstrap Spack.
The context manager is reference counted to ensure we don't swap multiple The context manager is reference counted to ensure we don't swap multiple
@ -107,7 +108,7 @@ def ensure_bootstrap_configuration():
_REF_COUNT -= 1 _REF_COUNT -= 1
def _read_and_sanitize_configuration(): def _read_and_sanitize_configuration() -> Dict[str, Any]:
"""Read the user configuration that needs to be reused for bootstrapping """Read the user configuration that needs to be reused for bootstrapping
and remove the entries that should not be copied over. and remove the entries that should not be copied over.
""" """
@ -120,9 +121,11 @@ def _read_and_sanitize_configuration():
return user_configuration return user_configuration
def _bootstrap_config_scopes(): def _bootstrap_config_scopes() -> Sequence["spack.config.ConfigScope"]:
tty.debug("[BOOTSTRAP CONFIG SCOPE] name=_builtin") tty.debug("[BOOTSTRAP CONFIG SCOPE] name=_builtin")
config_scopes = [spack.config.InternalConfigScope("_builtin", spack.config.config_defaults)] config_scopes: MutableSequence["spack.config.ConfigScope"] = [
spack.config.InternalConfigScope("_builtin", spack.config.config_defaults)
]
configuration_paths = (spack.config.configuration_defaults_path, ("bootstrap", _config_path())) configuration_paths = (spack.config.configuration_defaults_path, ("bootstrap", _config_path()))
for name, path in configuration_paths: for name, path in configuration_paths:
platform = spack.platforms.host().name platform = spack.platforms.host().name
@ -137,7 +140,7 @@ def _bootstrap_config_scopes():
return config_scopes return config_scopes
def _add_compilers_if_missing(): def _add_compilers_if_missing() -> None:
arch = spack.spec.ArchSpec.frontend_arch() arch = spack.spec.ArchSpec.frontend_arch()
if not spack.compilers.compilers_for_arch(arch): if not spack.compilers.compilers_for_arch(arch):
new_compilers = spack.compilers.find_new_compilers() new_compilers = spack.compilers.find_new_compilers()
@ -146,7 +149,7 @@ def _add_compilers_if_missing():
@contextlib.contextmanager @contextlib.contextmanager
def _ensure_bootstrap_configuration(): def _ensure_bootstrap_configuration() -> Generator:
bootstrap_store_path = store_path() bootstrap_store_path = store_path()
user_configuration = _read_and_sanitize_configuration() user_configuration = _read_and_sanitize_configuration()
with spack.environment.no_active_environment(): with spack.environment.no_active_environment():

View File

@ -29,7 +29,7 @@
import os.path import os.path
import sys import sys
import uuid import uuid
from typing import Callable, List, Optional from typing import Any, Callable, Dict, List, Optional, Tuple
from llnl.util import tty from llnl.util import tty
from llnl.util.lang import GroupedExceptionHandler from llnl.util.lang import GroupedExceptionHandler
@ -66,6 +66,9 @@
_bootstrap_methods = {} _bootstrap_methods = {}
ConfigDictionary = Dict[str, Any]
def bootstrapper(bootstrapper_type: str): def bootstrapper(bootstrapper_type: str):
"""Decorator to register classes implementing bootstrapping """Decorator to register classes implementing bootstrapping
methods. methods.
@ -86,7 +89,7 @@ class Bootstrapper:
config_scope_name = "" config_scope_name = ""
def __init__(self, conf): def __init__(self, conf: ConfigDictionary) -> None:
self.conf = conf self.conf = conf
self.name = conf["name"] self.name = conf["name"]
self.metadata_dir = spack.util.path.canonicalize_path(conf["metadata"]) self.metadata_dir = spack.util.path.canonicalize_path(conf["metadata"])
@ -100,7 +103,7 @@ def __init__(self, conf):
self.url = url self.url = url
@property @property
def mirror_scope(self): def mirror_scope(self) -> spack.config.InternalConfigScope:
"""Mirror scope to be pushed onto the bootstrapping configuration when using """Mirror scope to be pushed onto the bootstrapping configuration when using
this bootstrapper. this bootstrapper.
""" """
@ -121,7 +124,7 @@ def try_import(self, module: str, abstract_spec_str: str) -> bool:
""" """
return False return False
def try_search_path(self, executables: List[str], abstract_spec_str: str) -> bool: def try_search_path(self, executables: Tuple[str], abstract_spec_str: str) -> bool:
"""Try to search some executables in the prefix of specs satisfying the abstract """Try to search some executables in the prefix of specs satisfying the abstract
spec passed as argument. spec passed as argument.
@ -139,13 +142,15 @@ def try_search_path(self, executables: List[str], abstract_spec_str: str) -> boo
class BuildcacheBootstrapper(Bootstrapper): class BuildcacheBootstrapper(Bootstrapper):
"""Install the software needed during bootstrapping from a buildcache.""" """Install the software needed during bootstrapping from a buildcache."""
def __init__(self, conf): def __init__(self, conf) -> None:
super().__init__(conf) super().__init__(conf)
self.last_search = None self.last_search: Optional[ConfigDictionary] = None
self.config_scope_name = f"bootstrap_buildcache-{uuid.uuid4()}" self.config_scope_name = f"bootstrap_buildcache-{uuid.uuid4()}"
@staticmethod @staticmethod
def _spec_and_platform(abstract_spec_str): def _spec_and_platform(
abstract_spec_str: str,
) -> Tuple[spack.spec.Spec, spack.platforms.Platform]:
"""Return the spec object and platform we need to use when """Return the spec object and platform we need to use when
querying the buildcache. querying the buildcache.
@ -158,7 +163,7 @@ def _spec_and_platform(abstract_spec_str):
bincache_platform = spack.platforms.real_host() bincache_platform = spack.platforms.real_host()
return abstract_spec, bincache_platform return abstract_spec, bincache_platform
def _read_metadata(self, package_name): def _read_metadata(self, package_name: str) -> Any:
"""Return metadata about the given package.""" """Return metadata about the given package."""
json_filename = f"{package_name}.json" json_filename = f"{package_name}.json"
json_dir = self.metadata_dir json_dir = self.metadata_dir
@ -167,7 +172,13 @@ def _read_metadata(self, package_name):
data = json.load(stream) data = json.load(stream)
return data return data
def _install_by_hash(self, pkg_hash, pkg_sha256, index, bincache_platform): def _install_by_hash(
self,
pkg_hash: str,
pkg_sha256: str,
index: List[spack.spec.Spec],
bincache_platform: spack.platforms.Platform,
) -> None:
index_spec = next(x for x in index if x.dag_hash() == pkg_hash) index_spec = next(x for x in index if x.dag_hash() == pkg_hash)
# Reconstruct the compiler that we need to use for bootstrapping # Reconstruct the compiler that we need to use for bootstrapping
compiler_entry = { compiler_entry = {
@ -192,7 +203,13 @@ def _install_by_hash(self, pkg_hash, pkg_sha256, index, bincache_platform):
match, allow_root=True, unsigned=True, force=True, sha256=pkg_sha256 match, allow_root=True, unsigned=True, force=True, sha256=pkg_sha256
) )
def _install_and_test(self, abstract_spec, bincache_platform, bincache_data, test_fn): def _install_and_test(
self,
abstract_spec: spack.spec.Spec,
bincache_platform: spack.platforms.Platform,
bincache_data,
test_fn,
) -> bool:
# Ensure we see only the buildcache being used to bootstrap # Ensure we see only the buildcache being used to bootstrap
with spack.config.override(self.mirror_scope): with spack.config.override(self.mirror_scope):
# This index is currently needed to get the compiler used to build some # This index is currently needed to get the compiler used to build some
@ -217,13 +234,14 @@ def _install_and_test(self, abstract_spec, bincache_platform, bincache_data, tes
for _, pkg_hash, pkg_sha256 in item["binaries"]: for _, pkg_hash, pkg_sha256 in item["binaries"]:
self._install_by_hash(pkg_hash, pkg_sha256, index, bincache_platform) self._install_by_hash(pkg_hash, pkg_sha256, index, bincache_platform)
info = {} info: ConfigDictionary = {}
if test_fn(query_spec=abstract_spec, query_info=info): if test_fn(query_spec=abstract_spec, query_info=info):
self.last_search = info self.last_search = info
return True return True
return False return False
def try_import(self, module, abstract_spec_str): def try_import(self, module: str, abstract_spec_str: str) -> bool:
info: ConfigDictionary
test_fn, info = functools.partial(_try_import_from_store, module), {} test_fn, info = functools.partial(_try_import_from_store, module), {}
if test_fn(query_spec=abstract_spec_str, query_info=info): if test_fn(query_spec=abstract_spec_str, query_info=info):
return True return True
@ -235,7 +253,8 @@ def try_import(self, module, abstract_spec_str):
data = self._read_metadata(module) data = self._read_metadata(module)
return self._install_and_test(abstract_spec, bincache_platform, data, test_fn) return self._install_and_test(abstract_spec, bincache_platform, data, test_fn)
def try_search_path(self, executables, abstract_spec_str): def try_search_path(self, executables: Tuple[str], abstract_spec_str: str) -> bool:
info: ConfigDictionary
test_fn, info = functools.partial(_executables_in_store, executables), {} test_fn, info = functools.partial(_executables_in_store, executables), {}
if test_fn(query_spec=abstract_spec_str, query_info=info): if test_fn(query_spec=abstract_spec_str, query_info=info):
self.last_search = info self.last_search = info
@ -251,13 +270,13 @@ def try_search_path(self, executables, abstract_spec_str):
class SourceBootstrapper(Bootstrapper): class SourceBootstrapper(Bootstrapper):
"""Install the software needed during bootstrapping from sources.""" """Install the software needed during bootstrapping from sources."""
def __init__(self, conf): def __init__(self, conf) -> None:
super().__init__(conf) super().__init__(conf)
self.last_search = None self.last_search: Optional[ConfigDictionary] = None
self.config_scope_name = f"bootstrap_source-{uuid.uuid4()}" self.config_scope_name = f"bootstrap_source-{uuid.uuid4()}"
def try_import(self, module, abstract_spec_str): def try_import(self, module: str, abstract_spec_str: str) -> bool:
info = {} info: ConfigDictionary = {}
if _try_import_from_store(module, abstract_spec_str, query_info=info): if _try_import_from_store(module, abstract_spec_str, query_info=info):
self.last_search = info self.last_search = info
return True return True
@ -293,8 +312,8 @@ def try_import(self, module, abstract_spec_str):
return True return True
return False return False
def try_search_path(self, executables, abstract_spec_str): def try_search_path(self, executables: Tuple[str], abstract_spec_str: str) -> bool:
info = {} info: ConfigDictionary = {}
if _executables_in_store(executables, abstract_spec_str, query_info=info): if _executables_in_store(executables, abstract_spec_str, query_info=info):
self.last_search = info self.last_search = info
return True return True
@ -323,13 +342,13 @@ def try_search_path(self, executables, abstract_spec_str):
return False return False
def create_bootstrapper(conf): def create_bootstrapper(conf: ConfigDictionary):
"""Return a bootstrap object built according to the configuration argument""" """Return a bootstrap object built according to the configuration argument"""
btype = conf["type"] btype = conf["type"]
return _bootstrap_methods[btype](conf) return _bootstrap_methods[btype](conf)
def source_is_enabled_or_raise(conf): def source_is_enabled_or_raise(conf: ConfigDictionary):
"""Raise ValueError if the source is not enabled for bootstrapping""" """Raise ValueError if the source is not enabled for bootstrapping"""
trusted, name = spack.config.get("bootstrap:trusted"), conf["name"] trusted, name = spack.config.get("bootstrap:trusted"), conf["name"]
if not trusted.get(name, False): if not trusted.get(name, False):
@ -454,7 +473,7 @@ def ensure_executables_in_path_or_raise(
raise RuntimeError(msg) raise RuntimeError(msg)
def _add_externals_if_missing(): def _add_externals_if_missing() -> None:
search_list = [ search_list = [
# clingo # clingo
spack.repo.path.get_pkg_class("cmake"), spack.repo.path.get_pkg_class("cmake"),
@ -468,41 +487,41 @@ def _add_externals_if_missing():
spack.detection.update_configuration(detected_packages, scope="bootstrap") spack.detection.update_configuration(detected_packages, scope="bootstrap")
def clingo_root_spec(): def clingo_root_spec() -> str:
"""Return the root spec used to bootstrap clingo""" """Return the root spec used to bootstrap clingo"""
return _root_spec("clingo-bootstrap@spack+python") return _root_spec("clingo-bootstrap@spack+python")
def ensure_clingo_importable_or_raise(): def ensure_clingo_importable_or_raise() -> None:
"""Ensure that the clingo module is available for import.""" """Ensure that the clingo module is available for import."""
ensure_module_importable_or_raise(module="clingo", abstract_spec=clingo_root_spec()) ensure_module_importable_or_raise(module="clingo", abstract_spec=clingo_root_spec())
def gnupg_root_spec(): def gnupg_root_spec() -> str:
"""Return the root spec used to bootstrap GnuPG""" """Return the root spec used to bootstrap GnuPG"""
return _root_spec("gnupg@2.3:") return _root_spec("gnupg@2.3:")
def ensure_gpg_in_path_or_raise(): def ensure_gpg_in_path_or_raise() -> None:
"""Ensure gpg or gpg2 are in the PATH or raise.""" """Ensure gpg or gpg2 are in the PATH or raise."""
return ensure_executables_in_path_or_raise( return ensure_executables_in_path_or_raise(
executables=["gpg2", "gpg"], abstract_spec=gnupg_root_spec() executables=["gpg2", "gpg"], abstract_spec=gnupg_root_spec()
) )
def patchelf_root_spec(): def patchelf_root_spec() -> str:
"""Return the root spec used to bootstrap patchelf""" """Return the root spec used to bootstrap patchelf"""
# 0.13.1 is the last version not to require C++17. # 0.13.1 is the last version not to require C++17.
return _root_spec("patchelf@0.13.1:") return _root_spec("patchelf@0.13.1:")
def verify_patchelf(patchelf): def verify_patchelf(patchelf: "spack.util.executable.Executable") -> bool:
"""Older patchelf versions can produce broken binaries, so we """Older patchelf versions can produce broken binaries, so we
verify the version here. verify the version here.
Arguments: Arguments:
patchelf (spack.util.executable.Executable): patchelf executable patchelf: patchelf executable
""" """
out = patchelf("--version", output=str, error=os.devnull, fail_on_error=False).strip() out = patchelf("--version", output=str, error=os.devnull, fail_on_error=False).strip()
if patchelf.returncode != 0: if patchelf.returncode != 0:
@ -517,7 +536,7 @@ def verify_patchelf(patchelf):
return version >= spack.version.Version("0.13.1") return version >= spack.version.Version("0.13.1")
def ensure_patchelf_in_path_or_raise(): def ensure_patchelf_in_path_or_raise() -> None:
"""Ensure patchelf is in the PATH or raise.""" """Ensure patchelf is in the PATH or raise."""
# The old concretizer is not smart and we're doing its job: if the latest patchelf # The old concretizer is not smart and we're doing its job: if the latest patchelf
# does not concretize because the compiler doesn't support C++17, we try to # does not concretize because the compiler doesn't support C++17, we try to
@ -534,7 +553,7 @@ def ensure_patchelf_in_path_or_raise():
) )
def ensure_core_dependencies(): def ensure_core_dependencies() -> None:
"""Ensure the presence of all the core dependencies.""" """Ensure the presence of all the core dependencies."""
if sys.platform.lower() == "linux": if sys.platform.lower() == "linux":
ensure_patchelf_in_path_or_raise() ensure_patchelf_in_path_or_raise()
@ -543,7 +562,7 @@ def ensure_core_dependencies():
ensure_clingo_importable_or_raise() ensure_clingo_importable_or_raise()
def all_core_root_specs(): def all_core_root_specs() -> List[str]:
"""Return a list of all the core root specs that may be used to bootstrap Spack""" """Return a list of all the core root specs that may be used to bootstrap Spack"""
return [clingo_root_spec(), gnupg_root_spec(), patchelf_root_spec()] return [clingo_root_spec(), gnupg_root_spec(), patchelf_root_spec()]

View File

@ -9,6 +9,7 @@
import pathlib import pathlib
import sys import sys
import warnings import warnings
from typing import List
import archspec.cpu import archspec.cpu
@ -27,7 +28,7 @@ class BootstrapEnvironment(spack.environment.Environment):
"""Environment to install dependencies of Spack for a given interpreter and architecture""" """Environment to install dependencies of Spack for a given interpreter and architecture"""
@classmethod @classmethod
def spack_dev_requirements(cls): def spack_dev_requirements(cls) -> List[str]:
"""Spack development requirements""" """Spack development requirements"""
return [ return [
isort_root_spec(), isort_root_spec(),
@ -38,7 +39,7 @@ def spack_dev_requirements(cls):
] ]
@classmethod @classmethod
def environment_root(cls): def environment_root(cls) -> pathlib.Path:
"""Environment root directory""" """Environment root directory"""
bootstrap_root_path = root_path() bootstrap_root_path = root_path()
python_part = spec_for_current_python().replace("@", "") python_part = spec_for_current_python().replace("@", "")
@ -52,12 +53,12 @@ def environment_root(cls):
) )
@classmethod @classmethod
def view_root(cls): def view_root(cls) -> pathlib.Path:
"""Location of the view""" """Location of the view"""
return cls.environment_root().joinpath("view") return cls.environment_root().joinpath("view")
@classmethod @classmethod
def pythonpaths(cls): def pythonpaths(cls) -> List[str]:
"""Paths to be added to sys.path or PYTHONPATH""" """Paths to be added to sys.path or PYTHONPATH"""
python_dir_part = f"python{'.'.join(str(x) for x in sys.version_info[:2])}" python_dir_part = f"python{'.'.join(str(x) for x in sys.version_info[:2])}"
glob_expr = str(cls.view_root().joinpath("**", python_dir_part, "**")) glob_expr = str(cls.view_root().joinpath("**", python_dir_part, "**"))
@ -68,21 +69,21 @@ def pythonpaths(cls):
return result return result
@classmethod @classmethod
def bin_dirs(cls): def bin_dirs(cls) -> List[pathlib.Path]:
"""Paths to be added to PATH""" """Paths to be added to PATH"""
return [cls.view_root().joinpath("bin")] return [cls.view_root().joinpath("bin")]
@classmethod @classmethod
def spack_yaml(cls): def spack_yaml(cls) -> pathlib.Path:
"""Environment spack.yaml file""" """Environment spack.yaml file"""
return cls.environment_root().joinpath("spack.yaml") return cls.environment_root().joinpath("spack.yaml")
def __init__(self): def __init__(self) -> None:
if not self.spack_yaml().exists(): if not self.spack_yaml().exists():
self._write_spack_yaml_file() self._write_spack_yaml_file()
super().__init__(self.environment_root()) super().__init__(self.environment_root())
def update_installations(self): def update_installations(self) -> None:
"""Update the installations of this environment. """Update the installations of this environment.
The update is done using a depfile on Linux and macOS, and using the ``install_all`` The update is done using a depfile on Linux and macOS, and using the ``install_all``
@ -103,7 +104,7 @@ def update_installations(self):
self._install_with_depfile() self._install_with_depfile()
self.write(regenerate=True) self.write(regenerate=True)
def update_syspath_and_environ(self): def update_syspath_and_environ(self) -> None:
"""Update ``sys.path`` and the PATH, PYTHONPATH environment variables to point to """Update ``sys.path`` and the PATH, PYTHONPATH environment variables to point to
the environment view. the environment view.
""" """
@ -119,7 +120,7 @@ def update_syspath_and_environ(self):
+ [str(x) for x in self.pythonpaths()] + [str(x) for x in self.pythonpaths()]
) )
def _install_with_depfile(self): def _install_with_depfile(self) -> None:
spackcmd = spack.util.executable.which("spack") spackcmd = spack.util.executable.which("spack")
spackcmd( spackcmd(
"-e", "-e",
@ -141,7 +142,7 @@ def _install_with_depfile(self):
**kwargs, **kwargs,
) )
def _write_spack_yaml_file(self): def _write_spack_yaml_file(self) -> None:
tty.msg( tty.msg(
"[BOOTSTRAPPING] Spack has missing dependencies, creating a bootstrapping environment" "[BOOTSTRAPPING] Spack has missing dependencies, creating a bootstrapping environment"
) )
@ -159,32 +160,32 @@ def _write_spack_yaml_file(self):
self.spack_yaml().write_text(template.render(context), encoding="utf-8") self.spack_yaml().write_text(template.render(context), encoding="utf-8")
def isort_root_spec(): def isort_root_spec() -> str:
"""Return the root spec used to bootstrap isort""" """Return the root spec used to bootstrap isort"""
return _root_spec("py-isort@4.3.5:") return _root_spec("py-isort@4.3.5:")
def mypy_root_spec(): def mypy_root_spec() -> str:
"""Return the root spec used to bootstrap mypy""" """Return the root spec used to bootstrap mypy"""
return _root_spec("py-mypy@0.900:") return _root_spec("py-mypy@0.900:")
def black_root_spec(): def black_root_spec() -> str:
"""Return the root spec used to bootstrap black""" """Return the root spec used to bootstrap black"""
return _root_spec("py-black@:23.1.0") return _root_spec("py-black@:23.1.0")
def flake8_root_spec(): def flake8_root_spec() -> str:
"""Return the root spec used to bootstrap flake8""" """Return the root spec used to bootstrap flake8"""
return _root_spec("py-flake8") return _root_spec("py-flake8")
def pytest_root_spec(): def pytest_root_spec() -> str:
"""Return the root spec used to bootstrap flake8""" """Return the root spec used to bootstrap flake8"""
return _root_spec("py-pytest") return _root_spec("py-pytest")
def ensure_environment_dependencies(): def ensure_environment_dependencies() -> None:
"""Ensure Spack dependencies from the bootstrap environment are installed and ready to use""" """Ensure Spack dependencies from the bootstrap environment are installed and ready to use"""
with BootstrapEnvironment() as env: with BootstrapEnvironment() as env:
env.update_installations() env.update_installations()

View File

@ -4,6 +4,7 @@
# SPDX-License-Identifier: (Apache-2.0 OR MIT) # SPDX-License-Identifier: (Apache-2.0 OR MIT)
"""Query the status of bootstrapping on this machine""" """Query the status of bootstrapping on this machine"""
import platform import platform
from typing import List, Optional, Sequence, Tuple, Union
import spack.util.executable import spack.util.executable
@ -19,8 +20,12 @@
pytest_root_spec, pytest_root_spec,
) )
ExecutablesType = Union[str, Sequence[str]]
RequiredResponseType = Tuple[bool, Optional[str]]
SpecLike = Union["spack.spec.Spec", str]
def _required_system_executable(exes, msg):
def _required_system_executable(exes: ExecutablesType, msg: str) -> RequiredResponseType:
"""Search for an executable is the system path only.""" """Search for an executable is the system path only."""
if isinstance(exes, str): if isinstance(exes, str):
exes = (exes,) exes = (exes,)
@ -29,7 +34,9 @@ def _required_system_executable(exes, msg):
return False, msg return False, msg
def _required_executable(exes, query_spec, msg): def _required_executable(
exes: ExecutablesType, query_spec: SpecLike, msg: str
) -> RequiredResponseType:
"""Search for an executable in the system path or in the bootstrap store.""" """Search for an executable in the system path or in the bootstrap store."""
if isinstance(exes, str): if isinstance(exes, str):
exes = (exes,) exes = (exes,)
@ -38,7 +45,7 @@ def _required_executable(exes, query_spec, msg):
return False, msg return False, msg
def _required_python_module(module, query_spec, msg): def _required_python_module(module: str, query_spec: SpecLike, msg: str) -> RequiredResponseType:
"""Check if a Python module is available in the current interpreter or """Check if a Python module is available in the current interpreter or
if it can be loaded from the bootstrap store if it can be loaded from the bootstrap store
""" """
@ -47,7 +54,7 @@ def _required_python_module(module, query_spec, msg):
return False, msg return False, msg
def _missing(name, purpose, system_only=True): def _missing(name: str, purpose: str, system_only: bool = True) -> str:
"""Message to be printed if an executable is not found""" """Message to be printed if an executable is not found"""
msg = '[{2}] MISSING "{0}": {1}' msg = '[{2}] MISSING "{0}": {1}'
if not system_only: if not system_only:
@ -55,7 +62,7 @@ def _missing(name, purpose, system_only=True):
return msg.format(name, purpose, "@*y{{-}}") return msg.format(name, purpose, "@*y{{-}}")
def _core_requirements(): def _core_requirements() -> List[RequiredResponseType]:
_core_system_exes = { _core_system_exes = {
"make": _missing("make", "required to build software from sources"), "make": _missing("make", "required to build software from sources"),
"patch": _missing("patch", "required to patch source code before building"), "patch": _missing("patch", "required to patch source code before building"),
@ -80,7 +87,7 @@ def _core_requirements():
return result return result
def _buildcache_requirements(): def _buildcache_requirements() -> List[RequiredResponseType]:
_buildcache_exes = { _buildcache_exes = {
"file": _missing("file", "required to analyze files for buildcaches"), "file": _missing("file", "required to analyze files for buildcaches"),
("gpg2", "gpg"): _missing("gpg2", "required to sign/verify buildcaches", False), ("gpg2", "gpg"): _missing("gpg2", "required to sign/verify buildcaches", False),
@ -103,7 +110,7 @@ def _buildcache_requirements():
return result return result
def _optional_requirements(): def _optional_requirements() -> List[RequiredResponseType]:
_optional_exes = { _optional_exes = {
"zstd": _missing("zstd", "required to compress/decompress code archives"), "zstd": _missing("zstd", "required to compress/decompress code archives"),
"svn": _missing("svn", "required to manage subversion repositories"), "svn": _missing("svn", "required to manage subversion repositories"),
@ -114,7 +121,7 @@ def _optional_requirements():
return result return result
def _development_requirements(): def _development_requirements() -> List[RequiredResponseType]:
# Ensure we trigger environment modifications if we have an environment # Ensure we trigger environment modifications if we have an environment
if BootstrapEnvironment.spack_yaml().exists(): if BootstrapEnvironment.spack_yaml().exists():
with BootstrapEnvironment() as env: with BootstrapEnvironment() as env:
@ -139,7 +146,7 @@ def _development_requirements():
] ]
def status_message(section): def status_message(section) -> Tuple[str, bool]:
"""Return a status message to be printed to screen that refers to the """Return a status message to be printed to screen that refers to the
section passed as argument and a bool which is True if there are missing section passed as argument and a bool which is True if there are missing
dependencies. dependencies.
@ -161,7 +168,7 @@ def status_message(section):
with ensure_bootstrap_configuration(): with ensure_bootstrap_configuration():
missing_software = False missing_software = False
for found, err_msg in required_software(): for found, err_msg in required_software():
if not found: if not found and err_msg:
missing_software = True missing_software = True
msg += "\n " + err_msg msg += "\n " + err_msg
msg += "\n" msg += "\n"