Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 25 additions & 61 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

26 changes: 26 additions & 0 deletions src/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,29 @@ pub struct Componentize {
#[arg(short = 'm', long, value_parser = parse_key_value)]
pub module_worlds: Vec<(String, String)>,

/// Specify a world to *intersect* with the world(s) specified elsewhere.
///
/// The `--world` option, `--module-worlds` option, and
/// `componentize-py.toml` files may be used to specify any number of worlds
/// for the component to target. If more than one are specified, they are
/// unioned together to compute the target world. This option extends that
/// computation to calculate the *intersection* (i.e. shared subset) of that
/// union and the specified world.
///
/// This can be useful in cases where an application is using an SDK lbirary
/// containing a `componentize-py.toml` and pre-generated bindings, but the
/// target environment for the app only supports a subset of the features
/// covered by the SDK. By default, using that SDK would pull in all the
/// imports of the world(s) for which it was intended, but with this option,
/// we can exclude imports that are unsupported by the target environment
/// and still use the subset of the SDK which does not require those
/// excluded imports.
///
/// Note that this may lead to pre-init or runtime errors if the application
/// does in fact end up using any imports which were excluded.
#[arg(short = 'i', long)]
pub intersect_world: Option<String>,

/// Output file to which to write the resulting component
#[arg(short = 'o', long, default_value = "index.wasm")]
pub output: PathBuf,
Expand Down Expand Up @@ -271,6 +294,7 @@ fn componentize(common: Common, componentize: Componentize) -> Result<()> {
.map(|(a, b)| (a.as_str(), b.as_str()))
.collect(),
full_names: common.full_names,
intersect_world: componentize.intersect_world.as_deref(),
}
.generate(),
)?;
Expand Down Expand Up @@ -526,6 +550,7 @@ class Bindings(bindings.Bindings):
module_worlds: vec![],
output: out_dir.path().join("app.wasm"),
stub_wasi: false,
intersect_world: None,
};
componentize(common, componentize_opts)
}
Expand Down Expand Up @@ -639,6 +664,7 @@ world lib-world {
module_worlds: vec![],
output: dir.path().join("app.wasm"),
stub_wasi: false,
intersect_world: None,
},
)
}
Expand Down
78 changes: 72 additions & 6 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use {
serde::Deserialize,
std::{
borrow::Cow,
collections::HashMap,
collections::{HashMap, HashSet},
fs,
io::Cursor,
iter,
Expand Down Expand Up @@ -280,6 +280,7 @@ pub struct ComponentGenerator<'a> {
pub import_interface_names: &'a HashMap<&'a str, &'a str>,
pub export_interface_names: &'a HashMap<&'a str, &'a str>,
pub full_names: bool,
pub intersect_world: Option<&'a str>,
}

impl ComponentGenerator<'_> {
Expand Down Expand Up @@ -443,6 +444,24 @@ impl ComponentGenerator<'_> {

let worlds = select_worlds(&resolve, self.worlds, &packages)?;

let intersector = if let Some(world) = self.intersect_world {
let intersector = select_world(&resolve, Some(world), &packages)?;

for &world in &worlds {
intersect_world(&mut resolve, intersector, world);
}

for (_, worlds) in configs.values() {
for &world in worlds {
intersect_world(&mut resolve, intersector, world);
}
}

Some(intersector)
} else {
None
};

let mut all_worlds = worlds
.iter()
.copied()
Expand All @@ -459,10 +478,13 @@ impl ComponentGenerator<'_> {

if all_worlds.is_empty() {
// No worlds specified; pick the default one, if available:
all_worlds.insert(
select_world(&resolve, None, &packages)?,
Naming::from_full(self.full_names),
);
let world = select_world(&resolve, None, &packages)?;

if let Some(intersector) = intersector {
intersect_world(&mut resolve, intersector, world);
}

all_worlds.insert(world, Naming::from_full(self.full_names));
}

// Now that we've parsed all known WIT files and resolved all relevant
Expand Down Expand Up @@ -758,7 +780,10 @@ impl ComponentGenerator<'_> {
for (mounts, world_dir) in world_dir_mounts.iter() {
for mount in mounts {
if DEBUG_PYTHON_BINDINGS {
eprintln!("world dir path: {}", world_dir.path().display());
eprintln!(
"world dir path: {}; mount: {mount}",
world_dir.path().display()
);
}
wasi.preopened_dir(world_dir.path(), mount, DirPerms::all(), FilePerms::all())?;
}
Expand Down Expand Up @@ -970,6 +995,47 @@ fn union_world(
Ok(union_world)
}

fn intersect_world(resolve: &mut Resolve, intersector: WorldId, world: WorldId) {
let (imports_to_remove, exports_to_remove) = {
let intersector = &resolve.worlds[intersector];
let world = &resolve.worlds[world];

// Note that we make no effort to check whether the world items
// corresponding to the same keys found in `intersector` and `world`
// actually match. We only care about the keys in `intersector` and ignore
// the items they map to.

(
world
.imports
.keys()
.filter_map(|name| {
if intersector.imports.contains_key(name) {
None
} else {
Some(name.clone())
}
})
.collect::<HashSet<_>>(),
world
.exports
.keys()
.filter_map(|name| {
if intersector.exports.contains_key(name) {
None
} else {
Some(name.clone())
}
})
.collect::<HashSet<_>>(),
)
};

let world = &mut resolve.worlds[world];
world.imports.retain(|k, _| !imports_to_remove.contains(k));
world.exports.retain(|k, _| !exports_to_remove.contains(k));
}

fn add_wasi_and_stubs(
resolve: &Resolve,
worlds: &IndexSet<WorldId>,
Expand Down
Loading
Loading