Ir al contenido
Molto

Toolchains con pickup

pickup es quien responde qué compilador tiene esta máquina y qué puede demostrar — sus diez comandos, el TOML que hablan y qué le pide Molto.

Molto nunca elige un compilador. Tu manifiesto declara las capacidades que necesita el código, y pickup responde qué binario local las proporciona. Es el gestor de toolchains del ecosistema —rustup, pero para compiladores—: un programa aparte, con su propia línea de comandos, instalado al lado de molto.

Responde con la verdad a una sola pregunta: ¿qué compiladores existen en esta máquina y qué sabe hacer de verdad cada uno? Y la responde compilando, nunca consultando una tabla de versiones. En una máquina Linux cualquiera:

Compilador Acepta -std=c2x Compila [[nodiscard]]
gcc 9 sí no
gcc 11 sí sí
gcc 12 sí sí

Cualquier cosa que mire sólo el flag reporta gcc 9 como compilador de C23. Esa es la razón de que este programa exista, y la razón de que un build que le pregunta sea portable de una forma en que uno que nombra gcc-12 no lo es.

Los comandos

Comando Posicional Opciones
pickup list — -f/--format <text|toml>
pickup show <nombre> -f/--format
pickup scan — —
pickup tools — -f/--format
pickup doctor — -a/--all, -f/--format
pickup resolve — -l/--lang <c|c++>, -s/--std <nombre>, -r/--require <ids>, -v/--vendor <nombre>, --stdlib
pickup search [nombre] -v/--version, --refresh, -f/--format
pickup install <nombre>[@versión] -v/--version, --dry-run, --refresh
pickup uninstall <toolchain> -y/--yes
pickup default [toolchain] --clear, -f/--format

Los diez están implementados; aquí nada responde «no implementado». Todo comando que produce datos acepta --format text|toml, la respuesta va a stdout y el progreso y los avisos van a stderr sin excepción: pickup list --format toml > toolchains.toml deja un archivo sin ninguna barra de progreso dentro.

search e install aceptan cualquier nombre que publique el registry: una toolchain como clang o gcc, o una herramienta como clang-format o clang-tidy.

El modelo

Concepto Qué es
Toolchain Un driver con identidad resuelta: ruta, vendor, versión, triple de destino, su mitad de C++ y las features que demostró. Se llama vendor@versión[-etiqueta]
Feature Una entrada de catálogo con un id estable y un programa que la demuestra: attr_nodiscard, constexpr, concepts
Receta El compilador más los flags que lo hacen funcionar: compile_flags, link_flags, runtime_dirs, stdlib. Se encuentra probando hasta que uno compila, enlaza y ejecuta
Salud Si el driver funciona de verdad: health_ok, no_driver, no_headers, no_link, no_run
Default Una preferencia global, no una restricción, guardada en ~/.pickup/config.toml

No hay concepto de canal —ni stable ni nightly— ni override por directorio: nada parecido a rustup override, ni un archivo .pickup-toolchain. Lo que un proyecto necesita lo dice en Project.toml.

Lo que tiene esta máquina

$ pickup list
NAME              VENDOR  VERSION  TARGET                    SOURCE
clang@22.1.8      clang   22.1.8   x86_64-unknown-linux-gnu  pickup
clang@14.0.0      clang   14.0.0   x86_64-pc-linux-gnu       system
gcc@12.3.0-conda  gcc     12.3.0   x86_64-conda-linux-gnu    pickup
gcc@12.3.0        gcc     12.3.0   x86_64-linux-gnu          system

Una toolchain se nombra por lo que es, no por el enlace que llevó hasta ella. Un mismo GCC responde a cc, gcc-9, c89-gcc y g++-9; cuatro filas de eso dicen cuatro cosas donde hay una. La identidad es el vendor y la versión. Cuando dos chocarían, el target lo decide —gcc@12.3.0-conda junto al 12.3.0 del sistema—, y el sufijo sale del propio target, nunca de comparar toolchains entre sí. SOURCE dice quién es responsable: pickup para la que instaló él y puede volver a quitar, system para la que pertenece a la distribución.

Cualquier forma más corta del nombre vale allí donde se espera uno:

pickup show gcc@12.3.0-conda   # exactamente esa
pickup show gcc@12.3.0         # la versión, sea cual sea el target
pickup show gcc@12             # la 12 más nueva
pickup show gcc                # o gcc@latest

show es donde el probing se vuelve visible: imprime la identidad y luego cada feature, grupo a grupo, con un yes o un no al lado. Cuando una consulta nombra de verdad a más de una, se enseña igualmente la elegida y stderr dice qué otra cosa podía haber significado.

