Skip to content
Draft
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
16 changes: 15 additions & 1 deletion crates/csharp/src/csproj.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ pub struct CSProjectLLVMBuilder {
clean_targets: bool,
world_name: String,
binary: bool,
skip_wit_component: bool,
}

pub struct CSProjectMonoBuilder {
Expand All @@ -31,6 +32,7 @@ impl CSProject {
clean_targets: false,
world_name: world_name.to_string(),
binary: false,
skip_wit_component: false,
}
}

Expand Down Expand Up @@ -69,6 +71,14 @@ impl CSProjectLLVMBuilder {
"<OutputType>Library</OutputType>"
};

let component_type_linker_arg = if self.skip_wit_component {
"<CustomLinkerArg Include=\"-Wl,--skip-wit-component\" />".to_string()
} else {
format!(
"<CustomLinkerArg Include=\"-Wl,--component-type,{camel}_component_type.wit\" />"
)
};

let mut csproj = format!(
"<Project Sdk=\"Microsoft.NET.Sdk\">

Expand All @@ -94,7 +104,7 @@ impl CSProjectLLVMBuilder {
</ItemGroup>

<ItemGroup>
<CustomLinkerArg Include=\"-Wl,--component-type,{camel}_component_type.wit\" />
{component_type_linker_arg}
</ItemGroup>
"
);
Expand Down Expand Up @@ -178,6 +188,10 @@ impl CSProjectLLVMBuilder {
self.binary = true;
}

pub fn skip_wit_component(&mut self) {
self.skip_wit_component = true;
}

pub fn clean(&mut self) -> &mut Self {
self.clean_targets = true;

Expand Down
17 changes: 16 additions & 1 deletion crates/test/src/csharp.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
use crate::{Compile, LanguageMethods, Runner, Verify};
use anyhow::Result;
use heck::*;
use serde::Deserialize;
use std::env;
use std::fs;
use std::path::Path;
use std::process::Command;

pub struct Csharp;

#[derive(Default, Deserialize)]
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
struct LangConfig {
skip_wit_component: bool,
}

fn dotnet() -> Command {
let dotnet_cmd = match env::var("DOTNET_ROOT") {
Ok(val) => Path::new(&val).join("dotnet"),
Expand Down Expand Up @@ -59,6 +66,7 @@ impl LanguageMethods for Csharp {
}

fn compile(&self, runner: &Runner, compile: &Compile<'_>) -> Result<()> {
let config = compile.component.deserialize_lang_config::<LangConfig>()?;
let world_name = &compile.component.bindgen.world;
let path = &compile.component.path;
let test_dir = &compile.bindings_dir;
Expand All @@ -75,6 +83,9 @@ impl LanguageMethods for Csharp {
let mut csproj =
wit_bindgen_csharp::CSProject::new(test_dir.to_path_buf(), &assembly_name, world_name);
csproj.aot();
if config.skip_wit_component {
csproj.skip_wit_component();
}
csproj.generate()?;

let mut cmd = dotnet();
Expand Down Expand Up @@ -109,7 +120,11 @@ impl LanguageMethods for Csharp {

runner.run_command(&mut cmd)?;

fs::copy(&wasm_filename, &compile.output)?;
if config.skip_wit_component {
runner.convert_p1_to_component_appending_type_metadata(&wasm_filename, compile)?;
} else {
fs::copy(&wasm_filename, &compile.output)?;
}

Ok(())
}
Expand Down
26 changes: 25 additions & 1 deletion crates/test/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1081,14 +1081,38 @@ status: {}",
///
/// Stores the output at `compile.output`.
fn convert_p1_to_component(&self, p1: &Path, compile: &Compile<'_>) -> Result<()> {
self.convert_p1_to_component_impl(p1, compile, false)
}

/// Converts the WASIp1 module at `p1` to a component, appending component
/// type metadata from the test WIT even if existing metadata is present.
///
/// Some toolchains emit incomplete or stale component type metadata even
/// when asked to skip componentization, while still needing their own
/// metadata for WASI imports. Use this when the test harness must add
/// guest-world metadata without discarding toolchain metadata.
fn convert_p1_to_component_appending_type_metadata(
&self,
p1: &Path,
compile: &Compile<'_>,
) -> Result<()> {
self.convert_p1_to_component_impl(p1, compile, true)
}

fn convert_p1_to_component_impl(
&self,
p1: &Path,
compile: &Compile<'_>,
append_component_type: bool,
) -> Result<()> {
let mut resolve = wit_parser::Resolve::default();
let (pkg, _) = resolve
.push_path(&compile.component.bindgen.wit_path)
.context("failed to load WIT")?;
let world = resolve.select_world(&[pkg], Some(&compile.component.bindgen.world))?;
let mut module = fs::read(&p1).context("failed to read wasm file")?;

if !has_component_type_sections(&module) {
if append_component_type || !has_component_type_sections(&module) {
let encoded =
wit_component::metadata::encode(&resolve, world, StringEncoding::UTF8, None)?;
let section = wasm_encoder::CustomSection {
Expand Down
123 changes: 123 additions & 0 deletions tests/runtime/map/test.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
//@ wasmtime-flags = '-Wcomponent-model-map'
//@ [lang]
//@ skip-wit-component = true

using System.Diagnostics;
using System.Text;

namespace TestWorld.wit.Exports.test.maps
{
public class ToTestExportsImpl : IToTestExports
{
public static Dictionary<string, uint> NamedRoundtrip(Dictionary<uint, string> a)
{
Debug.Assert(a.Count == 2);
Debug.Assert(a[1] == "uno");
Debug.Assert(a[2] == "two");

return a.ToDictionary(entry => entry.Value, entry => entry.Key);
}

public static Dictionary<string, byte[]> BytesRoundtrip(Dictionary<string, byte[]> a)
{
Debug.Assert(a.Count == 2);
Debug.Assert(a["hello"].SequenceEqual(Encoding.UTF8.GetBytes("world")));
Debug.Assert(a["bin"].SequenceEqual(new byte[] { 0, 1, 2 }));

return a;
}

public static Dictionary<uint, string> EmptyRoundtrip(Dictionary<uint, string> a)
{
Debug.Assert(a.Count == 0);
return a;
}

public static Dictionary<string, uint?> OptionRoundtrip(Dictionary<string, uint?> a)
{
Debug.Assert(a.Count == 2);
Debug.Assert(a["some"] == 42);
Debug.Assert(a["none"] == null);

return a;
}

public static IToTestExports.LabeledEntry RecordRoundtrip(IToTestExports.LabeledEntry a)
{
Debug.Assert(a.label == "test-label");
Debug.Assert(a.values.Count == 2);
Debug.Assert(a.values[10] == "ten");
Debug.Assert(a.values[20] == "twenty");

return a;
}

public static Dictionary<string, uint> InlineRoundtrip(Dictionary<uint, string> a)
{
return a.ToDictionary(entry => entry.Value, entry => entry.Key);
}

public static Dictionary<uint, string> LargeRoundtrip(Dictionary<uint, string> a)
{
Debug.Assert(a.Count == 100);
return a;
}

public static (Dictionary<string, uint>, Dictionary<string, byte[]>) MultiParamRoundtrip(
Dictionary<uint, string> a,
Dictionary<string, byte[]> b)
{
Debug.Assert(a.Count == 2);
Debug.Assert(b.Count == 1);

return (a.ToDictionary(entry => entry.Value, entry => entry.Key), b);
}

public static Dictionary<string, Dictionary<uint, string>> NestedRoundtrip(
Dictionary<string, Dictionary<uint, string>> a)
{
Debug.Assert(a.Count == 2);
Debug.Assert(a["group-a"].Count == 2);
Debug.Assert(a["group-a"][1] == "one");
Debug.Assert(a["group-a"][2] == "two");
Debug.Assert(a["group-b"].Count == 1);
Debug.Assert(a["group-b"][10] == "ten");

return a;
}

public static IToTestExports.MapOrString VariantRoundtrip(IToTestExports.MapOrString a)
{
return a;
}

public static Dictionary<uint, string> ResultRoundtrip(
Result<Dictionary<uint, string>, string> a)
{
if (a.IsErr)
{
throw new WitException<string>(a.AsErr, 0);
}

return a.AsOk;
}

public static (Dictionary<uint, string>, ulong) TupleRoundtrip(
(Dictionary<uint, string>, ulong) a)
{
Debug.Assert(a.Item1.Count == 1);
Debug.Assert(a.Item1[7] == "seven");
Debug.Assert(a.Item2 == 42);

return a;
}

public static Dictionary<uint, string> SingleEntryRoundtrip(Dictionary<uint, string> a)
{
Debug.Assert(a.Count == 1);
Debug.Assert(a[99] == "ninety-nine");

return a;
}
}
}
Loading