Dependencias
Versiones exactas, las dos tablas de dependencias, recipe.toml, Molto.lock y las cachés que hay detrás.
[deps]
sqlite = "3.53.4" # una coordenada de registry, en esa versión exacta
http = { path = "modules/http" } # un directorio en el que estás trabajando
[dev-deps]
tinytest = { path = "modules/tinytest" } # sólo se compila dentro de tests/
molto add sqlite # la última release, escrita en el manifiesto como exacta
molto add sqlite@3.53.4 # esa
molto add tinytest --dev --path ../tt # a [dev-deps]
molto remove sqlite
Cuatro reglas sostienen casi todo el comportamiento
Las versiones son exactas. ^, ~ y >= se rechazan, no se interpretan. Un rango dejaría
entrar en tu build una release que nadie ha leído, sin diff.
Una versión por paquete, contando las dos tablas a la vez. Un binario de tests enlaza los
objetos de src/ junto a los de los tests, y dos versiones ahí son símbolos duplicados.
Las dependencias de desarrollo no son transitivas, y sus directorios de include se ponen
únicamente en la línea de comandos que compila tests/. Un archivo de src/ que incluya una de
ellas falla al compilar, en el primer build. Esa es la garantía, no una convención documentada.
Ambas tablas fallan en cerrado. Una clave desconocida, dos orígenes, un rango o una clave
especificada pero no implementada (recipe, artifact, optional, features) es un error que
nombra la dependencia: justo lo contrario que el resto del manifiesto, que descarta lo que no
conoce.
Declarar una
[deps]
yyjson = "1.2.32" # atajo de { version = … }
sqlite = { git = "https://github.com/sqlite/sqlite", tag = "3.50.0" }
http = { path = "modules/http" }
zlib = { archive = "https://…", sha256 = "…" }
fast = { version = "2.0.0", registry = "myorg" }
[registries]
myorg = "https://registry.example.com"
Claves de origen, hace falta exactamente una: version, git, path o archive.
Modificadores: branch / tag / rev junto a git y como mucho uno de ellos, sha256
obligatorio con archive, y registry, que debe estar declarado en [registries].
Las opciones de molto add se llaman igual que las claves, así que lo que tecleas y lo que acaba
en el manifiesto coinciden. add y remove editan líneas en vez de reescribir el archivo: los
comentarios, la alineación y el orden de las claves sobreviven, volver a añadir un nombre con otra
versión lo sustituye en su sitio, y cualquier edición que dejaría un manifiesto ilegible se rechaza
antes de tocar el archivo.
recipe.toml
Una dependencia se describe a sí misma con un recipe en la raíz de su código. Una dependencia
path o git sin él se rechaza con exactamente ese mensaje.
schema = 1
form = "source"
kind = "package"
name = "sqlite"
version = "3.53.4"
target = "any"
[source]
archive = "https://sqlite.org/2025/sqlite-amalgamation-3530400.zip"
sha256 = "…"
compression_format = "zip" # zip | tar | tar.gz | tar.bz2 | tar.xz | tar.zst
strip_prefix = "sqlite-amalgamation-3530400"
[build]
system = "none" # make | cmake | autotools | meson | none
[artifacts]
type = "source" # source | static | shared
sources = ["sqlite3.c"]
exclude = ["shell.c"]
include = ["."]
link = ["m", "pthread"]
defines = ["SQLITE_THREADSAFE=1"]
Hoy sólo se puede consumir [artifacts] type = "source". Molto compila el código como
unidades de traducción propias, fundiendo los includes, defines, flags y links del recipe en el
build. static y shared se rechazan con un mensaje, y también cualquier [build] system que no
sea none: nunca se ejecuta ningún sistema de build. Nada dentro de Molto invoca make,
cmake ni meson.
Un recipe nunca lleva un script. Ni para descomprimir ni para construir. Por eso
compression_format nombra un conjunto acotado y [build] nombra un sistema en lugar de una línea
de comandos: un recipe capaz de ejecutar una cadena convertiría cada dependencia en una ejecución
remota.
compression_format se declara, nunca se deduce: una URL puede acabar en .zip y servir un
tarball. Ausente significa deducirlo de la extensión, para recipes escritos antes de que existiera
la clave.
[about] es de donde sale la licencia de una dependencia, y molto metadata
reporta exactamente lo que encuentre ahí. Un recipe que no declara ninguna produce un componente
sin campo licenses: silencio en vez de una afirmación.
Resolución
- Lee
[deps],[dev-deps]yMolto.locksi está. - Para una coordenada, pregunta al registry una vez por nombre. Sin
@<versión>,molto addelige la más nueva por precedencia semver, no por orden de cadena, y escribe ese número en el manifiesto. - Descarga lo que no esté cacheado, recorre el
[deps]de cada recipe y repite. - Rechaza una resolución con la que el lock no esté de acuerdo; en caso contrario escribe
Molto.lock.
Dos versiones de un mismo paquete es tu decisión, no la de Molto. No hay rango que ensanchar ni versión más alta que elegir, así que todo el trabajo del mensaje es decir quién pidió qué:
molto: sqlite is required at two versions
3.50.3 ← your Project.toml
3.53.4 ← required by http 1.2.0
Molto.lock
Se genera, se commitea, se ordena por nombre y con cada lista ordenada, de modo que su diff merezca leerse, que es justo el sentido de commitearlo.
version = 1
root = "app"
[[package]]
name = "a"
version = "1.0.0"
source = "registry+https://…"
checksum = "…" # se omite en una dependencia path, nunca se escribe vacío
scopes = ["runtime", "dev"]
dependencies = ["b"]
Un lock ausente, ilegible, que no sea TOML válido o que venga de un formato más nuevo recibe el mismo trato: resolver otra vez. Un lock cuyas dependencias directas ya no coinciden con el manifiesto está caducado, y se vuelve a resolver en vez de parchearse.
Las cachés
| Ruta | Qué |
|---|---|
~/.molto/cache/sources/<nombre>/<versión>/<target> |
Un árbol descargado, una vez por coordenada, sellado al terminar |
~/.molto/cache/objects |
Objetos compilados de dependencias, indexados por el comando de compilación completo |
~/.molto/credentials.toml |
El token del registry, modo 0600 |
$MOLTO_CACHE reubica las dos primeras. Un directorio sin su sello son los restos de una descarga
interrumpida: se borra, no se lee.
La caché de objetos cubre sólo dependencias. Reutilizar un objeto exige saber que la fuente y
cada cabecera que incluye son idénticas, y en una dependencia la coordenada responde por el árbol
inmutable entero. Las fuentes propias de un proyecto son mutables y su grafo de cabeceras se
desconoce hasta que el compilador ha corrido: ese es el problema de ccache, y Molto no intenta ser
uno. Una dependencia path queda fuera por la misma razón: sus bytes son lo que haya en disco
ahora mismo.
Descargar delega en curl, tar, unzip y git, con el mismo razonamiento que delegar en un
compilador en vez de enlazar uno.
El registry
Las lecturas son públicas; sólo publicar necesita token. molto login cambia correo y contraseña
por uno, o guarda con --token uno copiado de la página de cuenta. El token nunca aparece en
argv: ps lo puede leer cualquiera, así que la cabecera va por un archivo de configuración de
curl creado con permisos 0600 y borrado después.
molto publish envía primero el blob y al final la fila. --dry-run lee y calcula los hashes sin
enviar nada, que es la única forma de revisar un recipe: una coordenada publicada es inmutable.