mvx-lang/mvpkg 1.23.0
MVPKG — the MultiValue package manager client repository ↗
Install
MVPKG install mvx-lang/mvpkg
rm sudo chown. Review the source before installing.About
mv_package
A package manager for MultiValue — Composer/npm for the PICK world. A pure
MultiValue BASIC client (MVPKG) talks to a small Node.js registry over
HTTP, resolves a package, downloads its release tar archive and installs it
into an account.
The repository is an MVX account (an mvx-git account, like
ev_eb): VOC/ and BP/ are directory
files, so every record is a plain tracked file and the working tree is the live
account — there is nothing to import.
Two ways to get a package
- Release (binary).
MVPKG install <name>downloads a pre-built release
tar from the registry and unpacks it whole. The client never inspects or
compiles the contents, so a release may be binary-only — pre-built
CATALOG/ executables and LIB/ libraries with no BP/ source at all — and
installs exactly like one that ships source.
- Dev / source. Clone the package's own mvx-git repository and build it with
the target platform's toolchain. This is the source-and-compile path (as
mvx-git is ported to more MV systems); it is not what MVPKG install does.
The client — MVPKG
Commands are case-insensitive (MV developers work with Caps Lock on):
MVPKG install <name> [<dest>] install a package and its dependencies
(<dest> defaults to a directory named <name>)
MVPKG install <name>:<version> install exactly that version, up or down
MVPKG install <name>@<stability> raise the stability floor for this install
MVPKG install install this account's declared set (the lock)
MVPKG update [<name>] move installed packages to the newest version
the account's stability policy admits
MVPKG remove <name> uninstall it, if nothing still needs it
MVPKG list [-a] [-l] what is installed here (-a: the whole system)
MVPKG info <name> show a package's registry metadata
MVPKG search <term> list packages whose name/description matches
MVPKG register <name> [<version>] adopt an already-installed package
MVPKG config show the registry URL and stability policy
MVPKG config <setting> [<value>] show or set one setting
MVPKG setup <url> set (and persist) the registry base URL
MVPKG init [<url>] provision this account for package management
MVPKG rebuild rebuild the host's native library (UniData)
MVPKG fixperms [<user>] hand MVPKG's files to the operator
MVPKG notify login-proc check: is a deployed package behind
--force overrides a refusal (see Changing a version, below); --source
builds from a git working copy instead of a release.
install resolves the package's dependencies first: a package names the
packages it needs in its registry metadata, and the client installs the
whole transitive set, dependencies before dependents. What is installed here is
recorded in MVPKG.LOCK, a record in the account's own master dictionary (VOC,
or MD on jBASE) — not an OS file — so a dependency already present is not
reinstalled, and an install that did nothing says so rather than exiting
quietly. On UniData this means installing an app that depends on curses pulls
the native bridge in and rebuilds the shared library — one command, nothing
manual.
Dependency syntax. Each entry in the dependencies list is a package name
with an optional ? prefix and three optional suffixes,
[?]name[@system][:constraint][@stability]:
?— optional. The dependency is installed like any other when the
registry has it, but a missing one is skipped with a warning instead of
failing the install. Use it for add-ons an app uses when present and does
without otherwise (e.g. ?udt_curses@udt); the app guards its use at run
time with CALL MVPKG.HAS(name, ok) (or CALLC.EXISTS for a native probe).
@system— a platform gate.name@udt,mvxapplies only on those systems;
name@!mvx applies everywhere except those. A package built into the
runtime on one platform declares the fallback for the others, e.g.
mapfield@!mvx (MAPFIELD is a builtin on MVX, a package on UniData).
:constraint— a version constraint, Composer/npm style:
mapfield:^1.2 (>=1.2.0 <2.0.0), ~1.4.0 (>=1.4.0 <1.5.0), >=1.0,<2.0
(comma = AND), 1.2., an exact 1.4.2, or for any. The client picks
the newest published version satisfying it.
@stability— a stability floor for this one dependency, raising it above
the project's: thing@beta, or after a constraint, thing:^1.2@beta.
All may appear together, in that order: ?udt_curses@udt:^1.0@beta.
> @ carries two meanings, and the word decides. @udt is a platform gate;
> @beta is a stability floor. The stability vocabulary is closed — dev,
> alpha, beta, rc, stable — and shares no member with the system names,
> so the two never collide. On the command line a third reading applies:
> name@<git-ref> builds from source (--source settles it).
Release channels. A registry version is stable unless it carries a
pre-release suffix (1.3.0-beta.1, 2.0.0-rc.2, 1.0.0-dev). The default
"latest" a bare MVPKG install <name> (or an unconstrained dependency)
resolves to is always the newest stable release, so following a stable
series is the default — a published beta never pulls anyone off it.
Minimum stability (Composer's minimum-stability) is how a project opts
into pre-releases as a policy rather than one constraint at a time:
MVPKG config minimum-stability beta # accept beta and rc as well as stable
MVPKG config prefer-stable on # ...but still follow the stable line
MVPKG config # show what is set
The floor is stored in the account manifest beside the declared dependencies, so a checkout carries its policy with it. Two rules make it predictable:
- The floor admits;
prefer-stabledecides what to do with the choice. Off
(the default, as in Composer) the newest admissible version wins outright — so
a floor of beta on a package whose newest release is 2.1.0-beta2 gets that
beta. On, a package takes the newest stable version satisfying its
constraint and drops to a pre-release only when nothing stable does. At a
floor of stable the setting has nothing to decide.
- Nothing lowers the floor for a package that did not ask. A per-package
@beta raises tolerance for that one dependency; there is no spec that makes
a dependency accept less than the project does.
For a single install without changing the project's policy, name the stability on the command line:
MVPKG install mvx-lang/cmd@beta
The spec is recorded in the manifest as typed, so a later bare MVPKG install
reproduces the same decision. (On the command line name@word is a git ref when
the word is not a stability — install thing@my-branch still builds from
source, and install thing@dev --source keeps a branch genuinely named dev
reachable, since --source says a ref was meant.)
Pinning still works and is unchanged: a constraint whose lower bound is itself a
pre-release opts in for that one package without any floor, e.g.
thing:^1.3.0-beta matches 1.3.0-beta.1, 1.3.0-rc.1, and the eventual
1.3.0.
MVPKG.LOCK records each resolved version's stability beside it, so a lock
shows what kind of build it pins.
Changing a version
update moves forward, never back: it resolves under the account's stability
policy — the same rules install uses — and leaves a package alone when what is
installed is already newer than anything the policy admits.
MVPKG update mvx-lang/git
mvx-lang/git 2.1.0-rc1 is newer than 2.0.3, the newest stable release — leaving it
To move to a particular version, say so. That works in either direction:
MVPKG install mvx-lang/getopt:1.0
A version is not a private decision, though — other installed packages declared ranges against this one, and a downgrade can walk out from under them. So it is checked first, and refused rather than warned:
mvpkg: mvx-lang/cmd needs mvx-lang/getopt ^1.1, and 1.0 does not satisfy it
refusing to install mvx-lang/getopt 1.0 (use --force to do it anyway)
--force does it anyway, and prints what it is overriding.
The registry URL is taken from $MVPKG_REGISTRY, then a persisted mvpkg.conf,
then the built-in default (https://mv-package.heydon.io).
Portability. The only non-MultiValue operations — unpacking a tar, making a
directory — go through the MVPKGOS subroutine, the one per-platform seam. HTTP
itself is a language extension on MVX (the http package: HTTPGET /
HTTPGETFILE); another MV system supplies its own equivalents. The rest of
MVPKG is portable BASIC.
UniData port (udt/). UniData has neither the HTTP nor the JSON
intrinsics, so the port fills both seams — udt/HTTPGET and
udt/HTTPGETFILE via curl, and udt/JSONDECODE + udt/MAPFIELD as a
minimal flat-JSON decoder (enough for the registry's metadata) — and
udt/MVPKG is the client in UniData idiom (@SENTENCE, GETENV, OSREAD
statements) declaring those seams as cataloged functions. MVPKG install
then runs on UniData: it fetches the release, unpacks it, and — for a
package that ships a udt-callc/ contribution — rebuilds the shared CallC
library, all in one command (see below). search awaits a fuller JSON seam
(issue #4).
The registry
The registry service, website, and release/build tooling live in their own
repository — mv-package-registry
— so installing the client doesn't pull in the server. It is live at
https://mv-package.heydon.io: browse packages there, or hit the JSON API
the client speaks (/package/<name>, /search, /tarball/…). Publishing a
release and hosting your own registry are documented in that repo.
Manifest
mvpkg.json is the manifest, and the only one. It carries name, version,
description, a dependencies list (each entry [?]name[@system][:constraint]
as above) and, for resolution across platforms, a systems list.
mkrelease.sh takes the dependencies as its fifth argument. The release tar
carries the account's own .mvx and mvpkg.json.
There used to be a second manifest, PKG, carrying the same five fields in line
order. Nothing kept the two in step and they drifted — mvpkg's PKG said 1.3
where its mvpkg.json said 1.3.0, and the json package shipped the two
disagreeing about a dependency. The registry only ever read mvpkg.json, so
PKG is gone.
provides. A package may also declare a provides list — virtual names it
satisfies. It is how a package stands in for another: if udt_curses is later
ported and renamed mvx-lang/cursors, its manifest declares
"provides": ["udt_curses"], and anything that still depends on udt_curses
is satisfied by installing cursors (its provided names are recorded as
present, so MVPKG.HAS("udt_curses") is true and the dependency resolves).
Resolving a bare dependency on a virtual name whose provider is not otherwise
in the graph needs the registry's provides index (a follow-up); a provider
already in the dependency set satisfies it today.
Native code on UniData — the shared CallC library
> Status: implemented (issue #3 part 2). The manager owns and aggregates
> libu2callc.so; the per-package registry, union relink on install/remove, and
> the authoritative cfuncdef for CALLC.EXISTS all ship today. The remaining
> half of #3 is optional-dependency / platform-gating support in the resolver.
Some packages reach into the host through native C: on UniData that means
functions compiled into CallC, which UniData exposes to BASIC as CALLC
NAME(...). UniData loads exactly one libu2callc.so, so native add-ons
cannot each own it — they must be aggregated. mv_package owns that
library on UniData.
A package that compiles into UniData ships a udt-callc/ directory — its
contribution:
udt-callc/
*.c C sources compiled into the library (or *.o for a binary release)
funcs cfuncdef declarations, one per line: name:rettype:nargs:argtypes
libs optional: a line of extra linker flags for this package
MVPKG install unpacks the release and then calls MVPKGOS("CALLC", …); the
UniData port stages the contribution into $UDTHOME/callc.d/<pkg>/ and
rebuilds libu2callc.so from every contribution installed — so adding
one package never disturbs another, and removing one and rebuilding drops it
cleanly (the system base functions are re-injected automatically). MVPKG
rebuild re-runs that aggregation on demand.
The rebuild lives in [udt/udt-callc-build.sh](udt/udt-callc-build.sh),
installed to $UDTHOME/bin/udt-callc-build by the UniData setup and driven
by [udt/MVPKGOS](udt/MVPKGOS). On MVX these ops are no-ops: native code is
an ordinary compiled subroutine library that installs as-is, with nothing to
aggregate.
Checking a capability without installing it
A program that uses an optional native add-on when present must be able to
ask before using it — and that check must work on an account where the
add-on was never installed. A CallC to an unregistered function aborts the
whole program (no trappable error), so the probe cannot itself be a CallC.
[udt/CALLC.EXISTS](udt/CALLC.EXISTS) is pure BASIC: it reads the canonical
CallC definition and returns 1/0, never invoking anything.
The UniData setup ([udt/setup.sh](udt/setup.sh)) catalogs it globally,
so it is present on every account regardless of what is installed. A program
guards on it and runs either way:
DEFFUN CALLC.EXISTS(A)
IF CALLC.EXISTS("CURSINIT") THEN
GOSUB FULL.SCREEN.UI ;* udt_curses is installed — use it
END ELSE
GOSUB PLAIN.UI ;* runs on a bare account too
END
Only CALLC.EXISTS itself must exist, and the setup guarantees it does. Set
up a UniData host once with:
UDTHOME=/usr/ud83 ./udt/setup.sh /path/to/an/account
which installs the aggregator and catalogs the probe. See the reference add-on udt_curses (mv_package issues #2, #3).
Build and test
MVX_HOME=/path/to/mvx-lang ./build.sh # catalog MVPKG + MVPKGOS
MVX_HOME=/path/to/mvx-lang ./tests/run.sh # the portable BASIC unit tests
build.sh needs an mvx-lang checkout with a built toolchain (the http and
json extension packages are installed by default). Installing runs tar
through EXECUTE "!...", so it needs the unrestricted privilege tier
(MVXPRIV=unrestricted) — installing software is inherently privileged. The
end-to-end install-loop test (client + registry) lives in the
registry repo.
License
GPL-2.0-only. See [LICENSE](LICENSE).