# Copyright 2022-2026 The Ramble Authors
#
# Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
# https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
# <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
# option. This file may not be copied, modified, or distributed
# except according to those terms.
import copy
from typing import Dict, List, Optional
import ramble.util.colors as rucolor
[docs]
def specs_conflict(new, existing, prefix="", skip_conflicting_when=False):
# Short circuit check if when clauses conflict
# (so specs should not be applied at the same time)
# Used for printing conflicting software specs.
if skip_conflicting_when:
new_when = set(new["when"]) if "when" in new else None
existing_when = set(existing["when"]) if "when" in existing else None
if new_when != existing_when:
return False
prefixed_keys = {}
for key in new.keys():
if new[key] is not None:
prefixed_keys[key] = f"{prefix}{key}"
for in_key, out_key in prefixed_keys.items():
if out_key in existing and new[in_key] != existing[out_key]:
return True
return False
[docs]
class SoftwareSpec:
def __init__(
self,
name: str,
pkg_spec: str,
prefix: str = "",
compiler: Optional[str] = None,
compiler_spec: Optional[str] = None,
inject_if_missing: bool = False,
when: Optional[List[str]] = None,
):
self.name = name
self.pkg_spec = pkg_spec
self.prefix = prefix
self.compiler = compiler
self.compiler_spec = compiler_spec
self.inject_if_missing = inject_if_missing
self.when = when.copy() if when else []
[docs]
def to_dict(self, apply_prefix: bool = False):
prefix_base = self.prefix if apply_prefix else ""
prefix_str = f"{prefix_base}_" if prefix_base else ""
attrs = ["pkg_spec", "compiler", "compiler_spec"]
output = {}
for attr in attrs:
val = getattr(self, attr, None)
if val is not None:
output[f"{prefix_str}{attr}"] = val
return output
[docs]
def config_opts(self):
self_dict = self.to_dict()
for key, val in self_dict.items():
yield f"software:packages:{self.name}:{key}:{val}"
[docs]
def as_str(self, n_indent: int = 0, verbose: bool = False):
base_indent = " " * n_indent
indentation = " " * (n_indent + 4)
self_dict = self.to_dict()
color_name = rucolor.section_title(self.name)
output = f"{base_indent}{color_name}:\n"
for key, val in self_dict.items():
output += f"{indentation}{rucolor.nested_1(key)}: {rucolor.plaintext(val)}\n"
if self.when:
output += rucolor.nested_2(f"\n{indentation}When:\n")
for condition in self.when:
output += f"{indentation} {rucolor.plaintext(condition)}\n"
return output
def __str__(self):
self_dict = self.to_dict()
self_dict["prefix"] = self.prefix
return str(self_dict)
[docs]
def copy(self):
return copy.deepcopy(self)
[docs]
def conflict_spec(self, test, skip_conflicting_when: bool = True):
if skip_conflicting_when:
new_when = set(getattr(self, "when", set()))
test_when = set(getattr(test, "when", set()))
if new_when != test_when:
return False
if self.prefix != test.prefix:
return False
for attr in ["pkg_spec", "compiler", "compiler_spec"]:
new_val = getattr(self, attr, None)
test_val = getattr(test, attr, None)
if new_val != test_val:
return True
return False
[docs]
def conflict_dict(self, test_dict: Dict):
self_dict = self.to_dict()
for key, val in self_dict.items():
if key in test_dict and val != test_dict[key]:
return True
return False