Skip to content

Commit

Permalink
optionally override module names; bump version to 0.16.0
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
dicej committed Nov 27, 2024
1 parent fc16d0c commit 880321e
Show file tree
Hide file tree
Showing 14 changed files with 169 additions and 29 deletions.
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,6 +1,6 @@
[package]
name = "componentize-py"
version = "0.15.2"
version = "0.16.0"
edition = "2021"
exclude = ["cpython"]

Expand Down
4 changes: 2 additions & 2 deletions examples/cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,15 @@ run a Python-based component targetting the [wasi-cli] `command` world.
## Prerequisites

* `Wasmtime` 26.0.0 or later
* `componentize-py` 0.15.2
* `componentize-py` 0.16.0

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

```
cargo install --version 26.0.0 wasmtime-cli
pip install componentize-py==0.15.2
pip install componentize-py==0.16.0
```

## Running the demo
Expand Down
4 changes: 2 additions & 2 deletions examples/http/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,15 @@ run a Python-based component targetting the [wasi-http] `proxy` world.
## Prerequisites

* `Wasmtime` 26.0.0 or later
* `componentize-py` 0.15.2
* `componentize-py` 0.16.0

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

```
cargo install --version 26.0.0 wasmtime-cli
pip install componentize-py==0.15.2
pip install componentize-py==0.16.0
```

## Running the demo
Expand Down
4 changes: 2 additions & 2 deletions examples/matrix-math/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ within a guest component.
## Prerequisites

* `wasmtime` 26.0.0 or later
* `componentize-py` 0.15.2
* `componentize-py` 0.16.0
* `NumPy`, built for WASI

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

```
cargo install --version 26.0.0 wasmtime-cli
pip install componentize-py==0.15.2
pip install componentize-py==0.16.0
curl -OL https://github.com/dicej/wasi-wheels/releases/download/v0.0.1/numpy-wasi.tar.gz
tar xf numpy-wasi.tar.gz
```
Expand Down
4 changes: 2 additions & 2 deletions examples/sandbox/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ sandboxed Python code snippets from within a Python app.
## Prerequisites

* `wasmtime-py` 25.0.0 or later
* `componentize-py` 0.15.2
* `componentize-py` 0.16.0

```
pip install componentize-py==0.15.2 wasmtime==25.0.0
pip install componentize-py==0.16.0 wasmtime==25.0.0
```

## Running the demo
Expand Down
4 changes: 2 additions & 2 deletions examples/tcp/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,15 @@ making an outbound TCP request using `wasi-sockets`.
## Prerequisites

* `Wasmtime` 26.0.0 or later
* `componentize-py` 0.15.2
* `componentize-py` 0.16.0

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

```
cargo install --version 26.0.0 wasmtime-cli
pip install componentize-py==0.15.2
pip install componentize-py==0.16.0
```

## Running the demo
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ features = ["pyo3/extension-module"]

[project]
name = "componentize-py"
version = "0.15.2"
version = "0.16.0"
description = "Tool to package Python applications as WebAssembly components"
readme = "README.md"
license = { file = "LICENSE" }
Expand Down
50 changes: 48 additions & 2 deletions src/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,24 @@ pub struct Common {
/// This enables using `@unstable` annotations in WIT files.
#[clap(long)]
all_features: bool,

/// Specify names to use for imported interfaces. May be specified more than once.
///
/// By default, the python module name generated for a given interface will be the snake-case form of the WIT
/// interface name, possibly qualified with the package name and namespace and/or version if that name would
/// otherwise clash with another interface. With this option, you may override that name with your own, unique
/// name.
#[arg(long, value_parser = parse_key_value)]
pub import_interface_name: Vec<(String, String)>,

/// Specify names to use for exported interfaces. May be specified more than once.
///
/// By default, the python module name generated for a given interface will be the snake-case form of the WIT
/// interface name, possibly qualified with the package name and namespace and/or version if that name would
/// otherwise clash with another interface. With this option, you may override that name with your own, unique
/// name.
#[arg(long, value_parser = parse_key_value)]
pub export_interface_name: Vec<(String, String)>,
}

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

/// Output file to which to write the resulting component
Expand Down Expand Up @@ -112,7 +130,7 @@ pub struct Bindings {
pub world_module: Option<String>,
}

fn parse_module_world(s: &str) -> Result<(String, String), String> {
fn parse_key_value(s: &str) -> Result<(String, String), String> {
let (k, v) = s
.split_once('=')
.ok_or_else(|| format!("expected string of form `<key>=<value>`; got `{s}`"))?;
Expand All @@ -137,6 +155,16 @@ fn generate_bindings(common: Common, bindings: Bindings) -> Result<()> {
common.all_features,
bindings.world_module.as_deref(),
&bindings.output_dir,
&common
.import_interface_name
.iter()
.map(|(a, b)| (a.as_str(), b.as_str()))
.collect(),
&common
.export_interface_name
.iter()
.map(|(a, b)| (a.as_str(), b.as_str()))
.collect(),
)
}

Expand Down Expand Up @@ -167,6 +195,16 @@ fn componentize(common: Common, componentize: Componentize) -> Result<()> {
&componentize.output,
None,
componentize.stub_wasi,
&common
.import_interface_name
.iter()
.map(|(a, b)| (a.as_str(), b.as_str()))
.collect(),
&common
.export_interface_name
.iter()
.map(|(a, b)| (a.as_str(), b.as_str()))
.collect(),
))?;

