sc_cli/
lib.rs

1// This file is part of Substrate.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
5
6// This program is free software: you can redistribute it and/or modify
7// it under the terms of the GNU General Public License as published by
8// the Free Software Foundation, either version 3 of the License, or
9// (at your option) any later version.
10
11// This program is distributed in the hope that it will be useful,
12// but WITHOUT ANY WARRANTY; without even the implied warranty of
13// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14// GNU General Public License for more details.
15
16// You should have received a copy of the GNU General Public License
17// along with this program. If not, see <https://www.gnu.org/licenses/>.
18
19//! Substrate CLI library.
20//!
21//! To see a full list of commands available, see [`commands`].
22
23#![warn(missing_docs)]
24#![warn(unused_extern_crates)]
25#![warn(unused_imports)]
26
27use clap::{CommandFactory, FromArgMatches, Parser};
28use log::warn;
29use sc_service::Configuration;
30
31pub mod arg_enums;
32pub mod commands;
33mod config;
34mod error;
35mod params;
36mod runner;
37mod signals;
38
39pub use arg_enums::*;
40pub use clap;
41pub use commands::*;
42pub use config::*;
43pub use error::*;
44pub use params::*;
45pub use runner::*;
46pub use sc_service::{ChainSpec, Role};
47pub use sc_tracing::logging::LoggerBuilder;
48pub use signals::Signals;
49pub use sp_version::RuntimeVersion;
50
51/// Substrate client CLI
52///
53/// This trait needs to be implemented on the root CLI struct of the application. It will provide
54/// the implementation `name`, `version`, `executable name`, `description`, `author`, `support_url`,
55/// `copyright start year` and most importantly: how to load the chain spec.
56pub trait SubstrateCli: Sized {
57	/// Implementation name.
58	fn impl_name() -> String;
59
60	/// Implementation version.
61	///
62	/// By default, it will look like this:
63	///
64	/// `2.0.0-b950f731c`
65	///
66	/// Where the hash is the short hash of the commit in the Git repository.
67	fn impl_version() -> String;
68
69	/// Executable file name.
70	///
71	/// Extracts the file name from `std::env::current_exe()`.
72	/// Resorts to the env var `CARGO_PKG_NAME` in case of Error.
73	fn executable_name() -> String {
74		std::env::current_exe()
75			.ok()
76			.and_then(|e| e.file_name().map(|s| s.to_os_string()))
77			.and_then(|w| w.into_string().ok())
78			.unwrap_or_else(|| env!("CARGO_PKG_NAME").into())
79	}
80
81	/// Executable file description.
82	fn description() -> String;
83
84	/// Executable file author.
85	fn author() -> String;
86
87	/// Support URL.
88	fn support_url() -> String;
89
90	/// Copyright starting year (x-current year)
91	fn copyright_start_year() -> i32;
92
93	/// Chain spec factory
94	fn load_spec(&self, id: &str) -> std::result::Result<Box<dyn ChainSpec>, String>;
95
96	/// Helper function used to parse the command line arguments. This is the equivalent of
97	/// [`clap::Parser::parse()`].
98	///
99	/// To allow running the node without subcommand, it also sets a few more settings:
100	/// [`clap::Command::propagate_version`], [`clap::Command::args_conflicts_with_subcommands`],
101	/// [`clap::Command::subcommand_negates_reqs`].
102	///
103	/// Creates `Self` from the command line arguments. Print the
104	/// error message and quit the program in case of failure.
105	fn from_args() -> Self
106	where
107		Self: Parser + Sized,
108	{
109		<Self as SubstrateCli>::from_iter(&mut std::env::args_os())
110	}
111
112	/// Helper function used to parse the command line arguments. This is the equivalent of
113	/// [`clap::Parser::parse_from`].
114	///
115	/// To allow running the node without subcommand, it also sets a few more settings:
116	/// [`clap::Command::propagate_version`], [`clap::Command::args_conflicts_with_subcommands`],
117	/// [`clap::Command::subcommand_negates_reqs`].
118	///
119	/// Creates `Self` from any iterator over arguments.
120	/// Print the error message and quit the program in case of failure.
121	fn from_iter<I>(iter: I) -> Self
122	where
123		Self: Parser + Sized,
124		I: IntoIterator,
125		I::Item: Into<std::ffi::OsString> + Clone,
126	{
127		let app = <Self as CommandFactory>::command();
128
129		let mut full_version = Self::impl_version();
130		full_version.push('\n');
131
132		let name = Self::executable_name();
133		let author = Self::author();
134		let about = Self::description();
135		let app = app
136			.name(name)
137			.author(author)
138			.about(about)
139			.version(full_version)
140			.propagate_version(true)
141			.args_conflicts_with_subcommands(true)
142			.subcommand_negates_reqs(true);
143
144		let matches = app.try_get_matches_from(iter).unwrap_or_else(|e| e.exit());
145
146		<Self as FromArgMatches>::from_arg_matches(&matches).unwrap_or_else(|e| e.exit())
147	}
148
149	/// Helper function used to parse the command line arguments. This is the equivalent of
150	/// [`clap::Parser::try_parse_from`]
151	///
152	/// To allow running the node without subcommand, it also sets a few more settings:
153	/// [`clap::Command::propagate_version`], [`clap::Command::args_conflicts_with_subcommands`],
154	/// [`clap::Command::subcommand_negates_reqs`].
155	///
156	/// Creates `Self` from any iterator over arguments.
157	/// Print the error message and quit the program in case of failure.
158	///
159	/// **NOTE:** This method WILL NOT exit when `--help` or `--version` (or short versions) are
160	/// used. It will return a [`clap::Error`], where the [`clap::Error::kind`] is a
161	/// [`clap::error::ErrorKind::DisplayHelp`] or [`clap::error::ErrorKind::DisplayVersion`]
162	/// respectively. You must call [`clap::Error::exit`] or perform a [`std::process::exit`].
163	fn try_from_iter<I>(iter: I) -> clap::error::Result<Self>
164	where
165		Self: Parser + Sized,
166		I: IntoIterator,
167		I::Item: Into<std::ffi::OsString> + Clone,
168	{
169		let app = <Self as CommandFactory>::command();
170
171		let mut full_version = Self::impl_version();
172		full_version.push('\n');
173
174		let name = Self::executable_name();
175		let author = Self::author();
176		let about = Self::description();
177		let app = app.name(name).author(author).about(about).version(full_version);
178
179		let matches = app.try_get_matches_from(iter)?;
180
181		<Self as FromArgMatches>::from_arg_matches(&matches)
182	}
183
184	/// Returns the client ID: `{impl_name}/v{impl_version}`
185	fn client_id() -> String {
186		format!("{}/v{}", Self::impl_name(), Self::impl_version())
187	}
188
189	/// Only create a Configuration for the command provided in argument
190	fn create_configuration<T: CliConfiguration<DVC>, DVC: DefaultConfigurationValues>(
191		&self,
192		command: &T,
193		tokio_handle: tokio::runtime::Handle,
194	) -> error::Result<Configuration> {
195		command.create_configuration(self, tokio_handle)
196	}
197
198	/// Create a runner for the command provided in argument. This will create a Configuration and
199	/// a tokio runtime
200	fn create_runner<T: CliConfiguration<DVC>, DVC: DefaultConfigurationValues>(
201		&self,
202		command: &T,
203	) -> Result<Runner<Self>> {
204		self.create_runner_with_logger_hook(command, |_, _| {})
205	}
206
207	/// Create a runner for the command provided in argument. The `logger_hook` can be used to setup
208	/// a custom profiler or update the logger configuration before it is initialized.
209	///
210	/// Example:
211	/// ```
212	/// use sc_tracing::{SpanDatum, TraceEvent};
213	/// struct TestProfiler;
214	///
215	/// impl sc_tracing::TraceHandler for TestProfiler {
216	///  	fn handle_span(&self, sd: &SpanDatum) {}
217	/// 		fn handle_event(&self, _event: &TraceEvent) {}
218	/// };
219	///
220	/// fn logger_hook() -> impl FnOnce(&mut sc_cli::LoggerBuilder, &sc_service::Configuration) -> () {
221	/// 	|logger_builder, config| {
222	/// 			logger_builder.with_custom_profiling(Box::new(TestProfiler{}));
223	/// 	}
224	/// }
225	/// ```
226	fn create_runner_with_logger_hook<
227		T: CliConfiguration<DVC>,
228		DVC: DefaultConfigurationValues,
229		F,
230	>(
231		&self,
232		command: &T,
233		logger_hook: F,
234	) -> Result<Runner<Self>>
235	where
236		F: FnOnce(&mut LoggerBuilder, &Configuration),
237	{
238		let tokio_runtime = build_runtime()?;
239
240		// `capture` needs to be called in a tokio context.
241		// Also capture them as early as possible.
242		let signals = tokio_runtime.block_on(async { Signals::capture() })?;
243
244		let config = command.create_configuration(self, tokio_runtime.handle().clone())?;
245
246		command.init(&Self::support_url(), &Self::impl_version(), |logger_builder| {
247			logger_hook(logger_builder, &config)
248		})?;
249
250		Runner::new(config, tokio_runtime, signals)
251	}
252}