variant.py: make Variant.default is str | bool | tuple[str] (#49836)

Warn if variant default is not among those types
This commit is contained in:
Harmen Stoppels
2025-04-03 13:07:08 +02:00
committed by GitHub
parent 22f26eec68
commit 751c79872f
23 changed files with 69 additions and 37 deletions

View File

@@ -34,11 +34,13 @@ class OpenMpi(Package):
import collections.abc
import os
import re
import warnings
from typing import Any, Callable, List, Optional, Tuple, Type, Union
import llnl.util.tty.color
import spack.deptypes as dt
import spack.error
import spack.fetch_strategy
import spack.package_base
import spack.patch
@@ -620,7 +622,7 @@ def conditional(*values: List[Any], when: Optional[WhenType] = None):
@directive("variants")
def variant(
name: str,
default: Optional[Any] = None,
default: Optional[Union[bool, str, Tuple[str, ...]]] = None,
description: str = "",
values: Optional[Union[collections.abc.Sequence, Callable[[Any], bool]]] = None,
multi: Optional[bool] = None,
@@ -650,6 +652,24 @@ def variant(
DirectiveError: If arguments passed to the directive are invalid
"""
# This validation can be removed at runtime and enforced with an audit in Spack v1.0.
# For now it's a warning to let people migrate faster.
if not (
default is None
or type(default) in (bool, str)
or (type(default) is tuple and all(type(x) is str for x in default))
):
if isinstance(default, (list, tuple)):
did_you_mean = f"default={','.join(str(x) for x in default)!r}"
else:
did_you_mean = f"default={str(default)!r}"
warnings.warn(
f"default value for variant '{name}' is not a boolean or string: default={default!r}. "
f"Did you mean {did_you_mean}?",
stacklevel=3,
category=spack.error.SpackAPIWarning,
)
def format_error(msg, pkg):
msg += " @*r{{[{0}, variant '{1}']}}"
return llnl.util.tty.color.colorize(msg.format(pkg.name, name))
@@ -665,7 +685,11 @@ def _raise_reserved_name(pkg):
# Ensure we have a sequence of allowed variant values, or a
# predicate for it.
if values is None:
if str(default).upper() in ("TRUE", "FALSE"):
if (
default in (True, False)
or type(default) is str
and default.upper() in ("TRUE", "FALSE")
):
values = (True, False)
else:
values = lambda x: True
@@ -698,12 +722,15 @@ def _raise_argument_error(pkg):
# or the empty string, as the former indicates that a default
# was not set while the latter will make the variant unparsable
# from the command line
if isinstance(default, tuple):
default = ",".join(default)
if default is None or default == "":
def _raise_default_not_set(pkg):
if default is None:
msg = "either a default was not explicitly set, " "or 'None' was used"
elif default == "":
msg = "either a default was not explicitly set, or 'None' was used"
else:
msg = "the default cannot be an empty string"
raise DirectiveError(format_error(msg, pkg))

View File

@@ -64,7 +64,7 @@ class Variant:
"""
name: str
default: Any
default: Union[bool, str]
description: str
values: Optional[Collection] #: if None, valid values are defined only by validators
multi: bool
@@ -77,7 +77,7 @@ def __init__(
self,
name: str,
*,
default: Any,
default: Union[bool, str],
description: str,
values: Union[Collection, Callable] = (True, False),
multi: bool = False,
@@ -200,7 +200,7 @@ def make_default(self):
"""
return self.make_variant(self.default)
def make_variant(self, value) -> "AbstractVariant":
def make_variant(self, value: Union[str, bool]) -> "AbstractVariant":
"""Factory that creates a variant holding the value passed as
a parameter.
@@ -298,7 +298,7 @@ class AbstractVariant:
_value: ValueType
_original_value: Any
def __init__(self, name: str, value: Any, propagate: bool = False):
def __init__(self, name: str, value: ValueType, propagate: bool = False) -> None:
self.name = name
self.propagate = propagate