Split satisfies(..., strict=True/False) into two functions (#35681)

This commit formalizes `satisfies(lhs, rhs, strict=True/False)`
and splits it into two functions: `satisfies(lhs, rhs)` and
`intersects(lhs, rhs)`.

- `satisfies(lhs, rhs)` means: all concrete specs matching the
   left hand side also match the right hand side
- `intersects(lhs, rhs)` means: there exist concrete specs
   matching both lhs and rhs.

`intersects` now has the property that it's commutative,
which previously was not guaranteed.

For abstract specs, `intersects(lhs, rhs)` implies that
`constrain(lhs, rhs)` works.

What's *not* done in this PR is ensuring that
`intersects(concrete, abstract)` returns false when the
abstract spec has additional properties not present in the
concrete spec, but `constrain(concrete, abstract)` will
raise an error.

To accomplish this, some semantics have changed, as well
as bugfixes to ArchSpec:
- GitVersion is now interpreted as a more constrained
  version
- Compiler flags are interpreted as strings since their
  order is important
- Abstract specs respect variant type (bool / multivalued)
This commit is contained in:
Massimiliano Culpo 2023-03-08 13:00:53 +01:00 committed by GitHub
parent 39adb65dc7
commit d54611af2c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
39 changed files with 1057 additions and 859 deletions

View File

@ -25,7 +25,7 @@ def architecture_compatible(self, target, constraint):
return ( return (
not target.architecture not target.architecture
or not constraint.architecture or not constraint.architecture
or target.architecture.satisfies(constraint.architecture) or target.architecture.intersects(constraint.architecture)
) )
@memoized @memoized
@ -104,7 +104,7 @@ def compiler_compatible(self, parent, child, **kwargs):
for cversion in child.compiler.versions: for cversion in child.compiler.versions:
# For a few compilers use specialized comparisons. # For a few compilers use specialized comparisons.
# Otherwise match on version match. # Otherwise match on version match.
if pversion.satisfies(cversion): if pversion.intersects(cversion):
return True return True
elif parent.compiler.name == "gcc" and self._gcc_compiler_compare( elif parent.compiler.name == "gcc" and self._gcc_compiler_compare(
pversion, cversion pversion, cversion

View File

@ -721,7 +721,7 @@ def _version_constraints_are_satisfiable_by_some_version_in_repo(pkgs, error_cls
dependency_pkg_cls = None dependency_pkg_cls = None
try: try:
dependency_pkg_cls = spack.repo.path.get_pkg_class(s.name) dependency_pkg_cls = spack.repo.path.get_pkg_class(s.name)
assert any(v.satisfies(s.versions) for v in list(dependency_pkg_cls.versions)) assert any(v.intersects(s.versions) for v in list(dependency_pkg_cls.versions))
except Exception: except Exception:
summary = ( summary = (
"{0}: dependency on {1} cannot be satisfied " "by known versions of {1.name}" "{0}: dependency on {1} cannot be satisfied " "by known versions of {1.name}"

View File

@ -208,7 +208,7 @@ def _install_and_test(self, abstract_spec, bincache_platform, bincache_data, tes
# This will be None for things that don't depend on python # This will be None for things that don't depend on python
python_spec = item.get("python", None) python_spec = item.get("python", None)
# Skip specs which are not compatible # Skip specs which are not compatible
if not abstract_spec.satisfies(candidate_spec): if not abstract_spec.intersects(candidate_spec):
continue continue
if python_spec is not None and python_spec not in abstract_spec: if python_spec is not None and python_spec not in abstract_spec:

View File

@ -361,7 +361,7 @@ def append_dep(s, d):
def _spec_matches(spec, match_string): def _spec_matches(spec, match_string):
return spec.satisfies(match_string) return spec.intersects(match_string)
def _remove_attributes(src_dict, dest_dict): def _remove_attributes(src_dict, dest_dict):
@ -938,7 +938,7 @@ def generate_gitlab_ci_yaml(
bs_arch = c_spec.architecture bs_arch = c_spec.architecture
bs_arch_family = bs_arch.target.microarchitecture.family bs_arch_family = bs_arch.target.microarchitecture.family
if ( if (
c_spec.satisfies(compiler_pkg_spec) c_spec.intersects(compiler_pkg_spec)
and bs_arch_family == spec_arch_family and bs_arch_family == spec_arch_family
): ):
# We found the bootstrap compiler this release spec # We found the bootstrap compiler this release spec

View File

@ -498,11 +498,11 @@ def list_fn(args):
if not args.allarch: if not args.allarch:
arch = spack.spec.Spec.default_arch() arch = spack.spec.Spec.default_arch()
specs = [s for s in specs if s.satisfies(arch)] specs = [s for s in specs if s.intersects(arch)]
if args.specs: if args.specs:
constraints = set(args.specs) constraints = set(args.specs)
specs = [s for s in specs if any(s.satisfies(c) for c in constraints)] specs = [s for s in specs if any(s.intersects(c) for c in constraints)]
if sys.stdout.isatty(): if sys.stdout.isatty():
builds = len(specs) builds = len(specs)
tty.msg("%s." % plural(builds, "cached build")) tty.msg("%s." % plural(builds, "cached build"))

View File

@ -283,7 +283,7 @@ def print_tests(pkg):
c_names = ("gcc", "intel", "intel-parallel-studio", "pgi") c_names = ("gcc", "intel", "intel-parallel-studio", "pgi")
if pkg.name in c_names: if pkg.name in c_names:
v_names.extend(["c", "cxx", "fortran"]) v_names.extend(["c", "cxx", "fortran"])
if pkg.spec.satisfies("llvm+clang"): if pkg.spec.intersects("llvm+clang"):
v_names.extend(["c", "cxx"]) v_names.extend(["c", "cxx"])
# TODO Refactor END # TODO Refactor END

View File

@ -335,7 +335,7 @@ def not_excluded_fn(args):
exclude_specs.extend(spack.cmd.parse_specs(str(args.exclude_specs).split())) exclude_specs.extend(spack.cmd.parse_specs(str(args.exclude_specs).split()))
def not_excluded(x): def not_excluded(x):
return not any(x.satisfies(y, strict=True) for y in exclude_specs) return not any(x.satisfies(y) for y in exclude_specs)
return not_excluded return not_excluded

View File

@ -134,7 +134,7 @@ def _valid_virtuals_and_externals(self, spec):
externals = spec_externals(cspec) externals = spec_externals(cspec)
for ext in externals: for ext in externals:
if ext.satisfies(spec): if ext.intersects(spec):
usable.append(ext) usable.append(ext)
# If nothing is in the usable list now, it's because we aren't # If nothing is in the usable list now, it's because we aren't
@ -200,7 +200,7 @@ def concretize_version(self, spec):
# List of versions we could consider, in sorted order # List of versions we could consider, in sorted order
pkg_versions = spec.package_class.versions pkg_versions = spec.package_class.versions
usable = [v for v in pkg_versions if any(v.satisfies(sv) for sv in spec.versions)] usable = [v for v in pkg_versions if any(v.intersects(sv) for sv in spec.versions)]
yaml_prefs = PackagePrefs(spec.name, "version") yaml_prefs = PackagePrefs(spec.name, "version")
@ -344,7 +344,7 @@ def concretize_architecture(self, spec):
new_target_arch = spack.spec.ArchSpec((None, None, str(new_target))) new_target_arch = spack.spec.ArchSpec((None, None, str(new_target)))
curr_target_arch = spack.spec.ArchSpec((None, None, str(curr_target))) curr_target_arch = spack.spec.ArchSpec((None, None, str(curr_target)))
if not new_target_arch.satisfies(curr_target_arch): if not new_target_arch.intersects(curr_target_arch):
# new_target is an incorrect guess based on preferences # new_target is an incorrect guess based on preferences
# and/or default # and/or default
valid_target_ranges = str(curr_target).split(",") valid_target_ranges = str(curr_target).split(",")

View File

@ -1525,7 +1525,7 @@ def _query(
if not (start_date < inst_date < end_date): if not (start_date < inst_date < end_date):
continue continue
if query_spec is any or rec.spec.satisfies(query_spec, strict=True): if query_spec is any or rec.spec.satisfies(query_spec):
results.append(rec.spec) results.append(rec.spec)
return results return results

View File

@ -349,7 +349,8 @@ def _is_dev_spec_and_has_changed(spec):
def _spec_needs_overwrite(spec, changed_dev_specs): def _spec_needs_overwrite(spec, changed_dev_specs):
"""Check whether the current spec needs to be overwritten because either it has """Check whether the current spec needs to be overwritten because either it has
changed itself or one of its dependencies have changed""" changed itself or one of its dependencies have changed
"""
# if it's not installed, we don't need to overwrite it # if it's not installed, we don't need to overwrite it
if not spec.installed: if not spec.installed:
return False return False
@ -2313,7 +2314,7 @@ def _concretize_from_constraints(spec_constraints, tests=False):
invalid_deps = [ invalid_deps = [
c c
for c in spec_constraints for c in spec_constraints
if any(c.satisfies(invd, strict=True) for invd in invalid_deps_string) if any(c.satisfies(invd) for invd in invalid_deps_string)
] ]
if len(invalid_deps) != len(invalid_deps_string): if len(invalid_deps) != len(invalid_deps_string):
raise e raise e

View File

@ -1501,7 +1501,7 @@ def _from_merged_attrs(fetcher, pkg, version):
return fetcher(**attrs) return fetcher(**attrs)
def for_package_version(pkg, version): def for_package_version(pkg, version=None):
"""Determine a fetch strategy based on the arguments supplied to """Determine a fetch strategy based on the arguments supplied to
version() in the package description.""" version() in the package description."""
@ -1512,8 +1512,18 @@ def for_package_version(pkg, version):
check_pkg_attributes(pkg) check_pkg_attributes(pkg)
if not isinstance(version, spack.version.VersionBase): if version is not None:
version = spack.version.Version(version) assert not pkg.spec.concrete, "concrete specs should not pass the 'version=' argument"
# Specs are initialized with the universe range, if no version information is given,
# so here we make sure we always match the version passed as argument
if not isinstance(version, spack.version.VersionBase):
version = spack.version.Version(version)
version_list = spack.version.VersionList()
version_list.add(version)
pkg.spec.versions = version_list
else:
version = pkg.version
# if it's a commit, we must use a GitFetchStrategy # if it's a commit, we must use a GitFetchStrategy
if isinstance(version, spack.version.GitVersion): if isinstance(version, spack.version.GitVersion):

View File

@ -492,7 +492,7 @@ def get_matching_versions(specs, num_versions=1):
break break
# Generate only versions that satisfy the spec. # Generate only versions that satisfy the spec.
if spec.concrete or v.satisfies(spec.versions): if spec.concrete or v.intersects(spec.versions):
s = spack.spec.Spec(pkg.name) s = spack.spec.Spec(pkg.name)
s.versions = VersionList([v]) s.versions = VersionList([v])
s.variants = spec.variants.copy() s.variants = spec.variants.copy()

View File

@ -207,7 +207,7 @@ def merge_config_rules(configuration, spec):
# evaluated in order of appearance in the module file # evaluated in order of appearance in the module file
spec_configuration = module_specific_configuration.pop("all", {}) spec_configuration = module_specific_configuration.pop("all", {})
for constraint, action in module_specific_configuration.items(): for constraint, action in module_specific_configuration.items():
if spec.satisfies(constraint, strict=True): if spec.satisfies(constraint):
if hasattr(constraint, "override") and constraint.override: if hasattr(constraint, "override") and constraint.override:
spec_configuration = {} spec_configuration = {}
update_dictionary_extending_lists(spec_configuration, action) update_dictionary_extending_lists(spec_configuration, action)

View File

@ -1197,7 +1197,7 @@ def _make_fetcher(self):
# one element (the root package). In case there are resources # one element (the root package). In case there are resources
# associated with the package, append their fetcher to the # associated with the package, append their fetcher to the
# composite. # composite.
root_fetcher = fs.for_package_version(self, self.version) root_fetcher = fs.for_package_version(self)
fetcher = fs.FetchStrategyComposite() # Composite fetcher fetcher = fs.FetchStrategyComposite() # Composite fetcher
fetcher.append(root_fetcher) # Root fetcher is always present fetcher.append(root_fetcher) # Root fetcher is always present
resources = self._get_needed_resources() resources = self._get_needed_resources()
@ -1308,7 +1308,7 @@ def provides(self, vpkg_name):
True if this package provides a virtual package with the specified name True if this package provides a virtual package with the specified name
""" """
return any( return any(
any(self.spec.satisfies(c) for c in constraints) any(self.spec.intersects(c) for c in constraints)
for s, constraints in self.provided.items() for s, constraints in self.provided.items()
if s.name == vpkg_name if s.name == vpkg_name
) )
@ -1614,7 +1614,7 @@ def content_hash(self, content=None):
# TODO: resources # TODO: resources
if self.spec.versions.concrete: if self.spec.versions.concrete:
try: try:
source_id = fs.for_package_version(self, self.version).source_id() source_id = fs.for_package_version(self).source_id()
except (fs.ExtrapolationError, fs.InvalidArgsError): except (fs.ExtrapolationError, fs.InvalidArgsError):
# ExtrapolationError happens if the package has no fetchers defined. # ExtrapolationError happens if the package has no fetchers defined.
# InvalidArgsError happens when there are version directives with args, # InvalidArgsError happens when there are version directives with args,
@ -1777,7 +1777,7 @@ def _get_needed_resources(self):
# conflict with the spec, so we need to invoke # conflict with the spec, so we need to invoke
# when_spec.satisfies(self.spec) vs. # when_spec.satisfies(self.spec) vs.
# self.spec.satisfies(when_spec) # self.spec.satisfies(when_spec)
if when_spec.satisfies(self.spec, strict=False): if when_spec.intersects(self.spec):
resources.extend(resource_list) resources.extend(resource_list)
# Sorts the resources by the length of the string representing their # Sorts the resources by the length of the string representing their
# destination. Since any nested resource must contain another # destination. Since any nested resource must contain another

View File

@ -73,7 +73,7 @@ def __call__(self, spec):
# integer is the index of the first spec in order that satisfies # integer is the index of the first spec in order that satisfies
# spec, or it's a number larger than any position in the order. # spec, or it's a number larger than any position in the order.
match_index = next( match_index = next(
(i for i, s in enumerate(spec_order) if spec.satisfies(s)), len(spec_order) (i for i, s in enumerate(spec_order) if spec.intersects(s)), len(spec_order)
) )
if match_index < len(spec_order) and spec_order[match_index] == spec: if match_index < len(spec_order) and spec_order[match_index] == spec:
# If this is called with multiple specs that all satisfy the same # If this is called with multiple specs that all satisfy the same
@ -185,7 +185,7 @@ def _package(maybe_abstract_spec):
), ),
extra_attributes=entry.get("extra_attributes", {}), extra_attributes=entry.get("extra_attributes", {}),
) )
if external_spec.satisfies(spec): if external_spec.intersects(spec):
external_specs.append(external_spec) external_specs.append(external_spec)
# Defensively copy returned specs # Defensively copy returned specs

View File

@ -10,7 +10,7 @@ def get_projection(projections, spec):
""" """
all_projection = None all_projection = None
for spec_like, projection in projections.items(): for spec_like, projection in projections.items():
if spec.satisfies(spec_like, strict=True): if spec.satisfies(spec_like):
return projection return projection
elif spec_like == "all": elif spec_like == "all":
all_projection = projection all_projection = projection

View File

@ -72,7 +72,7 @@ def providers_for(self, virtual_spec):
# Add all the providers that satisfy the vpkg spec. # Add all the providers that satisfy the vpkg spec.
if virtual_spec.name in self.providers: if virtual_spec.name in self.providers:
for p_spec, spec_set in self.providers[virtual_spec.name].items(): for p_spec, spec_set in self.providers[virtual_spec.name].items():
if p_spec.satisfies(virtual_spec, deps=False): if p_spec.intersects(virtual_spec, deps=False):
result.update(spec_set) result.update(spec_set)
# Return providers in order. Defensively copy. # Return providers in order. Defensively copy.
@ -186,7 +186,7 @@ def update(self, spec):
provider_spec = provider_spec_readonly.copy() provider_spec = provider_spec_readonly.copy()
provider_spec.compiler_flags = spec.compiler_flags.copy() provider_spec.compiler_flags = spec.compiler_flags.copy()
if spec.satisfies(provider_spec, deps=False): if spec.intersects(provider_spec, deps=False):
provided_name = provided_spec.name provided_name = provided_spec.name
provider_map = self.providers.setdefault(provided_name, {}) provider_map = self.providers.setdefault(provided_name, {})

View File

@ -501,7 +501,7 @@ def _compute_specs_from_answer_set(self):
key = providers[0] key = providers[0]
candidate = answer.get(key) candidate = answer.get(key)
if candidate and candidate.satisfies(input_spec): if candidate and candidate.intersects(input_spec):
self._concrete_specs.append(answer[key]) self._concrete_specs.append(answer[key])
self._concrete_specs_by_input[input_spec] = answer[key] self._concrete_specs_by_input[input_spec] = answer[key]
else: else:
@ -1878,7 +1878,7 @@ def define_version_constraints(self):
for pkg_name, versions in sorted(self.version_constraints): for pkg_name, versions in sorted(self.version_constraints):
# version must be *one* of the ones the spec allows. # version must be *one* of the ones the spec allows.
allowed_versions = [ allowed_versions = [
v for v in sorted(self.possible_versions[pkg_name]) if v.satisfies(versions) v for v in sorted(self.possible_versions[pkg_name]) if v.intersects(versions)
] ]
# This is needed to account for a variable number of # This is needed to account for a variable number of

View File

@ -191,9 +191,7 @@ def __call__(self, match):
@lang.lazy_lexicographic_ordering @lang.lazy_lexicographic_ordering
class ArchSpec(object): class ArchSpec(object):
"""Aggregate the target platform, the operating system and the target """Aggregate the target platform, the operating system and the target microarchitecture."""
microarchitecture into an architecture spec..
"""
@staticmethod @staticmethod
def _return_arch(os_tag, target_tag): def _return_arch(os_tag, target_tag):
@ -362,17 +360,11 @@ def target_or_none(t):
self._target = value self._target = value
def satisfies(self, other, strict=False): def satisfies(self, other: "ArchSpec") -> bool:
"""Predicate to check if this spec satisfies a constraint. """Return True if all concrete specs matching self also match other, otherwise False.
Args: Args:
other (ArchSpec or str): constraint on the current instance other: spec to be satisfied
strict (bool): if ``False`` the function checks if the current
instance *might* eventually satisfy the constraint. If
``True`` it check if the constraint is satisfied right now.
Returns:
True if the constraint is satisfied, False otherwise.
""" """
other = self._autospec(other) other = self._autospec(other)
@ -380,47 +372,69 @@ def satisfies(self, other, strict=False):
for attribute in ("platform", "os"): for attribute in ("platform", "os"):
other_attribute = getattr(other, attribute) other_attribute = getattr(other, attribute)
self_attribute = getattr(self, attribute) self_attribute = getattr(self, attribute)
if strict or self.concrete: if other_attribute and self_attribute != other_attribute:
if other_attribute and self_attribute != other_attribute: return False
return False
else:
if other_attribute and self_attribute and self_attribute != other_attribute:
return False
# Check target return self._target_satisfies(other, strict=True)
return self.target_satisfies(other, strict=strict)
def target_satisfies(self, other, strict): def intersects(self, other: "ArchSpec") -> bool:
need_to_check = ( """Return True if there exists at least one concrete spec that matches both
bool(other.target) if strict or self.concrete else bool(other.target and self.target) self and other, otherwise False.
)
This operation is commutative, and if two specs intersect it means that one
can constrain the other.
Args:
other: spec to be checked for compatibility
"""
other = self._autospec(other)
# Check platform and os
for attribute in ("platform", "os"):
other_attribute = getattr(other, attribute)
self_attribute = getattr(self, attribute)
if other_attribute and self_attribute and self_attribute != other_attribute:
return False
return self._target_satisfies(other, strict=False)
def _target_satisfies(self, other: "ArchSpec", strict: bool) -> bool:
if strict is True:
need_to_check = bool(other.target)
else:
need_to_check = bool(other.target and self.target)
# If there's no need to check we are fine
if not need_to_check: if not need_to_check:
return True return True
# self is not concrete, but other_target is there and strict=True # other_target is there and strict=True
if self.target is None: if self.target is None:
return False return False
return bool(self.target_intersection(other)) return bool(self._target_intersection(other))
def target_constrain(self, other): def _target_constrain(self, other: "ArchSpec") -> bool:
if not other.target_satisfies(self, strict=False): if not other._target_satisfies(self, strict=False):
raise UnsatisfiableArchitectureSpecError(self, other) raise UnsatisfiableArchitectureSpecError(self, other)
if self.target_concrete: if self.target_concrete:
return False return False
elif other.target_concrete: elif other.target_concrete:
self.target = other.target self.target = other.target
return True return True
# Compute the intersection of every combination of ranges in the lists # Compute the intersection of every combination of ranges in the lists
results = self.target_intersection(other) results = self._target_intersection(other)
# Do we need to dedupe here? attribute_str = ",".join(results)
self.target = ",".join(results)
def target_intersection(self, other): if self.target == attribute_str:
return False
self.target = attribute_str
return True
def _target_intersection(self, other):
results = [] results = []
if not self.target or not other.target: if not self.target or not other.target:
@ -464,7 +478,7 @@ def target_intersection(self, other):
results.append("%s:%s" % (n_min, n_max)) results.append("%s:%s" % (n_min, n_max))
return results return results
def constrain(self, other): def constrain(self, other: "ArchSpec") -> bool:
"""Projects all architecture fields that are specified in the given """Projects all architecture fields that are specified in the given
spec onto the instance spec if they're missing from the instance spec onto the instance spec if they're missing from the instance
spec. spec.
@ -479,7 +493,7 @@ def constrain(self, other):
""" """
other = self._autospec(other) other = self._autospec(other)
if not other.satisfies(self): if not other.intersects(self):
raise UnsatisfiableArchitectureSpecError(other, self) raise UnsatisfiableArchitectureSpecError(other, self)
constrained = False constrained = False
@ -489,7 +503,7 @@ def constrain(self, other):
setattr(self, attr, ovalue) setattr(self, attr, ovalue)
constrained = True constrained = True
self.target_constrain(other) constrained |= self._target_constrain(other)
return constrained return constrained
@ -505,7 +519,9 @@ def concrete(self):
@property @property
def target_concrete(self): def target_concrete(self):
"""True if the target is not a range or list.""" """True if the target is not a range or list."""
return ":" not in str(self.target) and "," not in str(self.target) return (
self.target is not None and ":" not in str(self.target) and "," not in str(self.target)
)
def to_dict(self): def to_dict(self):
d = syaml.syaml_dict( d = syaml.syaml_dict(
@ -591,11 +607,31 @@ def _autospec(self, compiler_spec_like):
return compiler_spec_like return compiler_spec_like
return CompilerSpec(compiler_spec_like) return CompilerSpec(compiler_spec_like)
def satisfies(self, other, strict=False): def intersects(self, other: "CompilerSpec") -> bool:
other = self._autospec(other) """Return True if all concrete specs matching self also match other, otherwise False.
return self.name == other.name and self.versions.satisfies(other.versions, strict=strict)
def constrain(self, other): For compiler specs this means that the name of the compiler must be the same for
self and other, and that the versions ranges should intersect.
Args:
other: spec to be satisfied
"""
other = self._autospec(other)
return self.name == other.name and self.versions.intersects(other.versions)
def satisfies(self, other: "CompilerSpec") -> bool:
"""Return True if all concrete specs matching self also match other, otherwise False.
For compiler specs this means that the name of the compiler must be the same for
self and other, and that the version range of self is a subset of that of other.
Args:
other: spec to be satisfied
"""
other = self._autospec(other)
return self.name == other.name and self.versions.satisfies(other.versions)
def constrain(self, other: "CompilerSpec") -> bool:
"""Intersect self's versions with other. """Intersect self's versions with other.
Return whether the CompilerSpec changed. Return whether the CompilerSpec changed.
@ -603,7 +639,7 @@ def constrain(self, other):
other = self._autospec(other) other = self._autospec(other)
# ensure that other will actually constrain this spec. # ensure that other will actually constrain this spec.
if not other.satisfies(self): if not other.intersects(self):
raise UnsatisfiableCompilerSpecError(other, self) raise UnsatisfiableCompilerSpecError(other, self)
return self.versions.intersect(other.versions) return self.versions.intersect(other.versions)
@ -736,24 +772,25 @@ def __init__(self, spec):
super(FlagMap, self).__init__() super(FlagMap, self).__init__()
self.spec = spec self.spec = spec
def satisfies(self, other, strict=False): def satisfies(self, other):
if strict or (self.spec and self.spec._concrete): return all(f in self and self[f] == other[f] for f in other)
return all(f in self and set(self[f]) == set(other[f]) for f in other)
else: def intersects(self, other):
if not all( common_types = set(self) & set(other)
set(self[f]) == set(other[f]) for f in other if (other[f] != [] and f in self) for flag_type in common_types:
): if not self[flag_type] or not other[flag_type]:
# At least one of the two is empty
continue
if self[flag_type] != other[flag_type]:
return False return False
# Check that the propagation values match if not all(
for flag_type in other: f1.propagate == f2.propagate for f1, f2 in zip(self[flag_type], other[flag_type])
if not all( ):
other[flag_type][i].propagate == self[flag_type][i].propagate # At least one propagation flag didn't match
for i in range(len(other[flag_type])) return False
if flag_type in self return True
):
return False
return True
def constrain(self, other): def constrain(self, other):
"""Add all flags in other that aren't in self to self. """Add all flags in other that aren't in self to self.
@ -2611,9 +2648,9 @@ def _old_concretize(self, tests=False, deprecation_warning=True):
# it's possible to build that configuration with Spack # it's possible to build that configuration with Spack
continue continue
for conflict_spec, when_list in x.package_class.conflicts.items(): for conflict_spec, when_list in x.package_class.conflicts.items():
if x.satisfies(conflict_spec, strict=True): if x.satisfies(conflict_spec):
for when_spec, msg in when_list: for when_spec, msg in when_list:
if x.satisfies(when_spec, strict=True): if x.satisfies(when_spec):
when = when_spec.copy() when = when_spec.copy()
when.name = x.name when.name = x.name
matches.append((x, conflict_spec, when, msg)) matches.append((x, conflict_spec, when, msg))
@ -2665,7 +2702,7 @@ def inject_patches_variant(root):
# Add any patches from the package to the spec. # Add any patches from the package to the spec.
patches = [] patches = []
for cond, patch_list in s.package_class.patches.items(): for cond, patch_list in s.package_class.patches.items():
if s.satisfies(cond, strict=True): if s.satisfies(cond):
for patch in patch_list: for patch in patch_list:
patches.append(patch) patches.append(patch)
if patches: if patches:
@ -2683,7 +2720,7 @@ def inject_patches_variant(root):
patches = [] patches = []
for cond, dependency in pkg_deps[dspec.spec.name].items(): for cond, dependency in pkg_deps[dspec.spec.name].items():
for pcond, patch_list in dependency.patches.items(): for pcond, patch_list in dependency.patches.items():
if dspec.parent.satisfies(cond, strict=True) and dspec.spec.satisfies(pcond): if dspec.parent.satisfies(cond) and dspec.spec.satisfies(pcond):
patches.extend(patch_list) patches.extend(patch_list)
if patches: if patches:
all_patches = spec_to_patches.setdefault(id(dspec.spec), []) all_patches = spec_to_patches.setdefault(id(dspec.spec), [])
@ -2941,7 +2978,7 @@ def _evaluate_dependency_conditions(self, name):
# evaluate when specs to figure out constraints on the dependency. # evaluate when specs to figure out constraints on the dependency.
dep = None dep = None
for when_spec, dependency in conditions.items(): for when_spec, dependency in conditions.items():
if self.satisfies(when_spec, strict=True): if self.satisfies(when_spec):
if dep is None: if dep is None:
dep = dp.Dependency(self.name, Spec(name), type=()) dep = dp.Dependency(self.name, Spec(name), type=())
try: try:
@ -2976,7 +3013,7 @@ def _find_provider(self, vdep, provider_index):
# result. # result.
for provider in providers: for provider in providers:
for spec in providers: for spec in providers:
if spec is not provider and provider.satisfies(spec): if spec is not provider and provider.intersects(spec):
providers.remove(spec) providers.remove(spec)
# Can't have multiple providers for the same thing in one spec. # Can't have multiple providers for the same thing in one spec.
if len(providers) > 1: if len(providers) > 1:
@ -3293,9 +3330,15 @@ def update_variant_validate(self, variant_name, values):
pkg_variant.validate_or_raise(self.variants[variant_name], pkg_cls) pkg_variant.validate_or_raise(self.variants[variant_name], pkg_cls)
def constrain(self, other, deps=True): def constrain(self, other, deps=True):
"""Merge the constraints of other with self. """Intersect self with other in-place. Return True if self changed, False otherwise.
Returns True if the spec changed as a result, False if not. Args:
other: constraint to be added to self
deps: if False, constrain only the root node, otherwise constrain dependencies
as well.
Raises:
spack.error.UnsatisfiableSpecError: when self cannot be constrained
""" """
# If we are trying to constrain a concrete spec, either the spec # If we are trying to constrain a concrete spec, either the spec
# already satisfies the constraint (and the method returns False) # already satisfies the constraint (and the method returns False)
@ -3375,6 +3418,9 @@ def constrain(self, other, deps=True):
if deps: if deps:
changed |= self._constrain_dependencies(other) changed |= self._constrain_dependencies(other)
if other.concrete and not self.concrete and other.satisfies(self):
self._finalize_concretization()
return changed return changed
def _constrain_dependencies(self, other): def _constrain_dependencies(self, other):
@ -3387,7 +3433,7 @@ def _constrain_dependencies(self, other):
# TODO: might want more detail than this, e.g. specific deps # TODO: might want more detail than this, e.g. specific deps
# in violation. if this becomes a priority get rid of this # in violation. if this becomes a priority get rid of this
# check and be more specific about what's wrong. # check and be more specific about what's wrong.
if not other.satisfies_dependencies(self): if not other._intersects_dependencies(self):
raise UnsatisfiableDependencySpecError(other, self) raise UnsatisfiableDependencySpecError(other, self)
if any(not d.name for d in other.traverse(root=False)): if any(not d.name for d in other.traverse(root=False)):
@ -3449,58 +3495,49 @@ def _autospec(self, spec_like):
return spec_like return spec_like
return Spec(spec_like) return Spec(spec_like)
def satisfies(self, other, deps=True, strict=False): def intersects(self, other: "Spec", deps: bool = True) -> bool:
"""Determine if this spec satisfies all constraints of another. """Return True if there exists at least one concrete spec that matches both
self and other, otherwise False.
There are two senses for satisfies, depending on the ``strict`` This operation is commutative, and if two specs intersect it means that one
argument. can constrain the other.
* ``strict=False``: the left-hand side and right-hand side have Args:
non-empty intersection. For example ``zlib`` satisfies other: spec to be checked for compatibility
``zlib@1.2.3`` and ``zlib@1.2.3`` satisfies ``zlib``. In this deps: if True check compatibility of dependency nodes too, if False only check root
sense satisfies is a commutative operation: ``x.satisfies(y)``
if and only if ``y.satisfies(x)``.
* ``strict=True``: the left-hand side is a subset of the right-hand
side. For example ``zlib@1.2.3`` satisfies ``zlib``, but ``zlib``
does not satisfy ``zlib@1.2.3``. In this sense satisfies is not
commutative: the left-hand side should be at least as constrained
as the right-hand side.
""" """
other = self._autospec(other) other = self._autospec(other)
# Optimizations for right-hand side concrete: if other.concrete and self.concrete:
# 1. For subset (strict=True) tests this means the left-hand side must return self.dag_hash() == other.dag_hash()
# be the same singleton with identical hash. Notice that package hashes
# can be different for otherwise indistinguishable concrete Spec objects.
# 2. For non-empty intersection (strict=False) we only have a fast path
# when the left-hand side is also concrete.
if other.concrete:
if strict:
return self.concrete and self.dag_hash() == other.dag_hash()
elif self.concrete:
return self.dag_hash() == other.dag_hash()
# If the names are different, we need to consider virtuals # If the names are different, we need to consider virtuals
if self.name != other.name and self.name and other.name: if self.name != other.name and self.name and other.name:
# A concrete provider can satisfy a virtual dependency. if self.virtual and other.virtual:
if not self.virtual and other.virtual: # Two virtual specs intersect only if there are providers for both
lhs = spack.repo.path.providers_for(str(self))
rhs = spack.repo.path.providers_for(str(other))
intersection = [s for s in lhs if any(s.intersects(z) for z in rhs)]
return bool(intersection)
# A provider can satisfy a virtual dependency.
elif self.virtual or other.virtual:
virtual_spec, non_virtual_spec = (self, other) if self.virtual else (other, self)
try: try:
# Here we might get an abstract spec # Here we might get an abstract spec
pkg_cls = spack.repo.path.get_pkg_class(self.fullname) pkg_cls = spack.repo.path.get_pkg_class(non_virtual_spec.fullname)
pkg = pkg_cls(self) pkg = pkg_cls(non_virtual_spec)
except spack.repo.UnknownEntityError: except spack.repo.UnknownEntityError:
# If we can't get package info on this spec, don't treat # If we can't get package info on this spec, don't treat
# it as a provider of this vdep. # it as a provider of this vdep.
return False return False
if pkg.provides(other.name): if pkg.provides(virtual_spec.name):
for provided, when_specs in pkg.provided.items(): for provided, when_specs in pkg.provided.items():
if any( if any(
self.satisfies(when, deps=False, strict=strict) for when in when_specs non_virtual_spec.intersects(when, deps=False) for when in when_specs
): ):
if provided.satisfies(other): if provided.intersects(virtual_spec):
return True return True
return False return False
@ -3511,75 +3548,41 @@ def satisfies(self, other, deps=True, strict=False):
and self.namespace != other.namespace and self.namespace != other.namespace
): ):
return False return False
if self.versions and other.versions: if self.versions and other.versions:
if not self.versions.satisfies(other.versions, strict=strict): if not self.versions.intersects(other.versions):
return False return False
elif strict and (self.versions or other.versions):
return False
# None indicates no constraints when not strict.
if self.compiler and other.compiler: if self.compiler and other.compiler:
if not self.compiler.satisfies(other.compiler, strict=strict): if not self.compiler.intersects(other.compiler):
return False return False
elif strict and (other.compiler and not self.compiler):
if not self.variants.intersects(other.variants):
return False return False
var_strict = strict
if (not self.name) or (not other.name):
var_strict = True
if not self.variants.satisfies(other.variants, strict=var_strict):
return False
# Architecture satisfaction is currently just string equality.
# If not strict, None means unconstrained.
if self.architecture and other.architecture: if self.architecture and other.architecture:
if not self.architecture.satisfies(other.architecture, strict): if not self.architecture.intersects(other.architecture):
return False return False
elif strict and (other.architecture and not self.architecture):
return False
if not self.compiler_flags.satisfies(other.compiler_flags, strict=strict): if not self.compiler_flags.intersects(other.compiler_flags):
return False return False
# If we need to descend into dependencies, do it, otherwise we're done. # If we need to descend into dependencies, do it, otherwise we're done.
if deps: if deps:
deps_strict = strict return self._intersects_dependencies(other)
if self._concrete and not other.name:
# We're dealing with existing specs
deps_strict = True
return self.satisfies_dependencies(other, strict=deps_strict)
else: else:
return True return True
def satisfies_dependencies(self, other, strict=False): def _intersects_dependencies(self, other):
"""
This checks constraints on common dependencies against each other.
"""
other = self._autospec(other) other = self._autospec(other)
# If there are no constraints to satisfy, we're done. if not other._dependencies or not self._dependencies:
if not other._dependencies: # one spec *could* eventually satisfy the other
return True
if strict:
# if we have no dependencies, we can't satisfy any constraints.
if not self._dependencies:
return False
# use list to prevent double-iteration
selfdeps = list(self.traverse(root=False))
otherdeps = list(other.traverse(root=False))
if not all(any(d.satisfies(dep, strict=True) for d in selfdeps) for dep in otherdeps):
return False
elif not self._dependencies:
# if not strict, this spec *could* eventually satisfy the
# constraints on other.
return True return True
# Handle first-order constraints directly # Handle first-order constraints directly
for name in self.common_dependencies(other): for name in self.common_dependencies(other):
if not self[name].satisfies(other[name], deps=False): if not self[name].intersects(other[name], deps=False):
return False return False
# For virtual dependencies, we need to dig a little deeper. # For virtual dependencies, we need to dig a little deeper.
@ -3607,6 +3610,89 @@ def satisfies_dependencies(self, other, strict=False):
return True return True
def satisfies(self, other: "Spec", deps: bool = True) -> bool:
"""Return True if all concrete specs matching self also match other, otherwise False.
Args:
other: spec to be satisfied
deps: if True descend to dependencies, otherwise only check root node
"""
other = self._autospec(other)
if other.concrete:
# The left-hand side must be the same singleton with identical hash. Notice that
# package hashes can be different for otherwise indistinguishable concrete Spec
# objects.
return self.concrete and self.dag_hash() == other.dag_hash()
# If the names are different, we need to consider virtuals
if self.name != other.name and self.name and other.name:
# A concrete provider can satisfy a virtual dependency.
if not self.virtual and other.virtual:
try:
# Here we might get an abstract spec
pkg_cls = spack.repo.path.get_pkg_class(self.fullname)
pkg = pkg_cls(self)
except spack.repo.UnknownEntityError:
# If we can't get package info on this spec, don't treat
# it as a provider of this vdep.
return False
if pkg.provides(other.name):
for provided, when_specs in pkg.provided.items():
if any(self.satisfies(when, deps=False) for when in when_specs):
if provided.intersects(other):
return True
return False
# namespaces either match, or other doesn't require one.
if (
other.namespace is not None
and self.namespace is not None
and self.namespace != other.namespace
):
return False
if not self.versions.satisfies(other.versions):
return False
if self.compiler and other.compiler:
if not self.compiler.satisfies(other.compiler):
return False
elif other.compiler and not self.compiler:
return False
if not self.variants.satisfies(other.variants):
return False
if self.architecture and other.architecture:
if not self.architecture.satisfies(other.architecture):
return False
elif other.architecture and not self.architecture:
return False
if not self.compiler_flags.satisfies(other.compiler_flags):
return False
# If we need to descend into dependencies, do it, otherwise we're done.
if not deps:
return True
# If there are no constraints to satisfy, we're done.
if not other._dependencies:
return True
# If we have no dependencies, we can't satisfy any constraints.
if not self._dependencies:
return False
# If we arrived here, then rhs is abstract. At the moment we don't care about the edge
# structure of an abstract DAG - hence the deps=False parameter.
return all(
any(lhs.satisfies(rhs, deps=False) for lhs in self.traverse(root=False))
for rhs in other.traverse(root=False)
)
def virtual_dependencies(self): def virtual_dependencies(self):
"""Return list of any virtual deps in this spec.""" """Return list of any virtual deps in this spec."""
return [spec for spec in self.traverse() if spec.virtual] return [spec for spec in self.traverse() if spec.virtual]

View File

@ -183,7 +183,7 @@ def test_optimization_flags_with_custom_versions(
def test_satisfy_strict_constraint_when_not_concrete(architecture_tuple, constraint_tuple): def test_satisfy_strict_constraint_when_not_concrete(architecture_tuple, constraint_tuple):
architecture = spack.spec.ArchSpec(architecture_tuple) architecture = spack.spec.ArchSpec(architecture_tuple)
constraint = spack.spec.ArchSpec(constraint_tuple) constraint = spack.spec.ArchSpec(constraint_tuple)
assert not architecture.satisfies(constraint, strict=True) assert not architecture.satisfies(constraint)
@pytest.mark.parametrize( @pytest.mark.parametrize(

View File

@ -82,8 +82,8 @@ def test_change_match_spec():
change("--match-spec", "mpileaks@2.2", "mpileaks@2.3") change("--match-spec", "mpileaks@2.2", "mpileaks@2.3")
assert not any(x.satisfies("mpileaks@2.2") for x in e.user_specs) assert not any(x.intersects("mpileaks@2.2") for x in e.user_specs)
assert any(x.satisfies("mpileaks@2.3") for x in e.user_specs) assert any(x.intersects("mpileaks@2.3") for x in e.user_specs)
def test_change_multiple_matches(): def test_change_multiple_matches():
@ -97,8 +97,8 @@ def test_change_multiple_matches():
change("--match-spec", "mpileaks", "-a", "mpileaks%gcc") change("--match-spec", "mpileaks", "-a", "mpileaks%gcc")
assert all(x.satisfies("%gcc") for x in e.user_specs if x.name == "mpileaks") assert all(x.intersects("%gcc") for x in e.user_specs if x.name == "mpileaks")
assert any(x.satisfies("%clang") for x in e.user_specs if x.name == "libelf") assert any(x.intersects("%clang") for x in e.user_specs if x.name == "libelf")
def test_env_add_virtual(): def test_env_add_virtual():
@ -111,7 +111,7 @@ def test_env_add_virtual():
hashes = e.concretized_order hashes = e.concretized_order
assert len(hashes) == 1 assert len(hashes) == 1
spec = e.specs_by_hash[hashes[0]] spec = e.specs_by_hash[hashes[0]]
assert spec.satisfies("mpi") assert spec.intersects("mpi")
def test_env_add_nonexistant_fails(): def test_env_add_nonexistant_fails():
@ -687,7 +687,7 @@ def test_env_with_config():
with e: with e:
e.concretize() e.concretize()
assert any(x.satisfies("mpileaks@2.2") for x in e._get_environment_specs()) assert any(x.intersects("mpileaks@2.2") for x in e._get_environment_specs())
def test_with_config_bad_include(): def test_with_config_bad_include():
@ -1630,9 +1630,9 @@ def test_stack_concretize_extraneous_deps(tmpdir, config, mock_packages):
assert concrete.concrete assert concrete.concrete
assert not user.concrete assert not user.concrete
if user.name == "libelf": if user.name == "libelf":
assert not concrete.satisfies("^mpi", strict=True) assert not concrete.satisfies("^mpi")
elif user.name == "mpileaks": elif user.name == "mpileaks":
assert concrete.satisfies("^mpi", strict=True) assert concrete.satisfies("^mpi")
def test_stack_concretize_extraneous_variants(tmpdir, config, mock_packages): def test_stack_concretize_extraneous_variants(tmpdir, config, mock_packages):

View File

@ -276,7 +276,7 @@ def test_install_commit(mock_git_version_info, install_mockery, mock_packages, m
assert filename in installed assert filename in installed
with open(spec.prefix.bin.join(filename), "r") as f: with open(spec.prefix.bin.join(filename), "r") as f:
content = f.read().strip() content = f.read().strip()
assert content == "[]" # contents are weird for another test assert content == "[0]" # contents are weird for another test
def test_install_overwrite_multiple( def test_install_overwrite_multiple(

View File

@ -293,11 +293,11 @@ def test_concretize_with_provides_when(self):
we ask for some advanced version. we ask for some advanced version.
""" """
repo = spack.repo.path repo = spack.repo.path
assert not any(s.satisfies("mpich2@:1.0") for s in repo.providers_for("mpi@2.1")) assert not any(s.intersects("mpich2@:1.0") for s in repo.providers_for("mpi@2.1"))
assert not any(s.satisfies("mpich2@:1.1") for s in repo.providers_for("mpi@2.2")) assert not any(s.intersects("mpich2@:1.1") for s in repo.providers_for("mpi@2.2"))
assert not any(s.satisfies("mpich@:1") for s in repo.providers_for("mpi@2")) assert not any(s.intersects("mpich@:1") for s in repo.providers_for("mpi@2"))
assert not any(s.satisfies("mpich@:1") for s in repo.providers_for("mpi@3")) assert not any(s.intersects("mpich@:1") for s in repo.providers_for("mpi@3"))
assert not any(s.satisfies("mpich2") for s in repo.providers_for("mpi@3")) assert not any(s.intersects("mpich2") for s in repo.providers_for("mpi@3"))
def test_provides_handles_multiple_providers_of_same_version(self): def test_provides_handles_multiple_providers_of_same_version(self):
""" """ """ """
@ -1462,7 +1462,7 @@ def test_concrete_specs_are_not_modified_on_reuse(
with spack.config.override("concretizer:reuse", True): with spack.config.override("concretizer:reuse", True):
s = spack.spec.Spec(spec_str).concretized() s = spack.spec.Spec(spec_str).concretized()
assert s.installed is expect_installed assert s.installed is expect_installed
assert s.satisfies(spec_str, strict=True) assert s.satisfies(spec_str)
@pytest.mark.regression("26721,19736") @pytest.mark.regression("26721,19736")
def test_sticky_variant_in_package(self): def test_sticky_variant_in_package(self):

View File

@ -157,7 +157,9 @@ def latest_commit():
return git("rev-list", "-n1", "HEAD", output=str, error=str).strip() return git("rev-list", "-n1", "HEAD", output=str, error=str).strip()
# Add two commits on main branch # Add two commits on main branch
write_file(filename, "[]")
# A commit without a previous version counts as "0"
write_file(filename, "[0]")
git("add", filename) git("add", filename)
commit("first commit") commit("first commit")
commits.append(latest_commit()) commits.append(latest_commit())

View File

@ -86,13 +86,13 @@ def test_env_change_spec_in_definition(tmpdir, mock_packages, config, mutable_mo
e.concretize() e.concretize()
e.write() e.write()
assert any(x.satisfies("mpileaks@2.1%gcc") for x in e.user_specs) assert any(x.intersects("mpileaks@2.1%gcc") for x in e.user_specs)
e.change_existing_spec(spack.spec.Spec("mpileaks@2.2"), list_name="desired_specs") e.change_existing_spec(spack.spec.Spec("mpileaks@2.2"), list_name="desired_specs")
e.write() e.write()
assert any(x.satisfies("mpileaks@2.2%gcc") for x in e.user_specs) assert any(x.intersects("mpileaks@2.2%gcc") for x in e.user_specs)
assert not any(x.satisfies("mpileaks@2.1%gcc") for x in e.user_specs) assert not any(x.intersects("mpileaks@2.1%gcc") for x in e.user_specs)
def test_env_change_spec_in_matrix_raises_error( def test_env_change_spec_in_matrix_raises_error(

View File

@ -16,6 +16,12 @@
from spack.version import VersionChecksumError from spack.version import VersionChecksumError
def pkg_factory(name):
"""Return a package object tied to an abstract spec"""
pkg_cls = spack.repo.path.get_pkg_class(name)
return pkg_cls(Spec(name))
@pytest.mark.usefixtures("config", "mock_packages") @pytest.mark.usefixtures("config", "mock_packages")
class TestPackage(object): class TestPackage(object):
def test_load_package(self): def test_load_package(self):
@ -184,8 +190,7 @@ def test_url_for_version_with_only_overrides_with_gaps(mock_packages, config):
) )
def test_fetcher_url(spec_str, expected_type, expected_url): def test_fetcher_url(spec_str, expected_type, expected_url):
"""Ensure that top-level git attribute can be used as a default.""" """Ensure that top-level git attribute can be used as a default."""
s = Spec(spec_str).concretized() fetcher = spack.fetch_strategy.for_package_version(pkg_factory(spec_str), "1.0")
fetcher = spack.fetch_strategy.for_package_version(s.package, "1.0")
assert isinstance(fetcher, expected_type) assert isinstance(fetcher, expected_type)
assert fetcher.url == expected_url assert fetcher.url == expected_url
@ -204,8 +209,7 @@ def test_fetcher_url(spec_str, expected_type, expected_url):
def test_fetcher_errors(spec_str, version_str, exception_type): def test_fetcher_errors(spec_str, version_str, exception_type):
"""Verify that we can't extrapolate versions for non-URL packages.""" """Verify that we can't extrapolate versions for non-URL packages."""
with pytest.raises(exception_type): with pytest.raises(exception_type):
s = Spec(spec_str).concretized() spack.fetch_strategy.for_package_version(pkg_factory(spec_str), version_str)
spack.fetch_strategy.for_package_version(s.package, version_str)
@pytest.mark.usefixtures("mock_packages", "config") @pytest.mark.usefixtures("mock_packages", "config")
@ -220,11 +224,12 @@ def test_fetcher_errors(spec_str, version_str, exception_type):
) )
def test_git_url_top_level_url_versions(version_str, expected_url, digest): def test_git_url_top_level_url_versions(version_str, expected_url, digest):
"""Test URL fetch strategy inference when url is specified with git.""" """Test URL fetch strategy inference when url is specified with git."""
s = Spec("git-url-top-level").concretized()
# leading 62 zeros of sha256 hash # leading 62 zeros of sha256 hash
leading_zeros = "0" * 62 leading_zeros = "0" * 62
fetcher = spack.fetch_strategy.for_package_version(s.package, version_str) fetcher = spack.fetch_strategy.for_package_version(
pkg_factory("git-url-top-level"), version_str
)
assert isinstance(fetcher, spack.fetch_strategy.URLFetchStrategy) assert isinstance(fetcher, spack.fetch_strategy.URLFetchStrategy)
assert fetcher.url == expected_url assert fetcher.url == expected_url
assert fetcher.digest == leading_zeros + digest assert fetcher.digest == leading_zeros + digest
@ -245,9 +250,9 @@ def test_git_url_top_level_url_versions(version_str, expected_url, digest):
) )
def test_git_url_top_level_git_versions(version_str, tag, commit, branch): def test_git_url_top_level_git_versions(version_str, tag, commit, branch):
"""Test git fetch strategy inference when url is specified with git.""" """Test git fetch strategy inference when url is specified with git."""
s = Spec("git-url-top-level").concretized() fetcher = spack.fetch_strategy.for_package_version(
pkg_factory("git-url-top-level"), version_str
fetcher = spack.fetch_strategy.for_package_version(s.package, version_str) )
assert isinstance(fetcher, spack.fetch_strategy.GitFetchStrategy) assert isinstance(fetcher, spack.fetch_strategy.GitFetchStrategy)
assert fetcher.url == "https://example.com/some/git/repo" assert fetcher.url == "https://example.com/some/git/repo"
assert fetcher.tag == tag assert fetcher.tag == tag
@ -259,9 +264,8 @@ def test_git_url_top_level_git_versions(version_str, tag, commit, branch):
@pytest.mark.parametrize("version_str", ["1.0", "1.1", "1.2", "1.3"]) @pytest.mark.parametrize("version_str", ["1.0", "1.1", "1.2", "1.3"])
def test_git_url_top_level_conflicts(version_str): def test_git_url_top_level_conflicts(version_str):
"""Test git fetch strategy inference when url is specified with git.""" """Test git fetch strategy inference when url is specified with git."""
s = Spec("git-url-top-level").concretized()
with pytest.raises(spack.fetch_strategy.FetcherConflict): with pytest.raises(spack.fetch_strategy.FetcherConflict):
spack.fetch_strategy.for_package_version(s.package, version_str) spack.fetch_strategy.for_package_version(pkg_factory("git-url-top-level"), version_str)
def test_rpath_args(mutable_database): def test_rpath_args(mutable_database):
@ -301,9 +305,8 @@ def test_bundle_patch_directive(mock_directive_bundle, clear_directive_functions
) )
def test_fetch_options(version_str, digest_end, extra_options): def test_fetch_options(version_str, digest_end, extra_options):
"""Test fetch options inference.""" """Test fetch options inference."""
s = Spec("fetch-options").concretized()
leading_zeros = "000000000000000000000000000000" leading_zeros = "000000000000000000000000000000"
fetcher = spack.fetch_strategy.for_package_version(s.package, version_str) fetcher = spack.fetch_strategy.for_package_version(pkg_factory("fetch-options"), version_str)
assert isinstance(fetcher, spack.fetch_strategy.URLFetchStrategy) assert isinstance(fetcher, spack.fetch_strategy.URLFetchStrategy)
assert fetcher.digest == leading_zeros + digest_end assert fetcher.digest == leading_zeros + digest_end
assert fetcher.extra_options == extra_options assert fetcher.extra_options == extra_options

File diff suppressed because it is too large Load Diff

View File

@ -1040,18 +1040,44 @@ def test_compare_abstract_specs():
assert a <= b or b < a assert a <= b or b < a
def test_git_ref_spec_equivalences(mock_packages): @pytest.mark.parametrize(
spec_hash_fmt = "develop-branch-version@git.{hash}=develop" "lhs_str,rhs_str,expected",
s1 = SpecParser(spec_hash_fmt.format(hash="a" * 40)).next_spec() [
s2 = SpecParser(spec_hash_fmt.format(hash="b" * 40)).next_spec() # Git shasum vs generic develop
s3 = SpecParser("develop-branch-version@git.0.2.15=develop").next_spec() (
s_no_git = SpecParser("develop-branch-version@develop").next_spec() f"develop-branch-version@git.{'a' * 40}=develop",
"develop-branch-version@develop",
(True, True, False),
),
# Two different shasums
(
f"develop-branch-version@git.{'a' * 40}=develop",
f"develop-branch-version@git.{'b' * 40}=develop",
(False, False, False),
),
# Git shasum vs. git tag
(
f"develop-branch-version@git.{'a' * 40}=develop",
"develop-branch-version@git.0.2.15=develop",
(False, False, False),
),
# Git tag vs. generic develop
(
"develop-branch-version@git.0.2.15=develop",
"develop-branch-version@develop",
(True, True, False),
),
],
)
def test_git_ref_spec_equivalences(mock_packages, lhs_str, rhs_str, expected):
lhs = SpecParser(lhs_str).next_spec()
rhs = SpecParser(rhs_str).next_spec()
intersect, lhs_sat_rhs, rhs_sat_lhs = expected
assert s1.satisfies(s_no_git) assert lhs.intersects(rhs) is intersect
assert s2.satisfies(s_no_git) assert rhs.intersects(lhs) is intersect
assert not s_no_git.satisfies(s1) assert lhs.satisfies(rhs) is lhs_sat_rhs
assert not s2.satisfies(s1) assert rhs.satisfies(lhs) is rhs_sat_lhs
assert not s3.satisfies(s1)
@pytest.mark.regression("32471") @pytest.mark.regression("32471")

View File

@ -638,11 +638,11 @@ def test_satisfies_and_constrain(self):
b["foobar"] = SingleValuedVariant("foobar", "fee") b["foobar"] = SingleValuedVariant("foobar", "fee")
b["shared"] = BoolValuedVariant("shared", True) b["shared"] = BoolValuedVariant("shared", True)
assert not a.satisfies(b) assert a.intersects(b)
assert b.satisfies(a) assert b.intersects(a)
assert not a.satisfies(b, strict=True) assert not a.satisfies(b)
assert not b.satisfies(a, strict=True) assert not b.satisfies(a)
# foo=bar,baz foobar=fee feebar=foo shared=True # foo=bar,baz foobar=fee feebar=foo shared=True
c = VariantMap(None) c = VariantMap(None)

View File

@ -600,6 +600,7 @@ def test_versions_from_git(git, mock_git_version_info, monkeypatch, mock_package
with working_dir(repo_path): with working_dir(repo_path):
git("checkout", commit) git("checkout", commit)
with open(os.path.join(repo_path, filename), "r") as f: with open(os.path.join(repo_path, filename), "r") as f:
expected = f.read() expected = f.read()
@ -607,30 +608,38 @@ def test_versions_from_git(git, mock_git_version_info, monkeypatch, mock_package
@pytest.mark.skipif(sys.platform == "win32", reason="Not supported on Windows (yet)") @pytest.mark.skipif(sys.platform == "win32", reason="Not supported on Windows (yet)")
def test_git_hash_comparisons(mock_git_version_info, install_mockery, mock_packages, monkeypatch): @pytest.mark.parametrize(
"commit_idx,expected_satisfies,expected_not_satisfies",
[
# Spec based on earliest commit
(-1, ("@:0",), ("@1.0",)),
# Spec based on second commit (same as version 1.0)
(-2, ("@1.0",), ("@1.1:",)),
# Spec based on 4th commit (in timestamp order)
(-4, ("@1.1", "@1.0:1.2"), tuple()),
],
)
def test_git_hash_comparisons(
mock_git_version_info,
install_mockery,
mock_packages,
monkeypatch,
commit_idx,
expected_satisfies,
expected_not_satisfies,
):
"""Check that hashes compare properly to versions""" """Check that hashes compare properly to versions"""
repo_path, filename, commits = mock_git_version_info repo_path, filename, commits = mock_git_version_info
monkeypatch.setattr( monkeypatch.setattr(
spack.package_base.PackageBase, "git", "file://%s" % repo_path, raising=False spack.package_base.PackageBase, "git", "file://%s" % repo_path, raising=False
) )
# Spec based on earliest commit spec = spack.spec.Spec(f"git-test-commit@{commits[commit_idx]}").concretized()
spec0 = spack.spec.Spec("git-test-commit@%s" % commits[-1]) for item in expected_satisfies:
spec0.concretize() assert spec.satisfies(item)
assert spec0.satisfies("@:0")
assert not spec0.satisfies("@1.0")
# Spec based on second commit (same as version 1.0) for item in expected_not_satisfies:
spec1 = spack.spec.Spec("git-test-commit@%s" % commits[-2]) assert not spec.satisfies(item)
spec1.concretize()
assert spec1.satisfies("@1.0")
assert not spec1.satisfies("@1.1:")
# Spec based on 4th commit (in timestamp order)
spec4 = spack.spec.Spec("git-test-commit@%s" % commits[-4])
spec4.concretize()
assert spec4.satisfies("@1.1")
assert spec4.satisfies("@1.0:1.2")
@pytest.mark.skipif(sys.platform == "win32", reason="Not supported on Windows (yet)") @pytest.mark.skipif(sys.platform == "win32", reason="Not supported on Windows (yet)")
@ -738,3 +747,27 @@ def test_git_ref_can_be_assigned_a_version(vstring, eq_vstring, is_commit):
assert v.is_ref assert v.is_ref
assert not v._ref_lookup assert not v._ref_lookup
assert v_equivalent.version == v.ref_version assert v_equivalent.version == v.ref_version
@pytest.mark.parametrize(
"lhs_str,rhs_str,expected",
[
# VersionBase
("4.7.3", "4.7.3", (True, True, True)),
("4.7.3", "4.7", (True, True, False)),
("4.7.3", "4", (True, True, False)),
("4.7.3", "4.8", (False, False, False)),
# GitVersion
(f"git.{'a' * 40}=develop", "develop", (True, True, False)),
(f"git.{'a' * 40}=develop", f"git.{'a' * 40}=develop", (True, True, True)),
(f"git.{'a' * 40}=develop", f"git.{'b' * 40}=develop", (False, False, False)),
],
)
def test_version_intersects_satisfies_semantic(lhs_str, rhs_str, expected):
lhs, rhs = ver(lhs_str), ver(rhs_str)
intersect, lhs_sat_rhs, rhs_sat_lhs = expected
assert lhs.intersects(rhs) is intersect
assert lhs.intersects(rhs) is rhs.intersects(lhs)
assert lhs.satisfies(rhs) is lhs_sat_rhs
assert rhs.satisfies(lhs) is rhs_sat_lhs

View File

@ -178,7 +178,7 @@ def visit_FunctionDef(self, func):
conditions.append(None) conditions.append(None)
else: else:
# Check statically whether spec satisfies the condition # Check statically whether spec satisfies the condition
conditions.append(self.spec.satisfies(cond_spec, strict=True)) conditions.append(self.spec.satisfies(cond_spec))
except AttributeError: except AttributeError:
# In this case the condition for the 'when' decorator is # In this case the condition for the 'when' decorator is

View File

@ -203,8 +203,7 @@ def implicit_variant_conversion(method):
@functools.wraps(method) @functools.wraps(method)
def convert(self, other): def convert(self, other):
# We don't care if types are different as long as I can convert # We don't care if types are different as long as I can convert other to type(self)
# other to type(self)
try: try:
other = type(self)(other.name, other._original_value) other = type(self)(other.name, other._original_value)
except (error.SpecError, ValueError): except (error.SpecError, ValueError):
@ -349,7 +348,12 @@ def satisfies(self, other):
# (`foo=bar` will never satisfy `baz=bar`) # (`foo=bar` will never satisfy `baz=bar`)
return other.name == self.name return other.name == self.name
@implicit_variant_conversion def intersects(self, other):
"""Returns True if there are variant matching both self and other, False otherwise."""
if isinstance(other, (SingleValuedVariant, BoolValuedVariant)):
return other.intersects(self)
return other.name == self.name
def compatible(self, other): def compatible(self, other):
"""Returns True if self and other are compatible, False otherwise. """Returns True if self and other are compatible, False otherwise.
@ -364,7 +368,7 @@ def compatible(self, other):
""" """
# If names are different then `self` is not compatible with `other` # If names are different then `self` is not compatible with `other`
# (`foo=bar` is incompatible with `baz=bar`) # (`foo=bar` is incompatible with `baz=bar`)
return other.name == self.name return self.intersects(other)
@implicit_variant_conversion @implicit_variant_conversion
def constrain(self, other): def constrain(self, other):
@ -475,6 +479,9 @@ def satisfies(self, other):
self.value == other.value or other.value == "*" or self.value == "*" self.value == other.value or other.value == "*" or self.value == "*"
) )
def intersects(self, other):
return self.satisfies(other)
def compatible(self, other): def compatible(self, other):
return self.satisfies(other) return self.satisfies(other)
@ -575,29 +582,11 @@ def substitute(self, vspec):
# Set the item # Set the item
super(VariantMap, self).__setitem__(vspec.name, vspec) super(VariantMap, self).__setitem__(vspec.name, vspec)
def satisfies(self, other, strict=False): def satisfies(self, other):
"""Returns True if this VariantMap is more constrained than other, return all(k in self and self[k].satisfies(other[k]) for k in other)
False otherwise.
Args: def intersects(self, other):
other (VariantMap): VariantMap instance to satisfy return all(self[k].intersects(other[k]) for k in other if k in self)
strict (bool): if True return False if a key is in other and
not in self, otherwise discard that key and proceed with
evaluation
Returns:
bool: True or False
"""
to_be_checked = [k for k in other]
strict_or_concrete = strict
if self.spec is not None:
strict_or_concrete |= self.spec._concrete
if not strict_or_concrete:
to_be_checked = filter(lambda x: x in self, to_be_checked)
return all(k in self and self[k].satisfies(other[k]) for k in to_be_checked)
def constrain(self, other): def constrain(self, other):
"""Add all variants in other that aren't in self to self. Also """Add all variants in other that aren't in self to self. Also

View File

@ -133,6 +133,9 @@ def __hash__(self):
def __str__(self): def __str__(self):
return self.data return self.data
def __repr__(self):
return f"VersionStrComponent('{self.data}')"
def __eq__(self, other): def __eq__(self, other):
if isinstance(other, VersionStrComponent): if isinstance(other, VersionStrComponent):
return self.data == other.data return self.data == other.data
@ -242,9 +245,9 @@ def __init__(self, string: str) -> None:
if string and not VALID_VERSION.match(string): if string and not VALID_VERSION.match(string):
raise ValueError("Bad characters in version string: %s" % string) raise ValueError("Bad characters in version string: %s" % string)
self.separators, self.version = self._generate_seperators_and_components(string) self.separators, self.version = self._generate_separators_and_components(string)
def _generate_seperators_and_components(self, string): def _generate_separators_and_components(self, string):
segments = SEGMENT_REGEX.findall(string) segments = SEGMENT_REGEX.findall(string)
components = tuple(int(m[0]) if m[0] else VersionStrComponent(m[1]) for m in segments) components = tuple(int(m[0]) if m[0] else VersionStrComponent(m[1]) for m in segments)
separators = tuple(m[2] for m in segments) separators = tuple(m[2] for m in segments)
@ -348,11 +351,26 @@ def isdevelop(self):
return False return False
@coerced @coerced
def satisfies(self, other): def intersects(self, other: "VersionBase") -> bool:
"""A Version 'satisfies' another if it is at least as specific and has """Return True if self intersects with other, False otherwise.
a common prefix. e.g., we want gcc@4.7.3 to satisfy a request for
gcc@4.7 so that when a user asks to build with gcc@4.7, we can find Two versions intersect if one can be constrained by the other. For instance
a suitable compiler. @4.7 and @4.7.3 intersect (the intersection being @4.7.3).
Arg:
other: version to be checked for intersection
"""
n = min(len(self.version), len(other.version))
return self.version[:n] == other.version[:n]
@coerced
def satisfies(self, other: "VersionBase") -> bool:
"""Return True if self is at least as specific and share a common prefix with other.
For instance, @4.7.3 satisfies @4.7 but not vice-versa.
Arg:
other: version to be checked for intersection
""" """
nself = len(self.version) nself = len(self.version)
nother = len(other.version) nother = len(other.version)
@ -466,9 +484,8 @@ def is_predecessor(self, other):
def is_successor(self, other): def is_successor(self, other):
return other.is_predecessor(self) return other.is_predecessor(self)
@coerced
def overlaps(self, other): def overlaps(self, other):
return self in other or other in self return self.intersects(other)
@coerced @coerced
def union(self, other): def union(self, other):
@ -548,7 +565,7 @@ def __init__(self, string):
if "=" in pruned_string: if "=" in pruned_string:
self.ref, self.ref_version_str = pruned_string.split("=") self.ref, self.ref_version_str = pruned_string.split("=")
_, self.ref_version = self._generate_seperators_and_components(self.ref_version_str) _, self.ref_version = self._generate_separators_and_components(self.ref_version_str)
self.user_supplied_reference = True self.user_supplied_reference = True
else: else:
self.ref = pruned_string self.ref = pruned_string
@ -578,6 +595,9 @@ def _cmp(self, other_lookups=None):
if ref_info: if ref_info:
prev_version, distance = ref_info prev_version, distance = ref_info
if prev_version is None:
prev_version = "0"
# Extend previous version by empty component and distance # Extend previous version by empty component and distance
# If commit is exactly a known version, no distance suffix # If commit is exactly a known version, no distance suffix
prev_tuple = VersionBase(prev_version).version if prev_version else () prev_tuple = VersionBase(prev_version).version if prev_version else ()
@ -587,14 +607,22 @@ def _cmp(self, other_lookups=None):
return self.version return self.version
@coerced
def intersects(self, other):
# If they are both references, they must match exactly
if self.is_ref and other.is_ref:
return self.version == other.version
# Otherwise the ref_version of the reference must intersect with the version of the other
v1 = self.ref_version if self.is_ref else self.version
v2 = other.ref_version if other.is_ref else other.version
n = min(len(v1), len(v2))
return v1[:n] == v2[:n]
@coerced @coerced
def satisfies(self, other): def satisfies(self, other):
"""A Version 'satisfies' another if it is at least as specific and has # In the case of two GitVersions we require the ref_versions
a common prefix. e.g., we want gcc@4.7.3 to satisfy a request for # to satisfy one another and the versions to be an exact match.
gcc@4.7 so that when a user asks to build with gcc@4.7, we can find
a suitable compiler. In the case of two GitVersions we require the ref_versions
to satisfy one another and the versions to be an exact match.
"""
self_cmp = self._cmp(other.ref_lookup) self_cmp = self._cmp(other.ref_lookup)
other_cmp = other._cmp(self.ref_lookup) other_cmp = other._cmp(self.ref_lookup)
@ -731,7 +759,7 @@ def __init__(self, start, end):
# means the range [1.2.3, 1.3), which is non-empty. # means the range [1.2.3, 1.3), which is non-empty.
min_len = min(len(start), len(end)) min_len = min(len(start), len(end))
if end.up_to(min_len) < start.up_to(min_len): if end.up_to(min_len) < start.up_to(min_len):
raise ValueError("Invalid Version range: %s" % self) raise ValueError(f"Invalid Version range: {self}")
def lowest(self): def lowest(self):
return self.start return self.start
@ -805,26 +833,32 @@ def __contains__(self, other):
) )
return in_upper return in_upper
@coerced def intersects(self, other) -> bool:
def satisfies(self, other): """Return two if two version ranges overlap with each other, False otherwise.
"""
x.satisfies(y) in general means that x and y have a
non-zero intersection. For VersionRange this means they overlap.
`satisfies` is a commutative binary operator, meaning that This is a commutative operation.
x.satisfies(y) if and only if y.satisfies(x).
Note: in some cases we have the keyword x.satisfies(y, strict=True) Examples:
to mean strict set inclusion, which is not commutative. However, this
lacks in VersionRange for unknown reasons.
Examples
- 1:3 satisfies 2:4, as their intersection is 2:3. - 1:3 satisfies 2:4, as their intersection is 2:3.
- 1:2 does not satisfy 3:4, as their intersection is empty. - 1:2 does not satisfy 3:4, as their intersection is empty.
- 4.5:4.7 satisfies 4.7.2:4.8, as their intersection is 4.7.2:4.7 - 4.5:4.7 satisfies 4.7.2:4.8, as their intersection is 4.7.2:4.7
Args:
other: version range to be checked for intersection
""" """
return self.overlaps(other) return self.overlaps(other)
@coerced
def satisfies(self, other):
"""A version range satisfies another if it is a subset of the other.
Examples:
- 1:2 does not satisfy 3:4, as their intersection is empty.
- 1:3 does not satisfy 2:4, as they overlap but neither is a subset of the other
- 1:3 satisfies 1:4.
"""
return self.intersection(other) == self
@coerced @coerced
def overlaps(self, other): def overlaps(self, other):
return ( return (
@ -882,34 +916,33 @@ def union(self, other):
@coerced @coerced
def intersection(self, other): def intersection(self, other):
if self.overlaps(other): if not self.overlaps(other):
if self.start is None:
start = other.start
else:
start = self.start
if other.start is not None:
if other.start > start or other.start in start:
start = other.start
if self.end is None:
end = other.end
else:
end = self.end
# TODO: does this make sense?
# This is tricky:
# 1.6.5 in 1.6 = True (1.6.5 is more specific)
# 1.6 < 1.6.5 = True (lexicographic)
# Should 1.6 NOT be less than 1.6.5? Hmm.
# Here we test (not end in other.end) first to avoid paradox.
if other.end is not None and end not in other.end:
if other.end < end or other.end in end:
end = other.end
return VersionRange(start, end)
else:
return VersionList() return VersionList()
if self.start is None:
start = other.start
else:
start = self.start
if other.start is not None:
if other.start > start or other.start in start:
start = other.start
if self.end is None:
end = other.end
else:
end = self.end
# TODO: does this make sense?
# This is tricky:
# 1.6.5 in 1.6 = True (1.6.5 is more specific)
# 1.6 < 1.6.5 = True (lexicographic)
# Should 1.6 NOT be less than 1.6.5? Hmm.
# Here we test (not end in other.end) first to avoid paradox.
if other.end is not None and end not in other.end:
if other.end < end or other.end in end:
end = other.end
return VersionRange(start, end)
def __hash__(self): def __hash__(self):
return hash((self.start, self.end)) return hash((self.start, self.end))
@ -1022,6 +1055,9 @@ def overlaps(self, other):
o += 1 o += 1
return False return False
def intersects(self, other):
return self.overlaps(other)
def to_dict(self): def to_dict(self):
"""Generate human-readable dict for YAML.""" """Generate human-readable dict for YAML."""
if self.concrete: if self.concrete:
@ -1040,31 +1076,10 @@ def from_dict(dictionary):
raise ValueError("Dict must have 'version' or 'versions' in it.") raise ValueError("Dict must have 'version' or 'versions' in it.")
@coerced @coerced
def satisfies(self, other, strict=False): def satisfies(self, other) -> bool:
"""A VersionList satisfies another if some version in the list # This exploits the fact that version lists are "reduced" and normalized, so we can
would satisfy some version in the other list. This uses # never have a list like [1:3, 2:4] since that would be normalized to [1:4]
essentially the same algorithm as overlaps() does for return all(any(lhs.satisfies(rhs) for rhs in other) for lhs in self)
VersionList, but it calls satisfies() on member Versions
and VersionRanges.
If strict is specified, this version list must lie entirely
*within* the other in order to satisfy it.
"""
if not other or not self:
return False
if strict:
return self in other
s = o = 0
while s < len(self) and o < len(other):
if self[s].satisfies(other[o]):
return True
elif self[s] < other[o]:
s += 1
else:
o += 1
return False
@coerced @coerced
def update(self, other): def update(self, other):

View File

@ -0,0 +1,18 @@
# Copyright 2013-2023 Lawrence Livermore National Security, LLC and other
# Spack Project Developers. See the top-level COPYRIGHT file for details.
#
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
from spack.package import *
class V1Consumer(Package):
"""Mimic the real netlib-lapack, that may be built on top of an
optimized blas.
"""
homepage = "https://dev.null"
version("1.0")
depends_on("v2")
depends_on("v1")

View File

@ -575,7 +575,7 @@ def validate_detected_spec(cls, spec, extra_attributes):
"languages=d": "d", "languages=d": "d",
"languages=fortran": "fortran", "languages=fortran": "fortran",
}.items(): }.items():
if spec.satisfies(constraint, strict=True): if spec.satisfies(constraint):
msg = "{0} not in {1}" msg = "{0} not in {1}"
assert key in compilers, msg.format(key, spec) assert key in compilers, msg.format(key, spec)

View File

@ -200,7 +200,7 @@ def install_pkgconfig(self):
# pkg-config generation is introduced in May 5, 2021. # pkg-config generation is introduced in May 5, 2021.
# It must not be overwritten by spack-generated tbb.pc. # It must not be overwritten by spack-generated tbb.pc.
# https://github.com/oneapi-src/oneTBB/commit/478de5b1887c928e52f029d706af6ea640a877be # https://github.com/oneapi-src/oneTBB/commit/478de5b1887c928e52f029d706af6ea640a877be
if self.spec.satisfies("@:2021.2.0", strict=True): if self.spec.satisfies("@:2021.2.0"):
mkdirp(self.prefix.lib.pkgconfig) mkdirp(self.prefix.lib.pkgconfig)
with open(join_path(self.prefix.lib.pkgconfig, "tbb.pc"), "w") as f: with open(join_path(self.prefix.lib.pkgconfig, "tbb.pc"), "w") as f:
@ -296,7 +296,7 @@ def install(self, pkg, spec, prefix):
# install debug libs if they exist # install debug libs if they exist
install(join_path("build", "*debug", lib_name + "_debug.*"), prefix.lib) install(join_path("build", "*debug", lib_name + "_debug.*"), prefix.lib)
if spec.satisfies("@2017.8,2018.1:", strict=True): if spec.satisfies("@2017.8,2018.1:"):
# Generate and install the CMake Config file. # Generate and install the CMake Config file.
cmake_args = ( cmake_args = (
"-DTBB_ROOT={0}".format(prefix), "-DTBB_ROOT={0}".format(prefix),

View File

@ -21,7 +21,7 @@ class Macsio(CMakePackage):
version("1.0", sha256="1dd0df28f9f31510329d5874c1519c745b5c6bec12e102cea3e9f4b05e5d3072") version("1.0", sha256="1dd0df28f9f31510329d5874c1519c745b5c6bec12e102cea3e9f4b05e5d3072")
variant("mpi", default=True, description="Build MPI plugin") variant("mpi", default=True, description="Build MPI plugin")
variant("silo", default=True, description="Build with SILO plugin") variant("silo", default=False, description="Build with SILO plugin")
# TODO: multi-level variants for hdf5 # TODO: multi-level variants for hdf5
variant("hdf5", default=False, description="Build HDF5 plugin") variant("hdf5", default=False, description="Build HDF5 plugin")
variant("zfp", default=False, description="Build HDF5 with ZFP compression") variant("zfp", default=False, description="Build HDF5 with ZFP compression")

View File

@ -954,7 +954,7 @@ def configure_args(self):
config_args.extend(self.with_or_without("schedulers")) config_args.extend(self.with_or_without("schedulers"))
config_args.extend(self.enable_or_disable("memchecker")) config_args.extend(self.enable_or_disable("memchecker"))
if spec.satisfies("+memchecker", strict=True): if spec.satisfies("+memchecker"):
config_args.extend(["--enable-debug"]) config_args.extend(["--enable-debug"])
# Package dependencies # Package dependencies

View File

@ -352,7 +352,7 @@ def flag_handler(self, name, flags):
# Fix for following issues for python with aocc%3.2.0: # Fix for following issues for python with aocc%3.2.0:
# https://github.com/spack/spack/issues/29115 # https://github.com/spack/spack/issues/29115
# https://github.com/spack/spack/pull/28708 # https://github.com/spack/spack/pull/28708
if self.spec.satisfies("%aocc@3.2.0", strict=True): if self.spec.satisfies("%aocc@3.2.0"):
if name == "cflags": if name == "cflags":
flags.extend(["-mllvm", "-disable-indvar-simplify=true"]) flags.extend(["-mllvm", "-disable-indvar-simplify=true"])
@ -473,7 +473,7 @@ def configure_args(self):
config_args.append("--with-lto") config_args.append("--with-lto")
config_args.append("--with-computed-gotos") config_args.append("--with-computed-gotos")
if spec.satisfies("@3.7 %intel", strict=True): if spec.satisfies("@3.7 %intel"):
config_args.append("--with-icc={0}".format(spack_cc)) config_args.append("--with-icc={0}".format(spack_cc))
if "+debug" in spec: if "+debug" in spec: