Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 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
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
51 changes: 51 additions & 0 deletions Cargo.lock

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

6 changes: 5 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
[workspace]
members = [
"bin/icp-cli",
"lib/icp-canister",
Comment thread
adamspofford-dfinity marked this conversation as resolved.
"lib/icp-fs",
"lib/icp-identity",
"lib/icp-network",
"lib/icp-fs",
"lib/icp-project",
]

resolver = "3"
Expand All @@ -12,6 +14,7 @@ resolver = "3"
camino = { version = "1.1.9", features = ["serde1"] }
candid = "0.10.14"
fd-lock = "4.0.4"
glob = "0.3.2"
hex = "0.4.3"
ic-agent = { version = "0.40.1" }
pocket-ic = "9.0.0"
Expand All @@ -20,6 +23,7 @@ reqwest = { version = "0.12.15", default-features = false, features = [
] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
serde_yaml = "0.9.34"
snafu = "0.8.5"
tempfile = "3"
time = "0.3.9"
Expand Down
4 changes: 3 additions & 1 deletion bin/icp-cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@ path = "src/main.rs"
[dependencies]
camino = { workspace = true }
clap = { version = "4.5.35", features = ["derive"] }
icp-network = { path = "../../lib/icp-network" }
icp-canister = { path = "../../lib/icp-canister" }
icp-fs = { path = "../../lib/icp-fs" }
icp-network = { path = "../../lib/icp-network" }
icp-project = { path = "../../lib/icp-project" }
snafu = { workspace = true }
tokio = { workspace = true }

Expand Down
8 changes: 7 additions & 1 deletion bin/icp-cli/src/commands.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
use crate::commands::network::NetworkCommandError;
use crate::commands::{build::BuildCommandError, network::NetworkCommandError};
use clap::{Parser, Subcommand};
use snafu::Snafu;

mod build;
mod network;

#[derive(Parser, Debug)]
Expand All @@ -12,18 +13,23 @@ pub struct Cli {

#[derive(Subcommand, Debug)]
pub enum Subcmd {
Build(build::Cmd),
Network(network::Cmd),
}

pub async fn dispatch(cli: Cli) -> Result<(), DispatchError> {
match cli.subcommand {
Subcmd::Build(opts) => build::exec(opts).await?,
Subcmd::Network(opts) => network::dispatch(opts).await?,
}
Ok(())
}

#[derive(Debug, Snafu)]
pub enum DispatchError {
#[snafu(transparent)]
Build { source: BuildCommandError },

#[snafu(transparent)]
Network { source: NetworkCommandError },
}
47 changes: 47 additions & 0 deletions bin/icp-cli/src/commands/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
use clap::Parser;
use icp_canister::model::{CanisterManifest, LoadCanisterManifestError};
use icp_project::directory::{FindProjectError, ProjectDirectory};
use icp_project::model::{LoadProjectManifestError, ProjectManifest};
use snafu::Snafu;

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

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

// Project Structure (paths, etc)
let pds = pd.structure();

// Load
let pm = ProjectManifest::load(pds)?;

// List canisters in project
let mut cs = Vec::new();

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

// Build canisters
println!("{cs:?}");

Ok(())
}

#[derive(Debug, Snafu)]
pub enum BuildCommandError {
#[snafu(transparent)]
FindProjectError { source: FindProjectError },

#[snafu(display("no project (icp.yaml) found in current directory or its parents"))]
ProjectNotFound,

#[snafu(transparent)]
ProjectLoad { source: LoadProjectManifestError },

#[snafu(transparent)]
CanisterLoad { source: LoadCanisterManifestError },
}
7 changes: 2 additions & 5 deletions bin/icp-cli/src/commands/network/run.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
use crate::project::directory::ProjectDirectory;
use crate::{
commands::network::run::RunNetworkCommandError::ProjectNotFound,
project::directory::FindProjectError,
};
use crate::commands::network::run::RunNetworkCommandError::ProjectNotFound;
use clap::Parser;
use icp_network::{ManagedNetworkModel, RunNetworkError, run_network};
use icp_project::directory::{FindProjectError, ProjectDirectory};
use snafu::Snafu;

/// Run the local network
Expand Down
1 change: 0 additions & 1 deletion bin/icp-cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ use crate::commands::Cli;
use clap::Parser;

mod commands;
mod project;

#[tokio::main]
async fn main() {
Expand Down
10 changes: 10 additions & 0 deletions lib/icp-canister/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[package]
name = "icp-canister"
version = "0.1.0"
edition = "2024"

[dependencies]
camino = { workspace = true }
icp-fs = { "path" = "../icp-fs" }
serde = { workspace = true }
snafu = { workspace = true }
1 change: 1 addition & 0 deletions lib/icp-canister/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pub mod model;
85 changes: 85 additions & 0 deletions lib/icp-canister/src/model.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
use camino::Utf8Path;
use icp_fs::yaml::{LoadYamlFileError, load_yaml_file};
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,
}

/// 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>,
}

