| git.druid.rocks | index | druid520 | mp | docs/ | guide/ | writing-a-port.btft |
docs/guide/writing-a-port.btft
[table class="topnav"]
[tr]
[td class="logotab"]mp[e]
[td][see name="index"]index[e][e]
[td][see name="guide/index"]guide[e][e]
[td][see name="reference/index"]reference[e][e]
[e]
[e]
[h1]writing a port[e]
a port is one directory, category/name (networking/curl, say), holding a single pkg.conf. ports/template/pkg.conf in the ports tree is the field-by-field reference, every key with its own inline comment. this page is the walkthrough: what to actually write, in what order, for the common cases.
the smallest real port is four lines of metadata and one phase pair:
[code]
pkg_name="hello"
pkg_ver="1.0"
pkg_fetch="git:https://example.com/hello.git"
install:
make install PREFIX=$MP_PREFIX
remove:
rm -rf $MP_PREFIX/bin/hello
[e]
fetch, patch, build, and check are all optional. a port with no build: phase just skips straight to install: against whatever fetch: (or pkg_fetch=) put in place. most real ports don't write fetch/build by hand at all: they set pkg_fetch= (a one-line git or tarball source) and inherit= one of classes/autotools.mp, cmake.mp, meson.mp, configure-make.mp, plain-make.mp, or bootstrap-make.mp, which supplies a matching build: for you.
[code]
pkg_name="hello"
pkg_ver="1.0"
pkg_fetch="git:https://example.com/hello.git"
inherit="autotools"
[e]
a class's own phases run first. anything you declare yourself under the same phase name in your own pkg.conf replaces the inherited one, it does not append to it. write your own fetch:, build:, or whatever only for the part that actually needs to differ, and inherit the rest.
classes/ lives at the REPO ROOT, a sibling of ports/ itself (same level as profiles/, sysroots/, template/) -- not ports/classes/, an easy directory to reach for by analogy with a port's own category/name nesting and get wrong (this doc's own classes weren't actually written for a long time after being named here, in part because of exactly that mix-up). inherit="cmake" resolves to classes/cmake.mp searched across every configured repo in the same priority order as a port lookup itself (reference/multi-repo.html) -- a class can ship from any repo, not only the highest-priority one.
what's actually in each of the six: autotools.mp and bootstrap-make.mp (autoreconf -fi, or ./bootstrap, then the standard ./configure/make/make install/make uninstall cycle -- bootstrap-make.mp for a checkout that ships no pre-generated configure at all and needs its own bootstrap script run first) both pull in gnu-autoconf/gnu-automake/gnu-libtool as pkg_deps; configure-make.mp is the same cycle for a checkout that already ships a working ./configure and needs no autoreconf step; plain-make.mp skips configure entirely (PREFIX=$MP_PREFIX passed straight to make); cmake.mp and meson.mp wrap their own respective build systems (cmake pulls in pkg_deps="cmake", building into mpbuild/ and removing via mpbuild/install_manifest.txt, this page's own "cleanup" paragraph above's canonical example; meson pulls in meson/ninja/pkgconf, building into build/ and removing via "ninja -C build uninstall"). every remove: in all six only runs its own uninstall command if the checkout it needs is actually still there ("[ -f Makefile ] || exit 0" or equivalent) -- remove: can genuinely run with no checkout present at all (before fetch: on a reinstall, or after "mp clean pkgname" wiped a stale one), and mp's own manifest sweep (pkg_manifest_cleanup, on by default) still deletes every file install: actually put down regardless, so a plain no-op here is always safe. write your own remove: the same defensive way if you're not inheriting one of these.
autoreconf specifically (autotools.mp, bootstrap-make.mp) needs "export TMPDIR=\"$PWD\"" before it runs, same as any nested "make -f -"-style sub-invocation inside a bigger build (gcc's own bootstrap is the other real example) -- see troubleshooting.html's "Read-only file system inside a build" entry for the full why (short version: /tmp isn't writable under the sandbox, and quite a few build tools default their own tempfile/jobserver handling to it unless told otherwise).
dependencies. pkg_deps="foo bar" covers the common "needed to build and at runtime" case. split into pkg_bdepend and pkg_rdepend only when a port genuinely needs the distinction, a codegen tool that's gone by runtime, say. a dependency can be a plain name, a version constraint (name>=1.2, name=1.2.3, name~1.2), an exact slot (name:slot), or a tag (%name, resolving to whichever provider has the lowest pkg_pref). you almost never need to list %libc, %shell, or %coreutils yourself, mp appends GLOBAL_DEPS to every port by default.
versioning. pkg_ver is whatever scheme upstream uses, or literally "git" to track head. pkg_fetch's ref template (git:https://x.git@v<ver_>) lets "mp install name=1.2.3" pin an exact version by substituting it into the fetch itself. write your own fetch: phase instead for anything a one-liner can't express, svn, auth, picking among several mirrors.
optional features. pkg_use="ssl:%openssl x11:libxcb,libx11 debug" declares three flags: ssl pulls in %openssl when enabled, x11 pulls in two plain deps, debug is a bare bit with no deps of its own (exported as USE_debug=1). a user turns one on globally (USE=ssl in mp.conf) or per-package (a "pkgname:" section's own indented USE= line). "mp use curl" lists what curl declares and its current state; "mp use curl +ssl" toggles and persists it. the "+flag"/"-flag" prefix is COMMAND syntax only, for "mp use" -- a raw USE= line inside mp.conf itself (global or per-package) takes a plain space-separated list of bare flag names with no +/- prefix at all ("USE=ssl x11", never "USE=+ssl -x11"): a leading +/- there isn't stripped, it becomes part of the flag's literal (and therefore never-matching) name. and since a "pkgname:" section is just a plain key/value section, writing the same "pkgname:" header twice in one mp.conf doesn't merge the two -- the second occurrence's USE= silently overwrites the first's, with no warning. every flag defaults OFF unless something says otherwise -- prefix the flag's own name with "+" in its pkg_use declaration ("pkg_use=\"+alwayson offbydefault\"") to flip that default to ON instead, for when a port should keep working exactly as before with nothing configured at all (meta/mp's own USE flags, one per core command plus one per extension, all work this way -- see reference/extensions.btft). a real PER-PACKAGE USE= (even an explicitly empty one) always wins outright over a declared default -- "mp use pkg -lastflag" against a port with no other flags left enabled persists that as genuinely nothing, not "back to the declared defaults." a GLOBAL USE= is different: it was never written with any one port's own defaults in mind (it's meant to flip on a same-named flag on whatever port happens to declare it, e.g. USE=ssl for curl), so it ADDS to a port's declared defaults instead of replacing them -- an unrelated global USE=ssl doesn't turn OFF some other port's own "+alwayson".
patches. drop *.patch files in patches/ next to pkg.conf and they apply automatically, sorted by filename, no patch: phase needed. patches/use-<flag>/ and patches/nouse-<flag>/ gate on a use flag by directory alone. patches/patches.conf adds per-patch conditions (IF_DEP, IF_VER, IF_USE), explicit AFTER= ordering when filename order isn't enough, and DEPS+= when a specific patch needs its own extra dependency. see [see name="reference/patches-conf-example"]patches.conf.example[e]'s closing example for a "one use flag drives a patch, a dependency, and a hook together" walkthrough.
slot, if this port needs to coexist with other builds of itself. pkg_slot="12" installs into MP_PREFIX/name-12 instead of flat, referenced as name:12. most ports never set this. pkg_slot_use goes further: list use flags there and each one's enabled state contributes to the effective slot automatically. curl built with USE=ssl and curl built without coexist as curl:ssl and curl with zero extra pkg_slot bookkeeping. a dependent port just writes pkg_deps="curl:ssl" like any other slot reference and gets that exact build's prefix wired into its own build flags automatically.
conflicts. pkg_conflicts="other-libc" (or !other-libc for a warn-only soft conflict) when two ports genuinely can't coexist, see ports/libcs/musl and glibc for the canonical case. checked both directions and matched by bare name regardless of which slot is actually installed, so you write it the way you'd expect: the plain name, no slot suffix, even if it's commonly installed slotted. a user can override this per-package via CONFLICTS= in mp.conf (or "mp conflicts pkgname +token/-token", a convenience wrapper over the same thing) -- same +/- delta convention as USE=, see reference/config.btft.
tags. pkg_tags="cc" declares this port as a provider of the %cc tag; pkg_pref="50" (lower wins) ranks it among other providers when more than one exists. only needed if this port is genuinely interchangeable with something else, most ports declare no tags at all.
cleanup. remove: only needs to handle what genuinely needs its own logic -- once it (or del.sh) has run, mp sweeps whatever this port's own install manifest still lists and deletes anything still on disk, so you don't need to hand-mirror every file install: touched back out in remove: yourself. pkg_manifest_cleanup="no" turns this off for a port that already manages its own complete removal (upstream "make uninstall", cmake's install_manifest.txt via classes/cmake.mp) or that deliberately leaves specific files behind on purpose, see meta/mp's own pkg.conf for why removing "mp" itself can't touch the mpx/mp binaries every other port's own remove needs to run at all.
testing. "mp dryrun yourport" resolves the whole dependency graph without installing anything (add --force to skip the static-conflict pre-check the same way "mp install --force" does). "mp verify" sanity-checks every port in the tree parses and has no dependency cycle. RUN_TESTS=yes, globally or per-package, opts a check: phase into running. see test/ in the mp repo for how this project tests its own resolution/slot/patches/hooks machinery end to end, if you're touching mp itself rather than just adding a port.
a small toolchain exists specifically for writing a port, not just installing one already finished, each one earned by a real bug an install/remove cycle caught the hard way:
[list]
[list-item]mp new (category/name): scaffolds a new port -- picks a build-system class interactively (or via --class=), asks for the fetch url and a few basics, and writes a pkg.conf (plus add.sh/del.sh only for --class=custom) with the right shape already in it: --depth 1, CFLAGS/LDFLAGS forwarding, a del.sh pattern that actually matches the class instead of guessing at "make uninstall". runs mp lint on its own output before it finishes.[e]
[list-item]mp lint (port names, or none for the whole tree): fast, no-build checks against pkg.conf/add.sh/del.sh -- a cmake port whose remove doesn't use install_manifest.txt, a build tool invoked with nothing in pkg_deps that could provide it, pkg_ver="git" fetching a versioned tarball instead, a remove phase that cd's somewhere install's own fetch never created, a bin/ symlink install creates that remove never mentions.[e]
[list-item]mp devtest (a port name): the real cycle -- clean any stale stage dir or ghost db entry first, install for real, a best-effort smoke check that a real binary landed and runs, remove, then verify every file the manifest listed is ACTUALLY gone from disk afterward (not just that the manifest itself disappeared -- a del.sh can exit 0 and still leave files behind).[e]
[list-item]mp depcheck (a port name; needs it genuinely installed -- mp devtest --keep first): runs ldd on everything in its manifest and flags any linked library whose owning package isn't covered by pkg_deps -- catches a real missing runtime dependency mp lint's static text search can't see.[e]
[list-item]mp doctor (optionally --fix): reports, and with --fix safely clears, db entries marked installed with nothing on disk to back them up -- but never touches a no-manifest entry that still has something plausible installed (a real, if pre-manifest-tracking, install), only ones truly gone.[e]
[list-item]mp fetchcheck (port names, or --all for the whole tree): actually reaches out to a port's fetch url -- unreachable is a hard error, and a git source's last commit being years old is flagged too, reachable is not the same as maintained.[e]
[e]
every one of these (mp doctor excluded, it has no single port to point at) also takes --dir=<root>, searched the same way CUSTOM_PORTS_DIR already is -- first, ahead of every configured repo. "mp new category/name --dir=/some/scratch/place" writes /some/scratch/place/category/name instead of the first configured repo's ports dir; "mp lint/devtest/depcheck/fetchcheck category/name --dir=/some/scratch/place" (the identical --dir=) then finds it there. lets a port be scaffolded, linted, and fully round-tripped through a real install/remove cycle before it's added to any repo at all.