La primera consulta en una máquina prueba cada compilador una vez —y probar es hacer compilar a todos los compiladores que hay—, así que dibuja una barra de progreso en stderr y la borra antes de imprimir la respuesta. Las siguientes salen de la caché en milisegundos. pickup scan tira esa caché y vuelve a probarlo todo, que es lo que hay que ejecutar tras instalar o actualizar un compilador por otros medios.

Qué está mal en esta máquina

El catálogo de capacidades prueba el lenguaje y evita las cabeceras a propósito. Eso deja una segunda pregunta, la que hace doctor: ¿puede este compilador producir un programa que se ejecute?

$ pickup doctor
compilers
  ✓ 6 found - clang 14.0.0 to 22.1.8, gcc 9.5.0 to 12.3.0
  ✓ 4 of them build C and C++

tools
  ✗ formatter none found
      -> pickup install clang-format
  ✗ linter    none found
      -> pickup install clang-tidy

1 more thing worth knowing; pickup doctor --all

✗ es lo que te deja sin poder hacer algo, y el código de salida sigue la misma regla. Un GCC instalado sin su mitad de C++ está roto, pero en una máquina con cuatro toolchains que compilan C y C++ no impide nada, así que no es un ✗. Conviene decirlo con todas las letras para la ejecución de arriba: un formateador ausente sí es bloqueante, así que esa máquina hace que pickup doctor salga con 1. En CI, esa es la diferencia entre una barrera y un informe.

Las líneas de resumen van primero y aparecen haya o no algo roto, porque qué tiene esta máquina se pregunta mucho más a menudo que qué está roto. Nada se oculta en silencio: la cuenta del final dice cuánto quedó fuera, y --all lo enseña con sus remedios. Un hallazgo es una causa, no un síntoma: un GCC sin su mitad de C++ asoma en dos sitios que parecen no tener relación y se reporta una sola vez.

Los remedios se nombran, nunca se aplican, con una excepción que queda del lado de pickup: un archivo .cfg que él escribió para una toolchain que él instaló registra una decisión que nada revalida, así que doctor la vuelve a poner al día y dice que lo hizo.

Pedir una

pickup resolve --require attr_nodiscard        # features demostradas
pickup resolve --lang c++ --std c++20          # lenguaje y flag de estándar
pickup resolve --vendor gcc --format toml      # para máquinas

--require y --std no son la misma pregunta. --require nombra features y se responde probando: un compilador pasa sólo si compiló un programa que usa cada una. --std se responde con el flag y nada más: pregunta si el driver puede invocarse en ese modo, que es lo que necesita quien va a construir la línea de compilación. gcc 12 acepta -std=c2x y aun así falla cuatro de las features atribuidas a C23. Pide las dos cosas cuando el código depende de las dos.

Cuando no hay nada que encaje, pickup nombra lo que le falta a cada candidato en vez de dejarte leer errores del compilador más tarde, y sale con 3:

$ pickup resolve --require constexpr,nullptr
pickup: no c compiler satisfies the request
  clang            (14.0.0) missing: constexpr nullptr
  gcc-12           (12.3.0) missing: constexpr nullptr

Las capacidades

Estos ids son exactamente lo que acepta [target].requires en el manifiesto. Cada uno es un programa real compilado con -fsyntax-only por stdin —sin escribir archivos, y sin usar cabeceras salvo spaceship—, porque la idea es probar el lenguaje y no la librería que tiene al lado.

Grupo Ids
C99 for_decl, line_comment, inline_fn
C11 static_assert, generic, noreturn
C23 attr_nodiscard, attr_maybe_unused, typeof, constexpr, nullptr, native_bool
C++11 auto_type, lambda, nullptr_cpp
C++17 structured_bindings, if_constexpr, fold_expressions
C++20 concepts, spaceship

Un id desconocido es un error de uso que lo nombra, no una petición que en silencio no casa con nada.

Instalar una

Cuando la máquina no tiene nada adecuado, pickup trae una. Todo viene de un único registry, bajo coordenadas propias: un nombre, una versión y el target para el que se construyó.

pickup search                        # todos los catálogos que hay
pickup search clang                  # las versiones de uno, instalables aquí
pickup search clang --refresh        # volver a preguntarle al registry
pickup install clang                 # la más nueva que se ofrezca
pickup install clang@19.1.6          # o --version 19.1.6; --version 19 significa cualquier 19
pickup install clang --dry-run       # resolverlo sin descargar nada