if !common.quiet {
Expand Down Expand Up @@ -300,6 +338,8 @@ mod tests {
quiet: false,
features: vec![],
all_features: false,
import_interface_name: Vec::new(),
export_interface_name: Vec::new(),
};
let bindings = Bindings {
output_dir: out_dir.path().into(),
Expand Down Expand Up @@ -328,6 +368,8 @@ mod tests {
quiet: false,
features: vec!["x".to_owned()],
all_features: false,
import_interface_name: Vec::new(),
export_interface_name: Vec::new(),
};
let bindings = Bindings {
output_dir: out_dir.path().into(),
Expand Down Expand Up @@ -356,6 +398,8 @@ mod tests {
quiet: false,
features: vec![],
all_features: true,
import_interface_name: Vec::new(),
export_interface_name: Vec::new(),
};
let bindings = Bindings {
output_dir: out_dir.path().into(),
Expand All @@ -382,6 +426,8 @@ mod tests {
quiet: false,
features: vec!["x".to_owned()],
all_features: false,
import_interface_name: Vec::new(),
export_interface_name: Vec::new(),
};
let bindings = Bindings {
output_dir: out_dir.path().into(),
Expand Down
51 changes: 49 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,12 +73,18 @@ impl WasiView for Ctx {
struct RawComponentizePyConfig {
bindings: Option<String>,
wit_directory: Option<String>,
#[serde(default)]
import_interface_names: HashMap<String, String>,
#[serde(default)]
export_interface_names: HashMap<String, String>,
}

#[derive(Debug)]
struct ComponentizePyConfig {
bindings: Option<PathBuf>,
wit_directory: Option<PathBuf>,
import_interface_names: HashMap<String, String>,
export_interface_names: HashMap<String, String>,
}

impl TryFrom<(&Path, RawComponentizePyConfig)> for ComponentizePyConfig {
Expand All @@ -97,6 +103,8 @@ impl TryFrom<(&Path, RawComponentizePyConfig)> for ComponentizePyConfig {
Ok(Self {
bindings: raw.bindings.map(convert).transpose()?,
wit_directory: raw.wit_directory.map(convert).transpose()?,
import_interface_names: raw.import_interface_names,
export_interface_names: raw.export_interface_names,
})
}
}
Expand Down Expand Up @@ -162,19 +170,27 @@ impl Invoker for MyInvoker {
}
}

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

let (resolve, world) = parse_wit(wit_path, world, features, all_features)?;
let summary = Summary::try_new(&resolve, &iter::once(world).collect())?;
let summary = Summary::try_new(
&resolve,
&iter::once(world).collect(),
import_interface_names,
export_interface_names,
)?;
let world_name = resolve.worlds[world].name.to_snake_case().escape();
let world_module = world_module.unwrap_or(&world_name);
let world_dir = output_dir.join(world_module.replace('.', "/"));
Expand Down Expand Up @@ -202,6 +218,8 @@ pub async fn componentize(
output_path: &Path,
add_to_linker: Option<&dyn Fn(&mut Linker<Ctx>) -> Result<()>>,
stub_wasi: bool,
import_interface_names: &HashMap<&str, &str>,
export_interface_names: &HashMap<&str, &str>,
) -> Result<()> {
// Remove non-existent elements from `python_path` so we don't choke on them later:
let python_path = &python_path
Expand All @@ -224,6 +242,30 @@ pub async fn componentize(
(None, None)
};

let import_interface_names = import_interface_names
.iter()
.map(|(a, b)| (*a, *b))
.chain(configs.iter().flat_map(|(_, (config, _))| {
config
.config
.import_interface_names
.iter()
.map(|(a, b)| (a.as_str(), b.as_str()))
}))
.collect();

let export_interface_names = export_interface_names
.iter()
.map(|(a, b)| (*a, *b))
.chain(configs.iter().flat_map(|(_, (config, _))| {
config
.config
.export_interface_names
.iter()
.map(|(a, b)| (a.as_str(), b.as_str()))
}))
.collect();

let configs = configs
.iter()
.map(|(module, (config, world))| {
Expand Down Expand Up @@ -271,7 +313,12 @@ pub async fn componentize(
.chain(main_world)
.collect::<IndexSet<_>>();

let summary = Summary::try_new(&resolve, &worlds)?;
let summary = Summary::try_new(
&resolve,
&worlds,
&import_interface_names,
&export_interface_names,
)?;

libraries.push(Library {
name: "libcomponentize_py_bindings.so".into(),
Expand Down
7 changes: 3 additions & 4 deletions src/prelink.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ pub fn embedded_helper_utils() -> Result<TempDir, io::Error> {
}

pub fn bundle_libraries(
library_path: Vec<(&str, Vec<std::path::PathBuf>)>,
library_path: Vec<(&str, Vec<PathBuf>)>,
) -> Result<Vec<Library>, anyhow::Error> {
let mut libraries = vec![
Library {
Expand Down Expand Up @@ -153,9 +153,8 @@ pub fn search_for_libraries_and_configs<'a>(
module_worlds: &'a [(&'a str, &'a str)],
world: Option<&'a str>,
) -> Result<(ConfigsMatchedWorlds<'a>, Vec<Library>), anyhow::Error> {
let mut raw_configs: Vec<crate::ConfigContext<crate::RawComponentizePyConfig>> = Vec::new();
let mut library_path: Vec<(&str, Vec<std::path::PathBuf>)> =
Vec::with_capacity(python_path.len());
let mut raw_configs: Vec<ConfigContext<RawComponentizePyConfig>> = Vec::new();
let mut library_path: Vec<(&str, Vec<PathBuf>)> = Vec::with_capacity(python_path.len());
for path in python_path {
let mut libraries = Vec::new();
search_directory(
Expand Down
Loading

0 comments on commit 880321e

Please sign in to comment.