use crate::utils::dependency::{Dependency, DependencyDetails};
use crate::{
cli::JsonAbiCommand,
utils::dependency,
utils::helpers::{
find_file_name, find_main_path, get_main_file, print_on_failure, print_on_success,
read_manifest,
},
};
use sway_types::{Function, JsonABI};
use sway_utils::find_manifest_dir;
use anyhow::Result;
use serde_json::{json, Value};
use std::collections::{HashMap, HashSet};
use std::fs::File;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use sway_core::{
create_module, BuildConfig, CompileAstResult, NamespaceRef, NamespaceWrapper, TreeType,
TypedParseTree,
};
pub fn build(command: JsonAbiCommand) -> Result<Value, String> {
let this_dir = if let Some(ref path) = command.path {
PathBuf::from(path)
} else {
std::env::current_dir().map_err(|e| format!("{:?}", e))?
};
let JsonAbiCommand {
json_outfile,
offline_mode,
silent_mode,
..
} = command;
let manifest_dir = match find_manifest_dir(&this_dir) {
Some(dir) => dir,
None => {
return Err(format!(
"No manifest file found in this directory or any parent directories of it: {:?}",
this_dir
))
}
};
let mut manifest = read_manifest(&manifest_dir)?;
let main_path = find_main_path(&manifest_dir, &manifest);
let file_name = find_file_name(&manifest_dir, &main_path)?;
let build_config = BuildConfig::root_from_file_name_and_manifest_path(
file_name.to_owned(),
manifest_dir.clone(),
);
let mut dependency_graph = HashMap::new();
let mut json_abi = vec![];
let namespace = create_module();
if let Some(ref mut deps) = manifest.dependencies {
for (dependency_name, dependency_details) in deps.iter_mut() {
let dep = match dependency_details {
Dependency::Simple(..) => {
return Err(
"Not yet implemented: Simple version-spec dependencies require a registry."
.into(),
);
}
Dependency::Detailed(dep_details) => dep_details,
};
if let Some(git) = &dep.git {
let fully_qualified_dep_name = format!("{}-{}", dependency_name, git);
let downloaded_dep_path = match dependency::download_github_dep(
&fully_qualified_dep_name,
git,
&dep.branch,
&dep.version,
offline_mode.into(),
) {
Ok(path) => path,
Err(e) => {
return Err(format!(
"Couldn't download dependency ({:?}): {:?}",
dependency_name, e
))
}
};
dep.path = Some(downloaded_dep_path);
}
json_abi.append(&mut compile_dependency_lib(
&this_dir,
dependency_name,
dependency_details,
namespace,
&mut dependency_graph,
silent_mode,
offline_mode,
)?);
}
}
let main_file = get_main_file(&manifest, &manifest_dir)?;
let mut res = compile(
main_file,
&manifest.project.name,
namespace,
build_config,
&mut dependency_graph,
silent_mode,
)?;
json_abi.append(&mut res);
let output_json = json!(json_abi);
if let Some(outfile) = json_outfile {
let file = File::create(outfile).map_err(|e| e.to_string())?;
serde_json::to_writer(&file, &output_json).map_err(|e| e.to_string())?;
} else {
println!("{}", output_json);
}
Ok(output_json)
}
fn compile_dependency_lib<'manifest>(
project_file_path: &Path,
dependency_name: &'manifest str,
dependency_lib: &mut Dependency,
namespace: NamespaceRef,
dependency_graph: &mut HashMap<String, HashSet<String>>,
silent_mode: bool,
offline_mode: bool,
) -> Result<Vec<Function>, String> {
let mut details = match dependency_lib {
Dependency::Simple(..) => {
return Err(
"Not yet implemented: Simple version-spec dependencies require a registry.".into(),
)
}
Dependency::Detailed(ref mut details) => details,
};
if let Some(ref git) = details.git {
let fully_qualified_dep_name = format!("{}-{}", dependency_name, git);
let downloaded_dep_path = match dependency::download_github_dep(
&fully_qualified_dep_name,
git,
&details.branch,
&details.version,
offline_mode.into(),
) {
Ok(path) => path,
Err(e) => {
return Err(format!(
"Couldn't download dependency ({:?}): {:?}",
dependency_name, e
))
}
};
details.path = Some(downloaded_dep_path);
}
let dep_path = match dependency_lib {
Dependency::Simple(..) => {
return Err(
"Not yet implemented: Simple version-spec dependencies require a registry.".into(),
)
}
Dependency::Detailed(DependencyDetails { path, .. }) => path,
};
let dep_path =
match dep_path {
Some(p) => p,
None => return Err(
"Only simple path imports are supported right now. Please supply a path relative \
to the manifest file."
.into(),
),
};
let mut project_path = PathBuf::from(project_file_path);
project_path.push(dep_path);
let manifest_dir = match find_manifest_dir(&project_path) {
Some(o) => o,
None => {
return Err(format!(
"Manifest not found for dependency {:?}.",
project_path
))
}
};
let mut manifest_of_dep = read_manifest(&manifest_dir)?;
let main_path = find_main_path(&manifest_dir, &manifest_of_dep);
let file_name = find_file_name(&manifest_dir, &main_path)?;
let build_config = BuildConfig::root_from_file_name_and_manifest_path(
file_name.to_owned(),
manifest_dir.clone(),
);
let dep_namespace = create_module();
if let Some(ref mut deps) = manifest_of_dep.dependencies {
for ref mut dep in deps {
compile_dependency_lib(
&manifest_dir,
dep.0,
dep.1,
dep_namespace,
dependency_graph,
silent_mode,
offline_mode,
)?;
}
}
let main_file = get_main_file(&manifest_of_dep, &manifest_dir)?;
let (compiled, json_abi) = compile_library(
main_file,
&manifest_of_dep.project.name,
dep_namespace,
build_config,
dependency_graph,
silent_mode,
)?;
namespace.insert_module_ref(dependency_name.to_string(), compiled);
Ok(json_abi)
}
fn compile_library(
source: Arc<str>,
proj_name: &str,
namespace: NamespaceRef,
build_config: BuildConfig,
dependency_graph: &mut HashMap<String, HashSet<String>>,
silent_mode: bool,
) -> Result<(NamespaceRef, Vec<Function>), String> {
let res = sway_core::compile_to_ast(source, namespace, &build_config, dependency_graph);
match res {
CompileAstResult::Success {
parse_tree,
tree_type,
warnings,
} => {
let errors = vec![];
match tree_type {
TreeType::Library { name } => {
print_on_success(silent_mode, proj_name, warnings, TreeType::Library { name });
let json_abi = generate_json_abi(&Some(*parse_tree.clone()));
Ok((parse_tree.get_namespace_ref(), json_abi))
}
_ => {
print_on_failure(silent_mode, warnings, errors);
Err(format!("Failed to compile {}", proj_name))
}
}
}
CompileAstResult::Failure { warnings, errors } => {
print_on_failure(silent_mode, warnings, errors);
Err(format!("Failed to compile {}", proj_name))
}
}
}
fn compile(
source: Arc<str>,
proj_name: &str,
namespace: NamespaceRef,
build_config: BuildConfig,
dependency_graph: &mut HashMap<String, HashSet<String>>,
silent_mode: bool,
) -> Result<Vec<Function>, String> {
let res = sway_core::compile_to_ast(source, namespace, &build_config, dependency_graph);
match res {
CompileAstResult::Success {
parse_tree,
tree_type,
warnings,
} => {
let errors = vec![];
match tree_type {
TreeType::Library { .. } => {
print_on_failure(silent_mode, warnings, errors);
Err(format!("Failed to compile {}", proj_name))
}
typ => {
print_on_success(silent_mode, proj_name, warnings, typ);
let json_abi = generate_json_abi(&Some(*parse_tree));
Ok(json_abi)
}
}
}
CompileAstResult::Failure { warnings, errors } => {
print_on_failure(silent_mode, warnings, errors);
Err(format!("Failed to compile {}", proj_name))
}
}
}
fn generate_json_abi(ast: &Option<TypedParseTree>) -> JsonABI {
match ast {
Some(TypedParseTree::Contract { abi_entries, .. }) => {
abi_entries.iter().map(|x| x.generate_json_abi()).collect()
}
_ => vec![],
}
}