Los catálogos se cachean una hora, porque search es de los comandos que la gente ejecuta dos veces seguidas. Un nombre que nadie publica vuelve como una lista de lo que sí casa, no como un error: las descargas van por curl -f, que reporta igual un 404 que una red caída, así que el nombre se resuelve contra los catálogos ya descargados.

Nada se instala sin comprobarse. El registry declara un sha256 por artefacto, y uno que llega sin digest se descarta al leerlo, en vez de llevarlo hasta un punto donde algo decida si verificarlo. No hay flag que abra un camino sin verificar.

Nada se da por bueno por haberse descomprimido. Un compilador tiene que compilar, enlazar y ejecutar un programa antes de ser adoptado; una herramienta no tiene nada que compilar, así que lo que hace las veces es que el binario que el registry nombró responda. Lo que falle no deja nada instalado: el árbol se monta con un nombre temporal dentro del directorio donde acabará y se renombra en su sitio sólo cuando está completo, así que una instalación interrumpida no deja nada con apariencia de terminado.

Nada de lo que se publica sobre él se obedece. El registry dice con qué flags se construyó un artefacto; pickup los enseña y luego calcula los suyos. Esos metadatos describen la máquina que construyó el artefacto, y el flag que hace funcionar a un compilador aquí nombra un directorio de esta máquina.

Una versión que el registry ha retirado sigue listada y deja de ofrecerse: nombrar la versión entera la instala igualmente, porque algo ya construido con ella tiene que seguir siendo explicable.

Instalar necesita curl, tar y zstd en el PATH; todo se empaqueta en tar.zst, y doctor reporta un zstd ausente como bloqueante porque sin él no se puede instalar nada en absoluto.

uninstall sólo quita lo que instaló pickup: lo que ya estaba en la máquina pertenece al gestor de paquetes, y pickup lo dice en vez de tocarlo. Un nombre que casa con más de una se rechaza sin más y las lista. La confirmación aparece donde hay alguien que pueda responderla, es decir, cuando stdin es una terminal; en una tubería el borrado sigue adelante sin preguntar, con --yes o sin él.

Las herramientas con las que se trabaja un proyecto

Una máquina que compila no es una máquina en la que se pueda trabajar. clang-format y clang-tidy vienen del registry por el mismo camino que un compilador, y aterrizan en ~/.pickup/tools/, aparte de las toolchains, porque nada resuelve contra ellas.

$ pickup tools
KIND       NAME          VERSION                      SOURCE
formatter  clang-format  clang-format version 22.1.8  pickup
linter     clang-tidy    LLVM version 22.1.8          pickup

doctor dice si falta alguna; tools dice cuáles hay y qué invocar. Sólo el TOML lleva la ruta, porque es lo que necesita un build. cppcheck se detecta cuando lo trae el sistema y no se puede instalar. Esta es la respuesta contra la que corren molto fmt y molto lint.

Elegir cuál gana

resolve responde con la toolchain más nueva que sea capaz de construir de verdad. default es cómo se dice otra cosa:

$ pickup default gcc@12.3.0-conda
✓ Default toolchain is now gcc@12.3.0-conda

Es una preferencia, no una restricción. Pídele algo que no pueda servir y obtienes una respuesta igualmente, más una línea en stderr diciendo por qué se descartó. Lo que se guarda es la identidad resuelta, nunca lo que escribiste: gcc@latest registrado tal cual significaría en silencio un compilador distinto la próxima vez que se instalara algo. pickup default a secas reporta la actual, --clear la olvida, list la marca, y desinstalarla la borra. Vive en ~/.pickup/config.toml, al lado de la caché y no dentro: vaciar la caché cuesta un rescan, y no debe costar una decisión.

Los flags son parte de la respuesta

Un compilador no es un comando; es un comando más todo lo que hay que decirle antes de que produzca un programa que se ejecute. Por eso resolve publica la receta, demostrada igual que todo lo demás: los candidatos se prueban en orden y el primero que compila, enlaza y ejecuta es el que se publica.

$ pickup resolve --lang c++ --format toml
[compiler]
id = "clang@22.1.8"
path = "~/.pickup/toolchains/clang-22.1.8-x86_64-unknown-linux-gnu/bin/clang++"
c_path = "~/.pickup/toolchains/clang-22.1.8-x86_64-unknown-linux-gnu/bin/clang"
cxx_path = "~/.pickup/toolchains/clang-22.1.8-x86_64-unknown-linux-gnu/bin/clang++"
vendor = "clang"
version = "22.1.8"
target = "x86_64-unknown-linux-gnu"