/// Configuration for a custom canister build adapter.
#[derive(Debug, Deserialize)]
pub struct CustomAdapter {
/// Path to a script or executable used to build the canister.
pub script: String,
}

/// Identifies the type of adapter used to build the canister,
/// along with its configuration.
///
/// The adapter type is specified via the `type` field in the YAML file.
/// For example:
///
/// ```yaml
/// type: rust
/// package: my_canister
/// ```
#[derive(Debug, Deserialize)]
#[serde(tag = "type", rename_all = "lowercase")]
pub enum Adapter {
/// A canister written in Rust.
Rust(RustAdapter),

/// A canister written in Motoko.
Motoko(MotokoAdapter),

/// A canister built using a custom script or command.
Custom(CustomAdapter),
}

/// Describes how the canister should be built into WebAssembly,
/// including the adapter responsible for the build.
#[derive(Debug, Deserialize)]
pub struct Build {
pub adapter: Adapter,
}

/// Represents the manifest describing a single canister,
/// including its name and how it should be built.
#[derive(Debug, Deserialize)]
pub struct CanisterManifest {
/// Name of the canister described by this manifest.
pub name: String,

/// Build configuration for producing the canister's WebAssembly.
pub build: Build,
}

impl CanisterManifest {
pub fn from_file<P: AsRef<Utf8Path>>(path: P) -> Result<Self, LoadCanisterManifestError> {
let path = path.as_ref();

// Load
let cm: CanisterManifest = load_yaml_file(path)?;

Ok(cm)
}
}

#[derive(Debug, Snafu)]
pub enum LoadCanisterManifestError {
#[snafu(transparent)]
Parse { source: LoadYamlFileError },
}
1 change: 1 addition & 0 deletions lib/icp-fs/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ edition = "2024"
camino = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
serde_yaml = { workspace = true }
snafu = { workspace = true }
1 change: 1 addition & 0 deletions lib/icp-fs/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
pub mod fs;
pub mod json;
pub mod yaml;
25 changes: 25 additions & 0 deletions lib/icp-fs/src/yaml.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
use camino::{Utf8Path, Utf8PathBuf};
use snafu::prelude::*;

use crate::fs::{ReadFileError, read};

#[derive(Snafu, Debug)]
pub enum LoadYamlFileError {
#[snafu(display("failed to parse {path} as yaml"))]
Parse {
path: Utf8PathBuf,
source: serde_yaml::Error,
},

#[snafu(transparent)]
Read { source: ReadFileError },
}

pub fn load_yaml_file<T: for<'a> serde::de::Deserialize<'a>>(
path: impl AsRef<Utf8Path>,
) -> Result<T, LoadYamlFileError> {
let path = path.as_ref();
let content = read(path)?;

serde_yaml::from_slice(content.as_ref()).context(ParseSnafu { path })
}
13 changes: 13 additions & 0 deletions lib/icp-project/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[package]
name = "icp-project"
version = "0.1.0"
edition = "2024"

[dependencies]
camino = { workspace = true }
glob = { workspace = true }
icp-canister = { path = "../icp-canister" }
icp-fs = { "path" = "../icp-fs" }
icp-network = { path = "../icp-network" }
serde = { workspace = true }
snafu = { workspace = true }
Loading