Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
e18974b
initial skeleton for build command
rikonor May 29, 2025
72452ee
pull project functionality into its own crate
rikonor May 29, 2025
d76e032
create canister and project manifest definitions
rikonor May 29, 2025
45507b8
add parsing logic for project and canister manifests
rikonor May 29, 2025
a40140e
revert some minor changes
rikonor May 29, 2025
c6c3de8
put adapter definitions in adapter enum
rikonor May 29, 2025
14aeab5
clean up snafu errors
rikonor May 29, 2025
fd56d1c
switch from parsing yaml bytes to taking the path so we can report be…
rikonor May 29, 2025
f3b4e96
rename error for clarity
rikonor May 30, 2025
5a905d6
specify path in certain errors
rikonor May 30, 2025
63b8740
remove assets canister variant until it can be further discussed
rikonor May 30, 2025
8c0ffbb
specify explicit defaults for project manifest
rikonor May 30, 2025
0ea957b
remove redundant check for file existence
rikonor May 30, 2025
dab4ac2
create a load_yaml_file function and use it in icp-project and icp-ca…
rikonor May 30, 2025
8cc67c6
Merge branch 'main' into or-build-skeleton
rikonor May 30, 2025
a6eb5ba
fix ProjectDirectory usage
rikonor May 30, 2025
54e54bf
get project manifest path from project directory object
rikonor May 30, 2025
f3201f5
remove redundant path in error
rikonor May 30, 2025
e47f35f
put structs into model.rs
rikonor May 30, 2025
596f19d
Merge branch 'main' into or-build-skeleton
rikonor May 30, 2025
a2f998d
update build code paths to use camino strings
rikonor May 30, 2025
e5a45e1
fix globbing not working from non-project root directory
rikonor May 30, 2025
a67b8f2
move project structure and directory to icp-project module
rikonor Jun 2, 2025
dcf6c8e
extract knowledge of canister yaml path into project directory structure
rikonor Jun 2, 2025
372e3ab
single group imports
rikonor Jun 3, 2025
f5d73d3
use canister manifest path from directory structure
rikonor Jun 3, 2025
bdd98b7
leaf subcommands should use an exec name and not dispatch
rikonor Jun 3, 2025
00afea2
move everything to model file
rikonor Jun 3, 2025
4db8935
move definitions to model file
rikonor Jun 4, 2025
00b365a
use project-directory-structure to load project manifest
rikonor Jun 4, 2025
3ef371f
use pds when available
rikonor Jun 4, 2025
b702c48
rename conversion function to load
rikonor Jun 4, 2025
ee4fb21
remove unused error variant
rikonor Jun 4, 2025
b6a2dbd
add documentation about load function behavior
rikonor Jun 4, 2025
21ea7c5
fix lint error
rikonor Jun 4, 2025
30e4519
create initial adapter skeletons
rikonor May 30, 2025
4a34b87
wip shell execution
rikonor Jun 4, 2025
32dec84
initial implementation of script builder
rikonor Jun 4, 2025
a5eb1a1
Merge branch 'main' into or-adapters
rikonor Jun 5, 2025
cf53ef5
remove shell handling for unix/windows, use shellwords instead and do…
rikonor Jun 5, 2025
59395c7
support command and commands for script builder
rikonor Jun 5, 2025
7d6647e
use map_or instead of map and unwrap_or
rikonor Jun 5, 2025
df4f2fa
Merge branch 'main' into or-adapters
rikonor Jun 6, 2025
7e901aa
quote printed variables, dont include source in template since its in…
rikonor Jun 6, 2025
487ee51
Merge branch 'main' into or-adapters
rikonor Jun 12, 2025
da226e3
pass a reference instead of value
rikonor Jun 12, 2025
70919ac
add various tests for the script build adapter
rikonor Jun 12, 2025
6d8f795
Merge branch 'main' into or-adapters
rikonor Jun 12, 2025
3f264be
canonicalize command path for script adapter
rikonor Jun 12, 2025
7b0e770
provide utility function for CommandField to extract vec in both sing…
rikonor Jun 12, 2025
174c4be
put unit tests under their respective package
rikonor Jun 13, 2025
a578810
run unit tests separately from integration tests
rikonor Jun 13, 2025
54e75c9
log reason for invalid command
rikonor Jun 13, 2025
fbbbd14
add integration test for script adapter
rikonor Jun 13, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 28 additions & 3 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ env:
# Use the local .curlrc
CURL_HOME: .
# Disable DFX telemetry
DFX_TELEMETRY: 'off'
DFX_TELEMETRY: "off"

jobs:
discover:
Expand All @@ -24,6 +24,31 @@ jobs:
- id: set-matrix
run: echo "matrix=$(python3 .github/scripts/test-matrix.py)" >> $GITHUB_OUTPUT

