Skip to content

Commit 1e7d719

Browse files
iamrajivrvolosatovs
authored andcommitted
feat: enhance interface generation with remapping options
1 parent 1087359 commit 1e7d719

File tree

3 files changed

+152
-18
lines changed

3 files changed

+152
-18
lines changed

crates/wit-bindgen-go/src/interface.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3338,6 +3338,7 @@ func ServeInterface(s {wrpc}.Server, h Handler) (stop func() error, err error) {
33383338
let InterfaceName {
33393339
import_name,
33403340
import_path,
3341+
..
33413342
} = &self.gen.interface_names[&interface];
33423343
if let Identifier::Interface(cur, _) = self.identifier {
33433344
if cur == interface {

crates/wit-bindgen-go/src/lib.rs

Lines changed: 149 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use crate::interface::InterfaceGenerator;
2-
use anyhow::Result;
2+
use anyhow::{bail, Result};
33
use core::fmt::Display;
44
use heck::{ToLowerCamelCase, ToSnakeCase, ToUpperCamelCase};
55
use std::collections::{BTreeMap, HashMap, HashSet};
@@ -9,21 +9,53 @@ use std::mem;
99
use std::process::{Command, Stdio};
1010
use wit_bindgen_core::{
1111
uwrite, uwriteln,
12-
wit_parser::{Function, InterfaceId, PackageId, Resolve, TypeId, World, WorldId, WorldKey},
12+
wit_parser::{
13+
Function, InterfaceId, PackageId, Resolve, TypeId, World, WorldId, WorldItem, WorldKey,
14+
},
1315
Files, InterfaceGenerator as _, Source, Types, WorldGenerator,
1416
};
1517

1618
mod interface;
1719

1820
#[derive(Clone)]
1921
struct InterfaceName {
22+
/// True when this interface name has been remapped through the use of `with`.
23+
remapped: bool,
24+
2025
/// The string import name for this interface.
2126
import_name: String,
2227

2328
/// The string import path for this interface.
2429
import_path: String,
2530
}
2631

32+
/// How an interface should be generated.
33+
enum InterfaceGeneration {
34+
/// Remapped to some other type at the given Go import path.
35+
Remap(String),
36+
/// Generate the interface.
37+
Generate,
38+
}
39+
40+
#[derive(Default)]
41+
struct GenerationConfiguration {
42+
map: HashMap<String, InterfaceGeneration>,
43+
generate_by_default: bool,
44+
}
45+
46+
impl GenerationConfiguration {
47+
fn get(&self, key: &str) -> Option<&InterfaceGeneration> {
48+
self.map.get(key).or_else(|| {
49+
self.generate_by_default
50+
.then_some(&InterfaceGeneration::Generate)
51+
})
52+
}
53+
54+
fn insert(&mut self, name: String, generate: InterfaceGeneration) {
55+
self.map.insert(name, generate);
56+
}
57+
}
58+
2759
#[derive(Default)]
2860
struct GoWrpc {
2961
types: Types,
@@ -40,6 +72,9 @@ struct GoWrpc {
4072

4173
export_paths: Vec<String>,
4274
deps: Deps,
75+
with_name_counter: usize,
76+
/// Interface names to how they should be generated.
77+
with: GenerationConfiguration,
4378
}
4479

4580
#[derive(Default)]
@@ -151,6 +186,45 @@ fn generated_preamble() -> String {
151186
)
152187
}
153188

189+
#[cfg(feature = "clap")]
190+
fn parse_with(s: &str) -> Result<(String, WithOption), String> {
191+
let (k, v) = s.split_once('=').ok_or_else(|| {
192+
format!("expected string of form `<key>=<value>[,<key>=<value>...]`; got `{s}`")
193+
})?;
194+
let v = match v {
195+
"generate" => WithOption::Generate,
196+
other => WithOption::Path(other.to_string()),
197+
};
198+
Ok((k.to_string(), v))
199+
}
200+
201+
/// Represents options for remapping interface bindings.
202+
#[derive(Debug, Clone)]
203+
pub enum WithOption {
204+
/// Remap the interface to an existing Go import path.
205+
Path(String),
206+
/// Generate the interface bindings.
207+
Generate,
208+
}
209+
210+
impl fmt::Display for WithOption {
211+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
212+
match self {
213+
WithOption::Path(p) => f.write_fmt(format_args!("\"{p}\"")),
214+
WithOption::Generate => f.write_str("generate"),
215+
}
216+
}
217+
}
218+
219+
impl From<WithOption> for InterfaceGeneration {
220+
fn from(opt: WithOption) -> Self {
221+
match opt {
222+
WithOption::Path(p) => InterfaceGeneration::Remap(p),
223+
WithOption::Generate => InterfaceGeneration::Generate,
224+
}
225+
}
226+
}
227+
154228
#[derive(Debug, Clone)]
155229
#[cfg_attr(feature = "clap", derive(clap::Args))]
156230
pub struct Opts {
@@ -161,13 +235,28 @@ pub struct Opts {
161235
/// Go package path containing the generated bindings
162236
#[cfg_attr(feature = "clap", arg(long, default_value = ""))]
163237
pub package: String,
238+
239+
/// Remapping of interface names to Go import paths.
240+
///
241+
/// Argument must be of the form `k=v` and this option can be passed
242+
/// multiple times or one option can be comma separated, for example
243+
/// `k1=v1,k2=v2`.
244+
#[cfg_attr(feature = "clap", arg(long, value_parser = parse_with, value_delimiter = ','))]
245+
pub with: Vec<(String, WithOption)>,
246+
247+
/// Indicates that all interfaces not specified in `with` should be
248+
/// generated.
249+
#[cfg_attr(feature = "clap", arg(long))]
250+
pub generate_all: bool,
164251
}
165252

166253
impl Default for Opts {
167254
fn default() -> Self {
168255
Self {
169256
gofmt: true,
170257
package: String::new(),
258+
with: Vec::new(),
259+
generate_all: false,
171260
}
172261
}
173262
}
@@ -224,21 +313,41 @@ impl GoWrpc {
224313
id: InterfaceId,
225314
name: &WorldKey,
226315
is_export: bool,
227-
) {
228-
let path = compute_module_path(name, resolve, is_export);
229-
let import_name = path.join("__");
230-
let import_path = if !self.opts.package.is_empty() {
231-
format!("{}/{}", self.opts.package, path.join("/"))
232-
} else {
233-
path.join("/")
316+
) -> Result<bool> {
317+
let with_name = resolve.name_world_key(name);
318+
let Some(remapping) = self.with.get(&with_name) else {
319+
bail!("no remapping found for {with_name:?} - use `--generate-all` or `--with {with_name}=generate` to generate bindings for this interface");
234320
};
235-
self.interface_names.insert(
236-
id,
237-
InterfaceName {
238-
import_name,
239-
import_path,
240-
},
241-
);
321+
322+
let entry = match remapping {
323+
InterfaceGeneration::Remap(remapped_path) => {
324+
let import_name = format!("__with_name{}", self.with_name_counter);
325+
self.with_name_counter += 1;
326+
InterfaceName {
327+
remapped: true,
328+
import_name,
329+
import_path: remapped_path.clone(),
330+
}
331+
}
332+
InterfaceGeneration::Generate => {
333+
let path = compute_module_path(name, resolve, is_export);
334+
let import_name = path.join("__");
335+
let import_path = if !self.opts.package.is_empty() {
336+
format!("{}/{}", self.opts.package, path.join("/"))
337+
} else {
338+
path.join("/")
339+
};
340+
InterfaceName {
341+
remapped: false,
342+
import_name,
343+
import_path,
344+
}
345+
}
346+
};
347+
348+
let remapped = entry.remapped;
349+
self.interface_names.insert(id, entry);
350+
Ok(remapped)
242351
}
243352

244353
/// Generates imports and a `Serve` function for the world
@@ -365,6 +474,23 @@ impl WorldGenerator for GoWrpc {
365474
fn preprocess(&mut self, resolve: &Resolve, world: WorldId) {
366475
self.types.analyze(resolve);
367476
self.world = Some(world);
477+
478+
let world = &resolve.worlds[world];
479+
// Specify that all imports/exports local to the world's package should be generated
480+
for (key, item) in world.imports.iter().chain(world.exports.iter()) {
481+
if let WorldItem::Interface { id, .. } = item {
482+
if resolve.interfaces[*id].package == world.package {
483+
let name = resolve.name_world_key(key);
484+
if self.with.get(&name).is_none() {
485+
self.with.insert(name, InterfaceGeneration::Generate);
486+
}
487+
}
488+
}
489+
}
490+
for (k, v) in &self.opts.with {
491+
self.with.insert(k.clone(), v.clone().into());
492+
}
493+
self.with.generate_by_default = self.opts.generate_all;
368494
}
369495

370496
fn import_interface(
@@ -377,7 +503,9 @@ impl WorldGenerator for GoWrpc {
377503
self.interface_last_seen_as_import.insert(id, true);
378504
let mut gen = self.interface(Identifier::Interface(id, name), resolve, true);
379505
let (snake, module_path) = gen.start_append_submodule(name);
380-
gen.gen.name_interface(resolve, id, name, false);
506+
if gen.gen.name_interface(resolve, id, name, false)? {
507+
return Ok(());
508+
}
381509
gen.types(id);
382510

383511
let identifier = Identifier::Interface(id, name);
@@ -446,7 +574,9 @@ impl WorldGenerator for GoWrpc {
446574
self.interface_last_seen_as_import.insert(id, false);
447575
let mut gen = self.interface(Identifier::Interface(id, name), resolve, false);
448576
let (snake, module_path) = gen.start_append_submodule(name);
449-
gen.gen.name_interface(resolve, id, name, true);
577+
if gen.gen.name_interface(resolve, id, name, true)? {
578+
return Ok(());
579+
}
450580
gen.types(id);
451581
let exports = gen.generate_exports(
452582
Identifier::Interface(id, name),
@@ -457,6 +587,7 @@ impl WorldGenerator for GoWrpc {
457587
let InterfaceName {
458588
import_name,
459589
import_path,
590+
..
460591
} = &self.interface_names[&id];
461592
self.export_paths
462593
.push(self.deps.import(import_name.clone(), import_path.clone()));

crates/wit-bindgen-go/tests/codegen.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ macro_rules! codegen_test {
1515
wit_bindgen_wrpc_go::Opts {
1616
gofmt: false,
1717
package: "bindings".to_string(),
18+
with: Vec::new(),
19+
generate_all: true,
1820
}
1921
.build()
2022
.generate(resolve, world, files)

0 commit comments

Comments
 (0)