1use crate::{
18 code_hash,
19 crate_metadata::CrateMetadata,
20 util,
21 verbose_eprintln,
22 workspace::{
23 ManifestPath,
24 Workspace,
25 },
26 BuildMode,
27 Features,
28 Lto,
29 Network,
30 OptimizationPasses,
31 Profile,
32 UnstableFlags,
33 Verbosity,
34};
35
36use anyhow::Result;
37use colored::Colorize;
38use contract_metadata::{
39 Compiler,
40 Contract,
41 ContractMetadata,
42 Language,
43 Source,
44 SourceCompiler,
45 SourceLanguage,
46 SourceWasm,
47 User,
48};
49use semver::Version;
50use std::{
51 fs,
52 path::{
53 Path,
54 PathBuf,
55 },
56};
57use url::Url;
58
59#[derive(serde::Serialize, serde::Deserialize)]
61pub struct MetadataArtifacts {
62 pub dest_metadata: PathBuf,
64 pub dest_bundle: PathBuf,
66}
67
68struct ExtendedMetadataResult {
70 source: Source,
71 contract: Contract,
72 user: Option<User>,
73}
74
75#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
80pub struct BuildInfo {
81 pub rust_toolchain: String,
83 pub cargo_contract_version: Version,
85 pub build_mode: BuildMode,
87 pub wasm_opt_settings: WasmOptSettings,
89}
90
91impl TryFrom<BuildInfo> for serde_json::Map<String, serde_json::Value> {
92 type Error = serde_json::Error;
93
94 fn try_from(build_info: BuildInfo) -> Result<Self, Self::Error> {
95 let tmp = serde_json::to_string(&build_info)?;
96 serde_json::from_str(&tmp)
97 }
98}
99
100#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
102pub struct WasmOptSettings {
103 pub optimization_passes: OptimizationPasses,
105 pub keep_debug_symbols: bool,
107}
108
109#[allow(clippy::too_many_arguments)]
113pub fn execute(
114 crate_metadata: &CrateMetadata,
115 final_contract_wasm: &Path,
116 metadata_artifacts: &MetadataArtifacts,
117 features: &Features,
118 network: Network,
119 verbosity: Verbosity,
120 unstable_options: &UnstableFlags,
121 build_info: BuildInfo,
122) -> Result<()> {
123 let ExtendedMetadataResult {
125 source,
126 contract,
127 user,
128 } = extended_metadata(crate_metadata, final_contract_wasm, build_info)?;
129
130 let generate_metadata = |manifest_path: &ManifestPath| -> Result<()> {
131 verbose_eprintln!(
132 verbosity,
133 " {} {}",
134 "[==]".bold(),
135 "Generating metadata".bright_green().bold(),
136 );
137 let target_dir = crate_metadata
138 .target_directory
139 .to_string_lossy()
140 .to_string();
141 let mut args = vec![
142 "--package".to_owned(),
143 "metadata-gen".to_owned(),
144 manifest_path.cargo_arg()?,
145 "--target-dir".to_owned(),
146 target_dir,
147 "--release".to_owned(),
148 ];
149 network.append_to_args(&mut args);
150 features.append_to_args(&mut args);
151
152 #[cfg(windows)]
153 let link_dead_code = "";
154
155 #[cfg(not(windows))]
156 let link_dead_code = "\x1f-Clink-dead-code";
157
158 let cmd = util::cargo_cmd(
159 "run",
160 args,
161 crate_metadata.manifest_path.directory(),
162 verbosity,
163 vec![(
164 "CARGO_ENCODED_RUSTFLAGS",
165 Some(format!("--cap-lints=allow{link_dead_code}")),
166 )],
167 );
168 let output = cmd.stdout_capture().run()?;
169
170 let ink_meta: serde_json::Map<String, serde_json::Value> =
171 serde_json::from_slice(&output.stdout)?;
172 let metadata = ContractMetadata::new(source, contract, None, user, ink_meta);
173
174 write_metadata(metadata_artifacts, metadata, &verbosity, false)?;
175
176 Ok(())
177 };
178
179 if unstable_options.original_manifest {
180 generate_metadata(&crate_metadata.manifest_path)?;
181 } else {
182 Workspace::new(&crate_metadata.cargo_meta, &crate_metadata.root_package.id)?
183 .with_root_package_manifest(|manifest| {
184 manifest
185 .with_added_crate_type("rlib")?
186 .with_profile_release_defaults(Profile {
187 lto: Some(Lto::Thin),
188 ..Profile::default()
189 })?
190 .with_merged_workspace_dependencies(crate_metadata)?
191 .with_empty_workspace();
192 Ok(())
193 })?
194 .with_metadata_gen_package()?
195 .using_temp(generate_metadata)?;
196 }
197
198 Ok(())
199}
200
201pub fn write_metadata(
202 metadata_artifacts: &MetadataArtifacts,
203 metadata: ContractMetadata,
204 verbosity: &Verbosity,
205 overwrite: bool,
206) -> Result<()> {
207 {
208 let mut metadata = metadata.clone();
209 metadata.remove_source_wasm_attribute();
210 let contents = serde_json::to_string_pretty(&metadata)?;
211 fs::write(&metadata_artifacts.dest_metadata, contents)?;
212 }
213
214 if overwrite {
215 verbose_eprintln!(
216 verbosity,
217 " {} {}",
218 "[==]".bold(),
219 "Updating paths".bright_cyan().bold()
220 );
221 } else {
222 verbose_eprintln!(
223 verbosity,
224 " {} {}",
225 "[==]".bold(),
226 "Generating bundle".bright_green().bold()
227 );
228 }
229 let contents = serde_json::to_string(&metadata)?;
230 fs::write(&metadata_artifacts.dest_bundle, contents)?;
231
232 Ok(())
233}
234
235fn extended_metadata(
237 crate_metadata: &CrateMetadata,
238 final_contract_wasm: &Path,
239 build_info: BuildInfo,
240) -> Result<ExtendedMetadataResult> {
241 let contract_package = &crate_metadata.root_package;
242 let ink_version = &crate_metadata.ink_version;
243 let rust_version = Version::parse(&rustc_version::version()?.to_string())?;
244 let contract_name = contract_package.name.clone();
245 let contract_version = Version::parse(&contract_package.version.to_string())?;
246 let contract_authors = contract_package.authors.clone();
247 let description = contract_package.description.clone();
249 let documentation = crate_metadata.documentation.clone();
250 let repository = contract_package
251 .repository
252 .as_ref()
253 .map(|repo| Url::parse(repo))
254 .transpose()?;
255 let homepage = crate_metadata.homepage.clone();
256 let license = contract_package.license.clone();
257 let source = {
258 let lang = SourceLanguage::new(Language::Ink, ink_version.clone());
259 let compiler = SourceCompiler::new(Compiler::RustC, rust_version);
260 let wasm = fs::read(final_contract_wasm)?;
261 let hash = code_hash(wasm.as_slice());
262 Source::new(
263 Some(SourceWasm::new(wasm)),
264 hash.into(),
265 lang,
266 compiler,
267 Some(build_info.try_into()?),
268 )
269 };
270
271 let mut builder = Contract::builder();
273 builder
274 .name(contract_name)
275 .version(contract_version)
276 .authors(contract_authors);
277
278 if let Some(description) = description {
279 builder.description(description);
280 }
281
282 if let Some(documentation) = documentation {
283 builder.documentation(documentation);
284 }
285
286 if let Some(repository) = repository {
287 builder.repository(repository);
288 }
289
290 if let Some(homepage) = homepage {
291 builder.homepage(homepage);
292 }
293
294 if let Some(license) = license {
295 builder.license(license);
296 }
297
298 let contract = builder.build().map_err(|err| {
299 anyhow::anyhow!("Invalid contract metadata builder state: {}", err)
300 })?;
301
302 let user = crate_metadata.user.clone().map(User::new);
304
305 Ok(ExtendedMetadataResult {
306 source,
307 contract,
308 user,
309 })
310}