use crate::ManifestPath;
use anyhow::{Context, Result};
use cargo_metadata::{Metadata as CargoMetadata, MetadataCommand, Package};
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 package_name: String,
pub root_package: Package,
pub original_wasm: PathBuf,
pub dest_wasm: PathBuf,
pub ink_version: Version,
pub documentation: Option<Url>,
pub homepage: Option<Url>,
pub user: Option<Map<String, Value>>,
}
impl CrateMetadata {
pub fn collect(manifest_path: &ManifestPath) -> Result<Self> {
let (metadata, root_package) = get_cargo_metadata(manifest_path)?;
let package_name = root_package.name.replace("-", "_");
let mut original_wasm = metadata.target_directory.clone();
original_wasm.push("wasm32-unknown-unknown");
original_wasm.push("release");
original_wasm.push(package_name.clone());
original_wasm.set_extension("wasm");
let mut dest_wasm = metadata.target_directory.clone();
dest_wasm.push(package_name.clone());
dest_wasm.set_extension("wasm");
let ink_version = metadata
.packages
.iter()
.find_map(|package| {
if package.name == "ink_lang" {
Some(
Version::parse(&package.version.to_string())
.expect("Invalid ink_lang version string"),
)
} else {
None
}
})
.ok_or(anyhow::anyhow!("No 'ink_lang' dependency found"))?;
let (documentation, homepage, user) = get_cargo_toml_metadata(manifest_path)?;
let crate_metadata = CrateMetadata {
manifest_path: manifest_path.clone(),
cargo_meta: metadata,
root_package,
package_name,
original_wasm,
dest_wasm,
ink_version,
documentation,
homepage,
user,
};
Ok(crate_metadata)
}
}
fn get_cargo_metadata(manifest_path: &ManifestPath) -> Result<(CargoMetadata, Package)> {
let mut cmd = MetadataCommand::new();
let metadata = cmd
.manifest_path(manifest_path.as_ref())
.exec()
.context("Error invoking `cargo metadata`")?;
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))
}
fn get_cargo_toml_metadata(
manifest_path: &ManifestPath,
) -> Result<(Option<Url>, Option<Url>, Option<Map<String, Value>>)> {
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(anyhow::anyhow!("package section not found"))?
.get(field_name)
.and_then(|v| v.as_str())
.map(Url::parse)
.transpose()
.context(format!("{} should be a valid URL", field_name))
.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((documentation, homepage, user))
}