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

            
27
use clap::{CommandFactory, FromArgMatches, Parser};
28
use sc_service::Configuration;
29

            
30
pub mod arg_enums;
31
pub mod commands;
32
mod config;
33
mod error;
34
mod params;
35
mod runner;
36
mod signals;
37

            
38
pub use arg_enums::*;
39
pub use clap;
40
pub use commands::*;
41
pub use config::*;
42
pub use error::*;
43
pub use params::*;
44
pub use runner::*;
45
pub use sc_service::{ChainSpec, Role};
46
pub use sc_tracing::logging::LoggerBuilder;
47
pub use signals::Signals;
48
pub use sp_version::RuntimeVersion;
49

            
50
/// Substrate client CLI
51
///
52
/// This trait needs to be implemented on the root CLI struct of the application. It will provide
53
/// the implementation `name`, `version`, `executable name`, `description`, `author`, `support_url`,
54
/// `copyright start year` and most importantly: how to load the chain spec.
55
pub trait SubstrateCli: Sized {
56
	/// Implementation name.
57
	fn impl_name() -> String;
58

            
59
	/// Implementation version.
60
	///
61
	/// By default, it will look like this:
62
	///
63
	/// `2.0.0-b950f731c`
64
	///
65
	/// Where the hash is the short hash of the commit in the Git repository.
66
	fn impl_version() -> String;
67

            
68
	/// Executable file name.
69
	///
70
	/// Extracts the file name from `std::env::current_exe()`.
71
	/// Resorts to the env var `CARGO_PKG_NAME` in case of Error.
72
	fn executable_name() -> String {
73
		std::env::current_exe()
74
			.ok()
75
			.and_then(|e| e.file_name().map(|s| s.to_os_string()))
76
			.and_then(|w| w.into_string().ok())
77
			.unwrap_or_else(|| env!("CARGO_PKG_NAME").into())
78
	}
79

            
80
	/// Executable file description.
81
	fn description() -> String;
82

            
83
	/// Executable file author.
84
	fn author() -> String;
85

            
86
	/// Support URL.
87
	fn support_url() -> String;
88

            
89
	/// Copyright starting year (x-current year)
90
	fn copyright_start_year() -> i32;
91

            
92
	/// Chain spec factory
93
	fn load_spec(&self, id: &str) -> std::result::Result<Box<dyn ChainSpec>, String>;
94

            
95
	/// Helper function used to parse the command line arguments. This is the equivalent of
96
	/// [`clap::Parser::parse()`].
97
	///
98
	/// To allow running the node without subcommand, it also sets a few more settings:
99
	/// [`clap::Command::propagate_version`], [`clap::Command::args_conflicts_with_subcommands`],
100
	/// [`clap::Command::subcommand_negates_reqs`].
101
	///
102
	/// Creates `Self` from the command line arguments. Print the
103
	/// error message and quit the program in case of failure.
104
	fn from_args() -> Self
105
	where
106
		Self: Parser + Sized,
107
	{
108
		<Self as SubstrateCli>::from_iter(&mut std::env::args_os())
109
	}
110

            
111
	/// Helper function used to parse the command line arguments. This is the equivalent of
112
	/// [`clap::Parser::parse_from`].
113
	///
114
	/// To allow running the node without subcommand, it also sets a few more settings:
115
	/// [`clap::Command::propagate_version`], [`clap::Command::args_conflicts_with_subcommands`],
116
	/// [`clap::Command::subcommand_negates_reqs`].
117
	///
118
	/// Creates `Self` from any iterator over arguments.
119
	/// Print the error message and quit the program in case of failure.
120
	fn from_iter<I>(iter: I) -> Self
121
	where
122
		Self: Parser + Sized,
123
		I: IntoIterator,
124
		I::Item: Into<std::ffi::OsString> + Clone,
125
	{
126
		let app = <Self as CommandFactory>::command();
127

            
128
		let mut full_version = Self::impl_version();
129
		full_version.push('\n');
130

            
131
		let name = Self::executable_name();
132
		let author = Self::author();
133
		let about = Self::description();
134
		let app = app
135
			.name(name)
136
			.author(author)
137
			.about(about)
138
			.version(full_version)
139
			.propagate_version(true)
140
			.args_conflicts_with_subcommands(true)
141
			.subcommand_negates_reqs(true);
142

            
143
		let matches = app.try_get_matches_from(iter).unwrap_or_else(|e| e.exit());
144

            
145
		<Self as FromArgMatches>::from_arg_matches(&matches).unwrap_or_else(|e| e.exit())
146
	}
147

            
148
	/// Helper function used to parse the command line arguments. This is the equivalent of
149
	/// [`clap::Parser::try_parse_from`]
150
	///
151
	/// To allow running the node without subcommand, it also sets a few more settings:
152
	/// [`clap::Command::propagate_version`], [`clap::Command::args_conflicts_with_subcommands`],
153
	/// [`clap::Command::subcommand_negates_reqs`].
154
	///
155
	/// Creates `Self` from any iterator over arguments.
156
	/// Print the error message and quit the program in case of failure.
157
	///
158
	/// **NOTE:** This method WILL NOT exit when `--help` or `--version` (or short versions) are
159
	/// used. It will return a [`clap::Error`], where the [`clap::Error::kind`] is a
160
	/// [`clap::error::ErrorKind::DisplayHelp`] or [`clap::error::ErrorKind::DisplayVersion`]
161
	/// respectively. You must call [`clap::Error::exit`] or perform a [`std::process::exit`].
162
	fn try_from_iter<I>(iter: I) -> clap::error::Result<Self>
163
	where
164
		Self: Parser + Sized,
165
		I: IntoIterator,
166
		I::Item: Into<std::ffi::OsString> + Clone,
167
	{
168
		let app = <Self as CommandFactory>::command();
169

            
170
		let mut full_version = Self::impl_version();
171
		full_version.push('\n');
172

            
173
		let name = Self::executable_name();
174
		let author = Self::author();
175
		let about = Self::description();
176
		let app = app.name(name).author(author).about(about).version(full_version);
177

            
178
		let matches = app.try_get_matches_from(iter)?;
179

            
180
		<Self as FromArgMatches>::from_arg_matches(&matches)
181
	}
182

            
183
	/// Returns the client ID: `{impl_name}/v{impl_version}`
184
	fn client_id() -> String {
185
		format!("{}/v{}", Self::impl_name(), Self::impl_version())
186
	}
187

            
188
	/// Only create a Configuration for the command provided in argument
189
	fn create_configuration<T: CliConfiguration<DVC>, DVC: DefaultConfigurationValues>(
190
		&self,
191
		command: &T,
192
		tokio_handle: tokio::runtime::Handle,
193
	) -> error::Result<Configuration> {
194
		command.create_configuration(self, tokio_handle)
195
	}
196

            
197
	/// Create a runner for the command provided in argument. This will create a Configuration and
198
	/// a tokio runtime
199
	fn create_runner<T: CliConfiguration<DVC>, DVC: DefaultConfigurationValues>(
200
		&self,
201
		command: &T,
202
	) -> Result<Runner<Self>> {
203
		self.create_runner_with_logger_hook(command, |_, _| {})
204
	}
205

            
206
	/// Create a runner for the command provided in argument. The `logger_hook` can be used to setup
207
	/// a custom profiler or update the logger configuration before it is initialized.
208
	///
209
	/// Example:
210
	/// ```
211
	/// use sc_tracing::{SpanDatum, TraceEvent};
212
	/// struct TestProfiler;
213
	///
214
	/// impl sc_tracing::TraceHandler for TestProfiler {
215
	///  	fn handle_span(&self, sd: &SpanDatum) {}
216
	/// 		fn handle_event(&self, _event: &TraceEvent) {}
217
	/// };
218
	///
219
	/// fn logger_hook() -> impl FnOnce(&mut sc_cli::LoggerBuilder, &sc_service::Configuration) -> () {
220
	/// 	|logger_builder, config| {
221
	/// 			logger_builder.with_custom_profiling(Box::new(TestProfiler{}));
222
	/// 	}
223
	/// }
224
	/// ```
225
	fn create_runner_with_logger_hook<
226
		T: CliConfiguration<DVC>,
227
		DVC: DefaultConfigurationValues,
228
		F,
229
	>(
230
		&self,
231
		command: &T,
232
		logger_hook: F,
233
	) -> Result<Runner<Self>>
234
	where
235
		F: FnOnce(&mut LoggerBuilder, &Configuration),
236
	{
237
		let tokio_runtime = build_runtime()?;
238

            
239
		// `capture` needs to be called in a tokio context.
240
		// Also capture them as early as possible.
241
		let signals = tokio_runtime.block_on(async { Signals::capture() })?;
242

            
243
		let config = command.create_configuration(self, tokio_runtime.handle().clone())?;
244

            
245
		command.init(&Self::support_url(), &Self::impl_version(), logger_hook, &config)?;
246
		Runner::new(config, tokio_runtime, signals)
247
	}
248
}