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 let app = Self::setup_command(app);
129
130 let matches = app.try_get_matches_from(iter).unwrap_or_else(|e| e.exit());
131
132 <Self as FromArgMatches>::from_arg_matches(&matches).unwrap_or_else(|e| e.exit())
133 }
134
135 /// Helper function used to parse the command line arguments. This is the equivalent of
136 /// [`clap::Parser::try_parse_from`]
137 ///
138 /// To allow running the node without subcommand, it also sets a few more settings:
139 /// [`clap::Command::propagate_version`], [`clap::Command::args_conflicts_with_subcommands`],
140 /// [`clap::Command::subcommand_negates_reqs`].
141 ///
142 /// Creates `Self` from any iterator over arguments.
143 /// Print the error message and quit the program in case of failure.
144 ///
145 /// **NOTE:** This method WILL NOT exit when `--help` or `--version` (or short versions) are
146 /// used. It will return a [`clap::Error`], where the [`clap::Error::kind`] is a
147 /// [`clap::error::ErrorKind::DisplayHelp`] or [`clap::error::ErrorKind::DisplayVersion`]
148 /// respectively. You must call [`clap::Error::exit`] or perform a [`std::process::exit`].
149 fn try_from_iter<I>(iter: I) -> clap::error::Result<Self>
150 where
151 Self: Parser + Sized,
152 I: IntoIterator,
153 I::Item: Into<std::ffi::OsString> + Clone,
154 {
155 let app = <Self as CommandFactory>::command();
156 let app = Self::setup_command(app);
157
158 let matches = app.try_get_matches_from(iter)?;
159
160 <Self as FromArgMatches>::from_arg_matches(&matches)
161 }
162
163 /// Returns the client ID: `{impl_name}/v{impl_version}`
164 fn client_id() -> String {
165 format!("{}/v{}", Self::impl_name(), Self::impl_version())
166 }
167
168 /// Only create a Configuration for the command provided in argument
169 fn create_configuration<T: CliConfiguration<DVC>, DVC: DefaultConfigurationValues>(
170 &self,
171 command: &T,
172 tokio_handle: tokio::runtime::Handle,
173 ) -> error::Result<Configuration> {
174 command.create_configuration(self, tokio_handle)
175 }
176
177 /// Create a runner for the command provided in argument. This will create a Configuration and
178 /// a tokio runtime
179 fn create_runner<T: CliConfiguration<DVC>, DVC: DefaultConfigurationValues>(
180 &self,
181 command: &T,
182 ) -> Result<Runner<Self>> {
183 self.create_runner_with_logger_hook(command, |_, _| {})
184 }
185
186 /// Create a runner for the command provided in argument. The `logger_hook` can be used to setup
187 /// a custom profiler or update the logger configuration before it is initialized.
188 ///
189 /// Example:
190 /// ```
191 /// use sc_tracing::{SpanDatum, TraceEvent};
192 /// struct TestProfiler;
193 ///
194 /// impl sc_tracing::TraceHandler for TestProfiler {
195 /// fn handle_span(&self, sd: &SpanDatum) {}
196 /// fn handle_event(&self, _event: &TraceEvent) {}
197 /// };
198 ///
199 /// fn logger_hook() -> impl FnOnce(&mut sc_cli::LoggerBuilder, &sc_service::Configuration) -> () {
200 /// |logger_builder, config| {
201 /// logger_builder.with_custom_profiling(Box::new(TestProfiler{}));
202 /// }
203 /// }
204 /// ```
205 fn create_runner_with_logger_hook<
206 T: CliConfiguration<DVC>,
207 DVC: DefaultConfigurationValues,
208 F,
209 >(
210 &self,
211 command: &T,
212 logger_hook: F,
213 ) -> Result<Runner<Self>>
214 where
215 F: FnOnce(&mut LoggerBuilder, &Configuration),
216 {
217 let tokio_runtime = build_runtime()?;
218
219 // `capture` needs to be called in a tokio context.
220 // Also capture them as early as possible.
221 let signals = tokio_runtime.block_on(async { Signals::capture() })?;
222
223 let config = command.create_configuration(self, tokio_runtime.handle().clone())?;
224
225 command.init(&Self::support_url(), &Self::impl_version(), |logger_builder| {
226 logger_hook(logger_builder, &config)
227 })?;
228
229 Runner::new(config, tokio_runtime, signals)
230 }
231 /// Augments a `clap::Command` with standard metadata like name, version, author, description,
232 /// etc.
233 ///
234 /// This is used internally in `from_iter`, `try_from_iter` and can be used externally
235 /// to manually set up a command with Substrate CLI defaults.
236 fn setup_command(mut cmd: clap::Command) -> clap::Command {
237 let mut full_version = Self::impl_version();
238 full_version.push('\n');
239
240 cmd = cmd
241 .name(Self::executable_name())
242 .version(full_version)
243 .author(Self::author())
244 .about(Self::description())
245 .long_about(Self::description())
246 .after_help(format!("Support: {}", Self::support_url()))
247 .propagate_version(true)
248 .args_conflicts_with_subcommands(true)
249 .subcommand_negates_reqs(true);
250
251 cmd
252 }
253}