diff --git a/Cargo.lock b/Cargo.lock index 5aa64b1233c0a3644b573587cee258202fc2bc19..48d96c8217f71d302bc2261419953c81673a66ca 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -289,7 +289,7 @@ checksum = "130aac562c0dd69c56b3b1cc8ffd2e17be31d0b6c25b61c96b76231aa23e39e1" [[package]] name = "cargo-contract" -version = "0.6.1" +version = "0.7.0" dependencies = [ "anyhow", "assert_matches", diff --git a/Cargo.toml b/Cargo.toml index 2fd654f50281ddad16c6a05693cde6e04bcecda1..6b81e4b08bc0a93cff35fd6909e7b03e6649278d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cargo-contract" -version = "0.6.1" +version = "0.7.0" authors = ["Parity Technologies "] build = "build.rs" edition = "2018" diff --git a/build.rs b/build.rs index 1cc7f382e5bc4caff511d779191768e8eab074c8..23ba938e4564fd296351c7959ccd60c2b78b066d 100644 --- a/build.rs +++ b/build.rs @@ -37,7 +37,7 @@ fn main() { .expect("OUT_DIR should be set by cargo") .into(); - let template_dir = manifest_dir.join("template"); + let template_dir = manifest_dir.join("templates").join("new"); let dst_file = out_dir.join("template.zip"); println!( diff --git a/src/cmd/metadata.rs b/src/cmd/metadata.rs index f87b1a7a7f1f19883bcd07ffb4c7810400726706..36146d3cb172b5441094431c84b83e17ad9c69a9 100644 --- a/src/cmd/metadata.rs +++ b/src/cmd/metadata.rs @@ -47,7 +47,7 @@ pub(crate) fn execute_generate_metadata( "run", &[ "--package", - "abi-gen", + "metadata-gen", &manifest_path.cargo_arg(), &target_dir_arg, "--release", @@ -68,6 +68,7 @@ pub(crate) fn execute_generate_metadata( .with_profile_release_lto(false)?; Ok(()) })? + .with_metadata_gen_package()? .using_temp(generate_metadata)?; } @@ -98,12 +99,12 @@ mod tests { .expect("generate metadata failed"); println!("{}", message); - let mut abi_file = working_dir; - abi_file.push("target"); - abi_file.push("metadata.json"); + let mut metadata_file = working_dir; + metadata_file.push("target"); + metadata_file.push("metadata.json"); assert!( - abi_file.exists(), - format!("Missing metadata file '{}'", abi_file.display()) + metadata_file.exists(), + format!("Missing metadata file '{}'", metadata_file.display()) ) }); } diff --git a/src/workspace/manifest.rs b/src/workspace/manifest.rs index 8dfafa381e7ec6adce20708320c79919a3ed87b5..d02c597560360fd89c158d92b5ac8443b491a41f 100644 --- a/src/workspace/manifest.rs +++ b/src/workspace/manifest.rs @@ -16,7 +16,7 @@ use anyhow::{Context, Result}; -use super::Profile; +use super::{metadata, Profile}; use std::convert::{TryFrom, TryInto}; use std::{ collections::HashSet, @@ -26,6 +26,8 @@ use std::{ use toml::value; const MANIFEST_FILE: &str = "Cargo.toml"; +const LEGACY_METADATA_PACKAGE_PATH: &str = ".ink/abi_gen"; +const METADATA_PACKAGE_PATH: &str = ".ink/metadata_gen"; /// Path to a Cargo.toml file #[derive(Clone, Debug)] @@ -90,6 +92,8 @@ impl AsRef for ManifestPath { pub struct Manifest { path: ManifestPath, toml: value::Table, + /// True if a metadata package should be generated for this manifest + metadata_package: bool, } impl Manifest { @@ -107,9 +111,15 @@ impl Manifest { Ok(Manifest { path: manifest_path, toml, + metadata_package: false, }) } + /// Get the path of the manifest file + pub(super) fn path(&self) -> &ManifestPath { + &self.path + } + /// Get mutable reference to `[lib] crate-types = []` section fn get_crate_types_mut(&mut self) -> Result<&mut value::Array> { let lib = self @@ -185,6 +195,40 @@ impl Manifest { Ok(self) } + /// Adds a metadata package to the manifest workspace for generating metadata + pub fn with_metadata_package(&mut self) -> Result<&mut Self> { + let workspace = self + .toml + .entry("workspace") + .or_insert(value::Value::Table(Default::default())); + let members = workspace + .as_table_mut() + .ok_or(anyhow::anyhow!("workspace should be a table"))? + .entry("members") + .or_insert(value::Value::Array(Default::default())) + .as_array_mut() + .ok_or(anyhow::anyhow!("members should be an array"))?; + + if members.contains(&LEGACY_METADATA_PACKAGE_PATH.into()) { + // warn user if they have legacy metadata generation artifacts + use colored::Colorize; + println!( + "{} {} {} {}", + "warning:".yellow().bold(), + "please remove".bold(), + LEGACY_METADATA_PACKAGE_PATH.bold(), + "from the `[workspace]` section in the `Cargo.toml`, \ + and delete that directory. These are now auto-generated." + .bold() + ); + } else { + members.push(METADATA_PACKAGE_PATH.into()); + } + + self.metadata_package = true; + Ok(self) + } + /// Replace relative paths with absolute paths with the working directory. /// /// Enables the use of a temporary amended copy of the manifest. @@ -294,16 +338,51 @@ impl Manifest { } /// Writes the amended manifest to the given path. - pub fn write(&self, path: &ManifestPath) -> Result<()> { - let manifest_path = path.as_ref(); + pub fn write(&self, manifest_path: &ManifestPath) -> Result<()> { + if let Some(dir) = manifest_path.directory() { + fs::create_dir_all(dir).context(format!("Creating directory '{}'", dir.display()))?; + } + + if self.metadata_package { + let dir = if let Some(manifest_dir) = manifest_path.directory() { + manifest_dir.join(METADATA_PACKAGE_PATH) + } else { + METADATA_PACKAGE_PATH.into() + }; - if let Some(dir) = manifest_path.parent() { fs::create_dir_all(&dir).context(format!("Creating directory '{}'", dir.display()))?; + + let name = self + .toml + .get("lib") + .ok_or(anyhow::anyhow!("lib section not found"))? + .get("name") + .ok_or(anyhow::anyhow!("[lib] name field not found"))? + .as_str() + .ok_or(anyhow::anyhow!("[lib] name should be a string"))?; + + let get_dependency = |name| -> Result<&value::Table> { + self.toml + .get("dependencies") + .ok_or(anyhow::anyhow!("[dependencies] section not found"))? + .get(name) + .ok_or(anyhow::anyhow!("{} dependency not found", name))? + .as_table() + .ok_or(anyhow::anyhow!("{} dependency should be a table", name)) + }; + + let ink_lang = get_dependency("ink_lang")?; + let ink_abi = get_dependency("ink_abi")?; + + metadata::generate_package(dir, name, ink_lang.clone(), ink_abi.clone())?; } let updated_toml = toml::to_string(&self.toml)?; - log::debug!("Writing updated manifest to '{}'", manifest_path.display()); - fs::write(&manifest_path, updated_toml)?; + log::debug!( + "Writing updated manifest to '{}'", + manifest_path.as_ref().display() + ); + fs::write(manifest_path, updated_toml)?; Ok(()) } } diff --git a/src/workspace/metadata.rs b/src/workspace/metadata.rs new file mode 100644 index 0000000000000000000000000000000000000000..fad12c373c82d5c8bc9c508a7660e34de042d563 --- /dev/null +++ b/src/workspace/metadata.rs @@ -0,0 +1,71 @@ +// Copyright 2018-2020 Parity Technologies (UK) Ltd. +// This file is part of cargo-contract. +// +// ink! is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// ink! is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with ink!. If not, see . + +use anyhow::Result; +use std::{fs, path::Path}; +use toml::value; + +/// Generates a cargo workspace package which will be invoked to generate contract metadata. +/// +/// # Note +/// +/// `ink!` dependencies are copied from the containing contract workspace to ensure the same +/// versions are utilized. +pub(super) fn generate_package>( + target_dir: P, + contract_package_name: &str, + ink_lang_dependency: value::Table, + mut ink_abi_dependency: value::Table, +) -> Result<()> { + let dir = target_dir.as_ref(); + log::debug!( + "Generating metadata package for {} in {}", + contract_package_name, + dir.display() + ); + + let cargo_toml = include_str!("../../templates/tools/generate-metadata/_Cargo.toml"); + let main_rs = include_str!("../../templates/tools/generate-metadata/main.rs"); + + let mut cargo_toml: value::Table = toml::from_str(cargo_toml)?; + let deps = cargo_toml + .get_mut("dependencies") + .expect("[dependencies] section specified in the template") + .as_table_mut() + .expect("[dependencies] is a table specified in the template"); + + // initialize contract dependency + let contract = deps + .get_mut("contract") + .expect("contract dependency specified in the template") + .as_table_mut() + .expect("contract dependency is a table specified in the template"); + contract.insert("package".into(), contract_package_name.into()); + + // make ink_abi dependency use default features + ink_abi_dependency.remove("default-features"); + ink_abi_dependency.remove("features"); + ink_abi_dependency.remove("optional"); + + // add ink dependencies copied from contract manifest + deps.insert("ink_lang".into(), ink_lang_dependency.into()); + deps.insert("ink_abi".into(), ink_abi_dependency.into()); + let cargo_toml = toml::to_string(&cargo_toml)?; + + fs::write(dir.join("Cargo.toml"), cargo_toml)?; + fs::write(dir.join("main.rs"), main_rs)?; + Ok(()) +} diff --git a/src/workspace/mod.rs b/src/workspace/mod.rs index 7bd0dda04c828b4988f897e0dff2f72123fc1392..51d5011f40bd2ac592989001da1d27129f528652 100644 --- a/src/workspace/mod.rs +++ b/src/workspace/mod.rs @@ -15,6 +15,7 @@ // along with ink!. If not, see . mod manifest; +mod metadata; mod profile; #[doc(inline)] @@ -95,6 +96,37 @@ impl Workspace { Ok(self) } + /// Amend the workspace manifest using the supplied function. + pub fn with_workspace_manifest(&mut self, f: F) -> Result<&mut Self> + where + F: FnOnce(&mut Manifest) -> Result<()>, + { + let workspace_root = self.workspace_root.clone(); + let workspace_manifest = self + .members + .iter_mut() + .find_map(|(_, (_, manifest))| { + if manifest.path().directory() == Some(&workspace_root) { + Some(manifest) + } else { + None + } + }) + .ok_or(anyhow::anyhow!( + "The workspace root package should be a workspace member" + ))?; + f(workspace_manifest)?; + Ok(self) + } + + /// Generates a package to invoke for generating contract metadata + pub(super) fn with_metadata_gen_package(&mut self) -> Result<&mut Self> { + self.with_workspace_manifest(|manifest| { + manifest.with_metadata_package()?; + Ok(()) + }) + } + /// Writes the amended manifests to the `target` directory, retaining the workspace directory /// structure, but only with the `Cargo.toml` files. /// diff --git a/template/.ink/abi_gen/_Cargo.toml b/template/.ink/abi_gen/_Cargo.toml deleted file mode 100644 index eedf518c894092cefcd6c89e06c68b659093d0ba..0000000000000000000000000000000000000000 --- a/template/.ink/abi_gen/_Cargo.toml +++ /dev/null @@ -1,16 +0,0 @@ -[package] -name = "abi-gen" -version = "0.1.0" -authors = ["Parity Technologies "] -edition = "2018" -publish = false - -[[bin]] -name = "abi-gen" -path = "main.rs" - -[dependencies] -contract = { path = "../..", package = "{{name}}", default-features = false, features = ["ink-generate-abi"] } -ink_lang = { version = "2", git = "https://github.com/paritytech/ink", tag = "latest-v2", package = "ink_lang", default-features = false, features = ["ink-generate-abi"] } -serde = "1.0" -serde_json = "1.0" diff --git a/template/.ink/abi_gen/main.rs b/template/.ink/abi_gen/main.rs deleted file mode 100644 index a196171e8b98b7617baead20d363528c4db2376d..0000000000000000000000000000000000000000 --- a/template/.ink/abi_gen/main.rs +++ /dev/null @@ -1,7 +0,0 @@ -fn main() -> Result<(), std::io::Error> { - let abi = ::generate_abi(); - let contents = serde_json::to_string_pretty(&abi)?; - std::fs::create_dir("target").ok(); - std::fs::write("target/metadata.json", contents)?; - Ok(()) -} diff --git a/template/_Cargo.toml b/template/_Cargo.toml deleted file mode 100644 index a343b237899399c7cb2384bac4fa60a42d20b212..0000000000000000000000000000000000000000 --- a/template/_Cargo.toml +++ /dev/null @@ -1,61 +0,0 @@ -[package] -name = "{{name}}" -version = "0.1.0" -authors = ["[your_name] <[your_email]>"] -edition = "2018" - -[dependencies] -ink_abi = { version = "2", git = "https://github.com/paritytech/ink", tag = "latest-v2", package = "ink_abi", default-features = false, features = ["derive"], optional = true } -ink_primitives = { version = "2", git = "https://github.com/paritytech/ink", tag = "latest-v2", package = "ink_primitives", default-features = false } -ink_core = { version = "2", git = "https://github.com/paritytech/ink", tag = "latest-v2", package = "ink_core", default-features = false } -ink_lang = { version = "2", git = "https://github.com/paritytech/ink", tag = "latest-v2", package = "ink_lang", default-features = false } - -scale = { package = "parity-scale-codec", version = "1.2", default-features = false, features = ["derive"] } - -[dependencies.type-metadata] -git = "https://github.com/type-metadata/type-metadata.git" -rev = "02eae9f35c40c943b56af5b60616219f2b72b47d" -default-features = false -features = ["derive"] -optional = true - -[lib] -name = "{{name}}" -path = "lib.rs" -crate-type = [ - # Used for normal contract Wasm blobs. - "cdylib", - # Required for ABI generation, and using this contract as a dependency. - # If using `cargo contract build`, it will be automatically disabled to produce a smaller Wasm binary - "rlib", -] - -[features] -default = ["test-env"] -std = [ - "ink_abi/std", - "ink_core/std", - "ink_primitives/std", - "scale/std", - "type-metadata/std", -] -test-env = [ - "std", - "ink_lang/test-env", -] -ink-generate-abi = [ - "std", - "ink_abi", - "type-metadata", - "ink_core/ink-generate-abi", - "ink_lang/ink-generate-abi", -] -ink-as-dependency = [] - -[workspace] -members = [ - ".ink/abi_gen" -] -exclude = [ - ".ink" -] diff --git a/template/.gitignore b/templates/new/.gitignore similarity index 100% rename from template/.gitignore rename to templates/new/.gitignore diff --git a/templates/new/_Cargo.toml b/templates/new/_Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..db4f2fe3911804c0fa67e9e7aca7a879984483d1 --- /dev/null +++ b/templates/new/_Cargo.toml @@ -0,0 +1,33 @@ +[package] +name = "{{name}}" +version = "0.1.0" +authors = ["[your_name] <[your_email]>"] +edition = "2018" + +[dependencies] +ink_abi = { git = "https://github.com/paritytech/ink", branch = "master", package = "ink_abi", default-features = false, features = ["derive"], optional = true } +ink_primitives = { git = "https://github.com/paritytech/ink", branch = "master", default-features = false } +ink_core = { git = "https://github.com/paritytech/ink", branch = "master", package = "ink_core", default-features = false } +ink_lang = { git = "https://github.com/paritytech/ink", branch = "master", package = "ink_lang", default-features = false } + +scale = { package = "parity-scale-codec", version = "1.3", default-features = false, features = ["derive"] } +scale-info = { version = "0.2", default-features = false, features = ["derive"], optional = true } + +[lib] +name = "{{name}}" +path = "lib.rs" +crate-type = [ + # Used for normal contract Wasm blobs. + "cdylib", +] + +[features] +default = ["std"] +std = [ + "ink_abi/std", + "ink_core/std", + "ink_primitives/std", + "scale/std", + "scale-info/std", +] +ink-as-dependency = [] diff --git a/template/lib.rs b/templates/new/lib.rs similarity index 77% rename from template/lib.rs rename to templates/new/lib.rs index 91295a3205bcb7233def6f9b79d9f1861a638148..6c850eab9470ee2786e39e10de93af91e0f5d109 100644 --- a/template/lib.rs +++ b/templates/new/lib.rs @@ -4,7 +4,6 @@ use ink_lang as ink; #[ink::contract(version = "0.1.0")] mod {{name}} { - use ink_core::storage; /// Defines the storage of your contract. /// Add new fields to the below struct in order @@ -12,22 +11,22 @@ mod {{name}} { #[ink(storage)] struct {{camel_name}} { /// Stores a single `bool` value on the storage. - value: storage::Value, + value: bool, } impl {{camel_name}} { /// Constructor that initializes the `bool` value to the given `init_value`. #[ink(constructor)] - fn new(&mut self, init_value: bool) { - self.value.set(init_value); + fn new(init_value: bool) -> Self { + Self { value: init_value } } /// Constructor that initializes the `bool` value to `false`. /// /// Constructors can delegate to other constructors. #[ink(constructor)] - fn default(&mut self) { - self.new(false) + fn default() -> Self { + Self::new(Default::default()) } /// A message that can be called on instantiated contracts. @@ -35,13 +34,13 @@ mod {{name}} { /// to `false` and vice versa. #[ink(message)] fn flip(&mut self) { - *self.value = !self.get(); + self.value = !self.value; } /// Simply returns the current value of our `bool`. #[ink(message)] fn get(&self) -> bool { - *self.value + self.value } } @@ -56,10 +55,6 @@ mod {{name}} { /// We test if the default constructor does its job. #[test] fn default_works() { - // Note that even though we defined our `#[ink(constructor)]` - // above as `&mut self` functions that return nothing we can call - // them in test code as if they were normal Rust constructors - // that take no `self` argument but return `Self`. let {{name}} = {{camel_name}}::default(); assert_eq!({{name}}.get(), false); } diff --git a/templates/tools/generate-metadata/_Cargo.toml b/templates/tools/generate-metadata/_Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..952d352d7bb0fa1c64153c8dce928215e8232e5c --- /dev/null +++ b/templates/tools/generate-metadata/_Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "metadata-gen" +version = "0.1.0" +authors = ["Parity Technologies "] +edition = "2018" +publish = false + +[[bin]] +name = "metadata-gen" +path = "main.rs" + +[dependencies] +contract = { path = "../.." } +serde = "1.0" +serde_json = "1.0" diff --git a/templates/tools/generate-metadata/main.rs b/templates/tools/generate-metadata/main.rs new file mode 100644 index 0000000000000000000000000000000000000000..3d5eeb2334f998ed35f6397ca6a456031608576a --- /dev/null +++ b/templates/tools/generate-metadata/main.rs @@ -0,0 +1,13 @@ +extern crate contract; + +extern "Rust" { + fn __ink_generate_metadata() -> ink_abi::InkProject; +} + +fn main() -> Result<(), std::io::Error> { + let ink_project = unsafe { __ink_generate_metadata() }; + let contents = serde_json::to_string_pretty(&ink_project)?; + std::fs::create_dir("target").ok(); + std::fs::write("target/metadata.json", contents)?; + Ok(()) +}