From a1e0e04d59b1df978e890876bb39fa42e3c401f7 Mon Sep 17 00:00:00 2001 From: "Jiaxiao Zhou (Mossaka)" Date: Sun, 3 Dec 2023 17:41:49 -0800 Subject: [PATCH] feat(go-namespaces): Initial draft with working smoke runtime test This commit overhauls the `WorldGenerator` file structure in the tinygo bindgen. Key changes include: - Splitting generated Go files into different packages for each interface, imported functions, and exported functions. - Simplifying function and symbol names by removing namespaces. - Adding a `package_name` option to Go bindgen for prefixed package names. Defaults to `main` if not specified. Internally, the C files are placed at a special Go package named "c_files_", and a dummy Go file is generated to expose the C APIs. Each interface generates a Go package that imports this special "c_files_" package to get the C apis. It still has to import the C header file which is also placed at "c_files_" by setting CFLAGS to locate where that is for that Go package. This restructuring enhances usability by making symbol and function names more readable and less verbose, significantly improving the user experience. Signed-off-by: Jiaxiao Zhou (Mossaka) --- crates/c/src/lib.rs | 15 ++- crates/go/src/interface.rs | 76 ++++++++--- crates/go/src/lib.rs | 247 ++++++++++++++++++++---------------- crates/go/src/path.rs | 24 ++-- crates/go/src/world.rs | 191 ++++++++++++++++++++++++++++ tests/runtime/main.rs | 10 +- tests/runtime/smoke/wasm.go | 7 +- 7 files changed, 424 insertions(+), 146 deletions(-) create mode 100644 crates/go/src/world.rs diff --git a/crates/c/src/lib.rs b/crates/c/src/lib.rs index 7e146e7d9..3d3283b60 100644 --- a/crates/c/src/lib.rs +++ b/crates/c/src/lib.rs @@ -5,6 +5,7 @@ use heck::*; use std::collections::{HashMap, HashSet}; use std::fmt::Write; use std::mem; +use std::path::PathBuf; use wit_bindgen_core::abi::{self, AbiVariant, Bindgen, Bitcast, Instruction, LiftLower, WasmType}; use wit_bindgen_core::{ dealias, uwrite, uwriteln, wit_parser::*, Direction, Files, InterfaceGenerator as _, Ns, @@ -52,6 +53,9 @@ pub struct Opts { // Skip generating C object file #[cfg_attr(feature = "clap", arg(long, default_value_t = false))] pub no_object_file: bool, + /// output directory + #[cfg_attr(feature = "clap", arg(long, hide = true))] + pub c_out_dir: Option, } impl Opts { @@ -375,11 +379,16 @@ impl WorldGenerator for C { #endif" ); - files.push(&format!("{snake}.h"), h_str.as_bytes()); - files.push(&format!("{snake}.c"), c_str.as_bytes()); + let dst = match &self.opts.c_out_dir { + Some(path) => path, + None => "", + }; + + files.push(&format!("{dst}{snake}.h"), h_str.as_bytes()); + files.push(&format!("{dst}{snake}.c"), c_str.as_bytes()); if !self.opts.no_object_file { files.push( - &format!("{snake}_component_type.o",), + &format!("{dst}{snake}_component_type.o",), component_type_object::object(resolve, id, self.opts.string_encoding) .unwrap() .as_slice(), diff --git a/crates/go/src/interface.rs b/crates/go/src/interface.rs index 7312dc80d..39e12237f 100644 --- a/crates/go/src/interface.rs +++ b/crates/go/src/interface.rs @@ -9,7 +9,9 @@ use wit_bindgen_core::wit_parser::{ Field, Function, FunctionKind, Handle, InterfaceId, LiveTypes, Resolve, Type, TypeDefKind, TypeId, TypeOwner, WorldKey, }; -use wit_bindgen_core::{uwriteln, Direction, InterfaceGenerator as _, Source}; +use wit_bindgen_core::{uwriteln, Direction, InterfaceGenerator as _, Source, WorldGenerator}; + +use crate::path::GoPath; use super::{avoid_keyword, bindgen, TinyGo}; @@ -61,7 +63,7 @@ impl InterfaceGenerator<'_> { assert!(prev.is_none()); // add Go types to the list - let mut name = self.owner_namespace(ty); + let mut name = "".to_string(); name.push_str(&self.ty_name(&Type::Id(ty))); let prev = self.gen.type_names.insert(ty, name.clone()); @@ -101,7 +103,7 @@ impl InterfaceGenerator<'_> { c_owner_namespace( self.interface, matches!(self.direction, Direction::Import), - self.gen.world.clone(), + self.gen.world.name().to_string(), self.resolve, id, ) @@ -127,18 +129,18 @@ impl InterfaceGenerator<'_> { WorldKey::Name(k) => k.to_upper_camel_case(), WorldKey::Interface(id) => { let mut name = String::new(); - if matches!(self.direction, Direction::Export) { - name.push_str("Exports"); - } + // if matches!(self.direction, Direction::Export) { + // name.push_str("Exports"); + // } let iface = &self.resolve.interfaces[*id]; - let pkg = &self.resolve.packages[iface.package.unwrap()]; - name.push_str(&pkg.name.namespace.to_upper_camel_case()); - name.push_str(&pkg.name.name.to_upper_camel_case()); - if let Some(version) = &pkg.name.version { - let version = version.to_string().replace(['.', '-', '+'], "_"); - name.push_str(&version); - name.push('_'); - } + // let pkg = &self.resolve.packages[iface.package.unwrap()]; + // name.push_str(&pkg.name.namespace.to_upper_camel_case()); + // name.push_str(&pkg.name.name.to_upper_camel_case()); + // if let Some(version) = &pkg.name.version { + // let version = version.to_string().replace(['.', '-', '+'], "_"); + // name.push_str(&version); + // name.push('_'); + // } name.push_str(&iface.name.as_ref().unwrap().to_upper_camel_case()); name } @@ -171,13 +173,13 @@ impl InterfaceGenerator<'_> { /// Otherwise, the type name is not converted. pub(crate) fn type_name(&self, ty_name: &str, convert: bool) -> String { let mut name = String::new(); - let namespace = self.namespace(); + // let namespace = self.namespace(); let ty_name = if convert { ty_name.to_upper_camel_case() } else { ty_name.into() }; - name.push_str(&namespace); + // name.push_str(&namespace); name.push_str(&ty_name); name } @@ -530,13 +532,13 @@ impl InterfaceGenerator<'_> { c_func_name( matches!(direction, Direction::Import), self.resolve, - &self.gen.world, + self.gen.world.name(), self.interface.map(|(_, key)| key), func, ) } else { // do not want to generate public functions - format!("{}{}", self.namespace(), self.func_name(func)).to_lower_camel_case() + self.func_name(func).to_lower_camel_case() }; if matches!(direction, Direction::Export) { @@ -577,8 +579,8 @@ impl InterfaceGenerator<'_> { match func.kind { FunctionKind::Freestanding => { - let namespace = self.namespace(); - self.src.push_str(&namespace); + // let namespace = self.namespace(); + // self.src.push_str(&namespace); } FunctionKind::Method(ty) => { let ty = self.get_ty(&Type::Id(ty)); @@ -820,7 +822,7 @@ impl InterfaceGenerator<'_> { let name = c_func_name( matches!(self.direction, Direction::Import), self.resolve, - &self.gen.world, + self.gen.world.name(), self.interface.map(|(_, key)| key), func, ); @@ -990,6 +992,37 @@ impl InterfaceGenerator<'_> { } self.src.push_str("}\n"); } + + pub(crate) fn start_append_submodule(&mut self, name: &WorldKey) -> (String, Vec) { + let snake = match name { + WorldKey::Name(name) => avoid_keyword(name), + WorldKey::Interface(id) => { + avoid_keyword(self.resolve.interfaces[*id].name.as_ref().unwrap()) + } + }; + let module_path: Vec = name.to_path(&self.resolve, self.direction); + (snake, module_path) + } + + pub(crate) fn finish_append_submodule(mut self, snake: &str, module_path: Vec) { + self.finish(); + let _ = match self.direction { + Direction::Import => self.gen.go_import_packages.push( + snake, + self.src, + self.preamble, + &self.gen.world, + module_path, + ), + Direction::Export => self.gen.go_export_packages.push( + snake, + self.src, + self.preamble, + &self.gen.world, + module_path, + ), + }; + } } impl<'a> wit_bindgen_core::InterfaceGenerator<'a> for InterfaceGenerator<'a> { @@ -1227,6 +1260,7 @@ impl<'a> wit_bindgen_core::InterfaceGenerator<'a> for InterfaceGenerator<'a> { ) { let name = self.type_name(name, true); let ty = self.get_ty(ty); + // TODO: determine where `ty` is from and add import path to preamble self.src.push_str(&format!("type {name} = {ty}\n")); } diff --git a/crates/go/src/lib.rs b/crates/go/src/lib.rs index 023e04d0b..fd99d1a95 100644 --- a/crates/go/src/lib.rs +++ b/crates/go/src/lib.rs @@ -1,19 +1,25 @@ use std::collections::{HashMap, HashSet}; -use std::io::{Read, Write}; +use std::fmt::Write; +use std::io::{Read, Write as _}; use std::mem; use std::process::Stdio; use anyhow::Result; -use heck::{ToKebabCase, ToSnakeCase}; +use heck::ToKebabCase; use wit_bindgen_c::imported_types_used_by_exported_interfaces; use wit_bindgen_core::wit_parser::{ - Function, InterfaceId, LiveTypes, Resolve, SizeAlign, Type, TypeId, WorldId, WorldKey, + Function, InterfaceId, LiveTypes, Resolve, Type, TypeId, WorldId, WorldKey, }; -use wit_bindgen_core::{Direction, Files, Source, WorldGenerator}; +use wit_bindgen_core::{generated_preamble, uwriteln, Direction, Files, Source, WorldGenerator}; +use world::{Packages, TinyGoWorld}; mod bindgen; mod imports; mod interface; +mod path; +mod world; + +static C_GEN_FILES_PATH: &'static str = "c_files_"; #[derive(Debug, Clone)] #[cfg_attr(feature = "clap", derive(clap::Args))] @@ -21,11 +27,18 @@ pub struct Opts { /// Whether or not `gofmt` is executed to format generated code. #[cfg_attr(feature = "clap", arg(long))] pub gofmt: bool, + + /// The optional package name to use for the generated code. + #[cfg_attr(feature = "clap", arg(long))] + pub package_name: Option, } impl Default for Opts { fn default() -> Self { - Self { gofmt: true } // Set the default value of gofmt to true + Self { + gofmt: true, // Set the default value of gofmt to true + package_name: None, // Set the default value of package_name to None + } } } @@ -40,22 +53,21 @@ impl Opts { #[derive(Default)] pub struct TinyGo { + // the options for the generator provided by the user opts: Opts, + + // the generated code src: Source, // the parts immediately precede the import of "C" preamble: Source, - world: String, + // the name of the world being generated + world: TinyGoWorld, // import requirements for the generated code import_requirements: imports::ImportRequirements, - sizes: SizeAlign, - - // mapping from interface ID to the name of the interface - interface_names: HashMap, - // C type names c_type_names: HashMap, @@ -69,16 +81,17 @@ pub struct TinyGo { // resource interface and the resource destructors exported_resources: HashSet, - // the world ID - world_id: Option, + /// tracking all the pending Go packages to be generated + go_import_packages: Packages, + go_export_packages: Packages, } impl TinyGo { - fn interface<'a>( - &'a mut self, - resolve: &'a Resolve, + fn interface<'b>( + &'b mut self, + resolve: &'b Resolve, direction: Direction, - ) -> interface::InterfaceGenerator<'a> { + ) -> interface::InterfaceGenerator { interface::InterfaceGenerator { src: Source::default(), preamble: Source::default(), @@ -145,10 +158,10 @@ impl TinyGo { impl WorldGenerator for TinyGo { fn preprocess(&mut self, resolve: &Resolve, world: WorldId) { - let name = &resolve.worlds[world].name; - self.world = name.to_string(); - self.sizes.fill(resolve); - self.world_id = Some(world); + self.world = TinyGoWorld::from_world_id(world, resolve); + + self.go_import_packages.prefix_name = self.opts.package_name.clone(); + self.go_export_packages.prefix_name = self.opts.package_name.clone(); } fn import_interface( @@ -161,20 +174,18 @@ impl WorldGenerator for TinyGo { let name_raw = &resolve.name_world_key(name); self.src .push_str(&format!("// Import functions from {name_raw}\n")); - self.interface_names.insert(id, name.clone()); let mut gen = self.interface(resolve, Direction::Import); gen.interface = Some((id, name)); + let (snake, module_path) = gen.start_append_submodule(name); + gen.define_interface_types(id); for (_name, func) in resolve.interfaces[id].functions.iter() { gen.import(resolve, func); } - let src = mem::take(&mut gen.src); - let preamble = mem::take(&mut gen.preamble); - self.src.push_str(&src); - self.preamble.append_src(&preamble); + gen.finish_append_submodule(&snake, module_path); } fn import_funcs( @@ -189,19 +200,18 @@ impl WorldGenerator for TinyGo { .push_str(&format!("// Import functions from {name}\n")); let mut gen = self.interface(resolve, Direction::Import); + gen.define_function_types(funcs); for (_name, func) in funcs.iter() { gen.import(resolve, func); } - let src = mem::take(&mut gen.src); - let preamble = mem::take(&mut gen.preamble); - self.src.push_str(&src); - self.preamble.append_src(&preamble); + + gen.finish_append_submodule(name, vec!["imports".to_string(), name.to_owned()]); } fn pre_export_interface(&mut self, resolve: &Resolve, _files: &mut Files) -> Result<()> { - let world = self.world_id.unwrap(); + let world = self.world.unwrap_id(); let live_import_types = imported_types_used_by_exported_interfaces(resolve, world); self.c_type_namespaces .retain(|k, _| live_import_types.contains(k)); @@ -218,12 +228,12 @@ impl WorldGenerator for TinyGo { id: InterfaceId, _files: &mut Files, ) -> Result<()> { - self.interface_names.insert(id, name.clone()); let name_raw = &resolve.name_world_key(name); self.src .push_str(&format!("// Export functions from {name_raw}\n")); let mut gen = self.interface(resolve, Direction::Export); + let (snake, module_path) = gen.start_append_submodule(name); gen.interface = Some((id, name)); gen.define_interface_types(id); @@ -231,12 +241,7 @@ impl WorldGenerator for TinyGo { gen.export(resolve, func); } - gen.finish(); - - let src = mem::take(&mut gen.src); - let preamble = mem::take(&mut gen.preamble); - self.src.push_str(&src); - self.preamble.append_src(&preamble); + gen.finish_append_submodule(&snake, module_path); Ok(()) } @@ -257,100 +262,130 @@ impl WorldGenerator for TinyGo { for (_name, func) in funcs.iter() { gen.export(resolve, func); } - - gen.finish(); - - let src = mem::take(&mut gen.src); - let preamble = mem::take(&mut gen.preamble); - self.src.push_str(&src); - self.preamble.append_src(&preamble); + gen.finish_append_submodule(name, vec!["exports".to_string(), name.to_owned()]); Ok(()) } fn import_types( &mut self, resolve: &Resolve, - _world: WorldId, + world: WorldId, types: &[(&str, TypeId)], _files: &mut Files, ) { + let name = &resolve.worlds[world].name; let mut gen = self.interface(resolve, Direction::Import); let mut live = LiveTypes::default(); for (_, id) in types { live.add_type_id(resolve, *id); } gen.define_live_types(&live); - let src = mem::take(&mut gen.src); - self.src.push_str(&src); + gen.finish_append_submodule(name, vec!["imports".to_string(), name.to_owned()]); } fn finish(&mut self, resolve: &Resolve, id: WorldId, files: &mut Files) { - // make sure all types are defined on top of the file - let src = mem::take(&mut self.src); - self.src.push_str(&src); - - // prepend package and imports header - let src = mem::take(&mut self.src); - wit_bindgen_core::generated_preamble(&mut self.src, env!("CARGO_PKG_VERSION")); - let snake = self.world.to_snake_case(); - // add package - self.src.push_str("package "); - self.src.push_str(&snake); - self.src.push_str("\n\n"); - - // import C - self.src.push_str("// #include \""); - self.src.push_str(self.world.to_snake_case().as_str()); - self.src.push_str(".h\"\n"); - if self.preamble.len() > 0 { - self.src.append_src(&self.preamble); - } - self.src.push_str("import \"C\"\n"); - let world = &resolve.worlds[id]; + self.go_import_packages.finish(resolve, id, files); + self.go_export_packages.finish(resolve, id, files); - self.import_requirements.generate( - snake, - files, - format!("{}_types.go", world.name.to_kebab_case()), - ); - self.src.push_str(&self.import_requirements.src); - - self.src.push_str(&src); - - if self.opts.gofmt { - let mut child = std::process::Command::new("gofmt") - .stdin(Stdio::piped()) - .stdout(Stdio::piped()) - .spawn() - .expect("failed to spawn gofmt"); - child - .stdin - .take() - .unwrap() - .write_all(self.src.as_bytes()) - .expect("failed to write to gofmt"); - self.src.as_mut_string().truncate(0); - child - .stdout - .take() - .unwrap() - .read_to_string(self.src.as_mut_string()) - .expect("failed to read from gofmt"); - let status = child.wait().expect("failed to wait on gofmt"); - assert!(status.success()); - } - files.push( - &format!("{}.go", world.name.to_kebab_case()), - self.src.as_bytes(), - ); + // TODO: + // 1. import_requirements + // 2. opts (gofmt) let mut opts = wit_bindgen_c::Opts::default(); opts.no_sig_flattening = true; opts.no_object_file = true; + opts.c_out_dir = Some(format!("{C_GEN_FILES_PATH}/")); opts.build() .generate(resolve, id, files) - .expect("C generator should be infallible") + .expect("C generator should be infallible"); + + let mut c_lib: Source = Source::default(); + let version = env!("CARGO_PKG_VERSION"); + generated_preamble(&mut c_lib, version); + c_lib.push_str(&format!("package {C_GEN_FILES_PATH}\n\n")); + uwriteln!( + c_lib, + " + // Go will only compile the C code if there is a C file in the same directory + // as the Go file. This file is a dummy Go package that it's only purpose is + // to compile the C code and expose it to the Go code. In order to use this Go + // package, you need to import it in your Go code + // and then import the C header file using CGo. + // + // This package is only used internally by wit-bindgen, and it's not meant to + // be used by the user. The user should not import this package. + " + ); + c_lib.push_str("import \"C\"\n\n"); + files.push(&format!("{C_GEN_FILES_PATH}/lib.go"), c_lib.as_bytes()); } + // fn finish_(&mut self, resolve: &Resolve, id: WorldId, files: &mut Files) { + // // make sure all types are defined on top of the file + // let src = mem::take(&mut self.src); + // self.src.push_str(&src); + + // // prepend package and imports header + // let src = mem::take(&mut self.src); + // wit_bindgen_core::generated_preamble(&mut self.src, env!("CARGO_PKG_VERSION")); + // let snake = self.world.to_snake_case(); + // // add package + // self.src.push_str("package "); + // self.src.push_str(&snake); + // self.src.push_str("\n\n"); + + // // import C + // self.src.push_str("// #include \""); + // self.src.push_str(self.world.to_snake_case().as_str()); + // self.src.push_str(".h\"\n"); + // if self.preamble.len() > 0 { + // self.src.append_src(&self.preamble); + // } + // self.src.push_str("import \"C\"\n"); + // let world = &resolve.worlds[id]; + + // self.import_requirements.generate( + // snake, + // files, + // format!("{}_types.go", world.name.to_kebab_case()), + // ); + // self.src.push_str(&self.import_requirements.src); + + // self.src.push_str(&src); + + // if self.opts.gofmt { + // let mut child = std::process::Command::new("gofmt") + // .stdin(Stdio::piped()) + // .stdout(Stdio::piped()) + // .spawn() + // .expect("failed to spawn gofmt"); + // child + // .stdin + // .take() + // .unwrap() + // .write_all(self.src.as_bytes()) + // .expect("failed to write to gofmt"); + // self.src.as_mut_string().truncate(0); + // child + // .stdout + // .take() + // .unwrap() + // .read_to_string(self.src.as_mut_string()) + // .expect("failed to read from gofmt"); + // let status = child.wait().expect("failed to wait on gofmt"); + // assert!(status.success()); + // } + // files.push( + // &format!("{}.go", world.name.to_kebab_case()), + // self.src.as_bytes(), + // ); + + // let mut opts = wit_bindgen_c::Opts::default(); + // opts.no_sig_flattening = true; + // opts.no_object_file = true; + // opts.build() + // .generate(resolve, id, files) + // .expect("C generator should be infallible") + // } } fn avoid_keyword(s: &str) -> String { diff --git a/crates/go/src/path.rs b/crates/go/src/path.rs index 202ac5fcd..724e39cc1 100644 --- a/crates/go/src/path.rs +++ b/crates/go/src/path.rs @@ -1,5 +1,8 @@ use heck::ToSnakeCase; -use wit_bindgen_core::{wit_parser::{WorldKey, Resolve, PackageId}, Direction}; +use wit_bindgen_core::{ + wit_parser::{PackageId, Resolve, WorldKey}, + Direction, +}; pub(crate) trait GoPath { fn to_path(&self, resolve: &Resolve, direction: Direction) -> Vec; @@ -8,18 +11,19 @@ pub(crate) trait GoPath { impl GoPath for WorldKey { fn to_path(&self, resolve: &Resolve, direction: Direction) -> Vec { let mut path = Vec::new(); - if matches!(direction, Direction::Export) { - path.push("exports".to_string()); + match direction { + Direction::Import => path.push("imports".to_string()), + Direction::Export => path.push("exports".to_string()), } - match self { + match self { WorldKey::Name(n) => path.push(n.to_snake_case()), WorldKey::Interface(id) => { - let iface = &resolve.interfaces[*id]; - let pkg = iface.package.unwrap(); - let pkgname = resolve.packages[pkg].name.clone(); - path.push(pkgname.namespace.to_snake_case()); - path.push(name_package_module(resolve, pkg)); - path.push(iface.name.as_ref().unwrap().to_snake_case()); + let iface = &resolve.interfaces[*id]; + let pkg = iface.package.unwrap(); + let pkgname = resolve.packages[pkg].name.clone(); + path.push(pkgname.namespace.to_snake_case()); + path.push(name_package_module(resolve, pkg)); + path.push(iface.name.as_ref().unwrap().to_snake_case()); } } path diff --git a/crates/go/src/world.rs b/crates/go/src/world.rs new file mode 100644 index 000000000..6bacd577b --- /dev/null +++ b/crates/go/src/world.rs @@ -0,0 +1,191 @@ +use std::{collections::HashMap, mem}; + +use crate::{ + interface::{self, InterfaceGenerator}, + C_GEN_FILES_PATH, +}; +use heck::{ToSnakeCase, ToUpperCamelCase}; +use wit_bindgen_core::{ + wit_parser::{Resolve, WorldId}, + Direction, Files, Source, +}; + +/// Bookkeeping for the name of the world being generated and its ID. +#[derive(Default, Debug, Clone)] +pub struct TinyGoWorld { + name: String, + id: Option, +} + +impl TinyGoWorld { + pub fn from_world_id(id: WorldId, resolve: &Resolve) -> Self { + Self { + name: resolve.worlds[id].name.to_string(), + id: Some(id), + } + } + pub fn to_snake_case(&self) -> String { + self.name.to_snake_case() + } + pub fn to_upper_camel_case(&self) -> String { + self.name.to_upper_camel_case() + } + pub fn unwrap_id(&self) -> WorldId { + self.id.unwrap() + } + pub fn name(&self) -> &str { + &self.name + } +} + +/// Bookkeeping for the name of the package being generated +/// and each package corresponds to either an imported or exported interface +/// , or all imported functions and types from the world, or all +/// exported functions from the world. +#[derive(Default)] +pub struct Packages { + pub prefix_name: Option, + packages: HashMap, +} + +pub struct Package { + pub src: Source, + pub preamble: Source, + pub world: TinyGoWorld, + pub path: Vec, + pub c_files_path: String, + pub prefix_name: Option, +} + +impl Packages { + pub fn push( + &mut self, + name: &str, + src: Source, + preamble: Source, + world: &'_ TinyGoWorld, + module_path: Vec, + ) -> Option { + let prefix_name = self.prefix_name.clone(); + let c_files_path = get_c_files_path(&module_path); + let mut package = Package::new( + src, + preamble, + world.clone(), + module_path, + c_files_path, + prefix_name, + ); + + let old_package = self.packages.insert(name.to_owned(), package); + assert!(old_package.is_none()); + old_package + } + + pub fn finish(&mut self, resolve: &Resolve, id: WorldId, files: &mut Files) { + for (_, mut package) in self { + package.finish(resolve, id); + let mut path_name = package.path.join("/"); + path_name.push_str(".wit.go"); + files.push(&path_name, package.src.as_bytes()); + } + } +} + +fn get_c_files_path(module_path: &[String]) -> String { + // for each path in module_path, we will go to the parent directory + // and then join the path with the C_GEN_FILES_PATH + let mut path = String::new(); + for _ in 0..module_path.len() - 1 { + path.push_str("../"); + } + path.push_str(C_GEN_FILES_PATH); + path +} + +impl Package { + pub fn new( + src: Source, + preamble: Source, + world: TinyGoWorld, + path: Vec, + c_files_path: String, + prefix_name: Option, + ) -> Self { + Self { + src, + preamble, + world, + path, + c_files_path, + prefix_name, + } + } + + pub fn finish(&mut self, resolve: &Resolve, id: WorldId) { + let src = mem::take(&mut self.src); + + // generate the preamble + wit_bindgen_core::generated_preamble(&mut self.src, env!("CARGO_PKG_VERSION")); + + // generate the package name + assert!(self.path.len() > 1); + let snake = self.path[self.path.len() - 2].to_snake_case(); + self.src.push_str("package "); + self.src.push_str(&snake); + self.src.push_str("\n\n"); + + // use the Go exposed C API + let prefix_name = match self.prefix_name.take() { + Some(name) => name, + None => "main".to_owned(), + }; + self.src.push_str(&format!( + "import _ \"{prefix_name}/{C_GEN_FILES_PATH}\"\n\n" + )); + + // generate CGo preamble + self.src.push_str("// #cgo CFLAGS: -I"); + self.src.push_str(&self.c_files_path); + self.src.push_str("\n"); + + self.src.push_str("// #include \""); + self.src.push_str(self.world.to_snake_case().as_str()); + self.src.push_str(".h\"\n"); + + if self.preamble.len() > 0 { + self.src.append_src(&self.preamble); + } + self.src.push_str("import \"C\"\n"); + let world = &resolve.worlds[id]; + + self.src.push_str(&src); + } +} + +impl IntoIterator for Packages { + type Item = (String, Package); + type IntoIter = std::collections::hash_map::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.packages.into_iter() + } +} + +impl<'a> IntoIterator for &'a Packages { + type Item = (&'a String, &'a Package); + type IntoIter = std::collections::hash_map::Iter<'a, String, Package>; + + fn into_iter(self) -> Self::IntoIter { + self.packages.iter() + } +} + +impl<'a> IntoIterator for &'a mut Packages { + type Item = (&'a String, &'a mut Package); + type IntoIter = std::collections::hash_map::IterMut<'a, String, Package>; + + fn into_iter(self) -> Self::IntoIter { + self.packages.iter_mut() + } +} diff --git a/tests/runtime/main.rs b/tests/runtime/main.rs index a213dd91a..cdae0920a 100644 --- a/tests/runtime/main.rs +++ b/tests/runtime/main.rs @@ -268,21 +268,25 @@ fn tests(name: &str, dir_name: &str) -> Result> { drop(fs::remove_dir_all(&out_dir)); let mut files = Default::default(); - wit_bindgen_go::Opts::default() + let mut go_opts = wit_bindgen_go::Opts::default(); + go_opts.package_name = Some(format!("{}", snake)); + go_opts .build() .generate(&resolve, world, &mut files) .unwrap(); - let gen_dir = out_dir.join("gen"); + let gen_dir = out_dir.clone(); fs::create_dir_all(&gen_dir).unwrap(); for (file, contents) in files.iter() { let dst = gen_dir.join(file); + fs::create_dir_all(dst.parent().unwrap()).unwrap(); + println!("write {dst:?}"); fs::write(dst, contents).unwrap(); } for go_impl in &go { fs::copy(&go_impl, out_dir.join(format!("{snake}.go"))).unwrap(); } - let go_mod = format!("module wit_{snake}_go\n\ngo 1.20"); + let go_mod = format!("module {snake}\n\ngo 1.20"); fs::write(out_dir.join("go.mod"), go_mod).unwrap(); let out_wasm = out_dir.join("go.wasm"); diff --git a/tests/runtime/smoke/wasm.go b/tests/runtime/smoke/wasm.go index ff3c0c316..7c2955a83 100644 --- a/tests/runtime/smoke/wasm.go +++ b/tests/runtime/smoke/wasm.go @@ -1,18 +1,19 @@ package main import ( - . "wit_smoke_go/gen" + exports "smoke/exports" + smoke "smoke/imports/test/smoke" ) func init() { n := SmokeImpl{} - SetSmoke(n) + exports.SetSmoke(n) } type SmokeImpl struct{} func (s SmokeImpl) Thunk() { - TestSmokeImportsThunk() + smoke.Thunk() } func main() {}