Skip to content

Commit 22b934f

Browse files
committed
optionally override module names; bump version to 0.16.0
This adds CLI and componentize-py.toml options for overriding the generated Python module names for one or more WIT interfaces. By default, the name is the snake-case version of the WIT name, qualified as necessary with the package namespace and name and/or the version in cases of ambiguity. Sometimes that's not what you want, though, so now you can override the naming on an individual basis as long as the name(s) you pick are unique. This can be especially useful for backwards compatibility when adding new versions of WIT interfaces. In that case, the generated module name may go from unqualified to qualified, but you can now force the name of the original version to be unqualified for compatibility. For example: - You release an SDK with an interface called `foo:bar/baz@1.0.0`. Since that's the only interface with the name `baz`, `componentize-py` will name the generated module `baz` also. - Later, you release a new version of the SDK with support for _both_ `foo:bar/baz@1.0.0` _and_ `foo:bar/baz@2.0.0`. In that case, `componentize-py` will name the generated modules `foo_bar_baz_1_0_0` and `foo_bar_baz_2_0_0` by default. However, you don't want to force users of your SDK to use the new name, so you pass `--import-interface-name foo:bar/baz@1.0.0=baz` to `componentize-py`, which tells it to use the original name. Signed-off-by: Joel Dice <joel.dice@fermyon.com>
1 parent fc16d0c commit 22b934f

File tree

14 files changed

+170
-29
lines changed

14 files changed

+170
-29
lines changed

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "componentize-py"
3-
version = "0.15.2"
3+
version = "0.16.0"
44
edition = "2021"
55
exclude = ["cpython"]
66

examples/cli/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,15 @@ run a Python-based component targetting the [wasi-cli] `command` world.
1010
## Prerequisites
1111

1212
* `Wasmtime` 26.0.0 or later
13-
* `componentize-py` 0.15.2
13+
* `componentize-py` 0.16.0
1414

