contract_build/
crate_metadata.rs1use crate::{
18 ManifestPath,
19 Target,
20};
21use anyhow::{
22 Context,
23 Result,
24};
25use cargo_metadata::{
26 Metadata as CargoMetadata,
27 MetadataCommand,
28 Package,
29 TargetKind,
30};
31use semver::Version;
32use serde_json::{
33 Map,
34 Value,
35};
36use std::{
37 fs,
38 path::PathBuf,
39};
40use toml::value;
41use url::Url;
42
43#[derive(Debug)]
45pub struct CrateMetadata {
46 pub manifest_path: ManifestPath,
47 pub cargo_meta: cargo_metadata::Metadata,
48 pub contract_artifact_name: String,
49 pub root_package: Package,
50 pub original_code: PathBuf,
51 pub dest_code: PathBuf,
52 pub ink_version: Version,
53 pub documentation: Option<Url>,
54 pub homepage: Option<Url>,
55 pub user: Option<Map<String, Value>>,
56 pub target_directory: PathBuf,
57 pub target_file_path: PathBuf,
58}
59
60impl CrateMetadata {
61 pub fn from_manifest_path(
63 manifest_path: Option<&PathBuf>,
64 target: Target,
65 ) -> Result<Self> {
66 let manifest_path = ManifestPath::try_from(manifest_path)?;
67 Self::collect(&manifest_path, target)
68 }
69
70 pub fn collect(manifest_path: &ManifestPath, target: Target) -> Result<Self> {
72 let (metadata, root_package) = get_cargo_metadata(manifest_path)?;
73 let mut target_directory = metadata.target_directory.as_path().join("ink");
74
75 let contract_artifact_name = root_package.name.replace('-', "_");
77
78 if let Some(lib_name) = &root_package
79 .targets
80 .iter()
81 .find(|target| target.kind.iter().any(|f| *f == TargetKind::Lib))
82 {
83 if lib_name.name != root_package.name {
84 use colored::Colorize;
87 eprintln!(
88 "{} the `name` field in the `[lib]` section of the `Cargo.toml`, \
89 is no longer used for the name of generated contract artifacts. \
90 The package name is used instead. Remove the `[lib] name` to \
91 stop this warning.",
92 "warning:".yellow().bold(),
93 );
94 }
95 }
96
97 let absolute_manifest_path = manifest_path.absolute_directory()?;
98 let absolute_workspace_root = metadata.workspace_root.canonicalize()?;
99 if absolute_manifest_path != absolute_workspace_root {
100 target_directory = target_directory.join(contract_artifact_name.clone());
103 }
104
105 let mut original_code = target_directory.clone();
107 original_code.push(target.llvm_target());
108 original_code.push("release");
109 original_code.push(root_package.name.clone());
110 original_code.set_extension(target.source_extension());
111
112 let mut dest_code = target_directory.clone();
114 dest_code.push(contract_artifact_name.clone());
115 dest_code.set_extension(target.dest_extension());
116
117 let ink_version = metadata
118 .packages
119 .iter()
120 .find_map(|package| {
121 if package.name == "ink" || package.name == "ink_lang" {
122 Some(
123 Version::parse(&package.version.to_string())
124 .expect("Invalid ink crate version string"),
125 )
126 } else {
127 None
128 }
129 })
130 .ok_or_else(|| anyhow::anyhow!("No 'ink' dependency found"))?;
131
132 let ExtraMetadata {
133 documentation,
134 homepage,
135 user,
136 } = get_cargo_toml_metadata(manifest_path)?;
137
138 let crate_metadata = CrateMetadata {
139 manifest_path: manifest_path.clone(),
140 cargo_meta: metadata,
141 root_package,
142 contract_artifact_name,
143 original_code: original_code.into(),
144 dest_code: dest_code.into(),
145 ink_version,
146 documentation,
147 homepage,
148 user,
149 target_file_path: target_directory.join(".target").into(),
150 target_directory: target_directory.into(),
151 };
152 Ok(crate_metadata)
153 }
154
155 pub fn metadata_path(&self) -> PathBuf {
157 let metadata_file = format!("{}.json", self.contract_artifact_name);
158 self.target_directory.join(metadata_file)
159 }
160
161 pub fn contract_bundle_path(&self) -> PathBuf {
163 let target_directory = self.target_directory.clone();
164 let fname_bundle = format!("{}.contract", self.contract_artifact_name);
165 target_directory.join(fname_bundle)
166 }
167}
168
169fn get_cargo_metadata(manifest_path: &ManifestPath) -> Result<(CargoMetadata, Package)> {
171 tracing::debug!(
172 "Fetching cargo metadata for {}",
173 manifest_path.as_ref().to_string_lossy()
174 );
175 let mut cmd = MetadataCommand::new();
176 let metadata = cmd
177 .manifest_path(manifest_path.as_ref())
178 .exec()
179 .with_context(|| {
180 format!(
181 "Error invoking `cargo metadata` for {}",
182 manifest_path.as_ref().display()
183 )
184 })?;
185 let root_package_id = metadata
186 .resolve
187 .as_ref()
188 .and_then(|resolve| resolve.root.as_ref())
189 .context("Cannot infer the root project id")?
190 .clone();
191 let root_package = metadata
194 .packages
195 .iter()
196 .find(|package| package.id == root_package_id)
197 .expect("The package is not found in the `cargo metadata` output")
198 .clone();
199 Ok((metadata, root_package))
200}
201
202struct ExtraMetadata {
204 documentation: Option<Url>,
205 homepage: Option<Url>,
206 user: Option<Map<String, Value>>,
207}
208
209fn get_cargo_toml_metadata(manifest_path: &ManifestPath) -> Result<ExtraMetadata> {
211 let toml = fs::read_to_string(manifest_path)?;
212 let toml: value::Table = toml::from_str(&toml)?;
213
214 let get_url = |field_name| -> Result<Option<Url>> {
215 toml.get("package")
216 .ok_or_else(|| anyhow::anyhow!("package section not found"))?
217 .get(field_name)
218 .and_then(|v| v.as_str())
219 .map(Url::parse)
220 .transpose()
221 .context(format!("{field_name} should be a valid URL"))
222 .map_err(Into::into)
223 };
224
225 let documentation = get_url("documentation")?;
226 let homepage = get_url("homepage")?;
227
228 let user = toml
229 .get("package")
230 .and_then(|v| v.get("metadata"))
231 .and_then(|v| v.get("contract"))
232 .and_then(|v| v.get("user"))
233 .and_then(|v| v.as_table())
234 .map(|v| {
235 serde_json::to_string(v).and_then(|json| serde_json::from_str(&json))
237 })
238 .transpose()?;
239
240 Ok(ExtraMetadata {
241 documentation,
242 homepage,
243 user,
244 })
245}