Skip to content

Commit 60a1314

Browse files
authored
Support language-specific configuration in wit-bindgen test (#1230)
This commit extends the configuration format with a `lang` field to be able to pass things like compiler flags. To help port one of the Rust tests I plan on adding eventual functionality for allowing Rust tests to specify an auxiliary file to build as a crate.
1 parent 70b7aeb commit 60a1314

File tree

6 files changed

+120
-14
lines changed

6 files changed

+120
-14
lines changed

crates/test/src/c.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1+
use crate::config::StringList;
12
use crate::{Compile, Kind, LanguageMethods, Runner, Verify};
23
use anyhow::Result;
34
use clap::Parser;
45
use heck::ToSnakeCase;
6+
use serde::Deserialize;
57
use std::env;
68
use std::path::PathBuf;
79
use std::process::Command;
@@ -17,6 +19,15 @@ pub struct C;
1719

1820
pub struct Cpp;
1921

22+
/// C/C++-specific configuration of component files
23+
#[derive(Default, Deserialize)]
24+
#[serde(deny_unknown_fields)]
25+
struct LangConfig {
26+
/// Space-separated list or array of compiler flags to pass.
27+
#[serde(default)]
28+
cflags: StringList,
29+
}
30+
2031
fn clang(runner: &Runner<'_>) -> PathBuf {
2132
match &runner.opts.c.wasi_sdk_path {
2233
Some(path) => path.join("bin/wasm32-wasip2-clang"),
@@ -124,6 +135,8 @@ fn prepare(runner: &mut Runner<'_>, compiler: PathBuf) -> Result<()> {
124135
}
125136

126137
fn compile(runner: &Runner<'_>, compile: &Compile<'_>, compiler: PathBuf) -> Result<()> {
138+
let config = compile.component.deserialize_lang_config::<LangConfig>()?;
139+
127140
// Compile the C-based bindings to an object file.
128141
let bindings_object = compile.output.with_extension("bindings.o");
129142
let mut cmd = Command::new(clang(runner));
@@ -163,6 +176,9 @@ fn compile(runner: &Runner<'_>, compile: &Compile<'_>, compiler: PathBuf) -> Res
163176
.arg("-g")
164177
.arg("-o")
165178
.arg(&compile.output);
179+
for flag in Vec::from(config.cflags) {
180+
cmd.arg(flag);
181+
}
166182
match compile.component.kind {
167183
Kind::Runner => {}
168184
Kind::Test => {

crates/test/src/config.rs

Lines changed: 30 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -30,33 +30,49 @@ use anyhow::Context;
3030
use anyhow::Result;
3131
use serde::de::DeserializeOwned;
3232
use serde::Deserialize;
33+
use std::collections::HashMap;
3334

3435
/// Configuration that can be placed at the top of runtime tests in source
35-
/// language files. This is currently language-agnostic.
36+
/// language files.
37+
///
38+
/// This is a union of language-agnostic and language-specific configuration.
39+
/// Language-agnostic configuration can be bindings generator arguments:
40+
///
41+
/// ```toml
42+
/// args = '--foo --bar'
43+
/// # or ...
44+
/// args = ['--foo', '--bar']
45+
/// ```
46+
///
47+
/// but languages may each have their own configuration:
48+
///
49+
/// ```toml
50+
/// [lang]
51+
/// rustflags = '-O'
52+
/// ```
53+
///
54+
/// The `Component::deserialize_lang_config` helper is used to deserialize the
55+
/// `lang` field here.
3656
#[derive(Default, Deserialize)]
3757
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
38-
pub struct RuntimeTestConfig {
58+
pub struct RuntimeTestConfig<T = HashMap<String, toml::Value>> {
3959
/// Extra command line arguments to pass to the language-specific bindings
4060
/// generator.
4161
///
4262
/// This is either a string which is whitespace delimited or it's an array
4363
/// of strings. By default no extra arguments are passed.
4464
#[serde(default)]
4565
pub args: StringList,
46-
//
47-
// Maybe add something like this eventually if necessary? For example plumb
48-
// arbitrary configuration from tests to the "compile" backend. This would
49-
// then thread through as `Compile` and could be used to pass compiler flags
50-
// for example.
51-
//
52-
// lang: HashMap<String, String>,
5366

54-
// ...
55-
//
56-
// or alternatively could also have something dedicated like:
57-
// compile_flags: StringList,
67+
/// Language-specific configuration
5868
//
59-
// unclear! This should be expanded on over time as necessary.
69+
// Note that this is an `Option<T>` where `T` defaults to a catch-all hash
70+
// map with a bunch of toml values in it. The idea here is that tests are
71+
// first parsed with the `HashMap` configuration. If that's not present
72+
// then the language uses its default configuration but if it is present
73+
// then the fields are re-parsed where `T` is specific-per-language. The
74+
// `Component::deserialize_lang_config` helper is intended for this.
75+
pub lang: Option<T>,
6076
}
6177

6278
#[derive(Deserialize)]

crates/test/src/lib.rs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,12 @@ struct Component {
151151
/// The WIT world that's being used with this component, loaded from
152152
/// `test.wit`.
153153
bindgen: Bindgen,
154+
155+
/// The contents of the test file itself.
156+
contents: String,
157+
158+
/// The contents of the test file itself.
159+
lang_config: Option<HashMap<String, toml::Value>>,
154160
}
155161

156162
#[derive(Clone)]
@@ -390,6 +396,8 @@ impl Runner<'_> {
390396
language,
391397
bindgen,
392398
kind,
399+
contents,
400+
lang_config: config.lang,
393401
})
394402
}
395403

@@ -1084,3 +1092,34 @@ fn write_if_different(path: &Path, contents: impl AsRef<[u8]>) -> Result<bool> {
10841092
fs::write(path, contents).with_context(|| format!("failed to write {path:?}"))?;
10851093
Ok(true)
10861094
}
1095+
1096+
impl Component {
1097+
/// Helper to convert `RuntimeTestConfig` to a `RuntimeTestConfig<T>` and
1098+
/// then extract the `T`.
1099+
///
1100+
/// This is called from within each language's implementation with a
1101+
/// specific `T` necessary for that language.
1102+
fn deserialize_lang_config<T>(&self) -> Result<T>
1103+
where
1104+
T: Default + serde::de::DeserializeOwned,
1105+
{
1106+
// If this test has no language-specific configuration then return this
1107+
// language's default configuration.
1108+
if self.lang_config.is_none() {
1109+
return Ok(T::default());
1110+
}
1111+
1112+
// Otherwise re-parse the TOML at the top of the file but this time
1113+
// with the specific `T` that we're interested in. This is expected
1114+
// to then produce a value in the `lang` field since
1115+
// `self.lang_config.is_some()` is true.
1116+
let config = config::parse_test_config::<config::RuntimeTestConfig<T>>(
1117+
&self.contents,
1118+
self.language
1119+
.obj()
1120+
.comment_prefix_for_test_config()
1121+
.unwrap(),
1122+
)?;
1123+
Ok(config.lang.unwrap())
1124+
}
1125+
}

crates/test/src/rust.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1+
use crate::config::StringList;
12
use crate::{Compile, Kind, LanguageMethods, Runner, Verify};
23
use anyhow::Result;
34
use clap::Parser;
45
use heck::ToSnakeCase;
6+
use serde::Deserialize;
57
use std::env;
68
use std::path::PathBuf;
79
use std::process::Command;
@@ -30,6 +32,17 @@ pub struct State {
3032
wit_bindgen_deps: Vec<PathBuf>,
3133
}
3234

35+
/// Rust-specific configuration of component files
36+
#[derive(Default, Deserialize)]
37+
#[serde(deny_unknown_fields)]
38+
struct RustConfig {
39+
/// Space-separated list or array of compiler flags to pass.
40+
#[serde(default)]
41+
rustflags: StringList,
42+
// TODO: something about auxiliary builds of crates to test cross-crate
43+
// behavior.
44+
}
45+
3346
impl LanguageMethods for Rust {
3447
fn display(&self) -> &str {
3548
"rust"
@@ -148,6 +161,8 @@ path = 'lib.rs'
148161
fn compile(&self, runner: &Runner<'_>, compile: &Compile) -> Result<()> {
149162
let mut cmd = runner.rustc(Edition::E2021);
150163

164+
let config = compile.component.deserialize_lang_config::<RustConfig>()?;
165+
151166
// If this rust target doesn't natively produce a component then place
152167
// the compiler output in a temporary location which is componentized
153168
// later on.
@@ -168,6 +183,10 @@ path = 'lib.rs'
168183
.arg(&compile.component.path)
169184
.arg("-o")
170185
.arg(&output);
186+
for flag in Vec::from(config.rustflags) {
187+
cmd.arg(flag);
188+
}
189+
171190
match compile.component.kind {
172191
Kind::Runner => {}
173192
Kind::Test => {

tests/runtime-new/demo/runner-opt.c

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
//@ [lang]
2+
//@ cflags = '-O'
3+
4+
#include <runner.h>
5+
6+
int main() {
7+
a_b_the_test_x();
8+
}

tests/runtime-new/demo/runner-opt.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
//@ [lang]
2+
//@ rustflags = '-O'
3+
4+
include!(env!("BINDINGS"));
5+
6+
fn main() {
7+
a::b::the_test::x();
8+
}

0 commit comments

Comments
 (0)