Skip to content

Commit 2728f93

Browse files
authored
Support multiple package in a single WIT source (#1577)
* Squashed and rebased previous work onto HEAD * Updated push_path and push_dir return signature * Fix pretty printing signature and output * Fix some more comments * Added ResloverKind and UnresolvedPackageGroup * SourceMap is actually per UnresolvedPackageGroup now * New append method is now topologically sorted * Add some tests * Cargo fmt * Fix cargo fmt crankiness * Resolve final comments
1 parent ebfcffd commit 2728f93

File tree

66 files changed

+2482
-484
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

66 files changed

+2482
-484
lines changed

crates/wit-component/src/encoding.rs

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2133,33 +2133,33 @@ mod test {
21332133

21342134
use super::*;
21352135
use std::path::Path;
2136-
use wit_parser::UnresolvedPackage;
2136+
use wit_parser::UnresolvedPackageGroup;
21372137

21382138
#[test]
21392139
fn it_renames_imports() {
21402140
let mut resolve = Resolve::new();
2141-
let pkg = resolve
2142-
.push(
2143-
UnresolvedPackage::parse(
2144-
Path::new("test.wit"),
2145-
r#"
2141+
let UnresolvedPackageGroup {
2142+
mut packages,
2143+
source_map,
2144+
} = UnresolvedPackageGroup::parse(
2145+
Path::new("test.wit"),
2146+
r#"
21462147
package test:wit;
21472148
21482149
interface i {
2149-
f: func();
2150+
f: func();
21502151
}
21512152
21522153
world test {
2153-
import i;
2154-
import foo: interface {
2155-
f: func();
2156-
}
2154+
import i;
2155+
import foo: interface {
2156+
f: func();
2157+
}
21572158
}
21582159
"#,
2159-
)
2160-
.unwrap(),
2161-
)
2162-
.unwrap();
2160+
)
2161+
.unwrap();
2162+
let pkg = resolve.push(packages.remove(0), &source_map).unwrap();
21632163

21642164
let world = resolve.select_world(pkg, None).unwrap();
21652165

crates/wit-component/src/lib.rs

Lines changed: 45 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@
55
use std::str::FromStr;
66
use std::{borrow::Cow, fmt::Display};
77

8-
use anyhow::{bail, Result};
8+
use anyhow::{bail, Context, Result};
99
use wasm_encoder::{CanonicalOption, Encode, Section};
10-
use wit_parser::{Resolve, WorldId};
10+
use wit_parser::{parse_use_path, PackageId, ParsedUsePath, Resolve, WorldId};
1111

1212
mod encoding;
1313
mod gc;
@@ -79,6 +79,43 @@ impl From<StringEncoding> for wasm_encoder::CanonicalOption {
7979
}
8080
}
8181

82+
/// Handles world name resolution for cases when multiple packages may have been resolved. If this
83+
/// is the case, and we're dealing with input that contains a user-supplied world name (like via a
84+
/// CLI command, for instance), we want to ensure that the world name follows the following rules:
85+
///
86+
/// * If there is a single resolved package with a single world, the world name name MAY be
87+
/// omitted.
88+
/// * If there is a single resolved package with multiple worlds, the world name MUST be supplied,
89+
/// but MAY or MAY NOT be fully-qualified.
90+
/// * If there are multiple resolved packages, the world name MUST be fully-qualified.
91+
pub fn resolve_world_from_name(
92+
resolve: &Resolve,
93+
resolved_packages: Vec<PackageId>,
94+
world_name: Option<&str>,
95+
) -> Result<WorldId> {
96+
match resolved_packages.len() {
97+
0 => bail!("all of the supplied WIT source files were empty"),
98+
1 => resolve.select_world(resolved_packages[0], world_name.as_deref()),
99+
_ => match world_name.as_deref() {
100+
Some(name) => {
101+
let world_path = parse_use_path(name).with_context(|| {
102+
format!("failed to parse world specifier `{name}`")
103+
})?;
104+
match world_path {
105+
ParsedUsePath::Name(name) => bail!("the world specifier must be of the fully-qualified, id-based form (ex: \"wasi:http/proxy\" rather than \"proxy\"); you used {name}"),
106+
ParsedUsePath::Package(pkg_name, _) => {
107+
match resolve.package_names.get(&pkg_name) {
108+
Some(pkg_id) => resolve.select_world(pkg_id.clone(), world_name.as_deref()),
109+
None => bail!("the world specifier you provided named {pkg_name}, but no package with that name was found"),
110+
}
111+
}
112+
}
113+
}
114+
None => bail!("the supplied WIT source files describe multiple packages; please provide a fully-qualified world-specifier to the `embed` command"),
115+
},
116+
}
117+
}
118+
82119
/// A producer section to be added to all modules and components synthesized by
83120
/// this crate
84121
pub(crate) fn base_producers() -> wasm_metadata::Producers {
@@ -112,7 +149,7 @@ mod tests {
112149

113150
use anyhow::Result;
114151
use wasmparser::Payload;
115-
use wit_parser::{Resolve, UnresolvedPackage};
152+
use wit_parser::{Resolve, UnresolvedPackageGroup};
116153

117154
use super::{embed_component_metadata, StringEncoding};
118155

@@ -147,8 +184,11 @@ world test-world {}
147184

148185
// Parse pre-canned WIT to build resolver
149186
let mut resolver = Resolve::default();
150-
let pkg = UnresolvedPackage::parse(&Path::new("in-code.wit"), COMPONENT_WIT)?;
151-
let pkg_id = resolver.push(pkg)?;
187+
let UnresolvedPackageGroup {
188+
mut packages,
189+
source_map,
190+
} = UnresolvedPackageGroup::parse(&Path::new("in-code.wit"), COMPONENT_WIT)?;
191+
let pkg_id = resolver.push(packages.remove(0), &source_map)?;
152192
let world = resolver.select_world(pkg_id, Some("test-world").into())?;
153193

154194
// Embed component metadata

crates/wit-component/src/metadata.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@
4242
//! the three arguments originally passed to `encode`.
4343
4444
use crate::validation::BARE_FUNC_MODULE_NAME;
45-
use crate::{DecodedWasm, StringEncoding};
45+
use crate::{resolve_world_from_name, DecodedWasm, StringEncoding};
4646
use anyhow::{bail, Context, Result};
4747
use indexmap::IndexMap;
4848
use std::borrow::Cow;
@@ -259,12 +259,12 @@ impl Bindgen {
259259
let world_name = reader.read_string()?;
260260
wasm = &data[reader.original_position()..];
261261

262-
let (r, pkg) = match crate::decode(wasm)? {
263-
DecodedWasm::WitPackage(resolve, pkg) => (resolve, pkg),
264-
DecodedWasm::Component(..) => bail!("expected an encoded wit package"),
262+
let (r, pkgs) = match crate::decode(wasm)? {
263+
DecodedWasm::WitPackages(resolve, pkgs) => (resolve, pkgs),
264+
DecodedWasm::Component(..) => bail!("expected encoded wit package(s)"),
265265
};
266266
resolve = r;
267-
world = resolve.packages[pkg].worlds[world_name];
267+
world = resolve_world_from_name(&resolve, pkgs, Some(world_name.into()))?;
268268
}
269269

270270
// Current format where `data` is a wasm component itself.

crates/wit-component/src/printing.rs

Lines changed: 49 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -50,37 +50,56 @@ impl WitPrinter {
5050
self
5151
}
5252

53-
/// Print the given WIT package to a string.
54-
pub fn print(&mut self, resolve: &Resolve, pkgid: PackageId) -> Result<String> {
55-
let pkg = &resolve.packages[pkgid];
56-
self.print_docs(&pkg.docs);
57-
self.output.push_str("package ");
58-
self.print_name(&pkg.name.namespace);
59-
self.output.push_str(":");
60-
self.print_name(&pkg.name.name);
61-
if let Some(version) = &pkg.name.version {
62-
self.output.push_str(&format!("@{version}"));
63-
}
64-
self.print_semicolon();
65-
self.output.push_str("\n\n");
66-
for (name, id) in pkg.interfaces.iter() {
67-
self.print_docs(&resolve.interfaces[*id].docs);
68-
self.print_stability(&resolve.interfaces[*id].stability);
69-
self.output.push_str("interface ");
70-
self.print_name(name);
71-
self.output.push_str(" {\n");
72-
self.print_interface(resolve, *id)?;
73-
writeln!(&mut self.output, "}}\n")?;
74-
}
53+
/// Print a set of one or more WIT packages into a string.
54+
pub fn print(&mut self, resolve: &Resolve, pkg_ids: &[PackageId]) -> Result<String> {
55+
let has_multiple_packages = pkg_ids.len() > 1;
56+
for (i, pkg_id) in pkg_ids.into_iter().enumerate() {
57+
if i > 0 {
58+
self.output.push_str("\n\n");
59+
}
7560

76-
for (name, id) in pkg.worlds.iter() {
77-
self.print_docs(&resolve.worlds[*id].docs);
78-
self.print_stability(&resolve.worlds[*id].stability);
79-
self.output.push_str("world ");
80-
self.print_name(name);
81-
self.output.push_str(" {\n");
82-
self.print_world(resolve, *id)?;
83-
writeln!(&mut self.output, "}}")?;
61+
let pkg = &resolve.packages[pkg_id.clone()];
62+
self.print_docs(&pkg.docs);
63+
self.output.push_str("package ");
64+
self.print_name(&pkg.name.namespace);
65+
self.output.push_str(":");
66+
self.print_name(&pkg.name.name);
67+
if let Some(version) = &pkg.name.version {
68+
self.output.push_str(&format!("@{version}"));
69+
}
70+
71+
if has_multiple_packages {
72+
self.output.push_str("{");
73+
self.output.indent += 1
74+
} else {
75+
self.print_semicolon();
76+
self.output.push_str("\n\n");
77+
}
78+
79+
for (name, id) in pkg.interfaces.iter() {
80+
self.print_docs(&resolve.interfaces[*id].docs);
81+
self.print_stability(&resolve.interfaces[*id].stability);
82+
self.output.push_str("interface ");
83+
self.print_name(name);
84+
self.output.push_str(" {\n");
85+
self.print_interface(resolve, *id)?;
86+
writeln!(&mut self.output, "}}\n")?;
87+
}
88+
89+
for (name, id) in pkg.worlds.iter() {
90+
self.print_docs(&resolve.worlds[*id].docs);
91+
self.print_stability(&resolve.worlds[*id].stability);
92+
self.output.push_str("world ");
93+
self.print_name(name);
94+
self.output.push_str(" {\n");
95+
self.print_world(resolve, *id)?;
96+
writeln!(&mut self.output, "}}")?;
97+
}
98+
99+
if has_multiple_packages {
100+
self.output.push_str("}");
101+
self.output.indent -= 1
102+
}
84103
}
85104

86105
Ok(std::mem::take(&mut self.output).into())

crates/wit-component/src/semver_check.rs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use crate::{
22
dummy_module, embed_component_metadata, encoding::encode_world, ComponentEncoder,
33
StringEncoding,
44
};
5-
use anyhow::{Context, Result};
5+
use anyhow::{bail, Context, Result};
66
use wasm_encoder::{ComponentBuilder, ComponentExportKind, ComponentTypeRef};
77
use wasmparser::{Validator, WasmFeatures};
88
use wit_parser::{Resolve, WorldId};
@@ -47,6 +47,18 @@ pub fn semver_check(mut resolve: Resolve, prev: WorldId, new: WorldId) -> Result
4747
pkg.name.version = None;
4848
}
4949

50+
let old_pkg_id = resolve.worlds[prev]
51+
.package
52+
.context("old world not in named package")?;
53+
let old_pkg_name = &resolve.packages[old_pkg_id].name;
54+
let new_pkg_id = resolve.worlds[new]
55+
.package
56+
.context("new world not in named package")?;
57+
let new_pkg_name = &resolve.packages[new_pkg_id].name;
58+
if old_pkg_id != new_pkg_id {
59+
bail!("the old world is in package {old_pkg_name}, which is not the same as the new world, which is in package {new_pkg_name}", )
60+
}
61+
5062
// Component that will be validated at the end.
5163
let mut root_component = ComponentBuilder::default();
5264

0 commit comments

Comments
 (0)