-
Notifications
You must be signed in to change notification settings - Fork 52
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add a way to automatically compile and deploy a contract (#77)
- Loading branch information
Showing
8 changed files
with
191 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
use anyhow::anyhow; | ||
use async_process::Command; | ||
use cargo_metadata::{Message, Metadata, MetadataCommand}; | ||
use std::env; | ||
use std::fmt::Debug; | ||
use std::fs; | ||
use std::path::Path; | ||
use std::process::Stdio; | ||
use tracing::debug; | ||
|
||
fn cargo_bin() -> Command { | ||
match env::var_os("CARGO") { | ||
Some(cargo) => Command::new(cargo), | ||
None => Command::new("cargo"), | ||
} | ||
} | ||
|
||
/// Fetch current project's metadata (i.e. project invoking this method, not the one that we are | ||
/// trying to compile). | ||
fn root_cargo_metadata() -> anyhow::Result<Metadata> { | ||
MetadataCommand::new().exec().map_err(Into::into) | ||
} | ||
|
||
async fn build_cargo_project<P: AsRef<Path> + Debug>( | ||
project_path: P, | ||
) -> anyhow::Result<Vec<Message>> { | ||
let metadata = root_cargo_metadata()?; | ||
let output = cargo_bin() | ||
.args([ | ||
"build", | ||
"--target", | ||
"wasm32-unknown-unknown", | ||
"--release", | ||
"--message-format=json", | ||
"--target-dir", | ||
metadata.target_directory.as_str(), | ||
]) | ||
.current_dir(&project_path) | ||
.stdout(Stdio::piped()) | ||
.stderr(Stdio::piped()) | ||
.output() | ||
.await?; | ||
debug!( | ||
target: "workspaces", | ||
"Building project '{:?}' resulted in status {:?}", | ||
&project_path, output.status | ||
); | ||
if output.status.success() { | ||
let reader = std::io::BufReader::new(output.stdout.as_slice()); | ||
Ok(Message::parse_stream(reader).map(|m| m.unwrap()).collect()) | ||
} else { | ||
Err(anyhow!( | ||
"Failed to build project '{:?}'.\n\ | ||
Stderr:\n\ | ||
{}\n\ | ||
Stdout:\n\ | ||
{}", | ||
project_path, | ||
String::from_utf8(output.stderr)?, | ||
String::from_utf8(output.stdout)?, | ||
)) | ||
} | ||
} | ||
|
||
/// Builds the cargo project located at `project_path` and returns the generated wasm file contents. | ||
/// | ||
/// NOTE: This function does not check whether the resulting wasm file is a valid smart | ||
/// contract or not. | ||
pub async fn compile_project(project_path: &str) -> anyhow::Result<Vec<u8>> { | ||
let messages = build_cargo_project(fs::canonicalize(project_path)?).await?; | ||
// We find the last compiler artifact message which should contain information about the | ||
// resulting .wasm file | ||
let compile_artifact = messages | ||
.iter() | ||
.filter_map(|m| match m { | ||
cargo_metadata::Message::CompilerArtifact(artifact) => Some(artifact), | ||
_ => None, | ||
}) | ||
.last() | ||
.ok_or(anyhow!( | ||
"Cargo failed to produce any compilation artifacts. \ | ||
Please check that your project contains a NEAR smart contract." | ||
))?; | ||
// The project could have generated many auxiliary files, we are only interested in .wasm files | ||
let wasm_files = compile_artifact | ||
.filenames | ||
.iter() | ||
.filter(|f| f.as_str().ends_with(".wasm")) | ||
.collect::<Vec<_>>(); | ||
match wasm_files.as_slice() { | ||
[] => Err(anyhow!( | ||
"Compilation resulted in no '.wasm' target files. \ | ||
Please check that your project contains a NEAR smart contract." | ||
)), | ||
[file] => Ok(tokio::fs::read(file.canonicalize()?).await?), | ||
_ => Err(anyhow!( | ||
"Compilation resulted in more than one '.wasm' target file: {:?}", | ||
wasm_files | ||
)), | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
#![cfg(feature = "unstable")] | ||
#![recursion_limit = "256"] | ||
use test_log::test; | ||
use workspaces::prelude::*; | ||
|
||
#[test(tokio::test)] | ||
async fn test_dev_deploy_project() -> anyhow::Result<()> { | ||
let worker = workspaces::sandbox(); | ||
let wasm = workspaces::compile_project("./tests/test-contracts/status-message").await?; | ||
let contract = worker.dev_deploy(&wasm).await?; | ||
|
||
let _res = contract | ||
.call(&worker, "set_status") | ||
.args_json(("foo",))? | ||
.max_gas() | ||
.transact() | ||
.await?; | ||
|
||
let res = contract | ||
.call(&worker, "get_status") | ||
.args_json((contract.id(),))? | ||
.view() | ||
.await?; | ||
assert_eq!(res.json::<String>()?, "foo"); | ||
|
||
Ok(()) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
[package] | ||
name = "test-contract-status-message" | ||
version = "0.0.0" | ||
authors = ["Near Inc <[email protected]>"] | ||
edition = "2018" | ||
|
||
[lib] | ||
crate-type = ["cdylib"] | ||
|
||
[dependencies] | ||
near-sdk = "4.0.0-pre.7" | ||
near-contract-standards = "4.0.0-pre.7" | ||
|
||
[profile.release] | ||
codegen-units = 1 | ||
# Tell `rustc` to optimize for small code size. | ||
opt-level = "z" | ||
lto = true | ||
debug = false | ||
panic = "abort" | ||
overflow-checks = true | ||
|
||
[workspace] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
use near_sdk::borsh::{self, BorshDeserialize, BorshSerialize}; | ||
use near_sdk::{env, log, metadata, near_bindgen, AccountId}; | ||
use std::collections::HashMap; | ||
|
||
#[near_bindgen] | ||
#[derive(Default, BorshDeserialize, BorshSerialize)] | ||
pub struct StatusMessage { | ||
records: HashMap<AccountId, String>, | ||
} | ||
|
||
#[near_bindgen] | ||
impl StatusMessage { | ||
#[payable] | ||
pub fn set_status(&mut self, message: String) { | ||
let account_id = env::signer_account_id(); | ||
log!("{} set_status with message {}", account_id, message); | ||
self.records.insert(account_id, message); | ||
} | ||
|
||
pub fn get_status(&self, account_id: AccountId) -> Option<String> { | ||
log!("get_status for account_id {}", account_id); | ||
self.records.get(&account_id).cloned() | ||
} | ||
} |