Skip to content

Commit

Permalink
feat: add two-step snap generation process
Browse files Browse the repository at this point in the history
This commit splits the snap generation into two steps. The goal is to
allow the user to edit the intermediate generated WASM library to adjust
the type resolution to compile his runtime call.

It will still attempt to provide an out-of-the-box correct
implementation.
  • Loading branch information
vlopes11 committed Nov 28, 2023
1 parent cda64a3 commit 6f1932a
Show file tree
Hide file tree
Showing 9 changed files with 822 additions and 605 deletions.
304 changes: 254 additions & 50 deletions Cargo.lock

Large diffs are not rendered by default.

9 changes: 8 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,12 @@ readme = "README.md"
[dependencies]
anyhow = "1.0"
clap = { version = "4.4", features = ["derive"] }
rustyline = { version = "12.0", features = ["derive"] }
duct = "0.13"
rustyline = { version = "10.0" }
rustyline-derive = "0.7"
smallvec = "1.11"
toml = "0.8"

[[bin]]
name = "cargo-sov-snap-generator"
path = "./src/main.rs"
80 changes: 51 additions & 29 deletions src/args.rs
Original file line number Diff line number Diff line change
@@ -1,42 +1,64 @@
use std::path::PathBuf;

use clap::Parser;
use clap::{Parser, Subcommand};

#[derive(Parser)]
pub struct Cli {
/// Path to the directory that contains the manifest TOML file of the project.
#[arg(short, long)]
pub path: Option<PathBuf>,
#[command(subcommand)]
pub command: Commands,

/// Target directory to output the generated project.
#[arg(short, long)]
pub target: Option<PathBuf>,
#[arg(
long,
short = 'q',
action = clap::ArgAction::Count,
global = true,
help = "Sets the verbosity level.",
)]
pub quiet: u8,

/// Git remote to use when cloning the origin repository.
#[arg(short, long)]
pub origin: Option<String>,
/// Defaults all inputs.
#[arg(long)]
pub defaults: bool,

/// Branch to use when cloning the origin repository.
/// Skips all confirmations.
#[arg(short, long)]
pub branch: Option<String>,
pub force: bool,
}

/// Context definition of the runtime spec.
#[arg(short, long)]
pub context: Option<String>,
pub struct InterfaceArgs {
pub quiet: u8,
pub defaults: bool,
pub force: bool,
}

/// DA definition of the runtime.
#[arg(short, long)]
pub da_spec: Option<String>,
impl Cli {
pub fn split_interface(self) -> (Subcommands, InterfaceArgs) {
let command = match self.command {
Commands::SovSnapGenerator { command } => command,
};

/// Runtime call definition.
#[arg(short, long)]
pub runtime: Option<String>,
(
command,
InterfaceArgs {
quiet: self.quiet,
defaults: self.defaults,
force: self.force,
},
)
}
}

/// Defaults all inputs.
#[arg(long)]
pub defaults: bool,
#[derive(Debug, Clone, Subcommand)]
pub enum Commands {
SovSnapGenerator {
#[command(subcommand)]
command: Subcommands,
},
}

/// Skips all confirmations.
#[arg(long)]
pub force: bool,
#[derive(Debug, Clone, Subcommand)]
pub enum Subcommands {
/// Initializes the project from the provided path.
Init(super::init::Init),

/// Builds a project initialized via `Init`.
Build(super::build::Build),
}
53 changes: 53 additions & 0 deletions src/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
use std::{env, path::PathBuf};

use anyhow::Context;
use clap::Parser;

use super::interface::Interface;

#[derive(Debug, Clone, Parser)]
pub struct Build {
/// Path of the generated WASM project.
#[arg(short, long)]
path: Option<PathBuf>,
}

pub fn build(args: Build, interface: &mut Interface) -> anyhow::Result<()> {
let Build { path } = args;

let cwd = env::current_dir()?;
interface.prompt("Insert the path to the generated `Cargo.toml`");
let path = interface.path_or_read(Some(&cwd.display().to_string()), path);
let path = path
.is_dir()
.then(|| path.join("Cargo.toml"))
.unwrap_or(path);

let path = path
.canonicalize()?
.parent()
.and_then(|p| p.parent())
.and_then(|p| p.parent())
.with_context(|| format!("Failed to locate the root snap of `{}`", path.display()))?
.canonicalize()?;

interface.info(format!("Using target root `{}`...", path.display()));

duct::cmd!("yarn", "install").dir(&path).run()?;

interface.info(format!(
"Yarn packages installed on `{}`...",
path.display()
));

duct::cmd!("yarn", "update-wasm").dir(&path).run()?;

interface.info(format!("WASM file built on `{}`...", path.display()));

duct::cmd!("yarn", "build").dir(&path).run()?;

interface.info(format!("Yarn project built on `{}`...", path.display()));
interface.info("To start the browser application, run `yarn start`.");

Ok(())
}
6 changes: 0 additions & 6 deletions src/definitions.rs

This file was deleted.

189 changes: 189 additions & 0 deletions src/init.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
use std::{env, fs, path::PathBuf};

