contract_build/
crate_metadata.rsuse crate::{
ManifestPath,
Target,
};
use anyhow::{
Context,
Result,
};
use cargo_metadata::{
Metadata as CargoMetadata,
MetadataCommand,
Package,
TargetKind,
};
use semver::Version;
use serde_json::{
Map,
Value,
};
use std::{
fs,
path::PathBuf,
};
use toml::value;
use url::Url;
#[derive(Debug)]
pub struct CrateMetadata {
pub manifest_path: ManifestPath,
pub cargo_meta: cargo_metadata::Metadata,
pub contract_artifact_name: String,
pub root_package: Package,
pub original_code: PathBuf,
pub dest_code: PathBuf,
pub ink_version: Version,
pub documentation: Option<Url>,
pub homepage: Option<Url>,
pub user: Option<Map<String, Value>>,
pub target_directory: PathBuf,
pub target_file_path: PathBuf,
}
impl CrateMetadata {
pub fn from_manifest_path(
manifest_path: Option<&PathBuf>,
target: Target,
) -> Result<Self> {
let manifest_path = ManifestPath::try_from(manifest_path)?;
Self::collect(&manifest_path, target)
}
pub fn collect(manifest_path: &ManifestPath, target: Target) -> Result<Self> {
let (metadata, root_package) = get_cargo_metadata(manifest_path)?;
let mut target_directory = metadata.target_directory.as_path().join("ink");
let contract_artifact_name = root_package.name.replace('-', "_");
if let Some(lib_name) = &root_package
.targets
.iter()
.find(|target| target.kind.iter().any(|f| *f == TargetKind::Lib))
{
if lib_name.name != root_package.name {
use colored::Colorize;
eprintln!(
"{} the `name` field in the `[lib]` section of the `Cargo.toml`, \
is no longer used for the name of generated contract artifacts. \
The package name is used instead. Remove the `[lib] name` to \
stop this warning.",
"warning:".yellow().bold(),
);
}
}
let absolute_manifest_path = manifest_path.absolute_directory()?;
let absolute_workspace_root = metadata.workspace_root.canonicalize()?;
if absolute_manifest_path != absolute_workspace_root {
target_directory = target_directory.join(contract_artifact_name.clone());
}
let mut original_code = target_directory.clone();
original_code.push(target.llvm_target());
original_code.push("release");
original_code.push(root_package.name.clone());
original_code.set_extension(target.source_extension());
let mut dest_code = target_directory.clone();
dest_code.push(contract_artifact_name.clone());
dest_code.set_extension(target.dest_extension());
let ink_version = metadata
.packages
.iter()
.find_map(|package| {
if package.name == "ink" || package.name == "ink_lang" {
Some(
Version::parse(&package.version.to_string())
.expect("Invalid ink crate version string"),
)
} else {
None
}
})
.ok_or_else(|| anyhow::anyhow!("No 'ink' dependency found"))?;
let ExtraMetadata {
documentation,
homepage,
user,
} = get_cargo_toml_metadata(manifest_path)?;
let crate_metadata = CrateMetadata {
manifest_path: manifest_path.clone(),
cargo_meta: metadata,
root_package,
contract_artifact_name,
original_code: original_code.into(),
dest_code: dest_code.into(),
ink_version,
documentation,
homepage,
user,
target_file_path: target_directory.join(".target").into(),
target_directory: target_directory.into(),
};
Ok(crate_metadata)
}
pub fn metadata_path(&self) -> PathBuf {
let metadata_file = format!("{}.json", self.contract_artifact_name);
self.target_directory.join(metadata_file)
}
pub fn contract_bundle_path(&self) -> PathBuf {
let target_directory = self.target_directory.clone();
let fname_bundle = format!("{}.contract", self.contract_artifact_name);
target_directory.join(fname_bundle)
}
}
fn get_cargo_metadata(manifest_path: &ManifestPath) -> Result<(CargoMetadata, Package)> {
tracing::debug!(
"Fetching cargo metadata for {}",
manifest_path.as_ref().to_string_lossy()
);
let mut cmd = MetadataCommand::new();
let metadata = cmd
.manifest_path(manifest_path.as_ref())
.exec()
.with_context(|| {
format!(
"Error invoking `cargo metadata` for {}",
manifest_path.as_ref().display()
)
})?;
let root_package_id = metadata
.resolve
.as_ref()
.and_then(|resolve| resolve.root.as_ref())
.context("Cannot infer the root project id")?
.clone();
let root_package = metadata
.packages
.iter()
.find(|package| package.id == root_package_id)
.expect("The package is not found in the `cargo metadata` output")
.clone();
Ok((metadata, root_package))
}
struct ExtraMetadata {
documentation: Option<Url>,
homepage: Option<Url>,
user: Option<Map<String, Value>>,
}
fn get_cargo_toml_metadata(manifest_path: &ManifestPath) -> Result<ExtraMetadata> {
let toml = fs::read_to_string(manifest_path)?;
let toml: value::Table = toml::from_str(&toml)?;
let get_url = |field_name| -> Result<Option<Url>> {
toml.get("package")
.ok_or_else(|| anyhow::anyhow!("package section not found"))?
.get(field_name)
.and_then(|v| v.as_str())
.map(Url::parse)
.transpose()
.context(format!("{field_name} should be a valid URL"))
.map_err(Into::into)
};
let documentation = get_url("documentation")?;
let homepage = get_url("homepage")?;
let user = toml
.get("package")
.and_then(|v| v.get("metadata"))
.and_then(|v| v.get("contract"))
.and_then(|v| v.get("user"))
.and_then(|v| v.as_table())
.map(|v| {
serde_json::to_string(v).and_then(|json| serde_json::from_str(&json))
})
.transpose()?;
Ok(ExtraMetadata {
documentation,
homepage,
user,
})
}