Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
32 changes: 0 additions & 32 deletions crates/icp-cli/tests/recipe_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -332,35 +332,3 @@ fn recipe_local_file_valid_checksum() {
.assert()
.success();
}

#[test]
fn recipe_builtin_ignores_checksum() {
let ctx = TestContext::new();

for recipe_type in ["assets", "motoko", "rust"] {
// Setup project
let project_dir = ctx.create_project_dir("icp");

let pm = formatdoc! {"
canister:
name: my-canister
recipe:
type: {recipe_type}
sha256: invalid_checksum_should_be_ignored
"};

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

// Invoke build - should succeed because local files don't verify checksums
ctx.icp()
.current_dir(project_dir)
.args(["build"])
.assert()
.append_context("test-case", recipe_type)
.success();
}
}
6 changes: 5 additions & 1 deletion crates/icp/src/canister/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ use tokio::sync::mpsc::{Sender, error::SendError};

use crate::{
canister::{build, script::ScriptError},
manifest::adapter::{prebuilt, script},
manifest::{
adapter::{prebuilt, script},
serde_helpers::non_empty_vec,
},
prelude::*,
};

Expand Down Expand Up @@ -51,6 +54,7 @@ impl fmt::Display for Step {
/// including the adapters and build steps responsible for the build.
#[derive(Clone, Debug, Deserialize, PartialEq, JsonSchema, Serialize)]
pub struct Steps {
#[serde(deserialize_with = "non_empty_vec")]
pub steps: Vec<Step>,
}

Expand Down
75 changes: 27 additions & 48 deletions crates/icp/src/canister/recipe/handlebars.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use indoc::formatdoc;
use serde::Deserialize;
use std::{str::FromStr, string::FromUtf8Error};

use crate::{
Expand Down Expand Up @@ -74,51 +75,17 @@ pub enum HandlebarsError {
#[async_trait]
impl Resolve for Handlebars {
async fn resolve(&self, recipe: &Recipe) -> Result<(build::Steps, sync::Steps), ResolveError> {
// Sanity check recipe type
let recipe_type = match &recipe.recipe_type {
RecipeType::Unknown(typ) => typ.to_owned(),
// Find the template
let tmpl = match &recipe.recipe_type {
RecipeType::File(path) => TemplateSource::LocalPath(Path::new(&path).into()),
RecipeType::Url(url) => TemplateSource::RemoteUrl(url.to_owned()),
RecipeType::Registry {
name,
recipe,
version,
} => TemplateSource::Registry(name.to_owned(), recipe.to_owned(), version.to_owned()),
};

// Infer source for recipe template (local, remote, built-in, etc)
let tmpl = (|recipe_type: String| {
if recipe_type.starts_with("file://") {
let path = recipe_type
.strip_prefix("file://")
.map(Path::new)
.expect("prefix missing")
.into();

return TemplateSource::LocalPath(path);
}

if recipe_type.starts_with("http://") || recipe_type.starts_with("https://") {
return TemplateSource::RemoteUrl(recipe_type);
}

if recipe_type.starts_with("@") {
let recipe_type = recipe_type.strip_prefix("@").expect("prefix missing");

// Check for version delimiter
let (v, version) = if recipe_type.contains("@") {
// Version is specified
recipe_type.rsplit_once("@").expect("delimiter missing")
} else {
// Assume latest
(recipe_type, "latest")
};

let (registry, recipe) = v.split_once("/").expect("delimiter missing");

return TemplateSource::Registry(
registry.to_owned(),
recipe.to_owned(),
version.to_owned(),
);
}

panic!("Invalid recipe type: {recipe_type}");
})(recipe_type.clone());

// TMP(or.ricon): Temporarily hardcode a dfinity registry
let tmpl = match tmpl {
TemplateSource::Registry(registry, recipe, version) => {
Expand Down Expand Up @@ -208,24 +175,36 @@ impl Resolve for Handlebars {
.map_err(|err| ResolveError::Handlebars {
source: HandlebarsError::Render {
source: err,
recipe: recipe_type.to_owned(),
recipe: recipe.recipe_type.clone().into(),
template: tmpl.to_owned(),
},
})?;

// Read the rendered YAML canister manifest
let insts = serde_yaml::from_str::<Instructions>(&out);
// Recipes can only render buid/sync
#[derive(Deserialize)]
struct BuildSyncHelper {
build: build::Steps,
#[serde(default)]
sync: sync::Steps,
}

let insts = serde_yaml::from_str::<BuildSyncHelper>(&out);
let insts = match insts {
Ok(insts) => insts,
Ok(helper) => Instructions::BuildSync {
build: helper.build,
sync: helper.sync,
},
Err(e) => panic!(
"{}",
formatdoc! {r#"
Unable to render template into valid yaml: {e}
Unable to render recipe {} template into valid yaml: {e}

Rendered content:
------
{out}
------
"#}
"#, recipe.recipe_type}
),
};

Expand Down
2 changes: 1 addition & 1 deletion crates/icp/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ pub enum LoadError {
#[error("failed to load path")]
Path,

#[error("failed to load manifest")]
#[error("failed to load the project manifest")]
Manifest,

#[error(transparent)]
Expand Down
Loading