Skip to content

Commit c1b2ac5

Browse files
authored
solver: partition classes related to requirement parsing into their own file (spack#47915)
1 parent 4693b32 commit c1b2ac5

File tree

3 files changed

+238
-224
lines changed

3 files changed

+238
-224
lines changed

lib/spack/spack/solver/asp.py

Lines changed: 4 additions & 224 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,6 @@
4848
import spack.version as vn
4949
import spack.version.git_ref_lookup
5050
from spack import traverse
51-
from spack.config import get_mark_from_yaml_data
52-
from spack.error import SpecSyntaxError
5351

5452
from .core import (
5553
AspFunction,
@@ -65,6 +63,7 @@
6563
parse_term,
6664
)
6765
from .counter import FullDuplicatesCounter, MinimalDuplicatesCounter, NoDuplicatesCounter
66+
from .requirements import RequirementKind, RequirementParser, RequirementRule
6867
from .version_order import concretization_version_order
6968

7069
GitOrStandardVersion = Union[spack.version.GitVersion, spack.version.StandardVersion]
@@ -144,17 +143,6 @@ def named_spec(
144143
spec.name = old_name
145144

146145

147-
class RequirementKind(enum.Enum):
148-
"""Purpose / provenance of a requirement"""
149-
150-
#: Default requirement expressed under the 'all' attribute of packages.yaml
151-
DEFAULT = enum.auto()
152-
#: Requirement expressed on a virtual package
153-
VIRTUAL = enum.auto()
154-
#: Requirement expressed on a specific package
155-
PACKAGE = enum.auto()
156-
157-
158146
class DeclaredVersion(NamedTuple):
159147
"""Data class to contain information on declared versions used in the solve"""
160148

@@ -757,17 +745,6 @@ def on_model(model):
757745
raise UnsatisfiableSpecError(msg)
758746

759747

760-
class RequirementRule(NamedTuple):
761-
"""Data class to collect information on a requirement"""
762-
763-
pkg_name: str
764-
policy: str
765-
requirements: List["spack.spec.Spec"]
766-
condition: "spack.spec.Spec"
767-
kind: RequirementKind
768-
message: Optional[str]
769-
770-
771748
class KnownCompiler(NamedTuple):
772749
"""Data class to collect information on compilers"""
773750

@@ -1146,6 +1123,7 @@ class SpackSolverSetup:
11461123
def __init__(self, tests: bool = False):
11471124
# these are all initialized in setup()
11481125
self.gen: "ProblemInstanceBuilder" = ProblemInstanceBuilder()
1126+
self.requirement_parser = RequirementParser(spack.config.CONFIG)
11491127
self.possible_virtuals: Set[str] = set()
11501128

11511129
self.assumptions: List[Tuple["clingo.Symbol", bool]] = [] # type: ignore[name-defined]
@@ -1332,8 +1310,7 @@ def compiler_facts(self):
13321310
self.gen.newline()
13331311

13341312
def package_requirement_rules(self, pkg):
1335-
parser = RequirementParser(spack.config.CONFIG)
1336-
self.emit_facts_from_requirement_rules(parser.rules(pkg))
1313+
self.emit_facts_from_requirement_rules(self.requirement_parser.rules(pkg))
13371314

13381315
def pkg_rules(self, pkg, tests):
13391316
pkg = self.pkg_class(pkg)
@@ -1811,9 +1788,8 @@ def provider_defaults(self):
18111788

18121789
def provider_requirements(self):
18131790
self.gen.h2("Requirements on virtual providers")
1814-
parser = RequirementParser(spack.config.CONFIG)
18151791
for virtual_str in sorted(self.possible_virtuals):
1816-
rules = parser.rules_from_virtual(virtual_str)
1792+
rules = self.requirement_parser.rules_from_virtual(virtual_str)
18171793
if rules:
18181794
self.emit_facts_from_requirement_rules(rules)
18191795
self.trigger_rules()
@@ -3088,202 +3064,6 @@ def value(self) -> str:
30883064
return "".join(self.asp_problem)
30893065

30903066

3091-
def parse_spec_from_yaml_string(string: str) -> "spack.spec.Spec":
3092-
"""Parse a spec from YAML and add file/line info to errors, if it's available.
3093-
3094-
Parse a ``Spec`` from the supplied string, but also intercept any syntax errors and
3095-
add file/line information for debugging using file/line annotations from the string.
3096-
3097-
Arguments:
3098-
string: a string representing a ``Spec`` from config YAML.
3099-
3100-
"""
3101-
try:
3102-
return spack.spec.Spec(string)
3103-
except SpecSyntaxError as e:
3104-
mark = get_mark_from_yaml_data(string)
3105-
if mark:
3106-
msg = f"{mark.name}:{mark.line + 1}: {str(e)}"
3107-
raise SpecSyntaxError(msg) from e
3108-
raise e
3109-
3110-
3111-
class RequirementParser:
3112-
"""Parses requirements from package.py files and configuration, and returns rules."""
3113-
3114-
def __init__(self, configuration):
3115-
self.config = configuration
3116-
3117-
def rules(self, pkg: "spack.package_base.PackageBase") -> List[RequirementRule]:
3118-
result = []
3119-
result.extend(self.rules_from_package_py(pkg))
3120-
result.extend(self.rules_from_require(pkg))
3121-
result.extend(self.rules_from_prefer(pkg))
3122-
result.extend(self.rules_from_conflict(pkg))
3123-
return result
3124-
3125-
def rules_from_package_py(self, pkg) -> List[RequirementRule]:
3126-
rules = []
3127-
for when_spec, requirement_list in pkg.requirements.items():
3128-
for requirements, policy, message in requirement_list:
3129-
rules.append(
3130-
RequirementRule(
3131-
pkg_name=pkg.name,
3132-
policy=policy,
3133-
requirements=requirements,
3134-
kind=RequirementKind.PACKAGE,
3135-
condition=when_spec,
3136-
message=message,
3137-
)
3138-
)
3139-
return rules
3140-
3141-
def rules_from_virtual(self, virtual_str: str) -> List[RequirementRule]:
3142-
requirements = self.config.get("packages", {}).get(virtual_str, {}).get("require", [])
3143-
return self._rules_from_requirements(
3144-
virtual_str, requirements, kind=RequirementKind.VIRTUAL
3145-
)
3146-
3147-
def rules_from_require(self, pkg: "spack.package_base.PackageBase") -> List[RequirementRule]:
3148-
kind, requirements = self._raw_yaml_data(pkg, section="require")
3149-
return self._rules_from_requirements(pkg.name, requirements, kind=kind)
3150-
3151-
def rules_from_prefer(self, pkg: "spack.package_base.PackageBase") -> List[RequirementRule]:
3152-
result = []
3153-
kind, preferences = self._raw_yaml_data(pkg, section="prefer")
3154-
for item in preferences:
3155-
spec, condition, message = self._parse_prefer_conflict_item(item)
3156-
result.append(
3157-
# A strong preference is defined as:
3158-
#
3159-
# require:
3160-
# - any_of: [spec_str, "@:"]
3161-
RequirementRule(
3162-
pkg_name=pkg.name,
3163-
policy="any_of",
3164-
requirements=[spec, spack.spec.Spec("@:")],
3165-
kind=kind,
3166-
message=message,
3167-
condition=condition,
3168-
)
3169-
)
3170-
return result
3171-
3172-
def rules_from_conflict(self, pkg: "spack.package_base.PackageBase") -> List[RequirementRule]:
3173-
result = []
3174-
kind, conflicts = self._raw_yaml_data(pkg, section="conflict")
3175-
for item in conflicts:
3176-
spec, condition, message = self._parse_prefer_conflict_item(item)
3177-
result.append(
3178-
# A conflict is defined as:
3179-
#
3180-
# require:
3181-
# - one_of: [spec_str, "@:"]
3182-
RequirementRule(
3183-
pkg_name=pkg.name,
3184-
policy="one_of",
3185-
requirements=[spec, spack.spec.Spec("@:")],
3186-
kind=kind,
3187-
message=message,
3188-
condition=condition,
3189-
)
3190-
)
3191-
return result
3192-
3193-
def _parse_prefer_conflict_item(self, item):
3194-
# The item is either a string or an object with at least a "spec" attribute
3195-
if isinstance(item, str):
3196-
spec = parse_spec_from_yaml_string(item)
3197-
condition = spack.spec.Spec()
3198-
message = None
3199-
else:
3200-
spec = parse_spec_from_yaml_string(item["spec"])
3201-
condition = spack.spec.Spec(item.get("when"))
3202-
message = item.get("message")
3203-
return spec, condition, message
3204-
3205-
def _raw_yaml_data(self, pkg: "spack.package_base.PackageBase", *, section: str):
3206-
config = self.config.get("packages")
3207-
data = config.get(pkg.name, {}).get(section, [])
3208-
kind = RequirementKind.PACKAGE
3209-
if not data:
3210-
data = config.get("all", {}).get(section, [])
3211-
kind = RequirementKind.DEFAULT
3212-
return kind, data
3213-
3214-
def _rules_from_requirements(
3215-
self, pkg_name: str, requirements, *, kind: RequirementKind
3216-
) -> List[RequirementRule]:
3217-
"""Manipulate requirements from packages.yaml, and return a list of tuples
3218-
with a uniform structure (name, policy, requirements).
3219-
"""
3220-
if isinstance(requirements, str):
3221-
requirements = [requirements]
3222-
3223-
rules = []
3224-
for requirement in requirements:
3225-
# A string is equivalent to a one_of group with a single element
3226-
if isinstance(requirement, str):
3227-
requirement = {"one_of": [requirement]}
3228-
3229-
for policy in ("spec", "one_of", "any_of"):
3230-
if policy not in requirement:
3231-
continue
3232-
3233-
constraints = requirement[policy]
3234-
# "spec" is for specifying a single spec
3235-
if policy == "spec":
3236-
constraints = [constraints]
3237-
policy = "one_of"
3238-
3239-
# validate specs from YAML first, and fail with line numbers if parsing fails.
3240-
constraints = [
3241-
parse_spec_from_yaml_string(constraint) for constraint in constraints
3242-
]
3243-
when_str = requirement.get("when")
3244-
when = parse_spec_from_yaml_string(when_str) if when_str else spack.spec.Spec()
3245-
3246-
constraints = [
3247-
x
3248-
for x in constraints
3249-
if not self.reject_requirement_constraint(pkg_name, constraint=x, kind=kind)
3250-
]
3251-
if not constraints:
3252-
continue
3253-
3254-
rules.append(
3255-
RequirementRule(
3256-
pkg_name=pkg_name,
3257-
policy=policy,
3258-
requirements=constraints,
3259-
kind=kind,
3260-
message=requirement.get("message"),
3261-
condition=when,
3262-
)
3263-
)
3264-
return rules
3265-
3266-
def reject_requirement_constraint(
3267-
self, pkg_name: str, *, constraint: spack.spec.Spec, kind: RequirementKind
3268-
) -> bool:
3269-
"""Returns True if a requirement constraint should be rejected"""
3270-
if kind == RequirementKind.DEFAULT:
3271-
# Requirements under all: are applied only if they are satisfiable considering only
3272-
# package rules, so e.g. variants must exist etc. Otherwise, they are rejected.
3273-
try:
3274-
s = spack.spec.Spec(pkg_name)
3275-
s.constrain(constraint)
3276-
s.validate_or_raise()
3277-
except spack.error.SpackError as e:
3278-
tty.debug(
3279-
f"[SETUP] Rejecting the default '{constraint}' requirement "
3280-
f"on '{pkg_name}': {str(e)}",
3281-
level=2,
3282-
)
3283-
return True
3284-
return False
3285-
3286-
32873067
class CompilerParser:
32883068
"""Parses configuration files, and builds a list of possible compilers for the solve."""
32893069

lib/spack/spack/solver/concretize.lp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1003,6 +1003,8 @@ variant_default_not_used(node(ID, Package), Variant, Value)
10031003
node_has_variant(node(ID, Package), Variant, _),
10041004
not attr("variant_value", node(ID, Package), Variant, Value),
10051005
not propagate(node(ID, Package), variant_value(Variant, _, _)),
1006+
% variant set explicitly don't count for this metric
1007+
not attr("variant_set", node(ID, Package), Variant, _),
10061008
attr("node", node(ID, Package)).
10071009

10081010
% The variant is set in an external spec

0 commit comments

Comments
 (0)