miden-project 0.22.0

Interface for working with Miden projects
Documentation

miden-project

This crate defines the interfaces for working with miden-project.toml files and the corresponding Miden project metadata.

Terminology

The following document uses some terminology that may be easier to reason about if you understand the specific definitions we're using for those terms in this context:

  • Package, a single library, program, or component that can be assembled, linked against, and distributed. A package in binary form is considered assembled. We also refer to packages as an organizational unit, i.e. before they are assembled, we organize source code into units that correspond to the packages we intend to produce from that source code. Typically the type of package we're referring to will be clear from context, but if we need to distinguish between them, then we use assembled package to refer to the binary form. For an intuition about packages, you can consider a Miden package roughly equivalent to a Rust crate.
  • Project, refers to the meta-organization of one or more packages into a single unit (i.e. defined by a single miden-project.toml). It may also be used as an umbrella term for either a single project, or a workspace (see below).
  • Workspace, refers to the meta-organization of one or more projects into a single hierarchy, i.e. a project-of-projects. Workspaces are useful when multiple projects would benefit from sharing dependencies and other configuration. This concept is based on Cargo workspaces from Rust.
  • Target, refers to a specific package artifact that is derivable from a project
  • Version, refers to the semantic version of a project/package
  • Digest, refers to the content digest/hash associated with a specific assembled package. Only assembled packages have content digests. When available for a specific package, the content digest becomes an extension of its version, and is taken into account during dependency resolution when a specific digest is required by a dependency spec.

What is a Miden project?

A Miden project, at its most basic, is comprised of two things:

  1. A manifest file (i.e. miden-project.toml) which describes the project and its dependencies, as well as configuration for tooling in the Miden ecosystem.
  2. The source code of the project. This could be any of the following:
  • Miden Assembly
  • Rust, using an installed cargo-miden/midenc toolchain
  • Any language that can compile to the Miden VM, specifically, are able to produce Miden packages (i.e. .masp files).
  • Some combination of the above in one project

The above is the simplest form of project, oriented towards building a single Miden package. However, Miden projects may also be organized into workspaces, similar to how Cargo supports organizing Rust crates into workspaces.

Workspaces

A Miden workspace is a meta-project that consists of one or more sub-projects that correspond to packages. A workspace is comprised of:

  • A workspace-level manifest (i.e. miden-project.toml), with workspace-specific syntax, that defines configuration and metadata for the workspace, and is shared amongst members of the workspace.
  • One or more subdirectories that contain a Miden project corresponding to a single package. These are the "members" of the workspace.

The benefit of using workspaces is when working with multiple package projects that depend on each other, or which share configuration - as opposed to managing them all independently, which would require much more duplication.

Defining a workspace

To define a workspace, simply create a new directory, and in that directory, create a miden-project.toml file which contains the following:

[workspace]
members = []

[workspace.package]
version = "0.1.0"

[dependencies]

This represents an empty workspace for a new project at version 0.1.0.

The next step is to create subdirectories for each sub-project, and initialize those as called for by the tooling of the language used in that project.

For examples of Miden Assembly and Rust-based projects, see the section below titled Defining a project.

Defining a project

To define a new project, all you need is a new directory containing the source code of the project (organization of which is language-specific), and in that directory, create a miden-project.toml which contains at least the following:

[package]
name = "foo"
version = "0.1.0" # or whatever semantic version you like

This provides the bare minimum metadata needed to construct a basic package. However, in practice you are likely going to want to define targets, and declare dependencies.

Defining targets

A target corresponds to a specific artifact that you want to produce from the project. Most projects will have a single target, but in some cases multiple targets may be useful, particularly for kernels.

Let's add a target to the miden-project.toml of the project we created in the previous section:

[package]
name = "foo"
version = "0.1.0"

# The following target is what would be inferred if no targets were declared
# in this file, also known as the _default target_.
# 
# Only one `[lib]` section can be present per-project.
[lib]
# The type of artifact we're producing
# 
# For `[lib]` the default kind is `library`, but other valid library kinds are:
# 
# * `kernel`
# * `account-component`
# * `note`
# * `tx-script`
kind = "library"
# The relative path to the root module, if the project is written in Miden 
# Assembly. Other languages, such as Rust, will omit this entirely.
path = "mod.masm" 
# The root namespace of modules parsed for this target
namespace = "name"

It is also possible to define one or more executable targets from a single package. Expanding on the example above, lets add two executables that share the same code as the library, but with a different root module:

[[bin]]
# The `name` field is required when multiple `[[bin]]` targets are present, in
# order to disambiguate them as targets.
name = "primary"
# The `path` field is required, and must specify the path to the module 
# containing the executable entrypoint.
path = "main.masm"

