Windows: auto-add WGL/SDK as externals (#43752)
Adds a pre-concretization check for the Windows SDK and WGL (Windows GL) packages as non-buildable externals. This is a redo of https://github.com/spack/spack/pull/43459, but makes sure to modify the configuration scope outside of the bootstrap scope: whichever is highest-precedence in the user's environment at the time the concretization runs, which should either be an env scope or the ~ scope. Adds pytest fixture mocking the check for WGL and WSDK as if they were present.
This commit is contained in:
		@@ -1572,6 +1572,8 @@ Microsoft Visual Studio
 | 
			
		||||
"""""""""""""""""""""""
 | 
			
		||||
 | 
			
		||||
Microsoft Visual Studio provides the only Windows C/C++ compiler that is currently supported by Spack.
 | 
			
		||||
Spack additionally requires that the Windows SDK (including WGL) to be installed as part of your
 | 
			
		||||
visual studio installation as it is required to build many packages from source.
 | 
			
		||||
 | 
			
		||||
We require several specific components to be included in the Visual Studio installation.
 | 
			
		||||
One is the C/C++ toolset, which can be selected as "Desktop development with C++" or "C++ build tools,"
 | 
			
		||||
@@ -1579,6 +1581,7 @@ depending on installation type (Professional, Build Tools, etc.)  The other requ
 | 
			
		||||
"C++ CMake tools for Windows," which can be selected from among the optional packages.
 | 
			
		||||
This provides CMake and Ninja for use during Spack configuration.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
If you already have Visual Studio installed, you can make sure these components are installed by
 | 
			
		||||
rerunning the installer.  Next to your installation, select "Modify" and look at the
 | 
			
		||||
"Installation details" pane on the right.
 | 
			
		||||
 
 | 
			
		||||
@@ -538,6 +538,41 @@ def ensure_patchelf_in_path_or_raise() -> spack.util.executable.Executable:
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def ensure_winsdk_external_or_raise() -> None:
 | 
			
		||||
    """Ensure the Windows SDK + WGL are available on system
 | 
			
		||||
    If both of these package are found, the Spack user or bootstrap
 | 
			
		||||
    configuration (depending on where Spack is running)
 | 
			
		||||
    will be updated to include all versions and variants detected.
 | 
			
		||||
    If either the WDK or WSDK are not found, this method will raise
 | 
			
		||||
    a RuntimeError.
 | 
			
		||||
 | 
			
		||||
    **NOTE:** This modifies the Spack config in the current scope,
 | 
			
		||||
    either user or environment depending on the calling context.
 | 
			
		||||
    This is different from all other current bootstrap dependency
 | 
			
		||||
    checks.
 | 
			
		||||
    """
 | 
			
		||||
    if set(["win-sdk", "wgl"]).issubset(spack.config.get("packages").keys()):
 | 
			
		||||
        return
 | 
			
		||||
    externals = spack.detection.by_path(["win-sdk", "wgl"])
 | 
			
		||||
    if not set(["win-sdk", "wgl"]) == externals.keys():
 | 
			
		||||
        missing_packages_lst = []
 | 
			
		||||
        if "wgl" not in externals:
 | 
			
		||||
            missing_packages_lst.append("wgl")
 | 
			
		||||
        if "win-sdk" not in externals:
 | 
			
		||||
            missing_packages_lst.append("win-sdk")
 | 
			
		||||
        missing_packages = " & ".join(missing_packages_lst)
 | 
			
		||||
        raise RuntimeError(
 | 
			
		||||
            f"Unable to find the {missing_packages}, please install these packages \
 | 
			
		||||
via the Visual Studio installer \
 | 
			
		||||
before proceeding with Spack or provide the path to a non standard install with \
 | 
			
		||||
'spack external find --path'"
 | 
			
		||||
        )
 | 
			
		||||
    # wgl/sdk are not required for bootstrapping Spack, but
 | 
			
		||||
    # are required for building anything non trivial
 | 
			
		||||
    # add to user config so they can be used by subsequent Spack ops
 | 
			
		||||
    spack.detection.update_configuration(externals, buildable=False)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def ensure_core_dependencies() -> None:
 | 
			
		||||
    """Ensure the presence of all the core dependencies."""
 | 
			
		||||
    if sys.platform.lower() == "linux":
 | 
			
		||||
 
 | 
			
		||||
@@ -214,8 +214,6 @@ def unit_test(parser, args, unknown_args):
 | 
			
		||||
 | 
			
		||||
    # Ensure clingo is available before switching to the
 | 
			
		||||
    # mock configuration used by unit tests
 | 
			
		||||
    # Note: skip on windows here because for the moment,
 | 
			
		||||
    # clingo is wholly unsupported from bootstrap
 | 
			
		||||
    with spack.bootstrap.ensure_bootstrap_configuration():
 | 
			
		||||
        spack.bootstrap.ensure_core_dependencies()
 | 
			
		||||
        if pytest is None:
 | 
			
		||||
 
 | 
			
		||||
@@ -809,12 +809,22 @@ def solve(self, setup, specs, reuse=None, output=None, control=None, allow_depre
 | 
			
		||||
            A tuple of the solve result, the timer for the different phases of the
 | 
			
		||||
            solve, and the internal statistics from clingo.
 | 
			
		||||
        """
 | 
			
		||||
        # avoid circular import
 | 
			
		||||
        import spack.bootstrap
 | 
			
		||||
 | 
			
		||||
        output = output or DEFAULT_OUTPUT_CONFIGURATION
 | 
			
		||||
        timer = spack.util.timer.Timer()
 | 
			
		||||
 | 
			
		||||
        # Initialize the control object for the solver
 | 
			
		||||
        self.control = control or default_clingo_control()
 | 
			
		||||
 | 
			
		||||
        # ensure core deps are present on Windows
 | 
			
		||||
        # needs to modify active config scope, so cannot be run within
 | 
			
		||||
        # bootstrap config scope
 | 
			
		||||
        if sys.platform == "win32":
 | 
			
		||||
            tty.debug("Ensuring basic dependencies {win-sdk, wgl} available")
 | 
			
		||||
            spack.bootstrap.core.ensure_winsdk_external_or_raise()
 | 
			
		||||
 | 
			
		||||
        timer.start("setup")
 | 
			
		||||
        asp_problem = setup.setup(specs, reuse=reuse, allow_deprecated=allow_deprecated)
 | 
			
		||||
        if output.out is not None:
 | 
			
		||||
@@ -1403,7 +1413,6 @@ def condition(
 | 
			
		||||
            raise ValueError(f"Must provide a name for anonymous condition: '{required_spec}'")
 | 
			
		||||
 | 
			
		||||
        with spec_with_name(required_spec, name):
 | 
			
		||||
 | 
			
		||||
            # Check if we can emit the requirements before updating the condition ID counter.
 | 
			
		||||
            # In this way, if a condition can't be emitted but the exception is handled in the
 | 
			
		||||
            # caller, we won't emit partial facts.
 | 
			
		||||
 
 | 
			
		||||
@@ -763,6 +763,28 @@ def mutable_empty_config(tmpdir_factory, configuration_dir):
 | 
			
		||||
        yield cfg
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# From  https://github.com/pytest-dev/pytest/issues/363#issuecomment-1335631998
 | 
			
		||||
# Current suggested implementation from issue compatible with pytest >= 6.2
 | 
			
		||||
# this may be subject to change as new versions of Pytest are released
 | 
			
		||||
# and update the suggested solution
 | 
			
		||||
@pytest.fixture(scope="session")
 | 
			
		||||
def monkeypatch_session():
 | 
			
		||||
    with pytest.MonkeyPatch.context() as monkeypatch:
 | 
			
		||||
        yield monkeypatch
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@pytest.fixture(scope="session", autouse=True)
 | 
			
		||||
def mock_wsdk_externals(monkeypatch_session):
 | 
			
		||||
    """Skip check for required external packages on Windows during testing
 | 
			
		||||
    Note: In general this should cover this behavior for all tests,
 | 
			
		||||
    however any session scoped fixture involving concretization should
 | 
			
		||||
    include this fixture
 | 
			
		||||
    """
 | 
			
		||||
    monkeypatch_session.setattr(
 | 
			
		||||
        spack.bootstrap.core, "ensure_winsdk_external_or_raise", _return_none
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@pytest.fixture(scope="function")
 | 
			
		||||
def concretize_scope(mutable_config, tmpdir):
 | 
			
		||||
    """Adds a scope for concretization preferences"""
 | 
			
		||||
@@ -842,7 +864,13 @@ def _store_dir_and_cache(tmpdir_factory):
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@pytest.fixture(scope="session")
 | 
			
		||||
def mock_store(tmpdir_factory, mock_repo_path, mock_configuration_scopes, _store_dir_and_cache):
 | 
			
		||||
def mock_store(
 | 
			
		||||
    tmpdir_factory,
 | 
			
		||||
    mock_wsdk_externals,
 | 
			
		||||
    mock_repo_path,
 | 
			
		||||
    mock_configuration_scopes,
 | 
			
		||||
    _store_dir_and_cache,
 | 
			
		||||
):
 | 
			
		||||
    """Creates a read-only mock database with some packages installed note
 | 
			
		||||
    that the ref count for dyninst here will be 3, as it's recycled
 | 
			
		||||
    across each install.
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user