Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
name = "kit"
authors = ["Sybil Technologies AG"]
version = "3.2.0"
version = "3.2.1"
edition = "2021"
description = "Development toolkit for Hyperware"
homepage = "https://hyperware.ai"
Expand Down
26 changes: 26 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,3 +96,29 @@ NodeJS (v18 or higher) and NPM are required to build and develop the UI.
The UI is written in React with Vite as the bundler + reloader.

To use `npm start` instead of `npm run dev`, use `kit dev-ui --release`.

## Appendix: Deps for Debian/Ubuntu

```bash
apt update
DEBIAN_FRONTEND=noninteractive apt install -y curl git build-essential pkg-config libssl-dev libclang-dev python3 python3-venv

curl https://sh.rustup.rs -sSf | sh -s -- -y
. ~/.bashrc
cargo install --git https://github.com/hyperware-ai/kit --locked
kit setup -d --non-interactive
. ~/.bashrc
```

## Appendix: Deps for macOS

```bash
xcode-select --install
brew install pkg-config llvm openssl@3 python@3.12 libusb

curl https://sh.rustup.rs -sSf | sh -s -- -y
. ~/.bashrc
cargo install --git https://github.com/hyperware-ai/kit --locked
kit setup -d --non-interactive
. ~/.bashrc
```
109 changes: 78 additions & 31 deletions src/build/caller_utils_generator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -254,42 +254,80 @@ struct SignatureStruct {
args_comment: Option<String>, // Parsed from // args: (name: type, ...) comment
}

