Tutorial: Source Packages¶
This tutorial walks you through creating, validating, signing, distributing, and building a source package. By the end you will have a working package that a consumer can build and load into a host application.
A source package is a directory containing plugin source code and a
package.toml manifest. The manifest declares the package name, version,
interface compatibility, optional dependencies, and an extensible [metadata]
section that hosts can validate against a custom schema. Source packages are
distributed as source and compiled on the consumer side — this avoids
cross-platform binary compatibility issues.
Prerequisites¶
- Rust toolchain installed
fidius-cliinstalled (cargo install fidius-cli)- Familiarity with creating a plugin (see Your First Plugin)
1. Create the interface crate¶
Start by scaffolding an interface crate for a simple calculator:
Edit calculator-interface/src/lib.rs to define the methods:
pub use fidius::{plugin_impl, PluginError};
#[fidius::plugin_interface(version = 1, buffer = PluginAllocated)]
pub trait Calculator: Send + Sync {
fn add(&self, input: String) -> String;
fn multiply(&self, input: String) -> String;
}
2. Create the plugin crate¶
Scaffold a plugin that implements the calculator interface:
Implement the trait in calc-plugin/src/lib.rs:
use calculator_interface::{plugin_impl, Calculator, PluginError};
pub struct MyCalculator;
#[plugin_impl(Calculator)]
impl Calculator for MyCalculator {
fn add(&self, input: String) -> String {
// Parse "a,b" and return the sum
let parts: Vec<f64> = input.split(',')
.filter_map(|s| s.trim().parse().ok())
.collect();
let sum: f64 = parts.iter().sum();
sum.to_string()
}
fn multiply(&self, input: String) -> String {
let parts: Vec<f64> = input.split(',')
.filter_map(|s| s.trim().parse().ok())
.collect();
let product: f64 = parts.iter().product();
product.to_string()
}
}
fidius_core::fidius_plugin_registry!();
3. Add a package manifest¶
Create calc-plugin/package.toml:
[package]
name = "calc-plugin"
version = "1.0.0"
interface = "calculator-interface"
interface_version = 1
[dependencies]
calculator-interface = ">=0.1"
[metadata]
category = "math"
description = "Basic arithmetic operations"
The [package] section contains fixed header fields required by fidius. The
[metadata] section is free-form — hosts can validate it against a custom
schema type (see Create a Package Schema).
4. Validate the manifest¶
Use the CLI to check that the manifest is well-formed:
Expected output:
Package: calc-plugin v1.0.0
Interface: calculator-interface (version 1)
Dependencies:
calculator-interface = ">=0.1"
Metadata: 2 field(s)
Manifest valid.
5. Inspect the package¶
To see all metadata including the [metadata] section values:
Expected output:
Package: ./calc-plugin
Name: calc-plugin
Version: 1.0.0
Interface: calculator-interface
Interface version: 1
Dependencies:
calculator-interface = ">=0.1"
Metadata:
category = "math"
description = "Basic arithmetic operations"
6. Build the package¶
Build the plugin cdylib from source:
This runs cargo build --release inside the package directory. Pass --debug
to build in debug mode instead:
7. Sign the package¶
Generate a keypair and sign the package manifest:
This signs the package.toml file and writes a .sig file alongside it.
8. Pack the package for distribution¶
Pack the signed package into a .fid archive for distribution:
Expected output:
The .fid file is a bzip2-compressed tar archive containing all source files
plus the package.sig. Distribute this single file via GitHub Releases, artifact
stores, or any file-based channel.
Note: If you pack without signing first, a warning is emitted:
warning: package is unsigned (no package.sig found)
Custom file extensions¶
Interface authors can define a custom extension via
fidius init-interface --extension <ext>. This writes a fidius.toml in the
interface crate, which fidius init-plugin propagates into the plugin's
package.toml. For example, with extension = "cloacina", the pack command
produces calc-plugin-1.0.0.cloacina instead of .fid.
9. Consumer: unpack, verify, and build¶
On the consumer side, unpack the archive, verify the signature, and build:
fidius package unpack calc-plugin-1.0.0.fid --dest ./plugins
fidius package verify --key publisher.public ./plugins/calc-plugin-1.0.0
fidius package build ./plugins/calc-plugin-1.0.0
10. Consumer: load the built plugin¶
In the host application, use PluginHost to load the compiled dylib:
use fidius_host::host::PluginHost;
use fidius_host::types::LoadPolicy;
let host = PluginHost::builder()
.search_path("./calc-plugin/target/release/")
.load_policy(LoadPolicy::Strict)
.build()?;
let plugin = host.load("MyCalculator")?;
With LoadPolicy::Strict (the default), signature and validation failures are
hard errors. Use LoadPolicy::Lenient during development to downgrade signature
failures to warnings.
What you learned¶
- A source package is a directory with plugin source and a
package.toml fidius package validatechecks the manifest structurefidius package inspectshows all manifest fieldsfidius package buildcompiles the cdylib from sourcefidius package sign/fidius package verifyprovide integrity checkingfidius package packcreates a distributable.fidarchive (warns if unsigned)fidius package unpackextracts an archive for building- Interface authors can set a custom archive extension via
--extension LoadPolicy::StrictvsLoadPolicy::Lenientcontrols enforcement at load time
Next steps¶
- Create a Package Schema — validate
[metadata]against a host-defined Rust type - Package Manifest Reference — complete
package.tomlformat andPackageErrorvariants - Signing and Verifying Plugins — deep dive into Ed25519 signing