Bootstrap should search for compilers after switching config scopes (#26029)

fixes #25992

Currently the bootstrapping process may need a compiler.

When bootstrapping from sources the need is obvious, while
when bootstrapping from binaries it's currently needed in
case patchelf is not on the system (since it will be then
bootstrapped from sources).

Before this PR we were searching for compilers as the
first operation, in case they were not declared in
the configuration. This fails in case we start
bootstrapping from within an environment.

The fix is to defer the search until we have swapped
configuration.
This commit is contained in:
Massimiliano Culpo 2021-09-18 02:28:48 +02:00 committed by GitHub
parent 4d36c40cfb
commit b847bb72f0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 80 additions and 47 deletions

View File

@ -490,18 +490,20 @@ def _bootstrap_config_scopes():
return config_scopes
@contextlib.contextmanager
def ensure_bootstrap_configuration():
# We may need to compile code from sources, so ensure we have compilers
# for the current platform before switching parts.
arch = spack.architecture.default_arch()
def _add_compilers_if_missing():
# Do not use spack.architecture.default_arch() since it memoize the result
arch = spack.architecture.Arch(
spack.architecture.real_platform(), 'default_os', 'default_target'
)
arch = spack.spec.ArchSpec(str(arch)) # The call below expects an ArchSpec object
if not spack.compilers.compilers_for_arch(arch):
compiler_cmd = spack.main.SpackCommand('compiler')
compiler_cmd(
'find', output=os.devnull, error=os.devnull, fail_on_error=False
)
new_compilers = spack.compilers.find_new_compilers()
if new_compilers:
spack.compilers.add_compilers_to_config(new_compilers, init_config=False)
@contextlib.contextmanager
def ensure_bootstrap_configuration():
bootstrap_store_path = store_path()
with spack.environment.deactivate_environment():
with spack.architecture.use_platform(spack.architecture.real_platform()):
@ -511,6 +513,9 @@ def ensure_bootstrap_configuration():
# and builtin but accounting for platform specific scopes
config_scopes = _bootstrap_config_scopes()
with spack.config.use_configuration(*config_scopes):
# We may need to compile code from sources, so ensure we have
# compilers for the current platform before switching parts.
_add_compilers_if_missing()
with spack.modules.disable_modules():
with spack_python_interpreter():
yield

View File

@ -18,7 +18,6 @@
import spack.compilers
import spack.config
import spack.spec
from spack.spec import ArchSpec, CompilerSpec
description = "manage compilers"
section = "system"
@ -78,24 +77,13 @@ def compiler_find(args):
# None signals spack.compiler.find_compilers to use its default logic
paths = args.add_paths or None
# Don't initialize compilers config via compilers.get_compiler_config.
# Just let compiler_find do the
# entire process and return an empty config from all_compilers
# Default for any other process is init_config=True
compilers = [c for c in spack.compilers.find_compilers(paths)]
new_compilers = []
for c in compilers:
arch_spec = ArchSpec((None, c.operating_system, c.target))
same_specs = spack.compilers.compilers_for_spec(
c.spec, arch_spec, init_config=False)
if not same_specs:
new_compilers.append(c)
# Below scope=None because we want new compilers that don't appear
# in any other configuration.
new_compilers = spack.compilers.find_new_compilers(paths, scope=None)
if new_compilers:
spack.compilers.add_compilers_to_config(new_compilers,
scope=args.scope,
init_config=False)
spack.compilers.add_compilers_to_config(
new_compilers, scope=args.scope, init_config=False
)
n = len(new_compilers)
s = 's' if n > 1 else ''
@ -110,7 +98,7 @@ def compiler_find(args):
def compiler_remove(args):
cspec = CompilerSpec(args.compiler_spec)
cspec = spack.spec.CompilerSpec(args.compiler_spec)
compilers = spack.compilers.compilers_for_spec(cspec, scope=args.scope)
if not compilers:
tty.die("No compilers match spec %s" % cspec)
@ -128,7 +116,7 @@ def compiler_remove(args):
def compiler_info(args):
"""Print info about all compilers matching a spec."""
cspec = CompilerSpec(args.compiler_spec)
cspec = spack.spec.CompilerSpec(args.compiler_spec)
compilers = spack.compilers.compilers_for_spec(cspec, scope=args.scope)
if not compilers:

View File

@ -192,15 +192,12 @@ def all_compiler_specs(scope=None, init_config=True):
def find_compilers(path_hints=None):
"""Returns the list of compilers found in the paths given as arguments.
"""Return the list of compilers found in the paths given as arguments.
Args:
path_hints (list or None): 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
Returns:
List of compilers found
"""
if path_hints is None:
path_hints = get_path('PATH')
@ -242,6 +239,30 @@ def remove_errors(item):
)
def find_new_compilers(path_hints=None, scope=None):
"""Same as ``find_compilers`` but return only the compilers that are not
already in compilers.yaml.
Args:
path_hints (list or None): 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
scope (str): scope to look for a compiler. If None consider the
merged configuration.
"""
compilers = find_compilers(path_hints)
compilers_not_in_config = []
for c in compilers:
arch_spec = spack.spec.ArchSpec((None, c.operating_system, c.target))
same_specs = compilers_for_spec(
c.spec, arch_spec, scope=scope, init_config=False
)
if not same_specs:
compilers_not_in_config.append(c)
return compilers_not_in_config
def supported_compilers():
"""Return a set of names of compilers supported by Spack.
@ -289,8 +310,9 @@ def all_compilers(scope=None):
@_auto_compiler_spec
def compilers_for_spec(compiler_spec, arch_spec=None, scope=None,
use_cache=True, init_config=True):
def compilers_for_spec(
compiler_spec, arch_spec=None, scope=None, use_cache=True, init_config=True
):
"""This gets all compilers that satisfy the supplied CompilerSpec.
Returns an empty list if none are found.
"""

View File

@ -5,6 +5,7 @@
import pytest
import spack.bootstrap
import spack.compilers
import spack.environment
import spack.store
import spack.util.path
@ -78,3 +79,23 @@ def test_bootstrap_disables_modulefile_generation(mutable_config):
assert 'lmod' not in spack.config.get('modules:enable')
assert 'tcl' in spack.config.get('modules:enable')
assert 'lmod' in spack.config.get('modules:enable')
@pytest.mark.regression('25992')
@pytest.mark.requires_executables('gcc')
def test_bootstrap_search_for_compilers_with_no_environment(no_compilers_yaml):
assert not spack.compilers.all_compiler_specs(init_config=False)
with spack.bootstrap.ensure_bootstrap_configuration():
assert spack.compilers.all_compiler_specs(init_config=False)
assert not spack.compilers.all_compiler_specs(init_config=False)
@pytest.mark.regression('25992')
@pytest.mark.requires_executables('gcc')
def test_bootstrap_search_for_compilers_with_environment_active(
no_compilers_yaml, active_mock_environment
):
assert not spack.compilers.all_compiler_specs(init_config=False)
with spack.bootstrap.ensure_bootstrap_configuration():
assert spack.compilers.all_compiler_specs(init_config=False)
assert not spack.compilers.all_compiler_specs(init_config=False)

View File

@ -16,18 +16,6 @@
compiler = spack.main.SpackCommand('compiler')
@pytest.fixture
def no_compilers_yaml(mutable_config):
"""Creates a temporary configuration without compilers.yaml"""
for scope, local_config in mutable_config.scopes.items():
compilers_yaml = os.path.join(
local_config.path, scope, 'compilers.yaml'
)
if os.path.exists(compilers_yaml):
os.remove(compilers_yaml)
@pytest.fixture
def mock_compiler_version():
return '4.5.3'

View File

@ -652,6 +652,15 @@ def mutable_empty_config(tmpdir_factory, configuration_dir):
yield cfg
@pytest.fixture
def no_compilers_yaml(mutable_config):
"""Creates a temporary configuration without compilers.yaml"""
for scope, local_config in mutable_config.scopes.items():
compilers_yaml = os.path.join(local_config.path, 'compilers.yaml')
if os.path.exists(compilers_yaml):
os.remove(compilers_yaml)
@pytest.fixture()
def mock_low_high_config(tmpdir):
"""Mocks two configuration scopes: 'low' and 'high'."""