Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ABI embedding #31

Merged
merged 49 commits into from
Aug 29, 2022
Merged
Show file tree
Hide file tree
Changes from 39 commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
70cf2c0
use near-abi-rs
miraclx Aug 16, 2022
509f83c
near_sdk -> near_abi
miraclx Aug 17, 2022
bcf9ae5
implement ABI embedding
miraclx Aug 21, 2022
4d52e4f
keep clippy happy
miraclx Aug 21, 2022
15abb7f
update near_sdk
miraclx Aug 21, 2022
b94667a
update
miraclx Aug 22, 2022
327b229
update near-abi, near-sdk
miraclx Aug 23, 2022
5202fc5
Merge branch 'main' into miraclx/lib-abi
miraclx Aug 23, 2022
fea4772
Merge branch 'miraclx/lib-abi' into miraclx/abi-embed
miraclx Aug 23, 2022
43fe4da
remove dependency on near-sdk entirely
miraclx Aug 23, 2022
335165a
Merge branch 'miraclx/lib-abi' into miraclx/abi-embed
miraclx Aug 23, 2022
725656a
remove dependency on near-sdk entirely
miraclx Aug 23, 2022
ef62676
Merge branch 'miraclx/lib-abi' into miraclx/abi-embed
miraclx Aug 23, 2022
5ada161
--doc arg requires --embed-abi
miraclx Aug 23, 2022
7764935
a silent clippy is a happy clippy
miraclx Aug 23, 2022
3c8e446
update near-abi
miraclx Aug 24, 2022
a060944
update sdk
miraclx Aug 24, 2022
7724992
update sdk
miraclx Aug 24, 2022
dc18b96
update sdk
miraclx Aug 24, 2022
fce274b
Merge branch 'miraclx/lib-abi' into miraclx/abi-embed
miraclx Aug 24, 2022
520d933
update near-sdk
miraclx Aug 24, 2022
6981c8a
Merge branch 'miraclx/lib-abi' into miraclx/abi-embed
miraclx Aug 24, 2022
3b0b662
apply suggestions
miraclx Aug 24, 2022
7fa12dd
update near-abi
miraclx Aug 25, 2022
0cc1ff4
update near-sdk
miraclx Aug 25, 2022
e8817df
update near-sdk + near-abi
miraclx Aug 25, 2022
11b1788
Merge branch 'miraclx/lib-abi' into miraclx/abi-embed
miraclx Aug 25, 2022
86ee9db
report non-existent out-dir + cleaner error reports
miraclx Aug 25, 2022
3a00f1f
RUSTFLAGS + default out_dir
miraclx Aug 25, 2022
e8d347f
contract::build -> build::run
miraclx Aug 25, 2022
0305c07
reorder imports
miraclx Aug 25, 2022
ed9b700
remove period in description
miraclx Aug 25, 2022
d9e61c7
update sdk
miraclx Aug 25, 2022
3ccfccb
Merge branch 'miraclx/lib-abi' into miraclx/abi-embed
miraclx Aug 25, 2022
c0ff333
auto create out-dir
miraclx Aug 25, 2022
c58a192
generate abi.json without --embed-abi
miraclx Aug 26, 2022
de564cd
update sdk
miraclx Aug 26, 2022
e52c9a9
apply clippy lints
miraclx Aug 26, 2022
498b799
impl --no-abi
miraclx Aug 26, 2022
fd0b098
add --out-dir to abi subcommand
miraclx Aug 26, 2022
55d7e0e
DRY; print abi path when built
miraclx Aug 26, 2022
d67ccc9
update near-sdk
miraclx Aug 26, 2022
afe13e9
fix sdk dep duplicate
miraclx Aug 26, 2022
d1eec0c
Merge branch 'miraclx/lib-abi' into miraclx/abi-embed
miraclx Aug 26, 2022
7ec431b
update sdk versions in _patch templates
miraclx Aug 26, 2022
302209a
Merge branch 'miraclx/lib-abi' into miraclx/abi-embed
miraclx Aug 26, 2022
4cc22b0
Merge branch 'main' into miraclx/abi-embed
miraclx Aug 26, 2022
1a41048
abi: paint build reports (#36)
miraclx Aug 29, 2022
a929c05
apply suggestions
miraclx Aug 29, 2022
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
7 changes: 4 additions & 3 deletions 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 All @@ -16,16 +17,16 @@ cargo_metadata = "0.14"
clap = { version = "3.2", features = ["derive", "env"] }
colored = "2.0"
env_logger = "0.9"
near-sdk = { version = "4.1.0-pre.1", features = ["abi"] }
log = "0.4"
prettyplease = "0.1"
toml = "0.5"
serde_json = "1.0"
symbolic-debuginfo = "8.8"
syn = "1.0"
quote = "1.0"
schemars = "0.8"
near-abi = { version = "0.1.0-pre.0", features = ["__chunked-entries"] }

[dev-dependencies]
function_name = "0.3"
proc-macro2 = "1.0"
schemars = "0.8"
7 changes: 3 additions & 4 deletions cargo-near/src/abi/generation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ pub(crate) fn generate_main_rs(dylib_path: &Path) -> anyhow::Result<String> {
let near_abi_function_defs = near_abi_symbols.iter().map(|s| {
let name = format_ident!("{}", s);
quote! {
fn #name() -> near_sdk::__private::AbiRoot;
fn #name() -> near_sdk::__private::ChunkedAbiEntry;
}
});
let near_abi_function_invocations = near_abi_symbols.iter().map(|s| {
Expand All @@ -141,9 +141,8 @@ pub(crate) fn generate_main_rs(dylib_path: &Path) -> anyhow::Result<String> {
}

fn main() -> Result<(), std::io::Error> {
let root_abis = vec![#(#near_abi_function_invocations),*];
let combined_root_abi = near_sdk::__private::AbiRoot::combine(root_abis);
let contents = serde_json::to_string_pretty(&combined_root_abi)?;
let abi_entries = vec![#(#near_abi_function_invocations),*];
let contents = serde_json::to_string_pretty(&abi_entries)?;
print!("{}", contents);
Ok(())
}
Expand Down
75 changes: 49 additions & 26 deletions cargo-near/src/abi/mod.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
use crate::cargo::{manifest::CargoManifestPath, metadata::CrateMetadata};
use crate::util;
use near_sdk::__private::schemars::schema::{Schema, SchemaObject};
use near_sdk::__private::{AbiMetadata, AbiRoot};
use crate::{util, AbiCommand};
use std::collections::HashMap;
use std::{fs, path::PathBuf};
use std::fs;
use std::path::PathBuf;

mod generation;

Expand All @@ -15,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 @@ -29,56 +27,69 @@ 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
miraclx marked this conversation as resolved.
Show resolved Hide resolved
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 near_abi: AbiRoot = serde_json::from_slice(&stdout)?;
let metadata = extract_metadata(&crate_metadata);
near_abi.metadata = metadata;
let mut contract_abi = near_abi::__private::ChunkedAbiEntry::combine(
serde_json::from_slice::<Vec<_>>(&stdout)?.into_iter(),
)?
.into_abi_root(extract_metadata(crate_metadata));
if !generate_docs {
strip_docs(&mut near_abi);
strip_docs(&mut contract_abi);
}
let near_abi_json = serde_json::to_string_pretty(&near_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)?;

Ok(AbiResult { path: out_path_abi })
}

fn extract_metadata(crate_metadata: &CrateMetadata) -> AbiMetadata {
fn extract_metadata(crate_metadata: &CrateMetadata) -> near_abi::AbiMetadata {
let package = &crate_metadata.root_package;
AbiMetadata {
near_abi::AbiMetadata {
name: Some(package.name.clone()),
version: Some(package.version.to_string()),
authors: package.authors.clone(),
other: HashMap::new(),
}
}

fn strip_docs(abi_root: &mut AbiRoot) {
for function in &mut abi_root.abi.functions {
fn strip_docs(abi_root: &mut near_abi::AbiRoot) {
for function in &mut abi_root.body.functions {
function.doc = None;
}
for schema in &mut abi_root.abi.root_schema.definitions.values_mut() {
if let Schema::Object(SchemaObject {
for schema in &mut abi_root.body.root_schema.definitions.values_mut() {
if let schemars::schema::Schema::Object(schemars::schema::SchemaObject {
metadata: Some(metadata),
..
}) = schema
Expand All @@ -87,3 +98,15 @@ fn strip_docs(abi_root: &mut 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 AbiResult { path } = write_to_file(&crate_metadata, args.doc)?;

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

Ok(())
}
82 changes: 82 additions & 0 deletions cargo-near/src/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
use crate::cargo::{manifest::CargoManifestPath, metadata::CrateMetadata};
use crate::{abi, util, BuildCommand};
use anyhow::Context;
use std::fs;
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 = args
.out_dir
.unwrap_or_else(|| crate_metadata.target_directory.clone());
fs::create_dir_all(&out_dir)
.with_context(|| format!("failed to create directory `{}`", out_dir.display()))?;
let out_dir = out_dir
.canonicalize()
.with_context(|| format!("failed to access output directory `{}`", out_dir.display()))?;

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 abi = (!args.no_abi)
.then(|| abi::write_to_file(&crate_metadata, args.doc))
.transpose()?;

let wasm_artifact = if args.embed_abi {
cargo_args.extend(&["--features", "near-sdk/__abi-embed"]);
build_env.push((
"CARGO_NEAR_ABI_PATH",
abi.as_ref().unwrap().path.to_str().unwrap(),
));

util::compile_project(
&crate_metadata.manifest_path,
&cargo_args,
build_env,
"wasm",
)?
} else {
util::compile_project(
&crate_metadata.manifest_path,
&cargo_args,
build_env,
"wasm",
)?
};

let mut out_path = wasm_artifact.path;
if out_path
.parent()
.map_or(false, |out_path| out_dir != out_path)
{
let new_out_path = out_dir.join(out_path.file_name().unwrap());
std::fs::copy(&out_path, &new_out_path).with_context(|| {
format!(
"failed to copy `{}` to `{}`",
out_path.display(),
new_out_path.display(),
)
})?;
out_path = new_out_path;
}

// todo! if we embedded, check that the binary exports the __contract_abi symbol
println!("Contract successfully built at {}", out_path.display());
miraclx marked this conversation as resolved.
Show resolved Hide resolved

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
Loading