Skip to content

packages

Gordon Heydon edited this page Sep 17, 2026 · 7 revisions

Packages

A package is an account-shaped directory that accounts link to gain its verbs and subroutines:

mypkg/
  PKG           manifest: name / version / description / systems / dependencies
  EXPORTS       expression functions this package adds  (NAME MINARGS MAXARGS)
  NATIVE        native extension build: C sources + optional pkg-config deps
  BP/           source (mains become verbs, SUBROUTINEs become library)
  VOC/          one text record per verb:  V  then  CATALOG/<name>
  CATALOG/      built verb executables      (mkpkg output)
  LIB/          built subroutine + extension libraries (mkpkg output)

Build with scripts/mkpkg.sh mypkg — main programs compile into CATALOG/, and all SUBROUTINE sources bundle into one shared library LIB/lib<name> (one dlopen, one artifact).

For a pure-BASIC package the BUILD-PKG <dir> verb builds it natively — compiling BP/ through the runtime COMPILE primitive (no shell, no mkpkg on PATH), so an installer can build a downloaded package with only developer privilege, not the unrestricted tier a shell-out needs. It compiles each SUBROUTINE into its own LIB/<name> library (the CALL resolver loads every library in LIB/) and each main into CATALOG/<name>; the package's shipped VOC/ is left untouched. A package with a native (C) extension — a NATIVE manifest or build-native.sh — needs a compiler, so BUILD-PKG reports BUILD-PKG-NATIVE and defers to mkpkg; *.BP fallback source directories are likewise mkpkg-only.

Linking

> LINK-PKG /path/to/git
linked /path/to/git
linked /path/to/cmd          <- dependency, auto-resolved
> LIST-PKGS
git@1.0   ok   /path/to/git   requires cmd
cmd@1.0   ok   /path/to/cmd

The PKG manifest is a plain record: attribute 1 name, 2 version, 3 description, 4 systems (the MV platforms the package targets — mvx, unidata, universe, … — space-separated), 5 onward dependency names. LINK-PKG refuses a package that declares systems but not mvx; a native extension package (below) is inherently mvx-only. LINK-PKG links the whole dependency closure — satisfying each dependency against already linked packages, then a directory of that name beside the requiring package, then $MVXPKGPATH — and refuses cleanly when one cannot be found. A dependency name prefixed ? is optional: it is still linked when it resolves (so the full package overrides), but its absence is not an error — the requiring package ships a fallback under the same names, so it runs standalone with no package manager present. For example git depends on ?cmd and bundles a copy of cmd in CMD.BP (compiled into libgit); a plain clone → mkpkgLINK-PKG of git works with no cmd package installed, and picks up the full cmd automatically once it is. UNLINK-PKG refuses while another linked package depends on the target.

Linked packages join verb resolution (after the local VOC, before the system account) and their LIB/ joins CALL resolution. Package verbs run in your account, on your data. A LINK-PKG takes effect in the same session.

The cmd framework

packages/cmd is a Cobra-style command framework:

CALL CMD.INIT("GIT", "work with the account's git repository")
CALL CMD.ADD("STATUS", "working tree status", "GIT.STATUS")
CALL CMD.ADD("LOG", "recent history", "GIT.LOG")
CALL CMD.RUN

CMD.RUN parses the sentence, dispatches to the handler subroutine (via the indirect CALL @VAR), and generates help for help, no-argument, and unknown-command cases. The git package is the reference consumer — a GIT verb with subcommands in ~10 lines plus one small handler subroutine each.

Native subroutines

A package can ship subroutines written in C, not just BASIC: a C function matching the subroutine ABI (void mvx_sub_NAME(mvx_ctx *, int32_t argc, mv_value **argv)) is CALLed exactly like a BASIC subroutine. Put the sources in the package and build them into LIB/ with a build-native.sh script (mkpkg runs it); link any native dependency into that library alone. The git package does this — its git operations are libgit2-backed native subroutines, so libgit2 burdens neither the runtime nor programs that never use git.

Language extensions (native expression functions)

Beyond statement subroutines, a package can add expression functions — value-returning intrinsics usable in expressions, X = JSONENCODE(rec, spec), exactly like a built-in. A native library declares them through the extension ABI (runtime/include/mvx_ext.h):

static void ext_jsonencode(mvx_ctx *ctx, mv_value *ret, int32_t argc,
                           mv_value **argv) { ... write ret ... }
static const mvx_extfn fns[] = { {"JSONENCODE", 2, 2, ext_jsonencode}, ... };
static const mvx_ext ext = {"json", 2, fns};
const mvx_ext *mvx_ext_entry(int abi) { return abi == MVX_EXT_ABI ? &ext : 0; }

The package declares the same names in EXPORTS (NAME MINARGS MAXARGS, one per line) so the compiler accepts them, and a NATIVE manifest (source files, plus optional pkgconfig: <libs> lines) that mkpkg builds into LIB/libmvxext_<pkg>. Runtime symbols (mv_*, mvx_*, the core mapper) resolve from the host at load, like the storage drivers. The functions are also callable as statements (CALL NAME(...)), and a package shipping only a prebuilt libmvxext_<pkg> with no sources is binary-only.

Inclusion is configuration, not a flag. A package is available to compile against and run once it is either installed into the toolchain or linked into an account with LINK-PKG (the compiler reads that account's linked PACKAGES). Two CMake lists install into the toolchain, and they differ in where the package comes from:

MVX_PACKAGES packages in the mvx tree, built from source at build time (http)
MVX_BUNDLED_PACKAGES published packages, downloaded already built at install time (git)

MVPKG is always installed and is in neither list: it is how everything else is installed afterwards, so it comes with the toolchain the way the toolchain's own verbs do.

Versions are not pinned in the build: the install asks, offering each package's latest stable release first and taking it automatically when nobody is at the terminal. MVX_PACKAGE_VERSION_mvpkg and MVX_PACKAGE_VERSION_git pin an exact version instead — see Getting started for the prompt. Each download is checked against the checksum published beside it. MVPKG publishes Linux x86-64 and arm64 builds, git x86-64 only; where a package has no build, the install warns and carries on without it.

The http package

packages/http is a native extension that adds an HTTP client to the language. It lives in the mvx tree and is installed by default (MVX_PACKAGES defaults to http), so the functions are always available:

X = HTTPGET(url)               ;* GET url -> the response body on a 2xx status,
                               ;* "" on a non-2xx status or a connection error
S = HTTPGETFILE(url, path)     ;* GET url, writing a 2xx body to `path`;
                               ;* returns the HTTP status (-1 connect, -2 write)

Plain HTTP/1.1 over POSIX sockets — no external dependency — with chunked transfer decoding. HTTPS is a follow-up. HTTPGET composes with JSONDECODE for JSON APIs, and the pair is the transport the MV package manager uses to reach a registry and pull release tarballs.

Subroutine libraries

CATALOG BP MYSUB for a SUBROUTINE source builds LIB/MYSUB in the account instead of a verb. CALL resolves at runtime: symbols already in the program, then the account's LIB/, each linked package's LIB/, then the system LIB/. The subroutine ABI is frozen, so libraries built at different times interoperate.

bin commands

A package may ship standalone OS commands in a bin/ directory — executables that run from the shell, not from TCL. mkpkg.sh links whatever it finds there onto the dev PATH (build/bin, beside mvx), and an install copies them into <prefix>/bin. The git package uses this to ship mvx-git (built from C by its build-native.sh), which is why an installed toolchain has mvx-git beside mvx. See Version Control.

Clone this wiki locally