unit-tests:
name: Unit tests on ${{ matrix.os }}
runs-on: ${{ matrix.os }}

strategy:
matrix:
os: [ubuntu-latest, macos-latest]

steps:
- uses: actions/checkout@v4

- name: Show Rust toolchain version
run: rustup show

- uses: actions/cache@v4
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: ${{ matrix.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}-test-1

- name: Run unit tests
run: cargo test --workspace --lib --bins

test:
name: ${{ matrix.test }} on ${{ matrix.os }}
needs: discover
Expand Down Expand Up @@ -61,8 +86,8 @@ jobs:
name: test:required
if: ${{ always() }}
runs-on: ubuntu-latest
needs: [test]
needs: [unit-tests, test]
steps:
- name: check result
if: ${{ needs.test.result != 'success' }}
if: ${{ needs.unit-tests.result != 'success' || needs.test.result != 'success' }}
run: exit 1
32 changes: 32 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 11 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
[workspace]
members = [
"bin/icp-cli",
"lib/icp-adapter",
"lib/icp-canister",
"lib/icp-dirs",
"lib/icp-fs",
Expand All @@ -12,18 +13,22 @@ members = [
resolver = "3"

[workspace.dependencies]
async-trait = "0.1.88"
bip32 = "0.5.0"
camino = { version = "1.1.9", features = ["serde1"] }
camino-tempfile = "1"
candid = "0.10.14"
clap = { version = "4.5.3", features = ["derive"] }
dialoguer = "0.11.0"
directories = "6.0.0"
dunce = "1.0.5"
ed25519-consensus = "2.1.0"
elliptic-curve = { version = "0.13.8", features = ["sec1", "std", "pkcs8"] }
fd-lock = "4.0.4"
glob = "0.3.2"
hex = "0.4.3"
ic-agent = { version = "0.40.1" }
icp-adapter = { path = "lib/icp-adapter" }
icp-canister = { path = "lib/icp-canister" }
icp-dirs = { path = "lib/icp-dirs" }
icp-fs = { path = "lib/icp-fs" }
Expand All @@ -37,13 +42,11 @@ pem = "3.0.5"
pkcs8 = { version = "0.10.2", features = ["encryption", "std"] }
pocket-ic = "9.0.0"
rand = "0.9.1"
reqwest = { version = "0.12.15", default-features = false, features = [
"rustls-tls",
] }
sec1 = { version = "0.7.3", features = ["pkcs8"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
serde_yaml = "0.9.34"
shellwords = "1.1.0"
snafu = "0.8.5"
strum = { version = "0.27", features = ["derive"] }
tempfile = "3"
Expand All @@ -53,6 +56,11 @@ tokio = { version = "1.45.0", features = ["macros", "rt-multi-thread"] }
uuid = { version = "1.16.0", features = ["serde", "v4"] }
zeroize = "1.8.1"

[workspace.dependencies.reqwest]
version = "0.12.15"
default-features = false
features = ["rustls-tls"]

[workspace.lints.rust]
missing_debug_implementations = "warn"

Expand Down
1 change: 1 addition & 0 deletions bin/icp-cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ dialoguer.workspace = true
elliptic-curve.workspace = true
hex.workspace = true
ic-agent.workspace = true
icp-adapter.workspace = true
icp-canister.workspace = true
icp-dirs.workspace = true
icp-fs.workspace = true
Expand Down
35 changes: 28 additions & 7 deletions bin/icp-cli/src/commands/build.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
use clap::Parser;
use icp_canister::model::{CanisterManifest, LoadCanisterManifestError};
use icp_project::directory::{FindProjectError, ProjectDirectory};
use icp_project::model::{LoadProjectManifestError, ProjectManifest};
use icp_adapter::{Adapter as _, AdapterCompileError};
use icp_canister::model::{Adapter, CanisterManifest, LoadCanisterManifestError};
use icp_project::{
directory::{FindProjectError, ProjectDirectory},
model::{LoadProjectManifestError, ProjectManifest},
};
use snafu::Snafu;

#[derive(Parser, Debug)]
pub struct Cmd;

pub async fn exec(_cmd: Cmd) -> Result<(), BuildCommandError> {
pub async fn exec(_: Cmd) -> Result<(), BuildCommandError> {
// Project
let pd = ProjectDirectory::find()?.ok_or(BuildCommandError::ProjectNotFound)?;

Expand All @@ -21,12 +24,27 @@ pub async fn exec(_cmd: Cmd) -> Result<(), BuildCommandError> {
let mut cs = Vec::new();

for c in pm.canisters {
let cm = CanisterManifest::from_file(pds.canister_yaml_path(&c))?;
cs.push(cm);
let mpath = pds.canister_yaml_path(&c);
let cm = CanisterManifest::from_file(&mpath)?;
cs.push((c, cm));
}

// Build canisters
println!("{cs:?}");
for (path, c) in cs {
match c.build.adapter {
Adapter::Script(adapter) => {
adapter.compile(&path).await?;
Comment thread
rikonor marked this conversation as resolved.
}

Adapter::Motoko(adapter) => {
adapter.compile(&path).await?;
}

Adapter::Rust(adapter) => {
adapter.compile(&path).await?;
}
}
}

Ok(())
}
Expand All @@ -44,4 +62,7 @@ pub enum BuildCommandError {

#[snafu(transparent)]
CanisterLoad { source: LoadCanisterManifestError },

#[snafu(transparent)]
BuildAdapter { source: AdapterCompileError },
}
50 changes: 50 additions & 0 deletions bin/icp-cli/tests/build_tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
use crate::common::TestEnv;
use icp_fs::fs::{create_dir_all, write};
use predicates::{ord::eq, str::PredicateStrExt};

mod common;

#[test]
fn build_adapter_script_simple() {
let env = TestEnv::new();

// Setup project
let project_dir = env.create_project_dir("icp");

// Project manifest
let pm = r#"
canisters:
- my-canister
"#;

write(
project_dir.join("icp.yaml"), // path
pm, // contents
)
.expect("failed to write project manifest");

// Canister manifest
let cm = r#"
name: my-canister
build:
adapter:
type: script
command: echo hi
"#;

create_dir_all(project_dir.join("my-canister")).expect("failed to create canister directory");

write(
project_dir.join("my-canister/canister.yaml"), // path
cm, // contents
)
.expect("failed to write project manifest");

// Invoke build
env.icp()
.current_dir(project_dir)
.args(["build"])
.assert()
.success()
.stdout(eq("hi").trim());
}
16 changes: 16 additions & 0 deletions lib/icp-adapter/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
[package]
name = "icp-adapter"
version = "0.1.0"
edition = "2024"

[dependencies]
async-trait = { workspace = true }
camino = { workspace = true }
dunce = { workspace = true }
serde = { workspace = true }
shellwords = { workspace = true }
snafu = { workspace = true }

[dev-dependencies]
tokio = { workspace = true }
camino-tempfile = { workspace = true }
27 changes: 27 additions & 0 deletions lib/icp-adapter/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
use async_trait::async_trait;
use camino::Utf8Path;
use motoko::MotokoAdapterCompileError;
use rust::RustAdapterCompileError;
use script::ScriptAdapterCompileError;
use snafu::Snafu;

pub mod motoko;
pub mod rust;
pub mod script;

#[async_trait]
pub trait Adapter {
async fn compile(&self, path: &Utf8Path) -> Result<(), AdapterCompileError>;
}

#[derive(Debug, Snafu)]
pub enum AdapterCompileError {
#[snafu(transparent)]
Rust { source: RustAdapterCompileError },

#[snafu(transparent)]
Motoko { source: MotokoAdapterCompileError },

#[snafu(transparent)]
Script { source: ScriptAdapterCompileError },
}
27 changes: 27 additions & 0 deletions lib/icp-adapter/src/motoko.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
use crate::{Adapter, AdapterCompileError};
use async_trait::async_trait;
use camino::Utf8Path;
use serde::Deserialize;
use snafu::Snafu;

/// Configuration for a Motoko-based canister build adapter.
#[derive(Debug, Deserialize)]
pub struct MotokoAdapter {
/// Optional path to the main Motoko source file.
/// If omitted, a default like `main.mo` may be assumed.
#[serde(default)]
pub main: Option<String>,
}

#[async_trait]
impl Adapter for MotokoAdapter {
async fn compile(&self, _path: &Utf8Path) -> Result<(), AdapterCompileError> {
Ok(())
}
}

#[derive(Debug, Snafu)]
pub enum MotokoAdapterCompileError {
#[snafu(display("an unexpected build error occurred"))]
Unexpected,
}
25 changes: 25 additions & 0 deletions lib/icp-adapter/src/rust.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
use crate::{Adapter, AdapterCompileError};
use async_trait::async_trait;
use camino::Utf8Path;
use serde::Deserialize;
use snafu::Snafu;

/// Configuration for a Rust-based canister build adapter.
#[derive(Debug, Deserialize)]
pub struct RustAdapter {
/// The name of the Cargo package to build.
pub package: String,
}

#[async_trait]
impl Adapter for RustAdapter {
async fn compile(&self, _path: &Utf8Path) -> Result<(), AdapterCompileError> {
Ok(())
}
}

#[derive(Debug, Snafu)]
pub enum RustAdapterCompileError {
#[snafu(display("an unexpected build error occurred"))]
Unexpected,
}
Loading