Implementation details#
This document provides an overview on how the conda-libmamba-solver
integrations are implemented,
both within the conda_libmamba_solver
package itself, and as a conda
plugin.
Repository structure#
.devcontainer
: Configuration for DevContainer development workflows..github/workflows/
: CI pipelines to run unit and upstream tests, as well as linting and performance benchmarks. Some extra workflows might be added by theconda/infra
settings.conda_libmamba_solver/
: The Python package. Check sections below for details.recipe/
: The conda-build recipe used for the PR build previews. It should be kept in sync withconda-forge
anddefaults
.dev/
: Supporting configuration files to set up development environments.docs/
: Documentation sources.tests/
: pytest testing infrastructure.pyproject.toml
: Project metadata. See below for details.
Project metadata#
The pyproject.toml
file stores the required packaging metadata,
as well as some the configuration for some tools (black
, pytest
, etc.).
Some peculiarities:
hatchling
is the chosen backend for the packaging.The
version
is calculated from thegit
info withhatchling-vcs
.black
uses a line length of 99 characters.
conda_libmamba_solver
package#
The package is a flat namespace:
conda_libmamba_solver.__init__
: Defines__version__
and the old-style plugin API.conda_libmamba_solver.exceptions
: Subclasses ofconda.exceptions
.conda_libmamba_solver.index
: Helper objects to deal with repodata fetching and loading, interfacing withlibmamba
helpers.conda_libmamba_solver.mamba_utils
: Utility functions to help set up thelibmamba
objects.conda_libmamba_solver.models
: Application-agnostic objects to assist in the metadata collection phases.conda_libmamba_solver.plugin
: Thepluggy
registration mechanism inconda.plugins
.conda_libmamba_solver.solver
: Theconda.core.solve.Solver
subclass with all the libmamba-specific logic.conda_libmamba_solver.state
: Solver-agnostic objects to assist in the solver state specification and collection.conda_libmamba_solver.utils
: Other application-agnostic utility functions.
Note
Refer to each module docstrings for further details!
Solver-agnostic parts#
The idea behind the module separation is to have better logic reusability and separation between the libmamba
library and the preparation logic conda
uses.
The following paragraphs assume you have read the Deep Dive guides in the conda
documentation, but as a refresher:
The
Solver
class will take a list ofMatchSpec
objects coming from diverse sources, like:The packages the user requested in the command line or an environment file
The installed packages in the target environment
The explicitly requested packages in previous commands (the history)
… and some more coming from configured settings
All these
MatchSpecs
are then merged and sorted depending on certain preparation logic.The final set of
MatchSpec
objects is used to query the so-called index of packages.The index is a long list of
PackageRecord
objects, loaded from therepodata.json
files obtained from the configured channels.The job of the solver is to find the set of
PackageRecord
objects that better satisfies the optimization criteria for the givenMatchSpec
objects.
All the preparation steps before the actual SAT solver starts working are conda-specific, and solver-agnostic!
In conda
classic, this logic is spread across the different layers, but in conda-libmamba-solver
we tried to synthesize it in a single module.
This is the conda_libmamba_solver.state
module, which contains the SolverInputState
and SolverOutputState
classes.
SolverInputState
deals with the collection and management of theMatchSpec
objects.SolverOutputState
will assist theSolver
class maintain its state through the different solving attempts, and will finally export a list ofPackageRecords
. The early exit and post-solve logics are also expressed here.
Both SolverInputState
and SolverOutputState
classes are supported by the TrackedMap
dictionary subclass,
which logs its own changes for better debugging and developer experience while analyzing solver problems.
libmamba-specific parts#
conda_libmamba_solver
interfaces with libmamba
objects through three modules only:
.solver
, which contains theconda.core.solve.Solver
subclass. It relies heavily onconda_libmamba_solver.state
in an effort to only contain the logic necessary to interface withlibmamba
..index
, which deals with the repodata fetching and loading. Initially, it invoked the necessarylibmamba
objects to download and load the repodata JSON files. In later releases, downloading is done withconda
objects, and we then pass the JSON files to thelibmamba
loaders..mamba_utils
, which contains utility functions borrowed and adapted frommamba
itself. Its main usage is the initialization of thelibmamba.Context
options fromconda
’sContext
.
Integrations with conda
#
With the plugin system#
Once co-installed with conda
, conda_libmamba_solver
registers itself via the conda.plugins.hookimpl
-decorated function in conda.plugin
, which yields a CondaSolver
plugin instance.
After that, conda
clients just need to get the configured solver via context.plugin_manager.get_cached_solver_backend()
.
Draft integrations (pre-plugin phase)#
Note
This is just here as a historical trivia item. Please check the Plugin implementation section for current details!
The first experimental releases of conda_libmamba_solver
used an ad-hoc mechanism based on try/except
hooks.
On the conda/conda
side, we had conda.core.solve._get_solver_class()
:
def _get_solver_class(key=None):
key = key or conda.base.context.Context.experimental_solver
if key == "classic":
return conda.core.solve.Solver # Classic
if key.startswith("libmamba"):
try:
from conda_libmamba_solver import get_solver_class
return get_solver_class(key)
except ImportError as exc:
raise CondaImportError(...)
raise ValueError(...)
The key
values were hard-coded in conda.base.constants
. Not very extensible!
This was only meant to be temporary as we iterated on the conda-libmamba-solver
side.
We had one more get_solver_class()
function in conda_libmamba_solver
so we could easily change the Solver
object import path without changing conda
itself.
The default value for the key
was set by the Context
object, which was populated by either:
The environment variable,
CONDA_EXPERIMENTAL_SOLVER
.The command-line flag,
--experimental-solver
.A configuration file (e.g.
~/.condarc
).