Internals of mamba#
Mamba comes with a C++ core (for speed and efficiency), and a clean Python API on top. The core of mamba uses:
libsolvto solve dependencies (the same library used in RedHat dnf and other Linux package managers)curlfor reliable and fast downloadslibarchiveto extract the packages
libsolv#
pool#
pool holds pointers of almost all the data manipulated in libsolv.Some are presented here:
string pool
Stringpoolrelations of dependency
Reldepsolvables/packages
Solvablerepositories
Repodata about providers of a specific name or relation, as
Ids andOffsets (resp.intandunsigned int) for maximum performance
Sizes of allocated memory or offset to the first free slot are also stored.
Ids#
Strings (package names, requirements, etc.) are converted to Id s using a hash table for efficient data manipulation (storage, usage).
Id pool_str2id(Pool *pool, const char *str, int create)is used to get anIdfrom a stringIt eventually creates a fresh one if not existing
const char *pool_id2str(const Pool *pool, Id id)is used to get back the string fromId
Warning
Do not use solvable Id s with pool_id2str, use the equivalent const char *pool_solvid2str(Pool *pool, Id p)
Reldeps#
A Reldep describes a relation of dependency with:
a
nameIdan epoque version
evrIdsome
flags:REL_CONDAin our case
An example could be {1, 2, 30}, with:
1the Id for"xtensor"2the Id for">=0.20"30theREL_CONDAflag
Reldep *rels pointer on the first memory location and the size int nrels of allocated memory.Reldep are also handled with Id s that have bit 31 used as a flag to distinguish them from regular Id s.
There are multiple macros that help to convert those special Id s:
MAKERELDEP(id): turn a regularIdinto aReldepIdISRELDEP(id): test the bit 31GETRELID(id): get the regularIdGETRELDEP(pool, id): get theReldepfrom itsId
Note
pool_id2str also works with Reldep Id s! But it will only returns the Reldep ‘s name
Offsets#
An Offset represents a positive or negative shift of a pointer on an array.
For example, a solvable does not contain all its data but rather holds multiple offsets on its repo->idarraydata storage.
idarraydatais anIdpointerprovides,requires, etc. are offsets inidarraydata
whatprovides#
A provider is a solvable fulfilling a specification. The following definitions are key to disambiguate how libsolv works:
a package is an identification of the resource handled: a name such as
xtensora solvable is a specific version of the package. It can be assimilated to its tarball.
in
Mamba, a solvable name MUST match the package namelibsolvhandles cases where solvables are providing different entities than what identified in their names (example:pynumprovidingnumpy)this is not used, but important to know to understand the terminology
a specification: an expression to match solvables providing the same package
the package name can be used to select all providers/solvables
a more specific spec like
s2="xtensor>=0.20"will only match a subset of solvables: the ones that have version >=0.20 (whatever the build string)
Let’s take a simple example to recap:
the package:
xtensorsolvable(s):
xtensor=0.20.10=hc9558a2_0xtensor=0.23.10=h4bd325d_0etc.
specification(s):
xtensorxtensor>=0.20etc.
Note
It’s possible that a package is not provided by any solvable. It is then uninstallable.
Note
Specifications are stored as Id s (see the previous section)
Warning
While a solvable is a libsolv notion, specs and packages are not.
Storage#
void pool_createwhatprovides(Pool *pool) is used to create hashes over pool of solvables to ease provide lookups.It computes and store what solvables provide each spec, using a two-step indirect list:
from the spec
Id, an offset is computed inId *whatprovidesdataOffset *whatprovidesstores regular string specsOffset *whatprovides_relstoresReldepspecs
whatprovidesdataat the given offset is a 0-terminated list of solvablesIds, providing the specId
Lookups#
The pool_whatprovides(Pool *pool, Id d) function returns the offset of the first solvable Id in whatprovidesdata:
Rules#
A rule is all about solvables, it represents a logical disjunction OR between one or more literals.
Rules are created to translate in mathematical logic:
a specification: installation/removal/updates
(A)meansAmust be installed(-A)meansAmust be removed or kept uninstalled(A|B1|B2|...)meansAcould be updated withB1,B2, etc.
a dependency:
Aneeds/requiresb(a spec)(-A|B1|B2|...)meansArequires one ofB1,B2, etc. (bproviders)
an incompatibility:
A1can’t be installed alongsideA2(-A1|-A2), (-A1|-A3), ...meansA1is not compatible withA2,A3, etc.this is a common case: multiple providers of the same package
etc.
Still for efficiency, rules are storing Id s of solvables and specs:
pis the packageIdofAdis the packageIdoffset into the list of providers (negative value means the rule is disabled)w1andw2are watchesn1andn2are the next rules in linked-lists, corresponding tow1andw2
Dependencies#
Each dependency is turned into a rule to be satisfied during the solving stage.
Example:
p1andp2are 2 specsp1is provided by a single solvables11p2is provided bys21ands22
The corresponding rule r is -s11|s21|s22.
That means that taking the decision to install s11:
-s11is not satisfiedeither
s21ors22need to be satisfied
Note
Exclusive rules are used to avoid installation of multiple solvables providing the same package
Watches#
propagation after decisions taken on previous rules for some solvable.The possible decisions on solvables are installation or removal/conflict, this is stored as resp. positive and negative Ids.
Related rules are then evaluated during another level of decision: those are the one with an opposite first literal.
Example:
the install spec is to install
p1provided by a single solvables1:r1=(s1)s1depends on specp2, provided bys21ors22:r2=(-s1|s21|s22)decision to install
s1triggers ruler2
Transaction#
Another important part of libsolv is the Transaction. A transaction governs what packages are installed or removed, and a transaction is the result of a successful solve process.
A transaction in libsolv is a single list (Queue) of Solvable Id's and is thus rather simple. The Queue contains either a positive or negative Id. Each negative Id is uninstalled from the environment, and each positive Id is to be installed. libsolv classifies the entire range of Id’s into different types of transaction operations. For example, if we have { -5, 5 } that would be a reinstall transaction for the Solvable with Id == 5.
If the Id's are different then it can be a downgrade or upgrade operation (first, the previous package needs removal before the higher or lower version can be installed). The transaction_classify and transaction_classify_pkgs functions of libsolv take care of this classification to present a nice output to the user.
Another crucial libsolv function is transaction_order to order the transaction in a way that they are installed with the lowest dependency first (topological sort). This ensures that e.g. python is installed before any packages depending on python as they are sometimes needed during installation (for example for noarch packages with entry_points).
Lastly, we can force installation or explicitly install from URL’s by crafting transactions without using the solver – just by adding the correct Id's into the Transaction queue.