Skip to content

Commit 7d82e7e

Browse files
authored
Fix parsing of --with on the CLI (#999)
This commit fixes the `--with` option in the Rust generate from changes in #972. Notably the fixes here are: * The `--with` option is no longer required. * Multiple `--with` options are now accepted again. * A new `--generate-all` option was added. * The `generate_all` macro option was documented. * Error messages on the CLI and the macro now mention all the variants of how to fix the error. Closes #995
1 parent 8ff0e17 commit 7d82e7e

File tree

5 files changed

+114
-56
lines changed

5 files changed

+114
-56
lines changed

crates/guest-rust/macro/src/lib.rs

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,28 @@ pub fn generate(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
1818
}
1919

2020
fn anyhow_to_syn(span: Span, err: anyhow::Error) -> Error {
21+
let err = attach_with_context(err);
2122
let mut msg = err.to_string();
2223
for cause in err.chain().skip(1) {
2324
msg.push_str(&format!("\n\nCaused by:\n {cause}"));
2425
}
2526
Error::new(span, msg)
2627
}
2728

29+
fn attach_with_context(err: anyhow::Error) -> anyhow::Error {
30+
if let Some(e) = err.downcast_ref::<wit_bindgen_rust::MissingWith>() {
31+
let option = e.0.clone();
32+
return err.context(format!(
33+
"missing one of:\n\
34+
* `generate_all` option\n\
35+
* `with: {{ \"{option}\": path::to::bindings, }}`\n\
36+
* `with: {{ \"{option}\": generate, }}`\
37+
"
38+
));
39+
}
40+
err
41+
}
42+
2843
struct Config {
2944
opts: Opts,
3045
resolve: Resolve,
@@ -100,7 +115,7 @@ impl Parse for Config {
100115
}
101116
Opt::With(with) => opts.with.extend(with),
102117
Opt::GenerateAll => {
103-
opts.with.generate_by_default = true;
118+
opts.generate_all = true;
104119
}
105120
Opt::TypeSectionSuffix(suffix) => {
106121
opts.type_section_suffix = Some(suffix.value());
@@ -186,7 +201,7 @@ impl Config {
186201
let mut generator = self.opts.build();
187202
generator
188203
.generate(&self.resolve, self.world, &mut files)
189-
.map_err(|e| Error::new(Span::call_site(), e))?;
204+
.map_err(|e| anyhow_to_syn(Span::call_site(), e))?;
190205
let (_, src) = files.iter().next().unwrap();
191206
let mut src = std::str::from_utf8(src).unwrap().to_string();
192207

crates/guest-rust/src/lib.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -694,6 +694,10 @@
694694
/// "some:package/my-interface": generate,
695695
/// },
696696
///
697+
/// // Indicates that all interfaces not present in `with` should be assumed
698+
/// // to be marked with `generate`.
699+
/// generate_all,
700+
///
697701
/// // An optional list of function names to skip generating bindings for.
698702
/// // This is only applicable to imports and the name specified is the name
699703
/// // of the function.

crates/rust/src/lib.rs

Lines changed: 37 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,18 @@ pub enum ExportKey {
105105
Name(String),
106106
}
107107

108+
#[cfg(feature = "clap")]
109+
fn parse_with(s: &str) -> Result<(String, WithOption), String> {
110+
let (k, v) = s.split_once('=').ok_or_else(|| {
111+
format!("expected string of form `<key>=<value>[,<key>=<value>...]`; got `{s}`")
112+
})?;
113+
let v = match v {
114+
"generate" => WithOption::Generate,
115+
other => WithOption::Path(other.to_string()),
116+
};
117+
Ok((k.to_string(), v))
118+
}
119+
108120
#[derive(Default, Debug, Clone)]
109121
#[cfg_attr(feature = "clap", derive(clap::Args))]
110122
pub struct Opts {
@@ -179,8 +191,13 @@ pub struct Opts {
179191
/// Argument must be of the form `k=v` and this option can be passed
180192
/// multiple times or one option can be comma separated, for example
181193
/// `k1=v1,k2=v2`.
182-
#[cfg_attr(feature = "clap", arg(long, value_parser = clap::value_parser!(WithGeneration), value_delimiter = ','))]
183-
pub with: WithGeneration,
194+
#[cfg_attr(feature = "clap", arg(long, value_parser = parse_with, value_delimiter = ','))]
195+
pub with: Vec<(String, WithOption)>,
196+
197+
/// Indicates that all interfaces not specified in `with` should be
198+
/// generated.
199+
#[cfg_attr(feature = "clap", arg(long))]
200+
pub generate_all: bool,
184201

185202
/// Add the specified suffix to the name of the custome section containing
186203
/// the component type.
@@ -307,10 +324,7 @@ impl RustWasm {
307324
) -> Result<bool> {
308325
let with_name = resolve.name_world_key(name);
309326
let Some(remapping) = self.with.get(&with_name) else {
310-
bail!("no remapping found for {with_name:?} - use the `generate!` macro's `with` option to force the interface to be generated or specify where it is already defined:
311-
```
312-
with: {{\n\t{with_name:?}: generate\n}}
313-
```")
327+
bail!(MissingWith(with_name));
314328
};
315329
self.generated_interfaces.insert(with_name);
316330
let entry = match remapping {
@@ -872,7 +886,7 @@ impl WorldGenerator for RustWasm {
872886
for (k, v) in self.opts.with.iter() {
873887
self.with.insert(k.clone(), v.clone().into());
874888
}
875-
self.with.generate_by_default = self.opts.with.generate_by_default;
889+
self.with.generate_by_default = self.opts.generate_all;
876890
}
877891

878892
fn import_interface(
@@ -1192,51 +1206,6 @@ impl fmt::Display for Ownership {
11921206
}
11931207
}
11941208

1195-
/// Configuration for how interfaces are generated.
1196-
#[derive(Debug, Clone, Default)]
1197-
pub struct WithGeneration {
1198-
/// How interface should be generated
1199-
with: HashMap<String, WithOption>,
1200-
/// Whether to generate interfaces not present in the `with` map
1201-
pub generate_by_default: bool,
1202-
}
1203-
1204-
impl WithGeneration {
1205-
fn iter(&self) -> impl Iterator<Item = (&String, &WithOption)> {
1206-
self.with.iter()
1207-
}
1208-
1209-
pub fn extend(&mut self, with: HashMap<String, WithOption>) {
1210-
self.with.extend(with);
1211-
}
1212-
}
1213-
1214-
impl FromStr for WithGeneration {
1215-
type Err = String;
1216-
1217-
fn from_str(s: &str) -> std::prelude::v1::Result<Self, Self::Err> {
1218-
let with = s
1219-
.trim()
1220-
.split(',')
1221-
.map(|s| {
1222-
let (k, v) = s.trim().split_once('=').ok_or_else(|| {
1223-
format!("expected string of form `<key>=<value>[,<key>=<value>...]`; got `{s}`")
1224-
})?;
1225-
let k = k.trim().to_string();
1226-
let v = match v.trim() {
1227-
"generate" => WithOption::Generate,
1228-
v => WithOption::Path(v.to_string()),
1229-
};
1230-
Ok((k, v))
1231-
})
1232-
.collect::<Result<HashMap<_, _>, Self::Err>>()?;
1233-
Ok(WithGeneration {
1234-
with,
1235-
generate_by_default: false,
1236-
})
1237-
}
1238-
}
1239-
12401209
/// Options for with "with" remappings.
12411210
#[derive(Debug, Clone)]
12421211
pub enum WithOption {
@@ -1462,3 +1431,19 @@ impl fmt::Display for RustFlagsRepr {
14621431
}
14631432
}
14641433
}
1434+
1435+
#[derive(Debug, Clone)]
1436+
pub struct MissingWith(pub String);
1437+
1438+
impl fmt::Display for MissingWith {
1439+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1440+
write!(f, "missing `with` mapping for the key `{}`", self.0)
1441+
}
1442+
}
1443+
1444+
impl std::error::Error for MissingWith {}
1445+
1446+
// bail!("no remapping found for {with_name:?} - use the `generate!` macro's `with` option to force the interface to be generated or specify where it is already defined:
1447+
// ```
1448+
// with: {{\n\t{with_name:?}: generate\n}}
1449+
// ```")

crates/rust/tests/codegen.rs

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -513,3 +513,46 @@ mod gated_features {
513513
z();
514514
}
515515
}
516+
517+
#[allow(unused)]
518+
mod simple_with_option {
519+
mod a {
520+
wit_bindgen::generate!({
521+
inline: r#"
522+
package foo:bar {
523+
interface a {
524+
x: func();
525+
}
526+
}
527+
528+
package foo:baz {
529+
world w {
530+
import foo:bar/a;
531+
}
532+
}
533+
"#,
534+
world: "foo:baz/w",
535+
generate_all,
536+
});
537+
}
538+
539+
mod b {
540+
wit_bindgen::generate!({
541+
inline: r#"
542+
package foo:bar {
543+
interface a {
544+
x: func();
545+
}
546+
}
547+
548+
package foo:baz {
549+
world w {
550+
import foo:bar/a;
551+
}
552+
}
553+
"#,
554+
world: "foo:baz/w",
555+
with: { "foo:bar/a": generate },
556+
});
557+
}
558+
}

src/bin/wit-bindgen.rs

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use anyhow::{bail, Context, Result};
1+
use anyhow::{bail, Context, Error, Result};
22
use clap::Parser;
33
use std::path::PathBuf;
44
use std::str;
@@ -123,7 +123,7 @@ fn main() -> Result<()> {
123123
Opt::CSharp { opts, args } => (opts.build(), args),
124124
};
125125

126-
gen_world(generator, &opt, &mut files)?;
126+
gen_world(generator, &opt, &mut files).map_err(attach_with_context)?;
127127

128128
for (name, contents) in files.iter() {
129129
let dst = match &opt.out_dir {
@@ -166,6 +166,17 @@ fn main() -> Result<()> {
166166
Ok(())
167167
}
168168

169+
fn attach_with_context(err: Error) -> Error {
170+
#[cfg(feature = "rust")]
171+
if let Some(e) = err.downcast_ref::<wit_bindgen_rust::MissingWith>() {
172+
let option = e.0.clone();
173+
return err.context(format!(
174+
"missing either `--generate-all` or `--with {option}=(...|generate)`"
175+
));
176+
}
177+
err
178+
}
179+
169180
fn gen_world(
170181
mut generator: Box<dyn WorldGenerator>,
171182
opts: &Common,

0 commit comments

Comments
 (0)