use clap::Parser;

use super::{
interface::Interface,
manifest::{Dependency, Manifest},
};

#[derive(Debug, Clone, Parser)]
pub struct Init {
/// Path of the runtime module cargo project.
#[arg(short, long)]
path: Option<PathBuf>,

/// Target directory to output the generated project.
#[arg(short, long)]
target: Option<PathBuf>,

/// Git remote to use when cloning the origin repository.
#[arg(short, long)]
origin: Option<String>,

/// Branch to use when cloning the origin repository.
#[arg(short, long)]
branch: Option<String>,
}

pub fn init(args: Init, interface: &mut Interface) -> anyhow::Result<()> {
let Init {
path,
target,
origin,
branch,
} = args;

let cwd = env::current_dir()?;
interface.prompt("Insert the path to your `Cargo.toml`");
let path = interface.path_or_read(Some(&cwd.display().to_string()), path);
let path = path
.is_dir()
.then(|| path.join("Cargo.toml"))
.unwrap_or(path);

if !path.exists() {
anyhow::bail!(
"Failed to locate `Cargo.toml`; {} does not exist",
path.display()
);
}

if !path.is_file() {
anyhow::bail!(
"Failed to locate `Cargo.toml`; {} is not a file",
path.display()
);
}

let path = path.canonicalize()?;

interface.info(format!("Using manifest `{}`...", path.display()));

let manifest = Manifest::read(&path, interface)?;

interface.prompt("Insert the target directory of the project");
let target_default = cwd
.parent()
.unwrap_or_else(|| cwd.as_path())
.join(format!("{}-snap", manifest.project.name))
.display()
.to_string();

let target = interface.path_or_read(Some(&target_default), target);
if target.is_file() {
interface.bail(format!(
"The provided target `{}` is a file; use a directory",
target.display()
));
}

if target.exists() {
if fs::remove_dir(&target).is_err() {
interface.prompt(format!(
"The target directory `{}` already exists; overwrite? [y/n]",
target.display()
));

interface.read_confirmation();

fs::remove_dir_all(&target)?;
}
}

interface.prompt("Insert the origin git repository of the snap template");
let origin_default = "https://github.com/Sovereign-Labs/sov-snap";
let origin = interface.line_or_read(Some(&origin_default), origin);

interface.prompt("Insert the branch of the snap template");
let branch_default = "v0.1.2";
let branch = interface.line_or_read(Some(&branch_default), branch);

interface.info(format!(
"Cloning the snap template into `{}`...",
target.display()
));

duct::cmd!(
"git",
"clone",
"--quiet",
"--progress",
"-c",
"advice.detachedHead=false",
"--branch",
branch,
"--single-branch",
"--depth",
"1",
origin,
&target,
)
.run()?;

interface.info(format!(
"Cloned the snap template into `{}`",
target.display()
));

let target_wasm = target.join("external").join("sov-wasm").canonicalize()?;
let target_wasm_manifest = target_wasm.join("Cargo.toml");
let target_definitions = target_wasm.join("src").join("definitions.rs");

let borsh = manifest
.dependencies
.get(&Dependency::new("borsh"))
.cloned()
.unwrap_or_else(|| String::from("\"0.10.3\""));
let serde_json = manifest
.dependencies
.get(&Dependency::new("serde_json"))
.cloned()
.unwrap_or_else(|| String::from("\"1.0\""));
let sov_modules_api = manifest.dependencies.get(&Dependency::new("sov-modules-api")).cloned().unwrap_or_else(|| String::from(r#"{ git = "https://github.com/Sovereign-Labs/sovereign-sdk.git", rev = "df169be", features = ["serde"] }"#));
let sov_mock_da = manifest.dependencies.get(&Dependency::new("sov-mock-da")).cloned().unwrap_or_else(|| String::from(r#"{ git = "https://github.com/Sovereign-Labs/sovereign-sdk.git", rev = "df169be" }"#));

let project = path
.parent()
.unwrap_or_else(|| path.as_path())
.display()
.to_string();
let wasm_manifest = format!(
r#"[package]
name = "sov-wasm"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib", "rlib"]
[dependencies]
{} = {{ path = "{}" }}
borsh = {}
serde_json = {}
sov-modules-api = {}
sov-mock-da = {}
"#,
manifest.project.name, project, borsh, serde_json, sov_modules_api, sov_mock_da
);

let definitions = format!(
r#"pub type Context = sov_modules_api::default_context::ZkDefaultContext;
pub type DaSpec = sov_mock_da::MockDaSpec;
pub type RuntimeCall = {}::RuntimeCall<Context, DaSpec>;
"#,
manifest.project.formatted
);

fs::write(&target_wasm_manifest, wasm_manifest)?;
fs::write(&target_definitions, definitions)?;

interface.info(format!(
"Generated the snap template into `{}`",
target.display()
));

interface.info("Edit the generated snap template and run `cargo sov-snap-generator build`");

Ok(())
}
Loading

0 comments on commit 6f1932a

Please sign in to comment.