Projects and packagesalpha
Lay out a multi-file project, use mops packages, import local libraries and Candid interfaces.
A moxzi project is a normal mops project: a mops.toml at the top, sources below it, and one entry file per canister. moxzi build reads mops.toml itself, so you don't pass package flags.
A project with dependencies#
greeter/
mops.toml
src/
Main.mo
lib/Greeting.mo
[package]
name = "greeter"
version = "0.1.0"
[dependencies]
core = "2.5.0"
import Text "mo:core/Text";
module {
public func format(name : Text) : Text {
"Hello, " # Text.toUpper(name) # "!"
};
};
import Greeting "lib/Greeting";
actor {
public query func greet(name : Text) : async Text {
Greeting.format(name)
};
};
Install the dependencies with mops, then build:
mops install
moxzi build src/Main.mo -o main.wasm
src/Main.mo -> main.wasm + main.did (246508 bytes, 15 file(s), 146 steps, 2.07s)
compiled by ~/.local/lib/moxzi/compiler.wasm (sha256 9294817c6e9069cbd0e712d0e9dde0ec1b6893d0e7b42d798fa3dd4c8cd772cb), moxzi 0.1.0-alpha.10
15 file(s) counts your two files and the core modules they import.
moxzi asks mops sources where each package lives, so you need mops installed and on your PATH. If mops is installed somewhere moxzi can't find, set MOXZI_MOPS to its path.
Imports#
| Import | Resolves to |
|---|---|
import G "lib/Greeting" | lib/Greeting.mo, relative to the importing file |
import P "lib/parser" where parser is a directory | lib/parser/lib.mo |
import Text "mo:core/Text" | Text.mo in the core package's source directory |
import S "mo:slug" | lib.mo in the slug package |
import C "idl:../interfaces/counter.did" | A Candid interface. See below |
An import that can't be found fails with M0020, unresolved import.
Which files are in a build#
moxzi deps lists every file a build reads, under the path the compiler sees:
moxzi deps src/Main.mo --paths-only
/pkg/core/Array.mo
/pkg/core/Char.mo
/pkg/core/Iter.mo
/pkg/core/Option.mo
/pkg/core/Order.mo
/pkg/core/Result.mo
/pkg/core/Runtime.mo
/pkg/core/Stack.mo
/pkg/core/Text.mo
/pkg/core/Types.mo
/pkg/core/VarArray.mo
/pkg/core/internal/SortHelper.mo
/pkg/core/pure/List.mo
/src/src/Main.mo
/src/src/lib/Greeting.mo
Your project's files appear under /src/, followed by their path from the project directory (so src/Main.mo becomes /src/src/Main.mo). Package files appear under /pkg/NAME/. Error messages use the same paths.
The project directory is the nearest directory above the entry file that contains a mops.toml or dfx.json. Override it with --root. Keep every source file under it: a file outside the project is compiled as /ext/FILENAME, and two such files with the same name collide.
Local packages without mops#
--package NAME PATH maps mo:NAME to a directory, as moc --package does. It repeats, and it works alongside mops.toml:
module {
public func slug(name : Text) : Text {
name # "-1"
};
};
import Slug "mo:slug";
actor {
public query func slug(name : Text) : async Text {
Slug.slug(name)
};
};
moxzi build src/Slugger.mo --package slug vendor/slug
src/Slugger.mo -> src/Slugger.wasm + src/Slugger.did (246388 bytes, 2 file(s), 51 steps, 1.07s)
compiled by ~/.local/lib/moxzi/compiler.wasm (sha256 9294817c6e9069cbd0e712d0e9dde0ec1b6893d0e7b42d798fa3dd4c8cd772cb), moxzi 0.1.0-alpha.10
moxzi deps takes the same option.
Importing a Candid interface#
To call a canister whose Motoko source you don't have, import its .did file with idl:. The path is relative, like any other import:
service : {
inc : () -> (nat);
get : () -> (nat) query;
}
import Counter "idl:../interfaces/counter.did";
actor {
transient let counter : Counter.Self = actor ("bkyz2-fmaaa-aaaaa-qaaaq-cai");
public func bump() : async Nat {
await counter.inc()
};
};
moxzi build src/Client.mo
src/Client.mo -> src/Client.wasm + src/Client.did (249130 bytes, 2 file(s), 52 steps, 1.03s)
compiled by ~/.local/lib/moxzi/compiler.wasm (sha256 9294817c6e9069cbd0e712d0e9dde0ec1b6893d0e7b42d798fa3dd4c8cd772cb), moxzi 0.1.0-alpha.10
The import is a module containing every type the .did file declares, renamed to Motoko style (get_thing becomes GetThing), and Self, the service's actor type. Use Self to type an actor "…" reference.
An idl: import fails with M0004 if the file has no service, or if it declares its own type named Self.
Packages that need moxzi features#
A library that uses a moxzi language extension, such as Nat256, declares it in its mops.toml. Projects that depend on it get the right compiler options automatically:
[package]
name = "widemath"
version = "0.1.0"
[moxzi]
version = ">=0.1.0-alpha.7"
features = ["nat256"]
moxzi deps shows each dependency's requirements and whether the installed moxzi meets them:
moxzi deps src/Main.mo
root : ./app
pkgs : mops sources
reqs : widemath moxzi=>=0.1.0-alpha.7 features=nat256
version: ok for moxzi 0.1.0-alpha.10
entry : /src/src/Main.mo
files : 2 (135 bytes)
48 /pkg/widemath/lib.mo
<- ./widemath/src/lib.mo
87 /src/src/Main.mo
<- ./app/src/Main.mo
If the installed version is outside the range, the build stops and names the package:
Error: package `widemath` requires moxzi >=0.2.0; this is moxzi 0.1.0-alpha.10
(from ../widemath/mops.toml)
| Feature | Effect on the build |
|---|---|
nat256 | None. Records that the library uses Nat256 or Nat128, which moxzi always supports and moc doesn't |
local-actors | Enables actor* with 4096 actor slots |
local-actor-persistence | Same as local-actors. Older libraries declare it; actor* instances are kept across upgrades by default |
An unknown feature is an error that names the package asking for it. The build prints a note: line for each option a feature turns on.
Your own project sets values that a library shouldn't choose, such as the number of actor* slots, under [moc] args in its mops.toml:
[moc]
args = ["--experimental-local-actors", "16384"]
Options on the command line take precedence over [moc] args, which take precedence over feature defaults. moc and mops ignore the [moxzi] table, so the library still publishes and installs normally.
Actor classes in other files#
If a file other than the entry declares an actor class, moxzi compiles and links it as a separate module first, so the entry can create instances of it. This happens even with --no-link, so linker.wasm must be available.
Limitations#
| Limitation | What to do |
|---|---|
moxzi doesn't install dependencies | Run mops install first |
Git dependencies need the mops CLI | Install mops and keep it on your PATH |
mops test can't use moxzi as its compiler yet | Test with moxzid, or with moc |
Next steps#
- Migrations: changing stored data between versions.
- Fixing build errors.