diff --git a/cargo-near/Cargo.toml b/cargo-near/Cargo.toml index 4b7e6564..37544a04 100644 --- a/cargo-near/Cargo.toml +++ b/cargo-near/Cargo.toml @@ -18,7 +18,6 @@ clap = { version = "3.2", features = ["derive", "env"] } colored = "2.0" env_logger = "0.9" log = "0.4" -toml = "0.5" serde_json = "1.0" symbolic-debuginfo = "8.8" schemars = "0.8" diff --git a/cargo-near/src/abi.rs b/cargo-near/src/abi.rs index 6ceb557e..0813d9c7 100644 --- a/cargo-near/src/abi.rs +++ b/cargo-near/src/abi.rs @@ -27,24 +27,48 @@ pub(crate) fn generate_abi( crate_metadata: &CrateMetadata, generate_docs: bool, ) -> anyhow::Result { - fs::create_dir_all(&crate_metadata.target_directory)?; + let root_node = crate_metadata + .raw_metadata + .resolve + .as_ref() + .and_then(|dep_graph| { + dep_graph + .nodes + .iter() + .find(|node| node.id == crate_metadata.root_package.id) + }) + .ok_or_else(|| anyhow::anyhow!("unable to appropriately resolve the dependency graph, perhaps your `Cargo.toml` file is malformed"))?; + + let near_sdk_dep = root_node + .deps + .iter() + .find(|dep| dep.name == "near_sdk") + .and_then(|near_sdk| { + crate_metadata + .raw_metadata + .packages + .iter() + .find(|pkg| pkg.id == near_sdk.pkg) + }) + .ok_or_else(|| anyhow::anyhow!("`near-sdk` dependency not found"))?; + + for required_feature in ["__abi-generate", "__abi-embed"] { + if !near_sdk_dep.features.contains_key(required_feature) { + anyhow::bail!("unsupported `near-sdk` version. expected 4.1.* or higher"); + } + } - let original_cargo_toml: toml::value::Table = - toml::from_slice(&fs::read(&crate_metadata.manifest_path.path)?)?; - - if !original_cargo_toml - .get("dependencies") - .ok_or_else(|| anyhow::anyhow!("[dependencies] section not found"))? - .get("near-sdk") - .ok_or_else(|| anyhow::anyhow!("near-sdk dependency not found"))? - .as_table() - .ok_or_else(|| anyhow::anyhow!("near-sdk dependency should be a table"))? - .get("features") - .and_then(|features| features.as_array()) - .map(|features| features.contains(&toml::Value::String("abi".to_string()))) - .unwrap_or(false) + if !crate_metadata + .root_package + .dependencies + .iter() + .find(|dep| dep.name == "near-sdk") + .ok_or_else(|| anyhow::anyhow!("`near-sdk` dependency not found"))? + .features + .iter() + .any(|feature| feature == "abi") { - anyhow::bail!("Unable to generate ABI: NEAR SDK \"abi\" feature is not enabled") + anyhow::bail!("`near-sdk` dependency must have the `abi` feature enabled") } let dylib_artifact = util::compile_project( @@ -88,6 +112,8 @@ pub(crate) fn write_to_file( )?, }; + fs::create_dir_all(&crate_metadata.target_directory)?; + let out_path_abi = crate_metadata.target_directory.join(format!( "{}_abi.{}", crate_metadata.root_package.name.replace('-', "_"), diff --git a/cargo-near/src/cargo/metadata.rs b/cargo-near/src/cargo/metadata.rs index 82cdee5f..7d81a0c3 100644 --- a/cargo-near/src/cargo/metadata.rs +++ b/cargo-near/src/cargo/metadata.rs @@ -9,6 +9,7 @@ pub(crate) struct CrateMetadata { pub root_package: Package, pub target_directory: PathBuf, pub manifest_path: CargoManifestPath, + pub raw_metadata: cargo_metadata::Metadata, } impl CrateMetadata { @@ -32,6 +33,7 @@ impl CrateMetadata { root_package, target_directory: target_directory.into(), manifest_path, + raw_metadata: metadata, }; Ok(crate_metadata) } @@ -49,7 +51,7 @@ fn get_cargo_metadata( let metadata = cmd .manifest_path(&manifest_path.path) .exec() - .context("Error invoking `cargo metadata`")?; + .context("Error invoking `cargo metadata`. Your `Cargo.toml` file is likely malformed")?; let root_package_id = metadata .resolve .as_ref() diff --git a/integration-tests/templates/negative/_Cargo_malformed.toml b/integration-tests/templates/negative/_Cargo_malformed.toml new file mode 100644 index 00000000..c50429a3 --- /dev/null +++ b/integration-tests/templates/negative/_Cargo_malformed.toml @@ -0,0 +1,23 @@ +[package] +name = "::name::" +version = "0.1.0" +edition = "2021" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +near-sdk = 4 +serde = { version = "1", features = ["derive"] } +schemars = "0.8" + +[workspace] +members = [] + +[profile.release] +codegen-units = 1 +# Tell `rustc` to optimize for small code size. +opt-level = "z" +lto = true +debug = false +panic = "abort" diff --git a/integration-tests/templates/_Cargo_no_abi_feature.toml b/integration-tests/templates/negative/_Cargo_no_abi_feature.toml similarity index 100% rename from integration-tests/templates/_Cargo_no_abi_feature.toml rename to integration-tests/templates/negative/_Cargo_no_abi_feature.toml diff --git a/integration-tests/templates/negative/_Cargo_not_a_table.toml b/integration-tests/templates/negative/_Cargo_not_a_table.toml new file mode 100644 index 00000000..e3e11c61 --- /dev/null +++ b/integration-tests/templates/negative/_Cargo_not_a_table.toml @@ -0,0 +1,23 @@ +[package] +name = "::name::" +version = "0.1.0" +edition = "2021" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +near-sdk = "4.1.0-pre.3" +serde = { version = "1", features = ["derive"] } +schemars = "0.8" + +[workspace] +members = [] + +[profile.release] +codegen-units = 1 +# Tell `rustc` to optimize for small code size. +opt-level = "z" +lto = true +debug = false +panic = "abort" diff --git a/integration-tests/templates/negative/_Cargo_old_sdk.toml b/integration-tests/templates/negative/_Cargo_old_sdk.toml new file mode 100644 index 00000000..9db3c65a --- /dev/null +++ b/integration-tests/templates/negative/_Cargo_old_sdk.toml @@ -0,0 +1,23 @@ +[package] +name = "::name::" +version = "0.1.0" +edition = "2021" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +near-sdk = "4.0.0" +serde = { version = "1", features = ["derive"] } +schemars = "0.8" + +[workspace] +members = [] + +[profile.release] +codegen-units = 1 +# Tell `rustc` to optimize for small code size. +opt-level = "z" +lto = true +debug = false +panic = "abort" diff --git a/integration-tests/templates/sdk-dependency/_Cargo_patch.toml b/integration-tests/templates/sdk-dependency/_Cargo_patch.toml index ea3e7f40..d8c8fe2e 100644 --- a/integration-tests/templates/sdk-dependency/_Cargo_patch.toml +++ b/integration-tests/templates/sdk-dependency/_Cargo_patch.toml @@ -7,7 +7,7 @@ edition = "2021" crate-type = ["cdylib"] [dependencies] -near-sdk = { version = "4.1.0-pre.3", features = ["abi"] } +near-sdk = "4.0.0" serde = { version = "1", features = ["derive"] } schemars = "0.8" diff --git a/integration-tests/templates/sdk-dependency/_Cargo_platform_specific.toml b/integration-tests/templates/sdk-dependency/_Cargo_platform_specific.toml index 8072ff81..7b404b42 100644 --- a/integration-tests/templates/sdk-dependency/_Cargo_platform_specific.toml +++ b/integration-tests/templates/sdk-dependency/_Cargo_platform_specific.toml @@ -10,10 +10,10 @@ crate-type = ["cdylib"] serde = { version = "1", features = ["derive"] } schemars = "0.8" -[target.'cfg(windows)'.dependencies.near-sdk] +[target.'cfg(windows)'.dependencies] near-sdk = { version = "4.1.0-pre.3", features = ["abi"] } -[target.'cfg(unix)'.dependencies.near-sdk] +[target.'cfg(unix)'.dependencies] near-sdk = { version = "4.1.0-pre.3", features = ["abi"] } [workspace] diff --git a/integration-tests/tests/abi/negative.rs b/integration-tests/tests/abi/negative.rs index 04139bf9..a6fdacf5 100644 --- a/integration-tests/tests/abi/negative.rs +++ b/integration-tests/tests/abi/negative.rs @@ -1,4 +1,4 @@ -use cargo_near_integration_tests::generate_abi_fn_with; +use cargo_near_integration_tests::{generate_abi_fn, generate_abi_fn_with}; use function_name::named; #[test] @@ -6,16 +6,94 @@ use function_name::named; fn test_abi_feature_not_enabled() -> anyhow::Result<()> { fn run_test() -> anyhow::Result<()> { generate_abi_fn_with! { - Cargo: "/templates/_Cargo_no_abi_feature.toml"; + Cargo: "/templates/negative/_Cargo_no_abi_feature.toml"; Code: - pub fn foo(&self, #[callback_unwrap] a: bool, #[callback_unwrap] b: u32) {} + pub fn foo(&self, a: u32, b: u32) {} }; Ok(()) } assert_eq!( run_test().unwrap_err().to_string(), - "Unable to generate ABI: NEAR SDK \"abi\" feature is not enabled" + "`near-sdk` dependency must have the `abi` feature enabled" + ); + + Ok(()) +} + +#[test] +#[named] +fn test_abi_not_a_table() -> anyhow::Result<()> { + fn run_test() -> anyhow::Result<()> { + generate_abi_fn_with! { + Cargo: "/templates/negative/_Cargo_not_a_table.toml"; + Code: + pub fn foo(&self, a: u32, b: u32) {} + }; + Ok(()) + } + + assert_eq!( + run_test().unwrap_err().to_string(), + "`near-sdk` dependency must have the `abi` feature enabled" + ); + + Ok(()) +} + +#[test] +#[named] +fn test_abi_old_sdk() -> anyhow::Result<()> { + fn run_test() -> anyhow::Result<()> { + generate_abi_fn_with! { + Cargo: "/templates/negative/_Cargo_old_sdk.toml"; + Code: + pub fn foo(&self, a: u32, b: u32) {} + }; + Ok(()) + } + + assert_eq!( + run_test().unwrap_err().to_string(), + "unsupported `near-sdk` version. expected 4.1.* or higher" + ); + + Ok(()) +} + +#[test] +#[named] +fn test_abi_weird_version() -> anyhow::Result<()> { + fn run_test() -> anyhow::Result<()> { + generate_abi_fn_with! { + Cargo: "/templates/negative/_Cargo_malformed.toml"; + Code: + pub fn foo(&self, a: u32, b: u32) {} + }; + Ok(()) + } + + assert_eq!( + run_test().unwrap_err().to_string(), + "Error invoking `cargo metadata`. Your `Cargo.toml` file is likely malformed" + ); + + Ok(()) +} + +// TODO: Arguably this should not be an error. Feels like generating ABI for a contract +// with no code should work. +#[test] +#[named] +fn test_abi_no_code() -> anyhow::Result<()> { + fn run_test() -> anyhow::Result<()> { + generate_abi_fn! {}; + Ok(()) + } + + assert_eq!( + run_test().unwrap_err().to_string(), + "No NEAR ABI symbols found in the dylib" ); Ok(()) diff --git a/integration-tests/tests/cargo/mod.rs b/integration-tests/tests/cargo/mod.rs index 96035bb3..37a1701a 100644 --- a/integration-tests/tests/cargo/mod.rs +++ b/integration-tests/tests/cargo/mod.rs @@ -110,8 +110,6 @@ fn test_dependency_multiple_features() -> anyhow::Result<()> { Ok(()) } -// TODO: Not sure if this even makes sense in the context of NEAR SDK, but in theory could add support for this -#[ignore] #[test] #[named] fn test_dependency_platform_specific() -> anyhow::Result<()> { @@ -168,7 +166,7 @@ fn test_dependency_renamed() -> anyhow::Result<()> { #[named] fn test_dependency_patch() -> anyhow::Result<()> { // [dependencies] - // near-sdk = { version = "4.1.0-pre.3", features = ["abi"] } + // near-sdk = "4.0.0" // // [patch.crates-io] // near-sdk = { git = "https://github.com/near/near-sdk-rs.git", rev = "792d5eb26d26a0878dbf59e304afa4e19540c317" }