Skip to content

Magnet.toml reference

Magnet.toml is the package manifest, like Cargo.toml. The root package is every .mag file next to it and in src/. Example:

[package]
name = "vehicle"
[dependencies]
motor = { path = "../motor" } # a directory: every *.mag inside it or its src/
util = { path = "../shared/util.mag" } # a single file
pid = { git = "https://git.example.com/org/pid.git", tag = "v1.2" }
[deploy] # optional: what `magnet flash` puts on a board
model = "controller::Controller"
[simulate] # optional: the run, when no flag says otherwise
dt = 0.0001
end = 1.0
[codegen] # optional: the generated crate's module files
module-files = "mod-rs"

Each table has its own section below. When any command loads the project, each of these is refused by name:

  • an unknown table
  • an unknown key
  • a value of the wrong type
[package]
name = "vehicle"

name is the root package’s name, and the generated crate’s. A name starts with a lowercase letter or _, followed by lowercase letters, digits, _ or -. These names are refused:

  • core, the embedded stdlib
  • magnetite, magnetite-common and serde, which the generated crate depends on
  • main and lib, which Rust reserves for a crate root
  • Rust and magscript keywords

A hyphenated name is written raw in the manifest and on disk but imports with underscores (use control_lib for a package control-lib). Names that fold to the same identifier conflict: foo-bar and foo_bar can’t coexist, and a dependency can’t fold to the root package’s name.

magnet-version is optional and sets the minimum magnet version:

[package]
name = "rig"
magnet-version = "0.1.0-alpha.21"

A pre-release sorts before its release: 0.1.0 satisfies 0.1.0-alpha.11, not the other way round. An older magnet refuses the package in every command and in the editor; run magnet self-update. Without the key, any version works. magnet new writes the current version.

[dependencies]
motor = { path = "../motor" }
util = { path = "../shared/util.mag" }
pid = { git = "https://git.example.com/org/pid.git", tag = "v1.2" }
obs = { git = "git@git.example.com:org/obs.git", rev = "a1b2c3d" }
plant = { git = "ssh://git@host/org/plant", branch = "main" }
aux = { git = "https://git.example.com/org/aux.git" } # the remote's default branch

Each key is a dependency, imported under that name, with - folded to _. The key is authoritative: any manifest in the dependency’s own directory is not consulted for its name. A dependency has exactly one source, a path or a git key, never both and never neither. The package’s lowercase-name rules apply, and core is reserved.

A path dependency is a directory (every .mag in it and its src/) or a single .mag file, resolved relative to the manifest:

motor = { path = "../motor" }
util = { path = "../shared/util.mag" }

A git dependency specifies a repository and at most one revision selector: tag, rev, or branch. Naming two is refused. With none, the remote’s default branch is used.

pinned = { git = "https://git.example.com/org/pid.git", tag = "v1.2" }
frozen = { git = "git@git.example.com:org/obs.git", rev = "a1b2c3d" }
moving = { git = "ssh://git@host/org/plant", branch = "main" }

The URL is handed to the system git verbatim, and magnet never second-guesses its syntax. These forms all work:

  • scp-style
  • ssh://
  • https://
  • file://

Fetching, freezing and re-resolving git sources are covered under magnet add and magnet update. The pinning rules are in Packages and libraries.

[deploy] specifies magnet flash behavior. The table is optional, and a package without it builds, simulates, and syncs exactly as before. Its four keys are optional, and each of the first three has a magnet flash flag that stands in for it:

[deploy]
model = "controller::Controller" # the model to flash, as a `use` path
chip = "…" # the target as `probe-rs chip list` prints it
firmware = "firmware" # the crate whose binary runs on the board
runtime = "embassy" # who steps the model on the board

runtime = "embassy" has a flash write the model’s embassy glue to target/codegen/embassy/<package>/ before it builds the firmware, so the firmware depends on that one crate and steps the model through it. See A firmware.

firmware is relative to the manifest, and it is your crate: it depends on the generated one by path, reads its own inputs and calls step. magnet builds it and flashes what it builds, emitting no runtime of its own for it to run under. Any other key is refused by name.

flash refuses everything it can before cargo starts, so a flash with the cable out fails at once rather than after a release build:

  • no chip or firmware from either place
  • a firmware directory with no crate in it
  • a model the package does not declare
  • no probe-rs on the PATH
  • no probe plugged in

A firmware walks through the whole thing.

[simulate] specifies the run when nothing else does. Both keys are seconds, both optional, and both refused unless positive:

[simulate]
dt = 0.0001 # the sample time
end = 1.0 # the end time; the tick count is end / dt

magnet simulate takes dt when --dt is not passed, and end over the sample time when --ticks is not. The editor’s Run uses the same two numbers, so a run’s length is written once and committed with the models it runs. The editor’s simulation settings write this table.

A rated entry, a step with @every, fixes its own sample time and ignores dt; end still counts its ticks, at the rate it declares.

[codegen] is optional and specifies the generated crate layout.

[codegen]
module-files = "mod-rs" # or "self-named"

It decides where a module with child modules writes its own file:

  • mod-rs, the default, puts it inside the directory of the same name (src/sensors/mod.rs), so the directory is self-contained.
  • self-named puts it beside the directory as src/sensors.rs.

A module without children is <name>.rs either way. Module paths are identical under both, so code that depends on the generated crate is unaffected by the choice.