Goal: when a policy has more than one root and they disagree on a shared dependency’s version, control which root wins the newest-version tie-break instead of leaving it to incidental list order.
Two application roots, one shared dependency, mutually exclusive requirements on it:
# GROMACS-2025.0-foss-2025b.eb (newest GROMACS)
dependencies = [
('OpenMPI', '==4.1.6'),
]
# LAMMPS-2024.08-foss-2025b.eb (newest LAMMPS)
dependencies = [
('OpenMPI', '>=5.0.3'),
]Both roots cannot get their newest version at once: only one OpenMPI
version is co-selected per lock. Feasibility, checked by resolvo’s conflict-driven clause learning (CDCL) Boolean satisfiability (SAT) core, will
happily return a consistent assignment, but which one depends on solver
internals unless the policy says which root matters more. See
the optimization model for why plain SAT
feasibility is not enough here.
{
"toolchain": {"name": "foss", "version": "2025b"},
"roots": ["GROMACS", "LAMMPS"],
"pins": [],
"forbid": [],
"objective": "prefer_newer"
}With no root_priority field, priority defaults to roots list order:
listing GROMACS first here keeps GROMACS 2025.0 (and OpenMPI 4.1.6),
yielding LAMMPS 2023.08. Reordering roots to ["LAMMPS", "GROMACS"]
flips the outcome to LAMMPS 2024.08 / GROMACS 2024.4. That coupling
(priority tied to incidental list order) is exactly what root_priority
lets you avoid.
{
"toolchain": {"name": "foss", "version": "2025b"},
"roots": ["LAMMPS", "GROMACS"],
"root_priority": ["GROMACS", "LAMMPS"],
"pins": [],
"forbid": [],
"objective": "prefer_newer"
}eb-stack stack solve \
--easyconfigs fixtures/two_root_openmpi_conflict/easyconfigs \
--stack-policy fixtures/two_root_openmpi_conflict/policies/priority_gromacs_roots_lammps_first.json \
--lock-out /tmp/root-priority.lock.json --sbom-out /tmp/root-priority.cdx.json
# GROMACS 2025.0 (kept: prioritized)
# LAMMPS 2023.08 (yielded: not prioritized)
# OpenMPI 4.1.6 (forced by GROMACS's exact pin)roots still lists LAMMPS first, but root_priority: ["GROMACS",
"LAMMPS"] keeps GROMACS at its newest version regardless. Swap the
roots order in the policy file and re-run: the outcome does not change,
because priority is now declared, not inferred.
For each root in root_priority order (or roots order when
root_priority is omitted), eb-stack pins that root to the newest version
that is still jointly feasible with every higher-priority root already
pinned, then moves to the next root. Lower-priority roots yield whatever
version remains consistent once the higher-priority pins are fixed. A root
missing from an explicit root_priority list is appended in roots order,
so every root always participates.
- Policy schema reference for the exact
root_priorityfield semantics. - Why this needs an optimization layer on top of SAT feasibility.