Reader: someone packaging a tool or a prebuilt library so that mcpp projects can declare it and mcpp installs it.
The question this chapter answers: what is an xim: payload made of, and
what must its descriptor say for a consumer to name it and get a working
program.
Not here: publishing a source package others import, which is
11 — Publishing a Library; making a host
library reachable from an artifact, which is
33 — Authoring a Runtime Adapter; and consuming a
payload, which is 23 — The Project Environment.
Before: 31 — Authoring a Rule Package, whose rules declare the payloads they drive. After: 33 — Authoring a Runtime Adapter.
Everything mcpp installs and does not compile: a compiler, a shader compiler, a
device toolkit, an emulator, a probe driver, a prebuilt C library. A project
names one in [xlings.workspace], or a rule package names it in
[feature-xlings.<f>], and mcpp provisions it before the build runs.
A payload lives in xim-pkgindex as one Lua file: a package table that
describes it, and two functions that place and register it.
The shortest one that works:
package = {
spec = "2",
name = "glslang",
description = "Khronos reference GLSL/ESSL front end and validator",
licenses = {"BSD-3-Clause", "Apache-2.0", "MIT"},
type = "package",
archs = {"x86_64"},
xpm = {
linux = {
["latest"] = { ref = "15.1.0" },
["15.1.0"] = {
url = {
GLOBAL = "https://github.com/…/glslang-15.1.0-linux-x86_64.tar.gz",
CN = "https://gitcode.com/…/glslang-15.1.0-linux-x86_64.tar.gz",
},
sha256 = "87167c9cb32f258addbedb607639b2c1f484c029ba91542a92f19ead21d65d13",
},
},
},
}
function install()
local dir = pkginfo.install_dir()
os.tryrm(dir)
os.mv("glslang-15.1.0", dir)
return true
end
function config()
xvm.add(package.name)
return true
endinstall() places the extracted tree where mcpp will look for it; config()
registers what the payload offers. Everything else in this chapter is one of
those two doing more.
When mcpp installs a package a project depends on, the package's install()
runs with the build's target in its environment, under the names and by the rule
a build program uses (build.mcpp): every variable is present,
and empty when it has no value, so a hook never reads a value inherited from the
process that started mcpp.
| Variable | Value while a dependency installs |
|---|---|
MCPP_TARGET |
the target triple the build was asked for, or the host triple for a native build |
MCPP_TARGET_OS, MCPP_TARGET_ARCH, MCPP_TARGET_ENV |
the segments of that triple |
MCPP_COMPILER, MCPP_CXX_STDLIB |
empty |
The toolchain values are empty because the toolchain is resolved after the
dependency graph: a package in the graph may supply a target-side layer, so no
compiler or standard library has been decided when a dependency installs, and a
hook that guessed one could build the wrong variant. On Windows an empty variable
is an absent one, and os.getenv answers nil for it. The hook of a toolchain
payload receives none of these variables.
A hook may use the target to refuse or to diagnose. It must not build a variant
into a store directory whose name does not state the variant: the store is keyed
by package and version, so the first consumer would decide the variant for every
later one. A package compiled against one C++ standard library states that with
requires = ["mcpp:c++-abi=libstdc++"], which is checked once the toolchain is
resolved (22 — The Target Side).
One version, two URLs. Every version carries a GLOBAL and a CN URL and
one sha256. The two mirrors serve the same bytes; a consumer behind either
mirror resolves the same hash, and a descriptor with one URL is unusable for
half the ecosystem.
latest is a reference, not a version. ["latest"] = { ref = "15.1.0" }.
It is what a consumer gets when it names no version, and moving it is a
deliberate act — a consumer pinned to 15.1.0 is unaffected.
archs and the platform table are what a resolution reads. A payload
published only for linux and x86_64 says so, and a consumer on another
platform is refused by name rather than handed something that will not run.
Dependencies are xim: addresses with floors.
deps = { "xim:gcc-runtime@>=15", "xim:glibc@>=2.38" },A payload that only unpacks is not usable. Three declarations turn a directory into something a build can consume.
A program on the path. xvm.add(package.name) registers the payload's
bin/ so mcpp can find the program by its bare name. A rule package should
name the program, not a path — mcpp searches the bin/ of every declared
payload and then PATH, and can report exactly which directories it searched.
Libraries a consumer will link or load.
exports = {
runtime = { libdirs = { "lib" } },
},elfpatch reads this from each dependency and writes the consumer's RPATH,
which is what makes a stack of payloads resolve without anyone setting
LD_LIBRARY_PATH.
Headers, so a compiler in this environment can build against it.
sysroot.declare_libs(...) and the header declaration place the payload into
the SubOS sysroot view. Declared rather than copied — xlings removes them
with the package, and a copy would outlive its owner.
"xim:qemu-arm" = { version = "9.2.4-1", when = "run" }when is a second, independent gate beside the feature that selects the
payload. The feature says who needs the tool; the tier says when. An
emulator is needed to run and not to compile, so a CI job that builds firmware
and never flashes it downloads nothing.
- A payload is published to
xim-pkgindex, which is a separate repository with its own review; nothing in mcpp publishes one. latestand "the highest version in the table" are two different questions, and a consumer that wants the newest published version nameslatest.- A payload's own CI cannot verify that a consumer resolves it: that is what a sandbox check against the published descriptor is for, and it is the only thing that verifies the published bytes rather than the working tree.