Skip to content
New issue

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

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

Already on GitHub? # to your account

Generate only runtime types #845

Merged
merged 13 commits into from
Mar 21, 2023
6 changes: 6 additions & 0 deletions cli/src/commands/codegen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ pub struct Opts {
/// Defaults to `false` (documentation is generated).
#[clap(long, action)]
no_docs: bool,
/// Whether to limit code generation to only runtime types.
#[clap(long)]
runtime_types_only: bool,
}

fn derive_for_type_parser(src: &str) -> Result<(String, String), String> {
Expand Down Expand Up @@ -80,6 +83,7 @@ pub async fn run(opts: Opts) -> color_eyre::Result<()> {
opts.derives_for_type,
opts.crate_path,
opts.no_docs,
opts.runtime_types_only,
)?;
Ok(())
}
Expand All @@ -90,6 +94,7 @@ fn codegen(
derives_for_type: Vec<(String, String)>,
crate_path: Option<String>,
no_docs: bool,
runtime_types_only: bool,
) -> color_eyre::Result<()> {
let item_mod = syn::parse_quote!(
pub mod api {}
Expand Down Expand Up @@ -120,6 +125,7 @@ fn codegen(
type_substitutes,
crate_path,
should_gen_docs,
runtime_types_only,
);
match runtime_api {
Ok(runtime_api) => println!("{runtime_api}"),
Expand Down
83 changes: 76 additions & 7 deletions codegen/src/api/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ impl CodegenError {
/// * `type_substitutes` - Provide custom type substitutes.
/// * `crate_path` - Path to the `subxt` crate.
/// * `should_gen_docs` - True if the generated API contains the documentation from the metadata.
/// * `runtime_types_only` - Whether to limit code generation to only runtime types.
///
/// **Note:** This is a wrapper over [RuntimeGenerator] for static metadata use-cases.
pub fn generate_runtime_api_from_path<P>(
Expand All @@ -130,6 +131,7 @@ pub fn generate_runtime_api_from_path<P>(
type_substitutes: TypeSubstitutes,
crate_path: CratePath,
should_gen_docs: bool,
runtime_types_only: bool,
) -> Result<TokenStream2, CodegenError>
where
P: AsRef<path::Path>,
Expand All @@ -147,6 +149,7 @@ where
type_substitutes,
crate_path,
should_gen_docs,
runtime_types_only,
)
}

Expand All @@ -162,6 +165,7 @@ where
/// * `type_substitutes` - Provide custom type substitutes.
/// * `crate_path` - Path to the `subxt` crate.
/// * `should_gen_docs` - True if the generated API contains the documentation from the metadata.
/// * `runtime_types_only` - Whether to limit code generation to only runtime types.
///
/// **Note:** This is a wrapper over [RuntimeGenerator] for static metadata use-cases.
pub fn generate_runtime_api_from_url(
Expand All @@ -171,6 +175,7 @@ pub fn generate_runtime_api_from_url(
type_substitutes: TypeSubstitutes,
crate_path: CratePath,
should_gen_docs: bool,
runtime_types_only: bool,
) -> Result<TokenStream2, CodegenError> {
let bytes = fetch_metadata_bytes_blocking(url)?;

Expand All @@ -181,6 +186,7 @@ pub fn generate_runtime_api_from_url(
type_substitutes,
crate_path,
should_gen_docs,
runtime_types_only,
)
}

Expand All @@ -194,6 +200,7 @@ pub fn generate_runtime_api_from_url(
/// * `type_substitutes` - Provide custom type substitutes.
/// * `crate_path` - Path to the `subxt` crate.
/// * `should_gen_docs` - True if the generated API contains the documentation from the metadata.
/// * `runtime_types_only` - Whether to limit code generation to only runtime types.
///
/// **Note:** This is a wrapper over [RuntimeGenerator] for static metadata use-cases.
pub fn generate_runtime_api_from_bytes(
Expand All @@ -203,17 +210,28 @@ pub fn generate_runtime_api_from_bytes(
type_substitutes: TypeSubstitutes,
crate_path: CratePath,
should_gen_docs: bool,
runtime_types_only: bool,
) -> Result<TokenStream2, CodegenError> {
let metadata = frame_metadata::RuntimeMetadataPrefixed::decode(&mut &bytes[..])?;

let generator = RuntimeGenerator::new(metadata);
generator.generate_runtime(
item_mod,
derives,
type_substitutes,
crate_path,
should_gen_docs,
)
if runtime_types_only {
generator.generate_runtime_types(
item_mod,
derives,
type_substitutes,
crate_path,
should_gen_docs,
)
} else {
generator.generate_runtime(
item_mod,
derives,
type_substitutes,
crate_path,
should_gen_docs,
)
}
}

/// Create the API for interacting with a Substrate runtime.
Expand All @@ -240,6 +258,57 @@ impl RuntimeGenerator {
///
/// * `item_mod` - The module declaration for which the API is implemented.
/// * `derives` - Provide custom derives for the generated types.
/// * `type_substitutes` - Provide custom type substitutes.
/// * `crate_path` - Path to the `subxt` crate.
/// * `should_gen_docs` - True if the generated API contains the documentation from the metadata.
pub fn generate_runtime_types(
&self,
item_mod: syn::ItemMod,
derives: DerivesRegistry,
type_substitutes: TypeSubstitutes,
crate_path: CratePath,
should_gen_docs: bool,
) -> Result<TokenStream2, CodegenError> {
let item_mod_attrs = item_mod.attrs.clone();

let item_mod_ir = ir::ItemMod::try_from(item_mod)?;
let mod_ident = &item_mod_ir.ident;
let rust_items = item_mod_ir.rust_items();

let type_gen = TypeGenerator::new(
&self.metadata.types,
"runtime_types",
type_substitutes,
derives,
crate_path,
should_gen_docs,
);
let types_mod = type_gen.generate_types_mod()?;

Ok(quote! {
#( #item_mod_attrs )*
#[allow(dead_code, unused_imports, non_camel_case_types)]
#[allow(clippy::all)]
pub mod #mod_ident {
// Preserve any Rust items that were previously defined in the adorned module
#( #rust_items ) *

// Make it easy to access the root via `root_mod` at different levels:
use super::#mod_ident as root_mod;
#types_mod
}
})
}

/// Generate the API for interacting with a Substrate runtime.
///
/// # Arguments
///
/// * `item_mod` - The module declaration for which the API is implemented.
/// * `derives` - Provide custom derives for the generated types.
/// * `type_substitutes` - Provide custom type substitutes.
/// * `crate_path` - Path to the `subxt` crate.
/// * `should_gen_docs` - True if the generated API contains the documentation from the metadata.
pub fn generate_runtime(
&self,
item_mod: syn::ItemMod,
Expand Down
66 changes: 66 additions & 0 deletions examples/examples/runtime_types_only.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// Copyright 2019-2022 Parity Technologies (UK) Ltd.
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
// see LICENSE for license details.

//! In some cases we are interested only in the `RuntimeCall` enum (or more generally, only in some
//! runtime types). We can ask `subxt` to generate only runtime types by passing a corresponding
//! flag.
//!
//! Here we present how to correctly create `Block` type for the Polkadot chain.

use sp_core::H256;
use sp_runtime::{
generic,
traits::{
BlakeTwo256,
Block as _,
Header as _,
},
Digest,
};
use subxt::PolkadotConfig;

#[subxt::subxt(
runtime_metadata_path = "../artifacts/polkadot_metadata.scale",
derive_for_all_types = "Clone, PartialEq, Eq",
runtime_types_only
)]
pub mod polkadot {}

type RuntimeCall = polkadot::runtime_types::polkadot_runtime::RuntimeCall;

type UncheckedExtrinsic = generic::UncheckedExtrinsic<
<PolkadotConfig as subxt::Config>::Address,
RuntimeCall,
<PolkadotConfig as subxt::Config>::Signature,
// Usually we are not interested in `SignedExtra`.
(),
>;

type Header = generic::Header<u32, BlakeTwo256>;
type Block = generic::Block<Header, UncheckedExtrinsic>;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
tracing_subscriber::fmt::init();

// Although we could build an online client, we do not have access to the full runtime API. For
// that, we would have to specify `runtime_types_only = false` (or just skipping it).
//
// let api = subxt::OnlineClient::<PolkadotConfig>::new().await?;
// let address = polkadot::constants().balances().existential_deposit(); <- this won't compile!

let polkadot_header = Header::new(
41,
H256::default(),
H256::default(),
H256::default(),
Digest::default(),
);

let polkadot_block = Block::new(polkadot_header, vec![]);

println!("{polkadot_block:?}");

Ok(())
}
19 changes: 17 additions & 2 deletions macro/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
//!
//! ### Adding derives for all types
//!
//! Add `derive_for_all_types` with a comma seperated list of the derives to apply to *all* types
//! Add `derive_for_all_types` with a comma separated list of the derives to apply to *all* types
//!
//! ```ignore
//! #[subxt::subxt(
Expand All @@ -60,7 +60,7 @@
//!
//! ### Adding derives for specific types
//!
//! Add `derive_for_type` for each specific type with a comma seperated list of the derives to
//! Add `derive_for_type` for each specific type with a comma separated list of the derives to
//! apply for that type only.
//!
//! ```ignore
Expand Down Expand Up @@ -95,6 +95,17 @@
//! ```
//!
//! By default the documentation is not generated.
//!
//! ### Runtime types generation
//!
//! In some cases, you may be interested only in the runtime types, like `RuntimeCall` enum. You can
//! limit code generation to just `runtime_types` module with `runtime_types_only` flag:
//!
//! ```ignore
//! #[subxt::subxt(runtime_types_only)]
//! // or equivalently
//! #[subxt::subxt(runtime_types_only = true)]
//! ```

#![deny(unused_crate_dependencies)]

Expand Down Expand Up @@ -136,6 +147,8 @@ struct RuntimeMetadataArgs {
crate_path: Option<String>,
#[darling(default)]
generate_docs: darling::util::Flag,
#[darling(default)]
runtime_types_only: bool,
}

#[derive(Debug, FromMeta)]
Expand Down Expand Up @@ -207,6 +220,7 @@ pub fn subxt(args: TokenStream, input: TokenStream) -> TokenStream {
type_substitutes,
crate_path,
should_gen_docs,
args.runtime_types_only,
)
.map_or_else(|err| err.into_compile_error().into(), Into::into)
}
Expand All @@ -221,6 +235,7 @@ pub fn subxt(args: TokenStream, input: TokenStream) -> TokenStream {
type_substitutes,
crate_path,
should_gen_docs,
args.runtime_types_only,
)
.map_or_else(|err| err.into_compile_error().into(), Into::into)
}
Expand Down