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.
Hardcoding the full soname you happened to link against makes a package uninstallable on systems carrying a different (possibly newer) build of the same library: a package wanting lib:libcmark.so.0.30.0 fails against a system providing libcmark.so.0.31.1. Since USM builds packages from source on the target system, a dependency is usually an API floor, not an ABI pin — express it with a version constraint on the base soname instead:
"runtime": ["lib:libcmark.so>=0.30.0"]
This is satisfied by any libcmark.so.<tail> whose trailing dotted numbers meet the operator (0.31.1 qualifies for >=0.30.0). Operators: >=, <=, == (equal, prefix-tolerant: 0.31 matches 0.31.0) and ~= (compatible release — ~=0.31.0 accepts 0.31.x only, ~=0.31 accepts 0.x from 0.31 up). The same syntax works for pc: refs, checked against the .pc file's Version: field:
"build": ["pc:gtk4.pc>=4.12"]
When to use what:
>= — the normal choice: "the API I use exists since this version".~= — when you know the library breaks ABI within the family you tested (common for pre-1.0 libraries that version their soname by minor).lib:libcmark.so.0.31.1) — only when you truly need one specific build.bin:, vapi:, …) are rejected: those names carry no version.Packages that themselves provide a pc: file may declare its version in provides ("pc:cmark.pc==0.31.1"); without a declaration the file is assumed to match the package version, and validation fails a package whose installed .pc Version: field contradicts either.
Note: constrained dependencies do not yet resolve through system package managers — only from local presence and USM packages.
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. A phase must be either all plain strings (standard) or all arrays (alternatives) — mixing strings and arrays in the same phase is a validation error.
Each inner array is a complete, self-contained candidate group. Common dependencies are repeated in every group:
"depends": {
"build": [
["bin:valac", "pc:glib-2.0.pc", "pc:gtk4.pc", "pc:libadwaita-1.pc"],
["bin:valac", "pc:glib-2.0.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.