El manifiesto
Todas las claves que lee Project.toml, qué emiten en la línea de comandos y los límites que se niega a sobrepasar.
Un único Project.toml vive en la raíz del proyecto. Todos los comandos lo buscan subiendo desde
el directorio actual, así que molto build funciona desde cualquier subdirectorio.
La regla sobre la que descansa el archivo entero:
Molto descubre tus fuentes; no descubre tus ajustes de build.
Cada archivo .c, .cpp y .cc bajo src/ y tests/ se compila automáticamente: nunca
enumeras archivos. Los include, los defines, las librerías y la disposición de los tests son
únicamente lo que diga este archivo. Nada se deduce de la estructura de directorios, y no hay
vuelta atrás a un Makefile.
Las rutas relativas de include y test.sources se resuelven contra la raíz del proyecto,
nunca contra el directorio desde el que lanzaste el comando. flags se pasa literalmente por
contrato y nunca se reescribe.
Lo que escribe molto new
[package]
name = "my_app"
version = "0.1.0"
[target]
std = "c17"
include = ["include"]
[profile.debug]
opt_level = 0
debug_info = true
[profile.release]
opt_level = 3
debug_info = false
Eso es un proyecto completo. No hay un segundo archivo ni un paso generador.
[package]
| Clave | Tipo | Notas |
|---|---|---|
name |
string | Obligatoria. snake_case, debe empezar por minúscula |
version |
string | Libre; por defecto 0.0.0 |
artifact |
string | Error duro. Hoy todo proyecto enlaza un ejecutable |
name y version llegan a cada unidad de traducción como MOLTO_PKG_NAME y MOLTO_PKG_VERSION,
así que un programa puede reportar su propia versión sin una cabecera que mantener sincronizada.
artifact se rechaza en vez de aceptarse y luego ignorarse: static y shared necesitarían ar,
-shared y -fPIC, y nada de eso existe todavía.
[target]
| Clave | Tipo | Emite |
|---|---|---|
compiler |
string | Nada directamente: gcc, g++, clang, llvm o msvc. Ausente significa autodetección |
std |
string | -std= para las fuentes C |
cpp_std |
string | -std= para las fuentes C++ |
requires |
array | Nada: capacidades que deben demostrar que compilan |
include |
array | -I, relativo a la raíz del proyecto |
defines |
array | -D, en la forma que corresponda a cada compilador |
link |
array | -l, nombres sin el prefijo |
flags |
array | Literal, tanto al compilador como al enlazador |
compiler es una preferencia de fabricante, no un binario. Nombrar un fabricante en vez de una
ruta es lo que mantiene portable un manifiesto: gcc es la versión 9 en una máquina y la 14 en
otra. Cualquier cosa fuera de esa lista es un error.
std elige el modo; requires declara lo que tiene que funcionar de verdad. Aceptar -std=c2x no
es lo mismo que implementarlo: un compilador puede tragarse el flag y aun así rechazar
[[nodiscard]]. Molto le pregunta a pickup qué toolchain local demuestra cada
capacidad compilando un programa que la usa, y cachea la respuesta en .bin/wsdb.
--refresh-toolchain vuelve a preguntar.
Omitir std hereda el valor por defecto del compilador, que cambia según la toolchain y la
versión. Decláralo.
Para un flag de enlazado que no es una librería —-Wl,... o -pthread en tiempo de compilación—
usa flags, no link.
Molto elige el driver de C para .c y el de C++ para .cpp y .cc, ambos de la misma toolchain
resuelta, así que un proyecto mixto nunca mezcla compiladores. Un proyecto con C++ cuya toolchain
no tiene driver de C++ se reporta de entrada, no se descubre al enlazar.
[test]
| Clave | Tipo | Notas |
|---|---|---|
mode |
string | per_file (por defecto) o single |
sources |
array | Fuentes extra compiladas sólo para los tests; los directorios se recorren |
defines, include, flags |
array | Se aplican sólo al compilar tests/ |
per_file construye un ejecutable por archivo de tests/, cada uno enlazado contra los objetos
del proyecto menos src/main.c, y cada uno con su propio main(). La salida va a
build/<perfil>/tests/<nombre>.
single enlaza todo en un solo binario, en build/<perfil>/tests/<paquete>_tests. Es lo que
necesita un framework que registra sus casos y es dueño de main().
Un framework que vive fuera de src/ no se compila en absoluto salvo que sources lo nombre. Esa
es la causa más habitual de que un build de tests falle con los símbolos del propio framework.
[profile.*]
Existen cuatro perfiles: debug (el de por defecto), release, bench y custom. Se elige con
molto build --profile release; la salida va a build/<perfil>/.
| Clave | Tipo | Notas |
|---|---|---|
opt_level |
entero | -O |
debug_info |
bool | -g |
defines, include, flags |
array | Se suman a [target], nunca lo sustituyen |
Valores por defecto cuando la tabla no está:
| Perfil | opt_level |
debug_info |
|---|---|---|
debug |
0 | true |
release |
3 | false |
bench |
3 | false |
custom |
2 | true |
Cambiar cualquier ajuste de compilación provoca una recompilación: Molto registra el comando exacto de cada objeto y reconstruye cuando difiere.
[env]
[env]
MY_APP_LOG = "debug"
Las claves son nombres de variables y los valores tienen que ser cadenas. Se exportan a las
invocaciones del compilador y el enlazador, y al programa bajo molto run y molto test. Las
variables se establecen en el proceso hijo después del fork, así que el entorno de Molto nunca
se modifica y el [env] de un proyecto no puede colarse en otro.
Límites
Sobrepasar uno es un error del manifiesto, nunca un recorte silencioso: perder un flag produciría un build en verde con opciones que no pediste.
| Límite | |
|---|---|
Entradas en defines / include / flags / requires / test.sources |
16 |
| Longitud de una de esas entradas | 95 caracteres |
Entradas en link, y longitud de una |
32 / 63 caracteres |
Entradas en [env] |
32 |
Longitud de nombre / valor en [env] |
63 / 255 caracteres |
Las claves desconocidas se descartan sin avisar
Una errata en el nombre de una clave simplemente desaparece. Si un ajuste parece no tener efecto, revisa primero cómo está escrito.
El manifiesto tolera claves desconocidas porque la RFC-0003 especifica tablas que este binario aún no implementa, y rechazarlas descartaría manifiestos que son válidos por diseño.
La excepción son [deps] y [dev-deps], que fallan en cerrado: una clave desconocida, dos
orígenes o un rango de versiones es un error que nombra la dependencia. Igual hacen format.json y
linter.json. Una dependencia mal leída es una dependencia que en silencio no está.
Especificado pero no implementado
| Tabla o clave | Qué pasa hoy |
|---|---|
package.artifact |
Error duro |
dep.recipe, artifact, optional, features |
Rechazadas, no ignoradas |
[features], [build-deps], [workspace] |
No se leen |
target.triple, tablas por sistema operativo |
No se leen |
profile.lto, strip, sanitizers, warnings_as_errors |
No se leen |
Si vienes de un Makefile
Lee CFLAGS y LDFLAGS y coloca cada pieza bajo la clave que le corresponde. Frente a un Makefile
que pasa -Iinclude -D_DEFAULT_SOURCE -Wall -Wextra -Wpedantic y construye sus tests como un solo
binario:
[target]
std = "c2x"
requires = ["attr_nodiscard"]
defines = ["_DEFAULT_SOURCE"]
include = ["include"]
flags = ["-Wall", "-Wextra", "-Wpedantic"]
[test]
mode = "single"
sources = ["modules/moltest/src"]
include = ["modules/moltest/include"]
Ese es el manifiesto del propio Molto, menos los perfiles: el repositorio se construye con su propia herramienta.
Resolución de problemas
| Síntoma | Causa | Arreglo |
|---|---|---|
fatal error: 'pkg/foo.h' file not found |
No hay include declarado | include = ["include"] |
Los tests no enlazan, falta o sobra main |
El framework es dueño de main() |
mode = "single" |
| Los tests fallan en las cabeceras del framework | Vive fuera de src/ |
test.sources y test.include |
| Faltan avisos que esperabas | flags sin declarar |
flags = ["-Wall", "-Wextra"] |
Un #ifdef nunca se activa |
defines sin declarar |
Añádelo a [target].defines |
undefined reference a sqrt, pthread_create |
Librería sin enlazar | link = ["m", "pthread"] |
unknown compiler '…' |
compiler nombra un binario |
Usa un fabricante, o quita la clave |
| Una clave parece no hacer nada | Errata, o no implementada | Compruébala en las tablas de arriba |
not inside a molto workspace |
No hay manifiesto aquí ni arriba | molto init |