-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Signed-off-by: karthik2804 <[email protected]>
- Loading branch information
1 parent
6409a9a
commit ccab564
Showing
9 changed files
with
261 additions
and
205 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,22 +1,25 @@ | ||
use anyhow::{bail, Context, Result}; | ||
use anyhow::Result; | ||
use clap::Subcommand; | ||
use dialoguer::{MultiSelect, Select}; | ||
use http::HttpAddCommand; | ||
use local::LocalAddCommand; | ||
use registry::RegistryAddCommand; | ||
use spin_manifest::{ | ||
manifest_from_file, | ||
schema::v2::{AppManifest, ComponentDependencies, ComponentDependency}, | ||
schema::v2::{AppManifest, ComponentDependency}, | ||
}; | ||
use spin_serde::{DependencyName, DependencyPackageName, KebabId}; | ||
use std::{collections::HashMap, path::PathBuf}; | ||
use tokio::fs; | ||
use toml_edit::DocumentMut; | ||
use wit_component::WitPrinter; | ||
use wit_parser::{PackageId, Resolve}; | ||
|
||
const SPIN_WIT_DIRECTORY: &str = ".wit"; | ||
const SPIN_COMPONENTS_WIT_DIRECTORY: &str = "components"; | ||
use crate::common::{ | ||
constants::{SPIN_COMPONENTS_WIT_DIRECTORY, SPIN_DEPS_WIT_FILE_NAME, SPIN_WIT_DIRECTORY}, | ||
interact::{select_multiple_prompt, select_prompt}, | ||
manifest::{edit_component_deps_in_manifest, get_component_ids, get_spin_manifest_path}, | ||
wit::{ | ||
get_exported_interfaces, merge_dependecy_package, parse_component_bytes, resolve_to_wit, | ||
}, | ||
}; | ||
|
||
mod http; | ||
mod local; | ||
|
@@ -40,60 +43,50 @@ impl AddCommand { | |
AddCommand::Registry(cmd) => cmd.get_component().await?, | ||
}; | ||
|
||
self.validate_component(&component)?; | ||
let (mut resolve, main) = parse_component_bytes(component)?; | ||
|
||
let mut manifest = manifest_from_file(get_spin_manifest_path()?)?; | ||
let component_ids = self.list_component_ids(&manifest); | ||
let selected_component = self.select_component(&component_ids)?; | ||
let component_ids = get_component_ids(&manifest); | ||
let selected_component_index = select_prompt( | ||
"Select a component to add the dependency to", | ||
&component_ids, | ||
None, | ||
)?; | ||
let selected_component = &component_ids[selected_component_index]; | ||
|
||
let decoded_wasm = wit_component::decode(&component)?; | ||
let mut resolve = decoded_wasm.resolve().clone(); | ||
let main = decoded_wasm.package(); | ||
let selected_interfaces = self.select_interfaces(&mut resolve, main)?; | ||
|
||
resolve.importize( | ||
resolve.select_world(main, None)?, | ||
Some("dependency-world".to_string()), | ||
)?; | ||
|
||
self.write_wit_to_file(&resolve, main, &selected_component) | ||
.await?; | ||
self.update_manifest(&mut manifest, &selected_component, selected_interfaces) | ||
.await?; | ||
let component_dir = PathBuf::from(SPIN_WIT_DIRECTORY) | ||
.join(SPIN_COMPONENTS_WIT_DIRECTORY) | ||
.join(selected_component); | ||
|
||
Ok(()) | ||
} | ||
let output_wit = component_dir.join(SPIN_DEPS_WIT_FILE_NAME); | ||
|
||
/// List all component IDs in the manifest. | ||
fn list_component_ids(&self, manifest: &AppManifest) -> Vec<String> { | ||
manifest.components.keys().map(|k| k.to_string()).collect() | ||
} | ||
let base_resolve_file = if std::fs::exists(&output_wit)? { | ||
Some(&output_wit) | ||
} else { | ||
None | ||
}; | ||
|
||
/// Prompts the user to select a component from a list. | ||
fn select_component(&self, component_ids: &[String]) -> Result<String> { | ||
let selected_component_index = Select::new() | ||
.with_prompt("Select a component") | ||
.items(component_ids) | ||
.default(0) | ||
.interact()?; | ||
let (merged_resolve, main) = merge_dependecy_package(base_resolve_file, &resolve, main)?; | ||
let wit_text = resolve_to_wit(&merged_resolve, main)?; | ||
fs::write(output_wit, wit_text).await?; | ||
|
||
Ok(component_ids[selected_component_index].clone()) | ||
} | ||
self.update_manifest(&mut manifest, selected_component, selected_interfaces) | ||
.await?; | ||
|
||
/// Validates the WebAssembly component. | ||
fn validate_component(&self, component: &[u8]) -> Result<()> { | ||
let t = wasmparser::validate(component) | ||
.context("Provided component does not seem to be a valid component"); | ||
match Result::from(t) { | ||
Ok(_) => Ok(()), | ||
Err(e) => bail!(e), | ||
} | ||
Ok(()) | ||
} | ||
|
||
/// Prompts the user to select an interface to import. | ||
fn select_interfaces(&self, resolve: &mut Resolve, main: PackageId) -> Result<Vec<String>> { | ||
let world_id = resolve.select_world(main, None)?; | ||
let exported_interfaces = self.get_exported_interfaces(resolve, world_id); | ||
let exported_interfaces = get_exported_interfaces(resolve, world_id); | ||
|
||
let mut package_interface_map: HashMap<String, Vec<String>> = HashMap::new(); | ||
let mut selected_interfaces: Vec<String> = Vec::new(); | ||
|
@@ -106,15 +99,15 @@ impl AddCommand { | |
.push(interface); | ||
} | ||
|
||
let package_names: Vec<_> = package_interface_map.keys().collect(); | ||
let package_names: Vec<_> = package_interface_map.keys().cloned().collect(); | ||
|
||
let selected_package_indices = MultiSelect::new() | ||
.with_prompt("Select packages to import (use space to select, enter to confirm)") | ||
.items(&package_names) | ||
.interact()?; | ||
let selected_package_indices = select_multiple_prompt( | ||
"Select packages to import (use space to select, enter to confirm)", | ||
&package_names, | ||
)?; | ||
|
||
for &package_idx in selected_package_indices.iter() { | ||
let package_name = package_names[package_idx]; | ||
let package_name = &package_names[package_idx]; | ||
let interfaces = package_interface_map.get(package_name).unwrap(); | ||
let interface_count = interfaces.len(); | ||
|
||
|
@@ -128,14 +121,14 @@ impl AddCommand { | |
}; | ||
|
||
// Prompt user to select an interface | ||
let selected_interface_idx = Select::new() | ||
.with_prompt(format!( | ||
let selected_interface_idx = select_prompt( | ||
&format!( | ||
"Select one or all interfaces to import from package '{}'", | ||
package_name | ||
)) | ||
.default(0) | ||
.items(&interface_options) | ||
.interact()?; | ||
), | ||
&interface_options, | ||
Some(0), | ||
)?; | ||
|
||
if interface_count > 1 && selected_interface_idx == 0 { | ||
selected_interfaces.push(package_name.clone()); | ||
|
@@ -148,67 +141,6 @@ impl AddCommand { | |
Ok(selected_interfaces) | ||
} | ||
|
||
/// Retrieves the exported interfaces from the resolved world. | ||
fn get_exported_interfaces( | ||
&self, | ||
resolve: &Resolve, | ||
world_id: wit_parser::WorldId, | ||
) -> Vec<(String, String)> { | ||
resolve.worlds[world_id] | ||
.exports | ||
.iter() | ||
.filter_map(|(_k, v)| match v { | ||
wit_parser::WorldItem::Interface { id, .. } => { | ||
let i = &resolve.interfaces[*id]; | ||
let pkg_id = i.package.unwrap(); | ||
let pkg = &resolve.packages[pkg_id]; | ||
let mut pkg_name = format!("{}:{}", pkg.name.namespace, pkg.name.name); | ||
if let Some(ver) = &pkg.name.version { | ||
pkg_name.push_str(&format!("@{}", ver)); | ||
} | ||
Some((pkg_name, i.name.clone().unwrap_or_default())) | ||
} | ||
_ => None, | ||
}) | ||
.collect() | ||
} | ||
|
||
/// Writes the WIT content to the specified file. | ||
async fn write_wit_to_file( | ||
&self, | ||
dep_resolve: &Resolve, | ||
dep_pkg_id: PackageId, | ||
selected_component: &str, | ||
) -> Result<()> { | ||
const SPIN_DEPS_WIT_FILE_NAME: &str = "deps.wit"; | ||
|
||
let component_dir = PathBuf::from(SPIN_WIT_DIRECTORY) | ||
.join(SPIN_COMPONENTS_WIT_DIRECTORY) | ||
.join(selected_component); | ||
|
||
let output_wit = component_dir.join(SPIN_DEPS_WIT_FILE_NAME); | ||
let mut resolve = Resolve::default(); | ||
|
||
let deps_package_id = if std::fs::exists(&output_wit)? { | ||
resolve.push_file(&output_wit)? | ||
} else { | ||
fs::create_dir_all(&component_dir).await?; | ||
resolve.push_str("component.wit", DEFAULT_WIT)? | ||
}; | ||
|
||
let deps_world_id = resolve.select_world(deps_package_id, Some("deps"))?; | ||
let dep_main_world_id = dep_resolve.select_world(dep_pkg_id, Some("dependency-world"))?; | ||
let remap = resolve.merge(dep_resolve.clone())?; | ||
let dependecy_world_id = remap.map_world(dep_main_world_id, None)?; | ||
resolve.merge_worlds(dependecy_world_id, deps_world_id)?; | ||
|
||
let wit_content = resolve_to_wit(&resolve, deps_package_id)?; | ||
|
||
fs::write(output_wit, wit_content).await?; | ||
|
||
Ok(()) | ||
} | ||
|
||
/// Updates the manifest file with the new component dependency. | ||
async fn update_manifest( | ||
&self, | ||
|
@@ -253,93 +185,3 @@ impl AddCommand { | |
Ok(()) | ||
} | ||
} | ||
|
||
/// Converts a Resolve object to WIT content. | ||
fn resolve_to_wit(resolve: &Resolve, package_id: PackageId) -> Result<String> { | ||
let mut printer = WitPrinter::default(); | ||
printer.emit_docs(false); | ||
|
||
let ids = resolve | ||
.packages | ||
.iter() | ||
.map(|(id, _)| id) | ||
.filter(|id| *id != package_id) | ||
.collect::<Vec<_>>(); | ||
|
||
printer.print(resolve, package_id, &ids) | ||
} | ||
|
||
// This is a helper function to edit the dependency table in the manifest file | ||
// while preserving the order of the manifest. | ||
async fn edit_component_deps_in_manifest( | ||
component_id: &str, | ||
component_deps: &ComponentDependencies, | ||
) -> Result<String> { | ||
let manifest_path = get_spin_manifest_path()?; | ||
let manifest = fs::read_to_string(manifest_path).await?; | ||
let mut doc = manifest.parse::<DocumentMut>()?; | ||
|
||
let mut dependencies_table = toml_edit::Table::new(); | ||
|
||
for (name, dep) in &component_deps.inner { | ||
let dep_src = match dep { | ||
ComponentDependency::Version(version) => { | ||
let mut ver_table = toml_edit::InlineTable::default(); | ||
ver_table.get_or_insert("version", version); | ||
toml_edit::Value::InlineTable(ver_table) | ||
} | ||
ComponentDependency::Package { | ||
version, | ||
registry, | ||
package, | ||
export: _, | ||
} => { | ||
let mut pkg_table = toml_edit::InlineTable::default(); | ||
pkg_table.get_or_insert("version", version); | ||
if let Some(reg) = registry.clone() { | ||
pkg_table.get_or_insert("registry", reg.to_string()); | ||
} | ||
if let Some(pkg) = package { | ||
pkg_table.get_or_insert("package", pkg); | ||
} | ||
toml_edit::Value::InlineTable(pkg_table) | ||
} | ||
ComponentDependency::Local { path, export: _ } => { | ||
let mut local_table = toml_edit::InlineTable::default(); | ||
local_table.get_or_insert("path", path.to_str().unwrap().to_owned()); | ||
toml_edit::Value::InlineTable(local_table) | ||
} | ||
ComponentDependency::HTTP { | ||
url, | ||
digest, | ||
export: _, | ||
} => { | ||
let mut http_table = toml_edit::InlineTable::default(); | ||
http_table.get_or_insert("url", url); | ||
http_table.get_or_insert("digest", digest); | ||
toml_edit::Value::InlineTable(http_table) | ||
} | ||
}; | ||
|
||
dependencies_table.insert(&name.to_string(), toml_edit::Item::Value(dep_src.clone())); | ||
} | ||
|
||
doc["component"][component_id]["dependencies"] = toml_edit::Item::Table(dependencies_table); | ||
|
||
Ok(doc.to_string()) | ||
} | ||
|
||
// TODO: Eventually bring this function with the proposed Spin functionality of searching in parent Directories. | ||
fn get_spin_manifest_path() -> Result<PathBuf> { | ||
let manifest_path = PathBuf::from("spin.toml"); | ||
if !manifest_path.exists() { | ||
bail!("No spin.toml file found in the current directory"); | ||
} | ||
Ok(manifest_path) | ||
} | ||
|
||
const DEFAULT_WIT: &str = r#"package spin-deps:[email protected]; | ||
world deps { | ||
} | ||
"#; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
use anyhow::Result; | ||
use clap::{Args, ValueEnum}; | ||
|
||
#[derive(Debug, Clone, ValueEnum)] | ||
pub enum BindingsLanguage { | ||
Ts, | ||
Rust, | ||
} | ||
|
||
#[derive(Args, Debug)] | ||
pub struct BindingsCommand { | ||
pub lang: Option<BindingsLanguage>, | ||
pub component_id: Option<String>, | ||
} | ||
|
||
impl BindingsCommand { | ||
pub async fn run(&self) -> Result<()> { | ||
Ok(()) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,2 @@ | ||
pub mod add; | ||
pub mod bindings; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
pub const SPIN_WIT_DIRECTORY: &str = ".wit"; | ||
pub const SPIN_COMPONENTS_WIT_DIRECTORY: &str = "components"; | ||
pub const SPIN_DEPS_WIT_FILE_NAME: &str = "deps.wit"; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
use anyhow::Result; | ||
|
||
use dialoguer::{MultiSelect, Select}; | ||
|
||
pub fn select_prompt( | ||
prompt: &str, | ||
selection_list: &[String], | ||
default: Option<usize>, | ||
) -> Result<usize> { | ||
let mut select = Select::new().with_prompt(prompt).items(selection_list); | ||
if let Some(index) = default { | ||
select = select.default(index); | ||
} | ||
Ok(select.interact()?) | ||
} | ||
|
||
pub fn select_multiple_prompt(prompt: &str, selection_list: &[String]) -> Result<Vec<usize>> { | ||
Ok(MultiSelect::new() | ||
.with_prompt(prompt) | ||
.items(selection_list) | ||
.interact()?) | ||
} |
Oops, something went wrong.