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

View File

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

View File

@ -192,15 +192,12 @@ def all_compiler_specs(scope=None, init_config=True):
def find_compilers(path_hints=None): 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: Args:
path_hints (list or None): list of path hints where to look for. path_hints (list or None): list of path hints where to look for.
A sensible default based on the ``PATH`` environment variable A sensible default based on the ``PATH`` environment variable
will be used if the value is None will be used if the value is None
Returns:
List of compilers found
""" """
if path_hints is None: if path_hints is None:
path_hints = get_path('PATH') 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(): def supported_compilers():
"""Return a set of names of compilers supported by Spack. """Return a set of names of compilers supported by Spack.
@ -289,8 +310,9 @@ def all_compilers(scope=None):
@_auto_compiler_spec @_auto_compiler_spec
def compilers_for_spec(compiler_spec, arch_spec=None, scope=None, def compilers_for_spec(
use_cache=True, init_config=True): compiler_spec, arch_spec=None, scope=None, use_cache=True, init_config=True
):
"""This gets all compilers that satisfy the supplied CompilerSpec. """This gets all compilers that satisfy the supplied CompilerSpec.
Returns an empty list if none are found. Returns an empty list if none are found.
""" """

View File

@ -5,6 +5,7 @@
import pytest import pytest
import spack.bootstrap import spack.bootstrap
import spack.compilers
import spack.environment import spack.environment
import spack.store import spack.store
import spack.util.path 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 'lmod' not in spack.config.get('modules:enable')
assert 'tcl' in spack.config.get('modules:enable') assert 'tcl' in spack.config.get('modules:enable')
assert 'lmod' 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') 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 @pytest.fixture
def mock_compiler_version(): def mock_compiler_version():
return '4.5.3' return '4.5.3'

View File

@ -652,6 +652,15 @@ def mutable_empty_config(tmpdir_factory, configuration_dir):
yield cfg 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() @pytest.fixture()
def mock_low_high_config(tmpdir): def mock_low_high_config(tmpdir):
"""Mocks two configuration scopes: 'low' and 'high'.""" """Mocks two configuration scopes: 'low' and 'high'."""