use crate::{
code_hash,
crate_metadata::CrateMetadata,
util,
verbose_eprintln,
workspace::{
ManifestPath,
Workspace,
},
BuildMode,
Features,
Lto,
Network,
OptimizationPasses,
Profile,
UnstableFlags,
Verbosity,
};
use anyhow::Result;
use colored::Colorize;
use contract_metadata::{
Compiler,
Contract,
ContractMetadata,
Language,
Source,
SourceCompiler,
SourceLanguage,
SourceWasm,
User,
};
use semver::Version;
use std::{
fs,
path::{
Path,
PathBuf,
},
};
use url::Url;
#[derive(serde::Serialize, serde::Deserialize)]
pub struct MetadataArtifacts {
pub dest_metadata: PathBuf,
pub dest_bundle: PathBuf,
}
struct ExtendedMetadataResult {
source: Source,
contract: Contract,
user: Option<User>,
}
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
pub struct BuildInfo {
pub rust_toolchain: String,
pub cargo_contract_version: Version,
pub build_mode: BuildMode,
pub wasm_opt_settings: WasmOptSettings,
}
impl TryFrom<BuildInfo> for serde_json::Map<String, serde_json::Value> {
type Error = serde_json::Error;
fn try_from(build_info: BuildInfo) -> Result<Self, Self::Error> {
let tmp = serde_json::to_string(&build_info)?;
serde_json::from_str(&tmp)
}
}
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
pub struct WasmOptSettings {
pub optimization_passes: OptimizationPasses,
pub keep_debug_symbols: bool,
}
#[allow(clippy::too_many_arguments)]
pub fn execute(
crate_metadata: &CrateMetadata,
final_contract_wasm: &Path,
metadata_artifacts: &MetadataArtifacts,
features: &Features,
network: Network,
verbosity: Verbosity,
unstable_options: &UnstableFlags,
build_info: BuildInfo,
) -> Result<()> {
let ExtendedMetadataResult {
source,
contract,
user,
} = extended_metadata(crate_metadata, final_contract_wasm, build_info)?;
let generate_metadata = |manifest_path: &ManifestPath| -> Result<()> {
verbose_eprintln!(
verbosity,
" {} {}",
"[==]".bold(),
"Generating metadata".bright_green().bold(),
);
let target_dir = crate_metadata
.target_directory
.to_string_lossy()
.to_string();
let mut args = vec![
"--package".to_owned(),
"metadata-gen".to_owned(),
manifest_path.cargo_arg()?,
"--target-dir".to_owned(),
target_dir,
"--release".to_owned(),
];
network.append_to_args(&mut args);
features.append_to_args(&mut args);
#[cfg(windows)]
let link_dead_code = "";
#[cfg(not(windows))]
let link_dead_code = "\x1f-Clink-dead-code";
let cmd = util::cargo_cmd(
"run",
args,
crate_metadata.manifest_path.directory(),
verbosity,
vec![(
"CARGO_ENCODED_RUSTFLAGS",
Some(format!("--cap-lints=allow{link_dead_code}")),
)],
);
let output = cmd.stdout_capture().run()?;
let ink_meta: serde_json::Map<String, serde_json::Value> =
serde_json::from_slice(&output.stdout)?;
let metadata = ContractMetadata::new(source, contract, None, user, ink_meta);
write_metadata(metadata_artifacts, metadata, &verbosity, false)?;
Ok(())
};
if unstable_options.original_manifest {
generate_metadata(&crate_metadata.manifest_path)?;
} else {
Workspace::new(&crate_metadata.cargo_meta, &crate_metadata.root_package.id)?
.with_root_package_manifest(|manifest| {
manifest
.with_added_crate_type("rlib")?
.with_profile_release_defaults(Profile {
lto: Some(Lto::Thin),
..Profile::default()
})?
.with_merged_workspace_dependencies(crate_metadata)?
.with_empty_workspace();
Ok(())
})?
.with_metadata_gen_package()?
.using_temp(generate_metadata)?;
}
Ok(())
}
pub fn write_metadata(
metadata_artifacts: &MetadataArtifacts,
metadata: ContractMetadata,
verbosity: &Verbosity,
overwrite: bool,
) -> Result<()> {
{
let mut metadata = metadata.clone();
metadata.remove_source_wasm_attribute();
let contents = serde_json::to_string_pretty(&metadata)?;
fs::write(&metadata_artifacts.dest_metadata, contents)?;
}
if overwrite {
verbose_eprintln!(
verbosity,
" {} {}",
"[==]".bold(),
"Updating paths".bright_cyan().bold()
);
} else {
verbose_eprintln!(
verbosity,
" {} {}",
"[==]".bold(),
"Generating bundle".bright_green().bold()
);
}
let contents = serde_json::to_string(&metadata)?;
fs::write(&metadata_artifacts.dest_bundle, contents)?;
Ok(())
}
fn extended_metadata(
crate_metadata: &CrateMetadata,
final_contract_wasm: &Path,
build_info: BuildInfo,
) -> Result<ExtendedMetadataResult> {
let contract_package = &crate_metadata.root_package;
let ink_version = &crate_metadata.ink_version;
let rust_version = Version::parse(&rustc_version::version()?.to_string())?;
let contract_name = contract_package.name.clone();
let contract_version = Version::parse(&contract_package.version.to_string())?;
let contract_authors = contract_package.authors.clone();
let description = contract_package.description.clone();
let documentation = crate_metadata.documentation.clone();
let repository = contract_package
.repository
.as_ref()
.map(|repo| Url::parse(repo))
.transpose()?;
let homepage = crate_metadata.homepage.clone();
let license = contract_package.license.clone();
let source = {
let lang = SourceLanguage::new(Language::Ink, ink_version.clone());
let compiler = SourceCompiler::new(Compiler::RustC, rust_version);
let wasm = fs::read(final_contract_wasm)?;
let hash = code_hash(wasm.as_slice());
Source::new(
Some(SourceWasm::new(wasm)),
hash.into(),
lang,
compiler,
Some(build_info.try_into()?),
)
};
let mut builder = Contract::builder();
builder
.name(contract_name)
.version(contract_version)
.authors(contract_authors);
if let Some(description) = description {
builder.description(description);
}
if let Some(documentation) = documentation {
builder.documentation(documentation);
}
if let Some(repository) = repository {
builder.repository(repository);
}
if let Some(homepage) = homepage {
builder.homepage(homepage);
}
if let Some(license) = license {
builder.license(license);
}
let contract = builder.build().map_err(|err| {
anyhow::anyhow!("Invalid contract metadata builder state: {}", err)
})?;
let user = crate_metadata.user.clone().map(User::new);
Ok(ExtendedMetadataResult {
source,
contract,
user,
})
}