|
48 | 48 | import spack.version as vn |
49 | 49 | import spack.version.git_ref_lookup |
50 | 50 | from spack import traverse |
51 | | -from spack.config import get_mark_from_yaml_data |
52 | | -from spack.error import SpecSyntaxError |
53 | 51 |
|
54 | 52 | from .core import ( |
55 | 53 | AspFunction, |
|
65 | 63 | parse_term, |
66 | 64 | ) |
67 | 65 | from .counter import FullDuplicatesCounter, MinimalDuplicatesCounter, NoDuplicatesCounter |
| 66 | +from .requirements import RequirementKind, RequirementParser, RequirementRule |
68 | 67 | from .version_order import concretization_version_order |
69 | 68 |
|
70 | 69 | GitOrStandardVersion = Union[spack.version.GitVersion, spack.version.StandardVersion] |
@@ -144,17 +143,6 @@ def named_spec( |
144 | 143 | spec.name = old_name |
145 | 144 |
|
146 | 145 |
|
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 | | - |
158 | 146 | class DeclaredVersion(NamedTuple): |
159 | 147 | """Data class to contain information on declared versions used in the solve""" |
160 | 148 |
|
@@ -757,17 +745,6 @@ def on_model(model): |
757 | 745 | raise UnsatisfiableSpecError(msg) |
758 | 746 |
|
759 | 747 |
|
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 | | - |
771 | 748 | class KnownCompiler(NamedTuple): |
772 | 749 | """Data class to collect information on compilers""" |
773 | 750 |
|
@@ -1146,6 +1123,7 @@ class SpackSolverSetup: |
1146 | 1123 | def __init__(self, tests: bool = False): |
1147 | 1124 | # these are all initialized in setup() |
1148 | 1125 | self.gen: "ProblemInstanceBuilder" = ProblemInstanceBuilder() |
| 1126 | + self.requirement_parser = RequirementParser(spack.config.CONFIG) |
1149 | 1127 | self.possible_virtuals: Set[str] = set() |
1150 | 1128 |
|
1151 | 1129 | self.assumptions: List[Tuple["clingo.Symbol", bool]] = [] # type: ignore[name-defined] |
@@ -1332,8 +1310,7 @@ def compiler_facts(self): |
1332 | 1310 | self.gen.newline() |
1333 | 1311 |
|
1334 | 1312 | 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)) |
1337 | 1314 |
|
1338 | 1315 | def pkg_rules(self, pkg, tests): |
1339 | 1316 | pkg = self.pkg_class(pkg) |
@@ -1811,9 +1788,8 @@ def provider_defaults(self): |
1811 | 1788 |
|
1812 | 1789 | def provider_requirements(self): |
1813 | 1790 | self.gen.h2("Requirements on virtual providers") |
1814 | | - parser = RequirementParser(spack.config.CONFIG) |
1815 | 1791 | 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) |
1817 | 1793 | if rules: |
1818 | 1794 | self.emit_facts_from_requirement_rules(rules) |
1819 | 1795 | self.trigger_rules() |
@@ -3088,202 +3064,6 @@ def value(self) -> str: |
3088 | 3064 | return "".join(self.asp_problem) |
3089 | 3065 |
|
3090 | 3066 |
|
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 | | - |
3287 | 3067 | class CompilerParser: |
3288 | 3068 | """Parses configuration files, and builds a list of possible compilers for the solve.""" |
3289 | 3069 |
|
|
0 commit comments