1515
Below, we use [Rust](https://rustup.rs/)'s `cargo` to install `Wasmtime`. If
1616
you don't have `cargo`, you can download and install from
1717
https://github.com/bytecodealliance/wasmtime/releases/tag/v26.0.0.
1818

1919
```
2020
cargo install --version 26.0.0 wasmtime-cli
21-
pip install componentize-py==0.15.2
21+
pip install componentize-py==0.16.0
2222
```
2323

2424
## Running the demo

examples/http/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,15 @@ run a Python-based component targetting the [wasi-http] `proxy` world.
1010
## Prerequisites
1111

1212
* `Wasmtime` 26.0.0 or later
13-
* `componentize-py` 0.15.2
13+
* `componentize-py` 0.16.0
1414

1515
Below, we use [Rust](https://rustup.rs/)'s `cargo` to install `Wasmtime`. If
1616
you don't have `cargo`, you can download and install from
1717
https://github.com/bytecodealliance/wasmtime/releases/tag/v26.0.0.
1818

1919
```
2020
cargo install --version 26.0.0 wasmtime-cli
21-
pip install componentize-py==0.15.2
21+
pip install componentize-py==0.16.0
2222
```
2323

2424
## Running the demo

examples/matrix-math/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ within a guest component.
1111
## Prerequisites
1212

1313
* `wasmtime` 26.0.0 or later
14-
* `componentize-py` 0.15.2
14+
* `componentize-py` 0.16.0
1515
* `NumPy`, built for WASI
1616

1717
Note that we use an unofficial build of NumPy since the upstream project does
@@ -23,7 +23,7 @@ https://github.com/bytecodealliance/wasmtime/releases/tag/v26.0.0.
2323

2424
```
2525
cargo install --version 26.0.0 wasmtime-cli
26-
pip install componentize-py==0.15.2
26+
pip install componentize-py==0.16.0
2727
curl -OL https://github.com/dicej/wasi-wheels/releases/download/v0.0.1/numpy-wasi.tar.gz
2828
tar xf numpy-wasi.tar.gz
2929
```

examples/sandbox/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,10 @@ sandboxed Python code snippets from within a Python app.
88
## Prerequisites
99

1010
* `wasmtime-py` 25.0.0 or later
11-
* `componentize-py` 0.15.2
11+
* `componentize-py` 0.16.0
1212

1313
```
14-
pip install componentize-py==0.15.2 wasmtime==25.0.0
14+
pip install componentize-py==0.16.0 wasmtime==25.0.0
1515
```
1616

1717
## Running the demo

examples/tcp/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,15 @@ making an outbound TCP request using `wasi-sockets`.
1111
## Prerequisites
1212

1313
* `Wasmtime` 26.0.0 or later
14-
* `componentize-py` 0.15.2
14+
* `componentize-py` 0.16.0
1515

1616
Below, we use [Rust](https://rustup.rs/)'s `cargo` to install `Wasmtime`. If
1717
you don't have `cargo`, you can download and install from
1818
https://github.com/bytecodealliance/wasmtime/releases/tag/v26.0.0.
1919

2020
```
2121
cargo install --version 26.0.0 wasmtime-cli
22-
pip install componentize-py==0.15.2
22+
pip install componentize-py==0.16.0
2323
```
2424

2525
## Running the demo

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ features = ["pyo3/extension-module"]
77

88
[project]
99
name = "componentize-py"
10-
version = "0.15.2"
10+
version = "0.16.0"
1111
description = "Tool to package Python applications as WebAssembly components"
1212
readme = "README.md"
1313
license = { file = "LICENSE" }

src/command.rs

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,24 @@ pub struct Common {
4848
/// This enables using `@unstable` annotations in WIT files.
4949
#[clap(long)]
5050
all_features: bool,
51+
52+
/// Specify names to use for imported interfaces. May be specified more than once.
53+
///
54+
/// By default, the python module name generated for a given interface will be the snake-case form of the WIT
55+
/// interface name, possibly qualified with the package name and namespace and/or version if that name would
56+
/// otherwise clash with another interface. With this option, you may override that name with your own, unique
57+
/// name.
58+
#[arg(long, value_parser = parse_key_value)]
59+
pub import_interface_name: Vec<(String, String)>,
60+
61+
/// Specify names to use for exported interfaces. May be specified more than once.
62+
///
63+
/// By default, the python module name generated for a given interface will be the snake-case form of the WIT
64+
/// interface name, possibly qualified with the package name and namespace and/or version if that name would
65+
/// otherwise clash with another interface. With this option, you may override that name with your own, unique
66+
/// name.
67+
#[arg(long, value_parser = parse_key_value)]
68+
pub export_interface_name: Vec<(String, String)>,
5169
}
5270

5371
#[derive(clap::Subcommand, Debug)]
@@ -82,7 +100,7 @@ pub struct Componentize {
82100
///
83101
/// Note that these must be specified in topological order (i.e. if a module containing WIT files depends on
84102
/// other modules containing WIT files, it must be listed after all its dependencies).
85-
#[arg(short = 'm', long, value_parser = parse_module_world)]
103+
#[arg(short = 'm', long, value_parser = parse_key_value)]
86104
pub module_worlds: Vec<(String, String)>,
87105

88106
/// Output file to which to write the resulting component
@@ -112,7 +130,7 @@ pub struct Bindings {
112130
pub world_module: Option<String>,
113131
}
114132

115-
fn parse_module_world(s: &str) -> Result<(String, String), String> {
133+
fn parse_key_value(s: &str) -> Result<(String, String), String> {
116134
let (k, v) = s
117135
.split_once('=')
118136
.ok_or_else(|| format!("expected string of form `<key>=<value>`; got `{s}`"))?;
@@ -137,6 +155,16 @@ fn generate_bindings(common: Common, bindings: Bindings) -> Result<()> {
137155
common.all_features,
138156
bindings.world_module.as_deref(),
139157
&bindings.output_dir,
158+
&common
159+
.import_interface_name
160+
.iter()
161+
.map(|(a, b)| (a.as_str(), b.as_str()))
162+
.collect(),
163+
&common
164+
.export_interface_name
165+
.iter()
166+
.map(|(a, b)| (a.as_str(), b.as_str()))
167+
.collect(),
140168
)
141169
}
142170

@@ -167,6 +195,16 @@ fn componentize(common: Common, componentize: Componentize) -> Result<()> {
167195
&componentize.output,
168196
None,
169197
componentize.stub_wasi,
198+
&common
199+
.import_interface_name
200+
.iter()
201+
.map(|(a, b)| (a.as_str(), b.as_str()))
202+
.collect(),
203+
&common
204+
.export_interface_name
205+
.iter()
206+
.map(|(a, b)| (a.as_str(), b.as_str()))
207+
.collect(),
170208
))?;
171209

172210
if !common.quiet {
@@ -300,6 +338,8 @@ mod tests {
300338
quiet: false,
301339
features: vec![],
302340
all_features: false,
341+
import_interface_name: Vec::new(),
342+
export_interface_name: Vec::new(),
303343
};
304344
let bindings = Bindings {
305345
output_dir: out_dir.path().into(),
@@ -328,6 +368,8 @@ mod tests {
328368
quiet: false,
329369
features: vec!["x".to_owned()],
330370
all_features: false,
371+
import_interface_name: Vec::new(),
372+
export_interface_name: Vec::new(),
331373
};
332374
let bindings = Bindings {
333375
output_dir: out_dir.path().into(),
@@ -356,6 +398,8 @@ mod tests {
356398
quiet: false,
357399
features: vec![],
358400
all_features: true,
401+
import_interface_name: Vec::new(),
402+
export_interface_name: Vec::new(),
359403
};
360404
let bindings = Bindings {
361405
output_dir: out_dir.path().into(),
@@ -382,6 +426,8 @@ mod tests {
382426
quiet: false,
383427
features: vec!["x".to_owned()],
384428
all_features: false,
429+
import_interface_name: Vec::new(),
430+
export_interface_name: Vec::new(),
385431
};
386432
let bindings = Bindings {
387433
output_dir: out_dir.path().into(),

src/lib.rs

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,12 +73,18 @@ impl WasiView for Ctx {
7373
struct RawComponentizePyConfig {
7474
bindings: Option<String>,
7575
wit_directory: Option<String>,
76+
#[serde(default)]
77+
import_interface_names: HashMap<String, String>,
78+
#[serde(default)]
79+
export_interface_names: HashMap<String, String>,
7680
}
7781

7882
#[derive(Debug)]
7983
struct ComponentizePyConfig {
8084
bindings: Option<PathBuf>,
8185
wit_directory: Option<PathBuf>,
86+
import_interface_names: HashMap<String, String>,
87+
export_interface_names: HashMap<String, String>,
8288
}
8389

8490
impl TryFrom<(&Path, RawComponentizePyConfig)> for ComponentizePyConfig {
@@ -97,6 +103,8 @@ impl TryFrom<(&Path, RawComponentizePyConfig)> for ComponentizePyConfig {
97103
Ok(Self {
98104
bindings: raw.bindings.map(convert).transpose()?,
99105
wit_directory: raw.wit_directory.map(convert).transpose()?,
106+
import_interface_names: raw.import_interface_names,
107+
export_interface_names: raw.export_interface_names,
100108
})
101109
}
102110
}
@@ -162,19 +170,27 @@ impl Invoker for MyInvoker {
162170
}
163171
}
164172

173+
#[allow(clippy::too_many_arguments)]
165174
pub fn generate_bindings(
166175
wit_path: &Path,
167176
world: Option<&str>,
168177
features: &[String],
169178
all_features: bool,
170179
world_module: Option<&str>,
171180
output_dir: &Path,
181+
import_interface_names: &HashMap<&str, &str>,
182+
export_interface_names: &HashMap<&str, &str>,
172183
) -> Result<()> {
173184
// TODO: Split out and reuse the code responsible for finding and using componentize-py.toml files in the
174185
// `componentize` function below, since that can affect the bindings we should be generating.
175186

176187
let (resolve, world) = parse_wit(wit_path, world, features, all_features)?;
177-
let summary = Summary::try_new(&resolve, &iter::once(world).collect())?;
188+
let summary = Summary::try_new(
189+
&resolve,
190+
&iter::once(world).collect(),
191+
import_interface_names,
192+
export_interface_names,
193+
)?;
178194
let world_name = resolve.worlds[world].name.to_snake_case().escape();
179195
let world_module = world_module.unwrap_or(&world_name);
180196
let world_dir = output_dir.join(world_module.replace('.', "/"));
@@ -202,6 +218,8 @@ pub async fn componentize(
202218
output_path: &Path,
203219
add_to_linker: Option<&dyn Fn(&mut Linker<Ctx>) -> Result<()>>,
204220
stub_wasi: bool,
221+
import_interface_names: &HashMap<&str, &str>,
222+
export_interface_names: &HashMap<&str, &str>,
205223
) -> Result<()> {
206224
// Remove non-existent elements from `python_path` so we don't choke on them later:
207225
let python_path = &python_path
@@ -224,6 +242,30 @@ pub async fn componentize(
224242
(None, None)
225243
};
226244

245+
let import_interface_names = import_interface_names
246+
.iter()
247+
.map(|(a, b)| (*a, *b))
248+
.chain(configs.iter().flat_map(|(_, (config, _))| {
249+
config
250+
.config
251+
.import_interface_names
252+
.iter()
253+
.map(|(a, b)| (a.as_str(), b.as_str()))
254+
}))
255+
.collect();
256+
257+
let export_interface_names = export_interface_names
258+
.iter()
259+
.map(|(a, b)| (*a, *b))
260+
.chain(configs.iter().flat_map(|(_, (config, _))| {
261+
config
262+
.config
263+
.export_interface_names
264+
.iter()
265+
.map(|(a, b)| (a.as_str(), b.as_str()))
266+
}))
267+
.collect();
268+
227269
let configs = configs
228270
.iter()
229271
.map(|(module, (config, world))| {
@@ -271,7 +313,12 @@ pub async fn componentize(
271313
.chain(main_world)
272314
.collect::<IndexSet<_>>();
273315

274-
let summary = Summary::try_new(&resolve, &worlds)?;
316+
let summary = Summary::try_new(
317+
&resolve,
318+
&worlds,
319+
&import_interface_names,
320+
&export_interface_names,
321+
)?;
275322

276323
libraries.push(Library {
277324
name: "libcomponentize_py_bindings.so".into(),

0 commit comments

Comments
 (0)