[[bin]]
name = "alternate"
path = "main2.masm"

When assembling an executable/bin target, all modules in the same directory as path are provided to the assembler, except the root modules of other executable targets. This allows the executable targets to build on top of the [lib] target easily. If you don't want this behavior, simply ensure that the [lib] target and any [[bin]] targets locate their sources in separate subdirectories, e.g. lib/mod.masm and primary/main.masm/alternate/main.masm for the example above.

There is a special caveat to be aware of when requesting assembly of an executable target from a project with multiple targets (either a [lib] and a [[bin]], multiple [[bin]] targets, or both): the name of the package produced for the executable targets must be disambiguated, and so the name of the executable target is appended to the project name. For example, in our example project defined above, assembling the primary target would produce a package named foo#primary.

Default target

As noted in the previous section, the [lib] target we defined is equivalent to the default target that would have been inferred if no targets had been specified at all, i.e. the default target is a library, whose root module is expected to be mod.masm, found in the same directory as the manifest, and whose modules will all belong to the foo namespace (individual modules will have their path derived from the directory structure).

To recap, there are two categories of target, library and executable, and library targets have multiple flavors/kinds, listed below, and which are specified using lib.kind:

  • library, lib - produce a package which exports one or more procedures that can be called from other artifacts, but cannot be executed by the Miden VM without additional setup. Libraries have no implicit dependency on any particular kernel.
  • kernel - a special library type that provides core functionality in conjunction with an executable program. This is the only artifact type whose exports can be called using syscall, and that can be installed as a kernel when instantiating the VM.
  • account-component - produce a package which is a valid account component in the Miden protocol, and contains all metadata needed to construct that component. This type is only valid in conjunction with the Miden transaction kernel.
  • note - produce a package which is a valid note script in the Miden protocol, and exports the necessary metadata and procedures to construct and execute the note. This type is only valid in conjunction with the Miden transaction kernel.
  • tx-script - produce a package which is a valid transaction script in the Miden protocol, and exports the necessary metadata and procedures to construct and execute the script. This type is only valid in conjunction with the Miden transaction kernel.

As noted earlier, you may define multiple targets in a single Miden project - however you must then request a specific target when assembling the project. Additionally, all targets in a project share the same dependency set.

Dependency management

A key benefit of Miden project manifests is the ability to declare dependencies on Miden packages, and then use those packages in your project, without having to manage the complexity of working with the contents of those packages yourself.

Dependencies are declared in miden-project.toml in one of the following forms:

[dependencies]
# A semantic version constraint
a = "=0.1.0"
# A specific package, given by its content digest
b = "0x......"
# A path dependency
c = { path = "../c" }
d = { path = "../c", version = "~> 0.1.0" }
# A git dependency
e = { git = "https://github.com/example/e", branch = "main" }
f = { git = "https://github.com/example/f", rev = "deadbeef" }
g = { git = "https://github.com/example/g", rev = "deadbeef", version = "~> 0.1.0" }

Linkage

Dependencies are dynamically-linked by default, which means that the assembled package will have a runtime dependency on the referenced package, and it is assumed that the dependency will have been loaded into the VM at runtime.

You may also specify that a dependency should be statically-linked, like so:

[dependencies]
a = { version = "=0.1.0", linkage = "static" }

When a dependency is statically-linked, it is as if the dependency was defined as part of your own package.

NOTE: Kernel dependencies are always statically-linked into executable targets, and dynamically-linked for library targets, and it is not possible to change this behavior.

Semantics

  • a specifies a semantic version requirement, these would be evaluated against a package registry implementation
  • b specifies a package digest, essentially this acts as a stricter SemVer requirement of the form =MAJ.MIN.PATCH, where what is required is a version that has exactly the given digest. This form is also evaluated against a package registry implementation.
  • c specifies that the package sources (or a package artifact) can be found at the given path. The version is inferred from the package at that path, but is essentially equivalent to version = "*".
  • d is the same as c, except it specifies a semantic version requirement that MUST match the package found at path
  • e specifies that the package sources can be found by cloning the git repo, and checking out the main branch.
  • f is the same as e, except it provides a specific revision in the git repo instead
  • g is the same as f, except it specifies a semantic version requirement that MUST match the package found in the cloned repo

In cases where the dependency is resolved to project sources and not an assembled package, the behavior would be to assemble those dependencies first, and then link against them when assembling the current project. This is most useful when linking against packages which are not contracts, or where the contracts are deployed together as a unit.

NOTE: Currently there is no canonical package registry, so the resolution of the first two forms described above is dependent on the specific tool that is doing the resolution, namely, how it populates the package index for the resolver provided by this crate.

License

This project is dual-licensed under the MIT and Apache 2.0 licenses.