substrate_manager/ops/
substrate_add.rs1use std::{
2 fs,
3 path::{Path, PathBuf},
4 process::Command,
5};
6
7use anyhow::Ok;
8use regex::Regex;
9
10use crate::{
11 core::manifest::Manifest,
12 util::{to_pascal_case, to_snake_case, Config, SubstrateResult},
13};
14
15pub struct AddOptions {
16 pub package_name: String,
18 pub package_path: PathBuf,
20 pub crate_spec: String,
22
23 pub features: Vec<String>,
25 pub default_features: Option<bool>,
27
28 pub source: CrateSource,
30}
31
32pub enum CrateSource {
33 DefaultRegistry,
34 Git(String, String),
35 Path(String),
36 CustomRegistry(String),
37}
38
39pub fn add_pallet_std_to_manifest(
40 cwd: &Path,
41 runtime_path: &Path,
42 crate_spec: &str,
43) -> SubstrateResult<()> {
44 let feature = format!("{}/std", crate_spec);
45 let mut runtime_manifest = Manifest::new(cwd.join(runtime_path).join("Cargo.toml"));
46 let mut runtime_document = runtime_manifest.read_document()?;
47 let feature_array = runtime_document["features"].as_table_mut().unwrap()["std"]
49 .as_array_mut()
50 .unwrap();
51 if !feature_array.iter().any(|f| f.as_str().unwrap() == feature) {
52 feature_array.push::<String>(feature);
53 }
54
55 runtime_manifest.write_document(runtime_document)?;
56
57 Ok(())
58}
59
60pub fn add_pallet_to_runtime(
62 cwd: &Path,
63 runtime_path: &Path,
64 crate_spec: &str,
65) -> SubstrateResult<Option<usize>> {
66 let runtime_lib_path = cwd.join(runtime_path).join("src/lib.rs");
67 let mod_name = to_snake_case(crate_spec);
68
69 let pallet_trait_existing = Regex::new(
70 format!(
71 r"(?xm)
72 ^impl\s+{}::Config\s+for\s+Runtime\s+\{{
73 [^\}}]+
74 \}}
75 ",
76 mod_name
77 )
78 .as_ref(),
79 )?;
80
81 let construct_runtime = Regex::new(
82 r"construct_runtime!\(\s*(?P<visibility>pub\s+)?(?P<variant>enum|struct)\s+Runtime\s*(where[^\{]+)?\{(?P<pallets>[\s\S]+)\}\s*\);",
83 )?;
84
85 let mut pallet_trait_impl = format!("impl {}::Config for Runtime {{ \n", mod_name);
86 pallet_trait_impl.push_str(&format!(" /* {} Trait config goes here */ \n", mod_name));
87 pallet_trait_impl.push('}');
88
89 let pallet_config = format!(
90 r"
91 {}: {},",
92 to_pascal_case(&mod_name),
93 mod_name
94 );
95
96 let original = fs::read_to_string(&runtime_lib_path)?;
97 let mut buffer = original.clone();
98 let mut line_number: Option<usize> = None;
99 if pallet_trait_existing.is_match(&original) {
100 buffer = pallet_trait_existing
101 .replace(&original, |caps: ®ex::Captures| {
102 line_number = Some(original[..caps.get(0).unwrap().start()].lines().count() + 1);
103 pallet_trait_impl.to_owned()
104 })
105 .to_string();
106 } else {
107 let mat = construct_runtime
108 .find(&original)
109 .ok_or_else(|| anyhow::anyhow!("couldn't find construct_runtime call"))?;
110 line_number = Some(original[..mat.start()].lines().count() + 1);
111 buffer.insert_str(mat.start(), format!("{}\n\n", pallet_trait_impl).as_str());
112 };
113
114 let modified = buffer.clone();
115 let caps = construct_runtime
116 .captures(&modified)
117 .ok_or_else(|| anyhow::anyhow!("couldn't find construct_runtime call"))?;
118 let pallets = caps.name("pallets").ok_or_else(|| {
119 anyhow::anyhow!("couldn't find runtime pallets config inside construct_runtime",)
120 })?;
121
122 let existing_pallets = modified.get(pallets.start()..pallets.end()).unwrap();
123 let line_number_with_mod_name = existing_pallets
124 .lines()
125 .position(|line| line.contains(&mod_name));
126
127 if let Some(line_number) = line_number_with_mod_name {
128 let line_to_replace = existing_pallets.lines().nth(line_number).unwrap();
129 let new_existing_pallets = existing_pallets.replace(line_to_replace, &pallet_config[1..]);
130
131 let new_buffer = modified.replacen(existing_pallets, &new_existing_pallets, 1);
132 fs::write(runtime_lib_path, new_buffer)?;
133 } else {
134 buffer.insert_str(pallets.end() - 2, &pallet_config);
136 fs::write(runtime_lib_path, buffer)?;
137 }
138
139 Ok(line_number)
140}
141
142pub fn add_pallet(opts: &AddOptions, config: &Config) -> SubstrateResult<()> {
146 let crate_source_arg = match &opts.source {
147 CrateSource::DefaultRegistry => vec![],
148 CrateSource::Git(url, branch) => {
149 let mut args = vec!["--git", url];
150 if !branch.is_empty() {
151 args.extend(["--branch", branch]);
152 }
153 args
154 },
155 CrateSource::Path(path) => vec!["--path", path],
156 CrateSource::CustomRegistry(registry) => vec!["--registry", registry],
157 };
158
159 let status = Command::new("cargo")
160 .arg("add")
161 .arg("-p")
162 .arg(&opts.package_name)
163 .arg(&opts.crate_spec)
164 .arg("--features")
165 .arg(&opts.features.join(","))
166 .arg("--no-default-features")
167 .args(crate_source_arg)
168 .status()?;
169
170 if !status.success() {
171 return Err(anyhow::anyhow!(
172 "Failed to install pallet: `{}`",
173 opts.crate_spec,
174 ));
175 }
176
177 add_pallet_std_to_manifest(config.cwd(), &opts.package_path, &opts.crate_spec)?;
178
179 let trait_line_number =
180 add_pallet_to_runtime(config.cwd(), &opts.package_path, &opts.crate_spec)?;
181 println!(
182 "\nPallet `{}` has been successfully added to the runtime!",
183 opts.crate_spec
184 );
185 if let Some(line_number) = trait_line_number {
186 println!(
187 "Don't forget to implement the `Config` trait in `runtime/src/lib.rs`, line: {}",
188 line_number
189 );
190 }
191
192 Ok(())
193}