Build a Mono Assembly from a .netmodule with al

Mono's al command bolts an assembly manifest onto a bare .netmodule, turning a compiler's leftover into a library the runtime will load. The examples use al from mono-devel version 6.8.0.105+dfsg-3.6ubuntu2. Allow about fifteen minutes if Mono and a compiler are already installed.

This is a developer tool, so the normal commands do not need sudo. You need a shell, al, and one or more module files. The linker combines modules and resources into an assembly manifest; it is not monolinker, which trims assemblies down to the code they actually use.

1. Check the installed tool

Confirm the command and package are actually the ones you expect. Read-only, so fire away:

$ command -v al
/usr/bin/al
$ dpkg-query -W -f='${Package} ${Version}\n' mono-devel
mono-devel 6.8.0.105+dfsg-3.6ubuntu2
$ al --help | head -3
Mono Assembly Linker (al.exe) version 6.8.0.105
Usage: al [options] [sources]
Options: ('/out' must be specified)

Checkpoint: the installed help uses slash-prefixed spellings such as /out, while the local manpage documents the equivalent hyphen form such as -out. Both forms worked in the installed version, a small leftover of al's Windows ancestry. Pick one style and use it consistently in scripts.

2. Create a small module for a safe test

Already have a module? Skip to step 3. Otherwise, compile this harmless class as one. Everything the compiler writes stays under a temporary directory:

$ work=$(mktemp -d /tmp/al-guide.XXXXXX)
$ printf '%s\n' 'public class Greeter { public static string Message() { return "linked"; } }' > "$work/Greeter.cs"
$ mcs -target:module -out:"$work/Greeter.netmodule" "$work/Greeter.cs"
$ file "$work/Greeter.netmodule"
/tmp/al-guide.XXXXXX/Greeter.netmodule: PE32 executable (DLL) (console) Intel 80386 Mono/.Net assembly, for MS Windows, 2 sections

The random directory name and exact file description will vary. What matters is that the compiler exits successfully and the .netmodule exists. A module carries metadata but no assembly manifest, and that manifest is exactly the part al bolts on next.

3. Link the module into a library

Give al an output name, a target kind and at least one source. Everything it changes stays inside the temporary directory:

$ al -target:library -out:"$work/Greeter.dll" "$work/Greeter.netmodule"
$ printf 'al status: %s\n' "$?"
al status: 0
$ file "$work/Greeter.dll"
/tmp/al-guide.XXXXXX/Greeter.dll: PE32 executable (DLL) (console) Intel 80386 Mono/.Net assembly, for MS Windows, 3 sections

-target:lib is the short target spelling. The other documented targets are exe for a console executable and win or winexe for a Windows executable. An output path is never optional: the installed help demands /out outright, and a missing source gets you an error rather than a quietly empty assembly.

4. Inspect the manifest

Use Mono's disassembler to confirm the result actually has an assembly manifest. Nothing here executes the linked code:

$ monodis --assembly "$work/Greeter.dll"
Assembly Table
Name:          Greeter
Hash Algoritm: 0x00008004
Version:       0.0.0.0
Flags:         0x00000000
PublicKey:     BlobPtr (0x00000000)
    Zero sized public key

Yes, Hash Algoritm is spelled that way, and it belongs to this installed monodis version, not a typo you introduced. The checks that matter are the assembly name, the version and a successful command. Need a particular version? Pass -v:1.2.3 to al; the manpage also allows * to auto-generate the remaining version numbers.

5. Repeat the link with a response file

Response files keep a long command readable, and al accepts them with @filename. Put one option or source per line:

$ printf '%s\n' \
    '-target:library' \
    "-out:$work/Response.dll" \
    "$work/Greeter.netmodule" > "$work/al.rsp"
$ al "@$work/al.rsp"
$ test -s "$work/Response.dll" && printf '%s\n' 'response-file link OK'
response-file link OK

Warning: keep response files private when they name a signing key path or other sensitive build details, and never put a private strong-name key itself inside one. For signing, -keyfile:FILE expects a strong-name key file, and a full key pair is required unless you deliberately opt into delay signing. Treat signing as a release step, not something you bolt on while troubleshooting.

6. Diagnose the common failures

A non-zero result usually means the input or a required option is wrong. Re-run with -fullpaths if an error names a file ambiguously. Check that every source exists, that -out names a writable destination, and that the target matches the intended output.

Do not add -base just to silence an error: the local manpage flags base-address support as unimplemented. Likewise, do not confuse al with monolinker, and do not assume a successful manifest build has merged the module's contents into a single native binary. The resulting assembly still leans on the managed inputs and runtime conventions of the application it ends up in.

Recovery: if a link overwrote an output you still needed, stop using that path and restore it from your build artefact or source-control copy. The examples above use fresh temporary names, so there is no persistent undo operation to run here.

Done means