// Find all interface imports in the world WIT file
// Find all interface imports in the selected world WIT file(s)
#[instrument(level = "trace", skip_all)]
fn find_interfaces_in_world(api_dir: &Path) -> Result<Vec<String>> {
debug!(dir = ?api_dir, "Finding interface imports in world definitions");
let mut interfaces = Vec::new();
fn find_interfaces_in_world(api_dir: &Path, world_name: &str) -> Result<Vec<String>> {
debug!(dir = ?api_dir, world = %world_name, "Finding interface imports in world definitions");
let mut world_defs: HashMap<String, String> = HashMap::new();

// Find world definition files
// Index world definition files by world name
for entry in WalkDir::new(api_dir)
.max_depth(1)
.into_iter()
.filter_map(Result::ok)
{
let path = entry.path();
if !(path.is_file() && path.extension().map_or(false, |ext| ext == "wit")) {
continue;
}
let Ok(content) = fs::read_to_string(path) else {
continue;
};
if !content.contains("world ") {
continue;
}
let world_name = content
.lines()
.find(|line| line.trim().starts_with("world "))
.and_then(|world_line| world_line.trim().split_whitespace().nth(1))
.map(|name| {
name.trim_end_matches(" {")
.trim_start_matches('%')
.to_string()
});
if let Some(clean_name) = world_name {
world_defs.insert(clean_name.clone(), content);
debug!(file = %path.display(), world = %clean_name, "Indexed world definition");
}
}

if path.is_file() && path.extension().map_or(false, |ext| ext == "wit") {
if let Ok(content) = fs::read_to_string(path) {
if content.contains("world ") {
debug!(file = %path.display(), "Analyzing world definition file for imports");

// Extract import statements
for line in content.lines() {
let line = line.trim();
if line.starts_with("import ") && line.ends_with(";") {
let interface = line
.trim_start_matches("import ")
.trim_end_matches(";")
.trim();

interfaces.push(interface.to_string());
debug!(interface = %interface, "Found interface import");
}
}
}
let mut interfaces = Vec::new();
let mut visited = std::collections::HashSet::new();
let mut stack = vec![world_name.to_string()];

while let Some(current_world) = stack.pop() {
let clean_world = current_world.trim_start_matches('%').to_string();
if !visited.insert(clean_world.clone()) {
continue;
}
let Some(content) = world_defs.get(&clean_world) else {
debug!(world = %clean_world, "World definition not found for imports");
continue;
};

debug!(world = %clean_world, "Analyzing world definition file for imports");
for line in content.lines() {
let line = line.trim();
if line.starts_with("import ") && line.ends_with(';') {
let interface = line
.trim_start_matches("import ")
.trim_end_matches(';')
.trim()
.trim_start_matches('%');
interfaces.push(interface.to_string());
debug!(interface = %interface, "Found interface import");
} else if line.starts_with("include ") && line.ends_with(';') {
let include_world = line
.trim_start_matches("include ")
.trim_end_matches(';')
.trim()
.trim_start_matches('%')
.to_string();
stack.push(include_world);
}
}
}

debug!(count = interfaces.len(), interfaces = ?interfaces, "Found interface imports");
Ok(interfaces)
}
Expand Down Expand Up @@ -450,8 +488,8 @@ fn generate_async_function(signature: &SignatureStruct) -> Option<String> {
if field.wit_type == "string" {
target_param = "&str";
} else {
// Use hyperware_process_lib::Address instead of WitAddress
target_param = "&Address";
// Use a distinct alias for hyperware_process_lib::Address to avoid WIT name clashes
target_param = "&ProcessAddress";
}
} else if field.name == "returning" {
return_type = rust_type;
Expand Down Expand Up @@ -620,8 +658,8 @@ crate-type = ["cdylib", "lib"]
"types"
};

// Get all interfaces from the world file
let interface_imports = find_interfaces_in_world(api_dir)?;
// Get all interfaces from the selected world
let interface_imports = find_interfaces_in_world(api_dir, world_name)?;

// Store all types from each interface
let mut interface_types: HashMap<String, Vec<String>> = HashMap::new();
Expand All @@ -638,8 +676,17 @@ crate-type = ["cdylib", "lib"]
// Exclude world definition files
if let Ok(content) = fs::read_to_string(path) {
if !content.contains("world ") {
debug!(file = %path.display(), "Adding WIT file for parsing");
wit_files.push(path.to_path_buf());
let interface_name = path.file_stem().unwrap().to_string_lossy();
let interface_name = interface_name.trim_start_matches('%');
if interface_imports
.iter()
.any(|i| i.trim_start_matches('%') == interface_name)
{
debug!(file = %path.display(), "Adding WIT file for parsing");
wit_files.push(path.to_path_buf());
} else {
debug!(file = %path.display(), "Skipping WIT file not in selected world");
}
} else {
debug!(file = %path.display(), "Skipping world definition WIT file");
}
Expand Down Expand Up @@ -735,7 +782,7 @@ crate-type = ["cdylib", "lib"]
// Add global imports
lib_rs.push_str("pub use hyperware_process_lib::hyperapp::AppSendError;\n");
lib_rs.push_str("pub use hyperware_process_lib::hyperapp::send;\n");
lib_rs.push_str("pub use hyperware_process_lib::{Address, Request};\n");
lib_rs.push_str("pub use hyperware_process_lib::{Address as ProcessAddress, Request};\n");
lib_rs.push_str("use serde_json::json;\n\n");

// Add interface use statements
Expand Down
7 changes: 6 additions & 1 deletion src/setup/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ const MINIMUM_NPM_MINOR: u32 = 0;
pub const REQUIRED_PY_MAJOR: u32 = 3;
pub const MINIMUM_PY_MINOR: u32 = 10;
pub const REQUIRED_PY_PACKAGE: &str = "componentize-py==0.11.0";
const WASM_TOOLS_VERSION: &str = "1.225.0";

#[derive(Clone)]
pub enum Dependency {
Expand Down Expand Up @@ -476,7 +477,11 @@ fn install_deps(deps: Vec<Dependency>, verbose: bool, toolchain: &str) -> Result
Dependency::RustWasm32Wasi => {
call_rustup("target add wasm32-wasip1", verbose, toolchain)?
}
Dependency::WasmTools => call_cargo("install wasm-tools", verbose, toolchain)?,
Dependency::WasmTools => call_cargo(
&format!("install wasm-tools --locked --version {WASM_TOOLS_VERSION}"),
verbose,
toolchain,
)?,
Dependency::Foundry => install_foundry(verbose)?,
Dependency::Docker => {}
}
Expand Down