Best practices for writing MANIFEST.usm files and creating packages that install correctly across platforms.
{
"name": "mylib",
"version": "1.2.3",
"summary": "A concise one-line description",
"licences": ["MIT"],
"flags": ["ninjaStyleProgress"],
"provides": { "lib:libmylib.so": "as-expected" },
"depends": {
"runtime": ["lib:libglib-2.0.so.0"],
"build": ["bin:valac", "pc:glib-2.0.pc"],
"manage": ["bin:bash"]
},
"execs": {
"build": "usm-scripts/build.sh",
"install": "usm-scripts/install.sh"
}
}
invercargill-sql, not InvercargillSql)One line, lowercase, no trailing period. This is what usm search shows.
lddldd shows what the linker resolved at load time on this specific machine. It is not a complete picture of what your program needs, and it often shows things you don't actually depend on:
ldd says |
Reality | Declare it? |
|---|---|---|
libselinux.so.1 |
Indirect via glib — only needed on SELinux systems | No — it's a transitive dep of glibc/glib, not yours |
libgcrypt.so.20 |
Indirect via glib (gcrypt backend) | No — same reason |
liblzma.so.5 |
Indirect via libarchive | No — if you depend on lib:libarchive.so, its own manifest declares lzma |
libglib-2.0.so.0 |
You linked against it directly | Yes — lib:libglib-2.0.so.0 |
libjson-glib-1.0.so.0 |
You linked against it directly | Yes — lib:libjson-glib-1.0.so.0 |
(missing from ldd) bin:valac |
Compile-time tool, not a library | Yes in build — ldd can't see build tools |
(missing from ldd) pc:glib-2.0.pc |
Pkg-config discovery for meson | Yes in build — ldd can't see header files |
(missing from ldd) gio:libgiognutls.so |
GIO TLS plugin loaded at runtime | Yes in runtime — ldd can't see dlopen'd modules |
Rule of thumb: declare what you link against or invoke, not what your dependencies pull in transitively. Each package's manifest handles its own dependency chain.
depends.runtime)Things the installed program needs at run time:
lib:libglib-2.0.so.0 (check with ldd ./myprogram | grep -v linux-vdso)gio:libgiognutls.so if the program uses HTTPS via GLib's GIOres: refs for share-time resources the program readsWhat NOT to put in runtime:
lib:libc.so.6 — the C library is implicit on every Linux system; musl doesn't even have itlib:libm.so.6 — same, the math library is always presentbuild)depends.build)Things needed to compile the package from source:
bin:valac, bin:gcc (gcc is not always transitive — alpine's apk doesn't pull it)bin:meson, bin:ninjapc:glib-2.0.pc, pc:json-glib-1.0.pc (these map to -dev/-devel system packages)bin:g-ir-compiler (if GIR/typelib are generated), bin:statum-mkpstm (for Statum pages)bin:python3 if the build uses Python toolspc:invercargill-1.pc (resolved from USM repositories, not the system)What NOT to put in build:
bin:bash — that goes in manage (build scripts run under the install environment's shell)depends.manage)Things needed to run the package's management scripts:
bin:bash — almost always needed (usm-scripts are bash)bin:curl — if the scripts download anythingprovides tells USM what your package installs and where to find it.
"as-expected" pathsThe default for most resources — USM knows the standard install location:
"lib:libmylib.so": "as-expected",
"inc:mylib.h": "as-expected",
"pc:mylib.pc": "as-expected",
"vapi:mylib.vapi": "as-expected"
USM derives the path from Paths.get_suggested_base_path_for_type() + the resource name.
For non-standard locations:
"bin:myhelper": "usr/lib/myapp/myhelper"
Provide what downstream packages might want to depend on:
lib:<soname> — the shared library (use the soname, not the full path)bin:<name> — installed executablespc:<name>.pc — the pkg-config fileinc:<name>.h or inc:<dirname> — headers (use the include directory name for multi-file headers)vapi:<name>.vapi — Vala API definitionsgir:<name>.gir — GObject introspection XMLtypelib:<name>.typelib — compiled introspection dataDon't provide internal implementation details that nothing else should link against.
When a package can be satisfied by either of multiple dependency sets, use the nested array form:
"depends": {
"build": [
"bin:valac",
["pc:gtk4.pc", "pc:libadwaita-1.pc"],
["pc:gtk3.pc", "pc:libhandy-1.pc"]
]
}
USM picks whichever group is fully satisfiable, preferring the one that installs the fewest packages.
When to use groups:
When NOT to use groups:
usm-scripts/)build.sh#!/bin/bash
set -e
SRC_DIR="$(cd "$(dirname "$0")/.." && pwd)"
BUILD_DIR="$1"
PREFIX="${PREFIX:-/usr}"
LIBDIR="${LIBDIR:-lib}"
cd "$SRC_DIR"
meson setup "$BUILD_DIR" --prefix="$PREFIX" --libdir="$LIBDIR"
ninja -C "$BUILD_DIR"
Rules:
$1 — USM provides itPREFIX, LIBDIR, BINDIR, INCLUDEDIR environment variablesrm -rf the build dirinstall.sh's jobinstall.sh#!/bin/bash
set -e
SRC_DIR="$(cd "$(dirname "$0")/.." && pwd)"
BUILD_DIR="$1"
INSTALL_DIR="$2"
TYPE="$3"
cd "$SRC_DIR"
DESTDIR="$INSTALL_DIR" meson install -C "$BUILD_DIR"
Rules:
$1 = build dir, $2 = install staging dir (DESTDIR), $3 = install type (FRESH, UPDATE, DOWNGRADE)provides entries exactly — if you provide lib:libfoo.so, the library must actually land where USM expects it.usmignoreExclude files that shouldn't be in the .usmc archive:
builddir*/
*.sqlite*
db.sqlite
web-config.json
.kilo/
.git/
*.tar.xz
Always exclude:
builddir*/, build/)*.sqlite*)web-config.json, *-key*, *.env).git/, .hg/).kilo/, .vscode/, *.swp)Symptom: The resolver pulls in packages you don't need; installs are slow.
Fix: Remove transitive dependencies. If you depend on lib:libglib-2.0.so.0, you don't also need lib:libpcre2-8.so.0 — glib's own manifest declares that.
Symptom: Build fails in a container but works on the dev machine.
Fix: The dev machine has tools installed that the container doesn't. Check: bin:gcc (alpine doesn't pull it), bin:g-ir-compiler (GIR generation), bin:python3 (build tooling).
Symptom: Downstream packages can't resolve lib:libfoo.so.1 because you provided lib:libfoo.so.
Fix: Use the actual soname (what readelf -d yourlib.so | grep SONAME reports). The soname includes the version suffix.
Symptom: usm install fails on alpine because lib:libc.so.6 doesn't exist on musl.
Fix: Don't declare the C library. If you must (rare), use dependency groups with platform alternatives.
Symptom: Install fails on alpine with "Expected to find file at gir-1.0/..."
Fix: Only declare gir: and typelib: in provides if gobject-introspection is available on all target platforms. If it's not, omit them — they're only needed for Python/JS bindings.
# Bump patch version (1.2.3 → 1.2.4)
usm manifest bump patch
# Bump + git commit + tag
usm manifest bump minor --git
# Bump + commit + tag + push
usm manifest bump major --git --push
# Set explicit version
usm manifest bump 2.0.0
# Manage the rebuildDependants flag
usm manifest bump patch --rebuild-dependants
usm manifest bump patch --no-rebuild-dependants
Only set rebuildDependants when the update includes C API changes that break downstream compilation. Don't set it for bug fixes or internal changes.
# Validate the manifest
usm manifest validate
# Build + install to a scratch DESTDIR
USM_DESTDIR=/tmp/kilo/test-stage usm manifest install
# Package it
usm manifest package
# Add to a scratch repository and test resolution
cd /tmp/kilo/test-repo && usm repository init test && usm repository add ../mylib-1.0.0.usmc
USM_CONFIGDIR=/tmp/kilo/test-config usm install mylib
Verify that every provides entry actually exists at the expected location in the DESTDIR. If it doesn't, either the install script or the manifest is wrong.