Work in progress
This page of the documentation is not yet finished and only contains a draft of the content.
Technical specification: solver state
Note
This document is a technical specification, which might not be the best way to learn about how the solver works. For that, refer to conda install and Solvers.
The Solver
API will pass a collection of MatchSpec
objects (from now on, we will refer to
them as specs
) to the underlying SAT solver. How this list is built from the prefix state
and context options is not a straightforward process, but an elaborate logic. This is better
understood if we examine the ingredients that participate in the construction of specs
. We
will label them like this:
These groups below will not change during the solver attempts:
requested
:MatchSpec
objects the user is explicitly asking for.installed
: Installed packages, expressed asPrefixRecord
objects. Empty if the environment did not exist.history
: Specs asked in the past: theHistory
. Empty if the environment did not exist.aggressive_updates
: Packages included in the aggressive updates list. These packages are always included in any requests to make sure they stay up-to-date under all circumstances.pinned
: Packages pinned to a specific version, either viapinned_packages
in your.condarc
or defined in a$PREFIX/conda-meta/pinned
file.virtual
: System properties exposed as virtual packages (e.g.__glibc=2.17
). They can’t really be installed or uninstalled, but they do participate in the solver by adding runtime constraints.do_not_remove
: A fixed list of packages that receive special treatment by the solver due to poor metadata in the early days of conda packaging. A legacy leftover.
This one group does change during the solver lifetime:
conflicting
: Specs that are suspected to be a conflict for the solver.
Also, two more sources that are not obvious at first. These are not labeled as a source, but they
do participate in the specs
collection:
In new environments, packages included in the
contex.create_default_packages
list. TheseMatchSpec
objects are injected in eachconda create
command, so the solver will see them as explicitly requested by the user (requested
).Specs added by command line modifiers. The specs here present aren’t new (they are already in other categories), but they might end up in the
specs
list only when a flag is added. For example,update --all
will add all the installed packages to thespecs
list, with no version constraint. Without this flag, the installed packages will still end up in thespecs
list, but with full constraints (--freeze-installed
defaults for the first attempt) unless:Frozen attempt failed.
--update-specs
(or any otherUpdateModifier
) was passed, overriding--freeze-installed
.
See? It gets involved. We will also use this vocabulary to help narrow down the type of change being done:
Types of spec
objects:
specs
: map of package name to its currently correspondingMatchSpec
instance.spec
: specific instance of aMatchSpec
object.Exact or frozen
spec
: aspec
where both theversion
andbuild
fields are constrained with==
operators (exact match).Fully constrained or tight
spec
: aspec
where bothversion
andbuild
are populated, but not necessarily with equality operators. It can also be inequalities (>
,<
, etc.) and fuzzy matches (*something*
).Version-only
spec
: aspec
where only theversion
field is populated. Thebuild
is not.Name-only, bare, or unconstrained
spec
: aspec
with noversion
orbuild
fields. Just the name of the package.Targeted
spec
: aspec
with thetarget
field populated. Extracted from the comments in the solver logic:target
is a reference to the package currently existing in the environment. Settingtarget
instructs the solver to not disturb that package if it’s not necessary. If the spec.name is being modified by inclusion inspecs_to_add
, we don’t settarget
, since we want the solver to modify/update that package.TL;DR: when working with
MatchSpec
objects,to minimize the version change, set
MatchSpec(name=name, target=prec.dist_str())
to freeze the package, set all the components of
MatchSpec
individually
if the
spec
object does not have an adjective, it should be assumed it’s being added to thespecs
map unmodified, as it came from its origin.
Pools (collections of PackageRecord
objects):
Installed pool: The installed packages, grouped by name. Each group should only contain one record!
Explicit pool: The full index, but reduced for the specs in
requested
.
The following sections will get dry and to the point. They will state what output to expect from
a given set of initial conditions. At least we’ll try. Take into account that the specs
list
is kept around across attempts! In other words, the specs
list is only really empty in the first
attempt; if this fails, the subsequent attempts will only overwrite (update) the existing one. In
practice, this should only affect how constrained packages are. The names should be the same.
It will also depend on whether we are adding (conda install|create|update
) or removing
(conda remove
) packages. There’s a common initialization part for both, but after that the
logic is separate.
Common initialization
Note: This happens in
Solver._collect_all_metadata()
This happens regardless of the type of command we are using (install
, update
, create
or
remove
).
Add specs from
history
, if any.Add specs from
do_not_remove
, but only if:There’s no spec for that name in
specs
already, andA package with that name is not installed.
Add
virtual
packages as unconstrainedspecs
.Add all those installed packages, as unconstrained
specs
, that satisfy any of these conditions:The history is empty (in that case, all installed packages are added)
The package name is part of
aggresive_updates
The package was not installed by
conda
, but by pip or other PyPI tools instead.
Preparing the index
At this point, the populated specs
and the requested
specs are merged together. This temporary
collection is used to determine how to reduce the index.
Processing specs for conda install
Preparation
Generate the explicit pool for the requested specs (via
Resolve._get_package_pool()
).Detect potential conflicts (via (
Resolve.get_conflicting_specs()
).
Refine specs
that match installed records
Check that each of
specs
match a single installed package or none! If there are two or more matches, it means that the environment is in bad shape and is basically broken. If thespec
matches one installed package (let’s call it installed match), we will modify the originalspec
.We will turn the
spec
into an exact (frozen) spec if:The installed match is unmanageable (installed by pip, virtual, etc.)
There’s no history, we are not in
--freeze-installed
mode, and:The spec is not a potential conflict, and
The package name cannot be found in the explicit pool index or, if it is, the installed match can be found in that explicit pool (to guarantee it will be found instead of creating one more conflict just because).
We relax the
spec
to a name-onlyspec
if it’s part of the aggressive updates list.We turn it into a targeted
spec
if:The spec is in
history
. In that case, we take its historic spec counterpart and set the target to the installed match version and build.None of the above conditions were met. In other words, we’ll try our best to match the installed package if none of the above applies, but if we fail we’ll stick to whatever was already present in the
specs
.