The manifest
Every key Project.toml reads, what it emits on the command line, and the limits it refuses to exceed.
One Project.toml sits at the root of a project. Every command searches the current directory and
its ancestors for it, so molto build works from any subdirectory.
The rule the whole file rests on:
Molto discovers your sources; it does not discover your build settings.
Every .c, .cpp and .cc file under src/ and tests/ is compiled automatically — you never
list files. Include paths, defines, link libraries and test layout are only what this file says.
Nothing is inferred from the directory layout, and there is no fallback to a Makefile.
Relative paths in include and test.sources resolve against the project root, never the
directory you ran the command from. flags is passed verbatim by contract and is never rewritten.
What molto new writes
[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
That is a complete project. There is no second file and no generator step.
[package]
| Key | Type | Notes |
|---|---|---|
name |
string | Required. snake_case, must start with a lowercase letter |
version |
string | Free-form; defaults to 0.0.0 |
artifact |
string | A hard error. Every project links an executable today |
name and version reach every translation unit as MOLTO_PKG_NAME and MOLTO_PKG_VERSION, so
a program can report its own version without a header to keep in step.
artifact is refused rather than accepted and ignored: static and shared would need ar,
-shared and -fPIC, none of which exists yet.
[target]
| Key | Type | Emits |
|---|---|---|
compiler |
string | Nothing directly — gcc, g++, clang, llvm or msvc. Absent means autodetect |
std |
string | -std= for C sources |
cpp_std |
string | -std= for C++ sources |
requires |
array | Nothing — capabilities that must prove they compile |
include |
array | -I, relative to the project root |
defines |
array | -D, in the right form per compiler |
link |
array | -l, names without the prefix |
flags |
array | Verbatim, to both the compiler and the linker |
compiler is a vendor preference, not a binary. Naming a vendor rather than a path is what
keeps a manifest portable: gcc is version 9 on one machine and 14 on another. Anything outside
that list is an error.
std selects the mode; requires states what has to actually work. Accepting -std=c2x is not
the same as implementing it — a compiler may take the flag and still reject [[nodiscard]].
Molto asks pickup which local toolchain proves each capability by compiling a
program that uses it, and caches the answer in .bin/wsdb. --refresh-toolchain asks again.
Omitting std inherits the compiler’s default, which differs by toolchain and version. Declare
it.
For a linker flag that is not a library — -Wl,..., or -pthread at compile time — use flags,
not link.
Molto picks the C driver for .c and the C++ driver for .cpp and .cc, both from the same
resolved toolchain, so a mixed project never mixes compilers. A C++ project whose toolchain has no
C++ driver is reported up front, not discovered at link time.
[test]
| Key | Type | Notes |
|---|---|---|
mode |
string | per_file (default) or single |
sources |
array | Extra sources compiled for tests only; directories are walked |
defines, include, flags |
array | Applied only when compiling tests/ |
per_file builds one executable per file under tests/, each linked against the project’s
objects minus src/main.c, and each supplying its own main(). Output lands in
build/<profile>/tests/<name>.
single links everything into one binary at build/<profile>/tests/<package>_tests. That is what
a framework which registers its cases and owns main() needs.
A framework living outside src/ is not compiled at all unless sources names it. This is the
most common reason a test build fails on the framework’s own symbols.
[profile.*]
Four profiles exist: debug (the default), release, bench and custom. Select one with
molto build --profile release; output goes to build/<profile>/.
| Key | Type | Notes |
|---|---|---|
opt_level |
integer | -O |
debug_info |
bool | -g |
defines, include, flags |
array | Added on top of [target], never replacing it |
Defaults when the table is absent:
| Profile | opt_level |
debug_info |
|---|---|---|
debug |
0 | true |
release |
3 | false |
bench |
3 | false |
custom |
2 | true |
Changing any compile setting triggers a recompile: Molto records the exact command per object and rebuilds when it differs.
[env]
[env]
MY_APP_LOG = "debug"
Keys are variable names and values must be strings. They are exported into the compiler and linker
invocations, and into the program under molto run and molto test. The variables are set in
the child process after forking, so Molto’s own environment is never modified and one project’s
[env] cannot leak into another.
Limits
Exceeding one is a manifest error, never a silent truncation — dropping a flag would produce a green build using options you did not ask for.
| Limit | |
|---|---|
Entries in defines / include / flags / requires / test.sources |
16 |
| Length of one such entry | 95 characters |
Entries in link, and the length of one |
32 / 63 characters |
Entries in [env] |
32 |
[env] name / value length |
63 / 255 characters |
Unknown keys are dropped without warning
A typo in a key name simply disappears. If a setting seems to have no effect, check its spelling first.
The manifest tolerates unknown keys because RFC-0003 specifies tables this binary does not implement yet, and refusing them would reject manifests that are valid by design.
The exception is [deps] and [dev-deps], which fail closed: an unknown key, two sources or a
version range is an error naming the dependency. So do format.json and linter.json. A
dependency read wrong is a dependency that silently is not there.
Specified but not implemented
| Table or key | What happens today |
|---|---|
package.artifact |
Hard error |
dep.recipe, artifact, optional, features |
Refused, not ignored |
[features], [build-deps], [workspace] |
Not read |
target.triple, per-OS tables |
Not read |
profile.lto, strip, sanitizers, warnings_as_errors |
Not read |
Coming from a Makefile
Read CFLAGS and LDFLAGS, and put each piece under the key that owns it. Against a Makefile
passing -Iinclude -D_DEFAULT_SOURCE -Wall -Wextra -Wpedantic and building its tests as one
binary:
[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"]
That is Molto’s own manifest, minus the profiles: the repository builds itself with its own tool.
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
fatal error: 'pkg/foo.h' file not found |
No include path declared | include = ["include"] |
Tests fail to link, missing or duplicate main |
The framework owns main() |
mode = "single" |
| Tests fail on the framework’s own headers | It lives outside src/ |
test.sources and test.include |
| Expected warnings missing | flags not declared |
flags = ["-Wall", "-Wextra"] |
An #ifdef never fires |
defines not declared |
Add it to [target].defines |
undefined reference to sqrt, pthread_create |
Library not linked | link = ["m", "pthread"] |
unknown compiler '…' |
compiler names a binary |
Use a vendor, or drop the key |
| A key seems to do nothing | Typo, or not implemented | Check it against the tables above |
not inside a molto workspace |
No manifest here or above | molto init |