An open API service indexing awesome lists of open source software.

https://github.com/dfinity/canpack

Package multiple libraries into one ICP canister.
https://github.com/dfinity/canpack

bundler candid canister cross-language dfinity icp language-bindings mops motoko rust

Last synced: about 1 month ago
JSON representation

Package multiple libraries into one ICP canister.

Awesome Lists containing this project

README

          

# `canpack`   [![npm version](https://img.shields.io/npm/v/canpack.svg?logo=npm&color=default)](https://www.npmjs.com/package/canpack) [![GitHub license](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)

Canpack is a code generation tool which simplifies cross-language communication in [Internet Computer](https://internetcomputer.org/) canisters (such as calling a [Rust](https://www.rust-lang.org/) crate from [Motoko](https://github.com/dfinity/motoko)). This works by generating a separate canister from the host language, combining fragments defined across multiple libraries.

**Note:** This project is early in development; unannounced breaking changes may occur at any time.

## Installation

Ensure that the following software is installed on your system:
* [dfx](https://support.dfinity.org/hc/en-us/articles/10552713577364-How-do-I-install-dfx) (latest version)
* [Rust](https://www.rust-lang.org/tools/install) `>= 1.71`
* [Node.js](https://nodejs.org/en) `>= 18`

Run the following command to install the Canpack CLI on your global system path:

```
npm install -g canpack
```

## Quick Start (Motoko + Rust)

Canpack has built-in support for the [Mops](https://mops.one/) package manager.

In your canister's `mops.toml` file, add a `rust-dependencies` section:

```toml
[rust-dependencies]
canpack-example-hello = "^0.1"
local-crate = { path = "path/to/local-crate" }
```

You can also specify `[rust-dependencies]` in a Motoko package's `mops.toml` file to include Rust crates in any downstream canisters.

Next, run the following command in the directory with the `mops.toml` and `dfx.json` files:

```bash
canpack
```

This will configure and generate a `motoko_rust` canister with Candid bindings for the specified dependencies. Here is a Motoko canister which uses a function defined in the [`canpack-example-hello`](https://docs.rs/canpack-example-hello/latest/src/canpack_example_hello/lib.rs.html) crate:

```motoko
import Rust "canister:motoko_rust";

actor {
public composite query func hello(name: Text) : async Text {
await Rust.canpack_example_hello(name)
}
}
```

Any Rust crate with Canpack compatibility can be specified as a standard [`Cargo.toml` dependency](https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html). See the [Rust Crates](#rust-crates) section for more details.

## Rust Crates

It's relatively simple to add Canpack support to any IC Wasm-compatible Rust crate.

Here is the full implementation of the [`canpack-example-hello`](https://docs.rs/canpack-example-hello/latest/src/canpack_example_hello/lib.rs.html) package:

```rust
canpack::export! {
pub fn canpack_example_hello(name: String) -> String {
format!("Hello, {name}!")
}
}
```

If needed, you can configure the generated Candid method using a `#[canpack]` attribute:

```rust
canpack::export! {
#[canpack(composite_query, rename = "canpack_example_hello")]
pub fn hello(name: String) -> String {
format!("Hello, {name}!")
}
}
```

Note that it is possible to reference local constants, methods, etc.

```rust
const WELCOME: &str = "Welcome";

fn hello(salutation: &str, name: String) -> String {
format!("{salutation}, {name}!")
}

canpack::export! {
pub fn canpack_example_hello(name: String) -> String {
hello(WELCOME, name)
}
}
```

The `canpack::export!` shorthand requires adding [`canpack`](https://crates.io/crates/canpack) as a dependency in your Cargo.toml file. It's also possible to manually define Candid methods by exporting a `canpack!` macro:

```rust
pub fn hello(name: String) -> String {
format!("Hello, {name}!")
}

#[macro_export]
macro_rules! canpack {
() => {
#[ic_cdk::query]
#[candid::candid_method(query)]
fn canpack_example_hello(name: String) -> String {
$crate::hello(name)
}
};
}
```

## Advanced Usage

### `canpack.json`

Pass the `-v` or `--verbose` flag to view the resolved JSON configuration for a project:

```bash
canpack --verbose
```

Below is a step-by-step guide for setting up a `dfx` project with a `canpack.json` config file. The goal here is to illustrate how one could use Canpack without additional tools such as Mops, which is specific to the Motoko ecosystem.

Run `dfx new my_project`, selecting "Motoko" for the backend and "No frontend canister" for the frontend. Once complete, run `cd my_project` and open in your editor of choice.

Add a new file named `canpack.json` in the same directory as `dfx.json`.

In the `canpack.json` file, define a Rust canister named `my_project_backend_rust`:

```json
{
"canisters": {
"my_project_backend_rust": {
"type": "rust",
"parts": [{
"package": "canpack-example-hello",
"version": "^0.1"
}]
}
}
}
```

Next, run the following command in this directory to generate all necessary files:

```bash
canpack
```

In your `dfx.json` file, configure the `"dependencies"` for the Motoko canister:

```json
{
"canisters": {
"my_project_backend": {
"dependencies": ["my_project_backend_rust"],
"main": "src/my_project_backend/main.mo",
"type": "motoko"
}
},
}
```

Now you can call Rust functions from Motoko using a canister import:

```motoko
import Rust "canister:my_project_backend_rust";

actor {
public func hello(name: Text) : async Text {
await Rust.canpack_example_hello(name)
}
}
```

Run the following commands to build and deploy the `dfx` project on your local machine:

```
dfx start --background
dfx deploy
```

### Programmatic API

Canpack may be used as a low-level building block for package managers and other development tools.

Add the `canpack` dependency to your Node.js project with the following command:

```bash
npm i --save canpack
```

The following example JavaScript code runs Canpack in the current working directory:

```js
import { canpack } from 'canpack';

const config = {
verbose: true,
canisters: {
my_canister: {
type: 'rust',
parts: [{
package: 'canpack-example-hello',
version: '^0.1',
}]
}
}
};

await canpack(config);
```

---

This project is early in development. Please feel free to report a bug, ask a question, or request a feature on the project's [GitHub issues](https://github.com/dfinity/canpack/issues) page.

Contributions are welcome! Please check out the [contributor guidelines](https://github.com/dfinity/canpack/blob/main/.github/CONTRIBUTING.md) for more information.