[cxx]
stdlib = "libc++"
compile_flags = ["-stdlib=libc++", "--gcc-install-dir=/usr/lib/gcc/x86_64-linux-gnu/11"]
link_flags = ["-stdlib=libc++", "-Wl,-rpath,…"]
runtime_dirs = ["~/.pickup/toolchains/clang-22.1.8-x86_64-unknown-linux-gnu/lib/…"]

--std añade una clave más, std_flag = "-std=c2x", para que quien llama pase el flag que pickup verificó y no el que supuso. --lang c produce una tabla [c] en su lugar.

En qué librería estándar acaba depende de quién es dueño del compilador. Para uno que es del sistema se prueba libstdc++ primero: es contra lo que se construyó todo lo demás de la máquina. Para uno que instaló pickup es al revés —lo que la toolchain trajo dentro de su propio prefijo gana a lo que le presta el sistema—, o un mismo pickup install sería un compilador distinto en cada máquina. La consecuencia que conviene saber: el C++ construido con un Clang instalado usa libc++ y no enlazará contra una librería de C++ del sistema construida sobre libstdc++. resolve --stdlib libstdc++ es el camino de vuelta.

runtime_dirs existe porque enlazar no es ejecutar. Sin ese -rpath el enlazado sale con cero y el programa muere con libc++.so.1: cannot open shared object file: una toolchain que pasa todas las comprobaciones y no construye nada.

Qué le pide Molto

Molto ejecuta pickup como un proceso aparte y lee su TOML —pickup habla TOML porque Molto ya lo parsea, así que consumir el resolutor no añade ningún parser del otro lado.

pickup resolve --lang <c|c++> [--std X] [--require a,b] [--vendor v] --format toml
pickup tools --format toml

--std sale de [target].std, --require de [target].requires y --vendor sólo cuando el manifiesto nombró un compilador. Molto cachea ambas respuestas en su propio .bin/wsdb y vuelve a preguntar con --refresh-toolchain / --refresh-tools, o cuando la petición cambia: la pregunta se hace una vez, no en cada build.

De la respuesta lee compiler.c_path (con path como alternativa), cxx_path, vendor y version. Las tablas [c] y [cxx] se publican y todavía no se consumen del lado de Molto. Que un Clang instalado funcione igualmente no es suerte: install deja la misma receta en un clang++.cfg junto al driver, que Clang lee por su cuenta.

MOLTO_PICKUP apunta al binario cuando no está en el PATH. C_COMPILER y CPP_COMPILER se saltan pickup por completo —y con él la verificación de [target].requires—, que es la razón de que Molto imprima un aviso cuando se usan.

Códigos de salida

Código Significado
0 Éxito
1 La operación falló; doctor encontró algo bloqueante
2 Uso inválido: comando desconocido, id de feature desconocido
3 Ninguna toolchain satisface la petición
4 No implementado: reservado, hoy nadie lo devuelve

El código 3 es distinto a propósito. «No hay nada que encaje» es una respuesta, no una avería, y quien llama tiene que poder distinguirlas. default también devuelve 3 cuando la preferencia apunta a algo que ya no está instalado.

Dónde vive todo

Todo está bajo $PICKUP_HOME, o ~/.pickup, así que instalar nunca pide permisos de administrador:

Ruta Contenido
cache/ El inventario probado y los catálogos del registry, con TTL de una hora
downloads/ Archivos en tránsito, eliminados una vez instalados
toolchains/ Un directorio por toolchain instalada, <vendor>-<versión>-<target>
tools/ El formateador y el linter, aparte
config.toml Las decisiones del usuario: default y registry

cache/ y downloads/ se pueden borrar en cualquier momento; el coste es un rescan, no una respuesta equivocada. La caché del inventario se invalida por la ruta, la mtime y el tamaño de cada binario, y por la huella del catálogo de capacidades: añadir una feature vuelve a probarlo todo.

Qué registry es de fiar lo deciden, en este orden, $PICKUP_REGISTRY_URL, luego registry = "…" en ~/.pickup/config.toml, y luego el valor compilado por defecto. Pickup lee esa clave y nunca la escribe: un comando capaz de cambiarla sería un comando capaz de redirigir todas las instalaciones futuras.

Plataforma

Linux en la práctica, POSIX por diseño, exactamente igual que Molto. Windows y macOS están en el roadmap; MSVC existe en el enum de vendors y en ningún otro sitio.