diff --git a/crates/csharp/Cargo.toml b/crates/csharp/Cargo.toml index fda3ed2c7..5b46e43f1 100644 --- a/crates/csharp/Cargo.toml +++ b/crates/csharp/Cargo.toml @@ -26,3 +26,7 @@ anyhow = { workspace = true } [dev-dependencies] test-helpers = { path = '../test-helpers' } + +[features] +default = ["aot"] +aot = [] \ No newline at end of file diff --git a/crates/csharp/src/lib.rs b/crates/csharp/src/lib.rs index 1dc6b7c96..b1564f884 100644 --- a/crates/csharp/src/lib.rs +++ b/crates/csharp/src/lib.rs @@ -57,6 +57,7 @@ impl Opts { struct InterfaceFragment { csharp_src: String, csharp_interop_src: String, + stub: String, } pub struct InterfaceTypeAndFragments { @@ -439,24 +440,32 @@ impl WorldGenerator for CSharp { files.push(&format!("{name}.cs"), indent(&src).as_bytes()); - let generate_stub = |name: String, files: &mut Files| { - let stub_file_name = format!("{name}Impl"); - let interface_name = CSharp::get_class_name_from_qualified_name(name.clone()); - let stub_class_name = format!("{interface_name}Impl"); + let generate_stub = + |name: String, files: &mut Files, fragments: &Vec| { + let stub_file_name = format!("{name}Impl"); + let interface_name = CSharp::get_class_name_from_qualified_name(name.clone()); + let stub_class_name = format!("{interface_name}Impl"); - let body = format!( - "// Generated by `wit-bindgen` {version}. DO NOT EDIT! + let body = fragments + .iter() + .map(|f| f.stub.deref()) + .collect::>() + .join("\n"); + + let body = format!( + "// Generated by `wit-bindgen` {version}. DO NOT EDIT! {CSHARP_IMPORTS} namespace {namespace}.{name}; public partial class {stub_class_name} : {interface_name} {{ + {body} }} " - ); + ); - files.push(&format!("{stub_file_name}.cs"), indent(&body).as_bytes()); - }; + files.push(&format!("{stub_file_name}.cs"), indent(&body).as_bytes()); + }; // TODO: is the world Impl class useful? // if self.opts.generate_stub { @@ -520,7 +529,7 @@ impl WorldGenerator for CSharp { files.push(&format!("{name}Interop.cs"), indent(&body).as_bytes()); if interface_type_and_fragments.is_export && self.opts.generate_stub { - generate_stub(format!("{name}"), files); + generate_stub(format!("{name}"), files, fragments); } } } @@ -563,6 +572,7 @@ impl InterfaceGenerator<'_> { .push(InterfaceFragment { csharp_src: self.src, csharp_interop_src: self.csharp_interop_src, + stub: self.stub, }); } @@ -570,6 +580,7 @@ impl InterfaceGenerator<'_> { self.gen.world_fragments.push(InterfaceFragment { csharp_src: self.src, csharp_interop_src: self.csharp_interop_src, + stub: self.stub, }); } @@ -975,7 +986,8 @@ impl InterfaceGenerator<'_> { .collect::>() .join(", "); - format!("public static {result_type} {name}({params})") + let camel_case = name.to_upper_camel_case(); + format!("public static {result_type} {camel_case}({params})") } } @@ -1510,7 +1522,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { } } - Instruction::Return { amt, func } => match func.results.len() { + Instruction::Return { amt: _, func } => match func.results.len() { 0 => (), 1 => uwriteln!(self.src, "return {};", operands[0]), _ => { diff --git a/crates/csharp/tests/codegen.rs b/crates/csharp/tests/codegen.rs index 0e2512d9f..7249ac9f5 100644 --- a/crates/csharp/tests/codegen.rs +++ b/crates/csharp/tests/codegen.rs @@ -1,6 +1,10 @@ // TODO: Implement tests similar to the other generators. // This requires that we have any dependencies either included here or published to NuGet or similar. -use std::path::Path; +use std::{ + env, fs, + path::{Path, PathBuf}, + process::{Command, Stdio}, +}; use wit_component::StringEncoding; macro_rules! codegen_test { @@ -31,6 +35,7 @@ macro_rules! codegen_test { "lists", "many-arguments", "multi-return", + "multiversion", "option-result", "records", "rename-interface", @@ -47,11 +52,14 @@ macro_rules! codegen_test { "result-empty", "ret-areas", "return-resource-from-export", + "same-names2", "same-names5", "simple-functions", "simple-http", "simple-lists", "small-anonymous", + "smoke-default", + "strings", "unused-import", "use-across-interfaces", "variants", @@ -77,6 +85,165 @@ macro_rules! codegen_test { } test_helpers::codegen_tests!(); -fn verify(_dir: &Path, _name: &str) { - // TODO? +fn verify(dir: &Path, name: &str) { + #[cfg(all(target_os = "windows", feature = "aot"))] + aot_verify(dir, name); +} + +fn aot_verify(dir: &Path, name: &str) { + let mut wasm_filename = dir.join(name); + wasm_filename.set_extension("wasm"); + + fs::write( + dir.join("nuget.config"), + r#" + + + + + + + + + + + + "#, + ).unwrap(); + + fs::write( + dir.join("rd.xml"), + format!( + r#" + + + + + "# + ), + ) + .unwrap(); + + let mut csproj = format!( + " + + + net8.0 + preview + {name} + enable + enable + true + + + + true + {name} + +" + ); + + csproj.push_str( + r#" + + + + +"#, + ); + + csproj.push_str("\t\n"); + csproj.push_str(&format!( + "\t\t\n" + )); + csproj.push_str("\t\n\n"); + + csproj.push_str( + r#" + + + + + + "#, + ); + + // In CI we run out of disk space if we don't clean up the files, we don't need to keep any of it around. + csproj.push_str(&format!( + " + + + + + + + + +", + wasm_filename.display() + )); + + csproj.push_str( + r#" + + + + + + "#, + ); + + fs::write(dir.join(format!("{name}.csproj")), csproj).unwrap(); + + let dotnet_root_env = "DOTNET_ROOT"; + let dotnet_cmd: PathBuf; + match env::var(dotnet_root_env) { + Ok(val) => dotnet_cmd = Path::new(&val).join("dotnet"), + Err(_e) => dotnet_cmd = "dotnet".into(), + } + + let mut cmd = Command::new(dotnet_cmd.clone()); + + cmd.current_dir(&dir); + + // add .arg("/bl") to diagnose dotnet build problems + cmd.arg("build") + .arg(dir.join(format!("{name}.csproj"))) + .arg("-r") + .arg("wasi-wasm") + .arg("-c") + .arg("Debug") + .arg("/p:PlatformTarget=AnyCPU") + .arg("/p:MSBuildEnableWorkloadResolver=false") + .arg("--self-contained") + .arg("/p:UseAppHost=false") + .arg("-o") + .arg(&wasm_filename); + let output = match cmd.output() { + Ok(output) => output, + Err(e) => panic!("failed to spawn compiler: {}", e), + }; + + if !output.status.success() { + println!("status: {}", output.status); + println!("stdout: ------------------------------------------"); + println!("{}", String::from_utf8_lossy(&output.stdout)); + println!("stderr: ------------------------------------------"); + println!("{}", String::from_utf8_lossy(&output.stderr)); + panic!("failed to compile"); + } + + let mut cmd = Command::new(dotnet_cmd); + match cmd + .stdout(Stdio::null()) + .current_dir(&dir) + .arg("clean") + .spawn() + { + Err(e) => println!( + "failed to clean project which may cause disk pressure in CI. {}", + e + ), + _ => {} + } }