Skip to content

Commit

Permalink
ABI embedding (#31)
Browse files Browse the repository at this point in the history
  • Loading branch information
miraclx authored Aug 29, 2022
1 parent 885437e commit 286f745
Show file tree
Hide file tree
Showing 18 changed files with 310 additions and 86 deletions.
3 changes: 2 additions & 1 deletion cargo-near/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
[package]
name = "cargo-near"
version = "0.1.0"
authors = ["Near Inc <[email protected]>"]
edition = "2021"
rust-version = "1.56.0"
license = "MIT OR Apache-2.0"
readme = "README.md"
repository = "https://github.com/near/cargo-near"
description = """
Cargo extension for building Rust smart contracts on NEAR.
Cargo extension for building Rust smart contracts on NEAR
"""

[dependencies]
Expand Down
62 changes: 47 additions & 15 deletions cargo-near/src/abi/mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
use crate::cargo::{manifest::CargoManifestPath, metadata::CrateMetadata};
use crate::util;
use crate::{util, AbiCommand};
use std::collections::HashMap;
use std::{fs, path::PathBuf};
use std::fs;
use std::path::PathBuf;

mod generation;

Expand All @@ -13,11 +14,10 @@ pub(crate) struct AbiResult {
pub path: PathBuf,
}

pub(crate) fn execute(
manifest_path: &CargoManifestPath,
pub(crate) fn write_to_file(
crate_metadata: &CrateMetadata,
generate_docs: bool,
) -> anyhow::Result<AbiResult> {
let crate_metadata = CrateMetadata::collect(manifest_path)?;
let near_abi_gen_dir = &crate_metadata
.target_directory
.join(crate_metadata.root_package.name.clone() + "-near-abi-gen");
Expand All @@ -27,35 +27,47 @@ pub(crate) fn execute(
near_abi_gen_dir.display()
);

let dylib_path = util::compile_dylib_project(manifest_path)?;
let dylib_artifact = util::compile_project(
&crate_metadata.manifest_path,
&["--features", "near-sdk/__abi-generate"],
vec![
("CARGO_PROFILE_DEV_OPT_LEVEL", "0"),
("CARGO_PROFILE_DEV_DEBUG", "0"),
("CARGO_PROFILE_DEV_LTO", "off"),
],
util::dylib_extension(),
)?;

let cargo_toml = generation::generate_toml(manifest_path)?;
fs::write(near_abi_gen_dir.join("Cargo.toml"), cargo_toml)?;
// todo! experiment with reusing cargo-near for extracting data from the dylib
if dylib_artifact.fresh {
let cargo_toml = generation::generate_toml(&crate_metadata.manifest_path)?;
fs::write(near_abi_gen_dir.join("Cargo.toml"), cargo_toml)?;

let build_rs = generation::generate_build_rs(&dylib_path)?;
fs::write(near_abi_gen_dir.join("build.rs"), build_rs)?;
let build_rs = generation::generate_build_rs(&dylib_artifact.path)?;
fs::write(near_abi_gen_dir.join("build.rs"), build_rs)?;

let main_rs = generation::generate_main_rs(&dylib_path)?;
fs::write(near_abi_gen_dir.join("main.rs"), main_rs)?;
let main_rs = generation::generate_main_rs(&dylib_artifact.path)?;
fs::write(near_abi_gen_dir.join("main.rs"), main_rs)?;
}

let stdout = util::invoke_cargo(
"run",
&["--package", "near-abi-gen"],
Some(near_abi_gen_dir),
vec![(
"LD_LIBRARY_PATH",
&dylib_path.parent().unwrap().to_string_lossy(),
&dylib_artifact.path.parent().unwrap().to_string_lossy(),
)],
)?;

let mut contract_abi = near_abi::__private::ChunkedAbiEntry::combine(
serde_json::from_slice::<Vec<_>>(&stdout)?.into_iter(),
)?
.into_abi_root(extract_metadata(&crate_metadata));
.into_abi_root(extract_metadata(crate_metadata));
if !generate_docs {
strip_docs(&mut contract_abi);
}
let near_abi_json = serde_json::to_string_pretty(&contract_abi)?;
let near_abi_json = serde_json::to_string(&contract_abi)?;
let out_path_abi = crate_metadata.target_directory.join(ABI_FILE);
fs::write(&out_path_abi, near_abi_json)?;

Expand Down Expand Up @@ -86,3 +98,23 @@ fn strip_docs(abi_root: &mut near_abi::AbiRoot) {
}
}
}

pub(crate) fn run(args: AbiCommand) -> anyhow::Result<()> {
let crate_metadata = CrateMetadata::collect(CargoManifestPath::try_from(
args.manifest_path.unwrap_or_else(|| "Cargo.toml".into()),
)?)?;

let out_dir = util::force_canonicalize_dir(
&args
.out_dir
.unwrap_or_else(|| crate_metadata.target_directory.clone()),
)?;

let AbiResult { path } = write_to_file(&crate_metadata, args.doc)?;

let abi_path = util::copy(&path, &out_dir)?;

println!("ABI successfully generated at `{}`", abi_path.display());

Ok(())
}
72 changes: 72 additions & 0 deletions cargo-near/src/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
use crate::abi::AbiResult;
use crate::cargo::{manifest::CargoManifestPath, metadata::CrateMetadata};
use crate::{abi, util, BuildCommand};
use colored::Colorize;
use std::io::BufRead;

const COMPILATION_TARGET: &str = "wasm32-unknown-unknown";

pub(crate) fn run(args: BuildCommand) -> anyhow::Result<()> {
if !util::invoke_rustup(&["target", "list", "--installed"])?
.lines()
.any(|target| target.as_ref().map_or(false, |t| t == COMPILATION_TARGET))
{
anyhow::bail!("rust target `{}` is not installed", COMPILATION_TARGET);
}

let crate_metadata = CrateMetadata::collect(CargoManifestPath::try_from(
args.manifest_path.unwrap_or_else(|| "Cargo.toml".into()),
)?)?;

let out_dir = util::force_canonicalize_dir(
&args
.out_dir
.unwrap_or_else(|| crate_metadata.target_directory.clone()),
)?;

let mut build_env = vec![("RUSTFLAGS", "-C link-arg=-s")];
let mut cargo_args = vec!["--target", COMPILATION_TARGET];
if args.release {
cargo_args.push("--release");
}

let mut abi_path = None;
if !args.no_abi {
let AbiResult { path } = abi::write_to_file(&crate_metadata, args.doc)?;
abi_path.replace(util::copy(&path, &out_dir)?);
}

if let (true, Some(abi_path)) = (args.embed_abi, &abi_path) {
cargo_args.extend(&["--features", "near-sdk/__abi-embed"]);
build_env.push(("CARGO_NEAR_ABI_PATH", abi_path.to_str().unwrap()));
}

let mut wasm_artifact = util::compile_project(
&crate_metadata.manifest_path,
&cargo_args,
build_env,
"wasm",
)?;

wasm_artifact.path = util::copy(&wasm_artifact.path, &out_dir)?;

// todo! if we embedded, check that the binary exports the __contract_abi symbol
println!("{}", "Contract Successfully Built!".green().bold());
println!(
" - Binary: {}",
wasm_artifact
.path
.display()
.to_string()
.bright_yellow()
.bold()
);
if let Some(abi_path) = abi_path {
println!(
" - ABI: {}",
abi_path.display().to_string().yellow().bold()
);
}

Ok(())
}
17 changes: 13 additions & 4 deletions cargo-near/src/cargo/manifest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,21 @@ impl TryFrom<PathBuf> for CargoManifestPath {
fn try_from(manifest_path: PathBuf) -> Result<Self, Self::Error> {
if let Some(file_name) = manifest_path.file_name() {
if file_name != MANIFEST_FILE_NAME {
anyhow::bail!("Manifest file must be a Cargo.toml")
anyhow::bail!("the manifest-path must be a path to a Cargo.toml file")
}
}
let canonical_manifest_path = manifest_path.canonicalize().map_err(|err| {
anyhow::anyhow!("Failed to canonicalize {:?}: {:?}", manifest_path, err)
})?;
let canonical_manifest_path =
manifest_path
.canonicalize()
.map_err(|err| match err.kind() {
std::io::ErrorKind::NotFound => {
anyhow::anyhow!(
"manifest path `{}` does not exist",
manifest_path.display()
)
}
_ => err.into(),
})?;
Ok(CargoManifestPath {
path: canonical_manifest_path,
})
Expand Down
10 changes: 6 additions & 4 deletions cargo-near/src/cargo/metadata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,21 @@ use std::path::PathBuf;
pub(crate) struct CrateMetadata {
pub root_package: Package,
pub target_directory: PathBuf,
pub manifest_path: CargoManifestPath,
}

impl CrateMetadata {
/// Parses the contract manifest and returns relevant metadata.
pub fn collect(manifest_path: &CargoManifestPath) -> Result<Self> {
let (metadata, root_package) = get_cargo_metadata(manifest_path)?;
pub fn collect(manifest_path: CargoManifestPath) -> Result<Self> {
let (metadata, root_package) = get_cargo_metadata(&manifest_path)?;
let mut target_directory = metadata.target_directory.as_path().join("near");

// Normalize the package and lib name.
let package_name = root_package.name.replace('-', "_");

let absolute_manifest_path = manifest_path.directory()?;
let absolute_manifest_dir = manifest_path.directory()?;
let absolute_workspace_root = metadata.workspace_root.canonicalize()?;
if absolute_manifest_path != absolute_workspace_root {
if absolute_manifest_dir != absolute_workspace_root {
// If the contract is a package in a workspace, we use the package name
// as the name of the sub-folder where we put the `.contract` bundle.
target_directory = target_directory.join(package_name);
Expand All @@ -30,6 +31,7 @@ impl CrateMetadata {
let crate_metadata = CrateMetadata {
root_package,
target_directory: target_directory.into(),
manifest_path,
};
Ok(crate_metadata)
}
Expand Down
65 changes: 45 additions & 20 deletions cargo-near/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
use cargo::manifest::CargoManifestPath;
use clap::{AppSettings, Args, Parser, Subcommand};
use std::path::PathBuf;

mod abi;
mod build;
mod cargo;
mod util;

#[derive(Debug, Parser)]
#[clap(bin_name = "cargo")]
#[clap(bin_name = "cargo", version, author, about)]
pub enum Opts {
#[clap(name = "near")]
#[clap(setting = AppSettings::DeriveDisplayOrder)]
#[clap(
name = "near",
version,
author,
about,
setting = AppSettings::DeriveDisplayOrder,
)]
Near(NearArgs),
}

Expand All @@ -25,31 +30,51 @@ pub enum NearCommand {
/// Generates ABI for the contract
#[clap(name = "abi")]
Abi(AbiCommand),
/// Build a NEAR contract and optionally embed ABI
#[clap(name = "build")]
Build(BuildCommand),
}

#[derive(Debug, clap::Args)]
#[clap(name = "abi")]
#[clap(setting = AppSettings::DeriveDisplayOrder)]
pub struct AbiCommand {
/// Include rustdocs in the ABI file
#[clap(long)]
pub doc: bool,
/// Copy final artifacts to the this directory
#[clap(long, parse(from_os_str), value_name = "PATH")]
pub out_dir: Option<PathBuf>,
/// Path to the `Cargo.toml` of the contract to build
#[clap(long, parse(from_os_str))]
#[clap(long, parse(from_os_str), value_name = "PATH")]
pub manifest_path: Option<PathBuf>,
/// Include rustdocs in the ABI file
#[clap(long, takes_value = false)]
}

#[derive(Debug, clap::Args)]
#[clap(setting = AppSettings::DeriveDisplayOrder)]
pub struct BuildCommand {
/// Build contract in release mode, with optimizations
#[clap(short, long)]
pub release: bool,
/// Embed the ABI in the contract binary
#[clap(long)]
pub embed_abi: bool,
/// Include rustdocs in the embedded ABI
#[clap(long, requires = "embed-abi")]
pub doc: bool,
/// Do not generate ABI for the contract
#[clap(long, conflicts_with_all = &["doc", "embed-abi"])]
pub no_abi: bool,
/// Copy final artifacts to the this directory
#[clap(long, parse(from_os_str), value_name = "PATH")]
pub out_dir: Option<PathBuf>,
/// Path to the `Cargo.toml` of the contract to build
#[clap(long, parse(from_os_str), value_name = "PATH")]
pub manifest_path: Option<PathBuf>,
}

pub fn exec(cmd: NearCommand) -> anyhow::Result<()> {
match &cmd {
NearCommand::Abi(abi) => {
let manifest_path = abi
.manifest_path
.clone()
.unwrap_or_else(|| "Cargo.toml".into());
let manifest_path = CargoManifestPath::try_from(manifest_path)?;

let result = abi::execute(&manifest_path, abi.doc)?;
println!("ABI successfully generated at {}", result.path.display());
Ok(())
}
match cmd {
NearCommand::Abi(args) => abi::run(args),
NearCommand::Build(args) => build::run(args),
}
}
6 changes: 1 addition & 5 deletions cargo-near/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,7 @@ fn main() {
match cargo_near::exec(args.cmd) {
Ok(()) => {}
Err(err) => {
eprintln!(
"{} {}",
"ERROR:".bright_red().bold(),
format!("{:?}", err).bright_red()
);
eprintln!("{} {:?}", "error:".bright_red().bold(), err);
std::process::exit(1);
}
}
Expand Down
Loading

0 comments on commit 286f745

Please sign in to comment.