Docs
Plugins
Declaring what a plugin exports, what adm build packs into an .admplugin, and how it shares the runtime with its host.
Declaring a plugin
A plugin is a compilation unit like an application, without an entry point: plugin Name { } at file scope, partial allowed, its own use list. What makes it a plugin is the export list: the modules and symbols a host may reach, written with the same selectors as use. Everything else in the plugin stays private.
@manifest(version = "1.2.0")
plugin ImageTools {
use (
std.io
acme.imaging
acme.imaging.filters
)
export (
acme.imaging.filters // every non-internal symbol of the module
acme.imaging::(Image, decode) // a subset
)
def new() !none { ... } // optional lifecycle
def dispose() { ... }
}
The compiler checks the list: every module named is one the plugin uses, every symbol exists and is not internal, and the signatures of exported symbols only mention types that are exported too, so a host never sees a type it cannot name. @manifest(version = ...) sets the plugin's version; without it the version is 0.1. Exported modules must sit under the plugin's own name prefix, the same ownership rule libraries follow; see Libraries → What ships.
Building
adm build on a plugin unit produces one file, <Name>.admplugin, a zip archive that holds everything a host or an IDE needs:
manifest.json name, version, exports, entry symbol,
compiler and runtime it was built with
meta/exports.json every exported symbol: kind, signature, doc
linux-amd64/libImageTools.so the native object, uncompressed, mappable
linux-arm64/libImageTools.so one directory per platform requested
files/... what the [package] files rules of adm.toml select
(assets, headers), as for a library
The shared object exports a single symbol, the plugin's entry, and is linked position-independent with its own symbols bound, so two plugins never clash with each other or with the host. adm build --platforms linux-amd64,linux-arm64,darwin-arm64,windows-amd64 runs the build once per platform and merges the results into one package; a cross target needs the platform's sysroot, named by ADM_SYSROOT_<OS>_<ARCH>, and without one only the host platform is built. Applications built the same way land in <out>/<os>-<arch>/.
Inspecting a package
adm doc --plugin ImageTools.admplugin prints the manifest and the export index without loading any code: the modules, the symbols with their signatures and doc comments, the platforms inside, and the compiler and runtime versions it was built against. Tooling reads the same entries, so what the IDE shows for a plugin is exactly what it exports.
The shared runtime
Native code needs the ADM runtime: memory management, tasks, strings, the service registry. An application normally carries it inside its binary. A plugin cannot, because two copies of the runtime in one process would each count references and each run a scheduler, so plugins are always linked against libadm_runtime.so, placed beside the output and found through a relative rpath, and the package records which runtime version it needs.
A host that loads plugins is built with adm build --runtime shared, which gives it the same library beside its binary; the plugin then shares the host's objects, tasks and services. Static linking stays the default for everything else because a shared runtime turns every retain and release into a call through the dynamic linker, a cost only a plugin host should pay. Linux and macOS today.
Loading
The host side is the part still being built: a std.plugins module that opens a package, checks its runtime version and type layouts against the host, loads the platform's object and hands back the exported symbols, plus the plugin's new(host) receiving the host services. Until it lands, packages can be built, inspected and shipped, and a host written against the exported API can be linked to a plugin's object directly.