recipe.toml
Every artifact in this registry is published with a recipe: one TOML file describing one coordinate. It is what a client reads to decide whether to download the blob at all.
The shape
A recipe describes exactly one
(kind, name, version, target). Publishing clang for two
targets means two recipes and two archives — coordinates are immutable
one at a time, and so are recipes.
The four keys at the top are the coordinate. They are the same in both kinds, and they must match what you publish: the registry rejects a recipe whose coordinates disagree with the archive it arrives with.
kind |
toolchain, tool or package. It decides which table below is required. |
name |
Lowercase: [a-z0-9][a-z0-9._-]*. It is a path segment and a directory. |
version |
Opaque to the registry. Semver resolution is the client's job. |
target |
A triple such as linux-x86_64, or any when not platform-specific. |
sha256 or size key. Both are
computed from the archive when it is published, so a recipe cannot claim
a blob it does not describe.
Toolchains — kind = "toolchain"
A toolchain declares what it can compile. The tables
mirror what pickup resolve --format toml already emits, so
installing from this registry answers the same question probing does —
without probing.
toolchain.vendor |
Required. clang, gcc, msvc — what the driver reports itself as. |
toolchain.triple |
Required. The real target triple, e.g. x86_64-unknown-linux-gnu. |
toolchain.c_driver |
Required. Path to the C driver inside the archive. |
toolchain.cxx_driver |
Path to the C++ driver, when the toolchain ships one. |
toolchain.c / .cxx |
std, compile_flags, link_flags, runtime_dirs, stdlib — the recipe pickup resolves to. |
provides |
Capability ids the toolchain claims. Not validated against the catalogue, so a new id does not break publishing. |
kind = "toolchain"
name = "clang"
version = "19.1.0"
target = "linux-x86_64"
# Capability ids from pickup's catalogue. A declaration, not a proof:
# pickup may still probe the compiler before believing it.
provides = [
"for_decl", "static_assert", "generic", "typeof",
"attr_nodiscard", "constexpr", "nullptr", "native_bool",
"auto_type", "lambda", "structured_bindings", "concepts",
]
[toolchain]
vendor = "clang"
triple = "x86_64-unknown-linux-gnu"
c_driver = "bin/clang" # relative to the root of the archive
cxx_driver = "bin/clang++"
[toolchain.c]
std = ["c99", "c11", "c17", "c2x"]
compile_flags = []
link_flags = []
runtime_dirs = ["lib"]
[toolchain.cxx]
std = ["c++11", "c++14", "c++17", "c++20"]
stdlib = "libc++"
compile_flags = []
link_flags = []
runtime_dirs = ["lib"]
[about]
description = "LLVM C and C++ compiler, trimmed to what a build needs"
license = "Apache-2.0 WITH LLVM-exception"
homepage = "https://llvm.org"
Paths are relative to the root of the archive. A toolchain whose
c_driver is bin/clang must unpack to a
directory containing bin/clang, with no extra level in
between.
Tools — kind = "tool"
A tool declares nothing about compiling, because
nothing resolves against it. It is its own kind so that
pickup resolve can never answer with a formatter, and so
that a linker does not have to pretend to be a toolchain to be
published.
tool.kind |
Required. formatter, linter or linker — the vocabulary pickup tools already emits. |
tool.binary |
Required. The entry point inside the archive, which is not always the file: lld links only when invoked as ld.lld. |
tool.aliases |
Other names in the archive that reach the same binary. |
kind = "tool"
name = "clang-format"
version = "19.1.6"
target = "linux-x86_64"
[tool]
kind = "formatter" # formatter | linter | linker
binary = "bin/clang-format" # relative to the root of the archive
[about]
description = "LLVM's C and C++ formatter, the one molto fmt drives"
license = "Apache-2.0 WITH LLVM-exception"
A tool that parses source needs the same builtin headers the compiler
does, and carries its own copy: pickup installs tools apart from
toolchains, so there is nothing next door to borrow from.
clang-tidy is 134 MB for that reason and
clang-format, which resolves no #include, is 4
MB.
Packages — kind = "package"
A package declares what it needs to be compiled. The
keys are the ones molto already understands in
Project.toml, so a published package does not invent
settings the build system cannot read.
package.std |
C standard the sources need, as molto passes it to -std. |
package.include |
Include directories to add with -I, relative to the archive root. |
package.link |
System libraries to link with -l. |
package.defines |
Preprocessor defines, added with -D. |
package.flags |
Passed verbatim to compiler and linker; never rewritten. |
deps |
Dependencies, in the syntax molto's RFC-0003 specifies for Project.toml. |
kind = "package"
name = "yyjson"
version = "0.10.0"
target = "any" # "any" for source packages and recipes
[package]
std = "c17"
include = ["include"] # -I, relative to the root of the archive
link = ["m"] # -l
defines = [] # -D
flags = [] # passed verbatim
[deps]
# The syntax molto's RFC-0003 specifies for [deps] in Project.toml.
sqlite = "3.50.0"
[about]
description = "A high performance JSON library written in C"
license = "MIT"
Source packages and recipes use target = "any". A package
built per platform uses the same triples a toolchain does.
Publishing
From a directory holding the recipe and its archive, this is one command:
molto login
molto publish
Underneath it is two requests, in this order.
PUT /v1/{kind}s/{name}/{version}/{target}/blob streams the
archive into object storage, which verifies it hashes to
x-molto-checksum as it lands. Then
POST /v1/{kind}s, whose body is this file, records it.
The archive goes first and the row is written last — a catalogue entry
without its blob is the one inconsistency the API cannot serve around.
The recipe is stored whole, and comes back on
GET /v1/{kind}s/{name}/{version}/{target} as
metadata.
Withdrawing a version
A published coordinate is never overwritten and never deleted, so a
publish that should not have happened is withdrawn instead:
POST /v1/{kind}s/{name}/{version}/{target}/yank, and
unyank to undo it. Both take the same bearer token
publishing does.
A yank changes one column and no bytes. The download keeps working and
keeps answering x-molto-yanked: true, so a lock file that
already names the version keeps building; what changes is that a
resolution starting now will not choose it.