Skip to content

Commit 40036ec

Browse files
authored
[csharp] Verify codegen (#738)
* Add ability to verify the code gen builds Signed-off-by: James Sturtevant <[email protected]> * formating and feature Signed-off-by: James Sturtevant <[email protected]> * Remove some unused files for more space Signed-off-by: James Sturtevant <[email protected]> * run clean up on powershell Signed-off-by: James Sturtevant <[email protected]> * Clean up after build Signed-off-by: James Sturtevant <[email protected]> * use correct working dir Signed-off-by: James Sturtevant <[email protected]> * Ignore clean output Signed-off-by: James Sturtevant <[email protected]> * skip more with errors Signed-off-by: James Sturtevant <[email protected]> * remove ci storage checks Signed-off-by: James Sturtevant <[email protected]> --------- Signed-off-by: James Sturtevant <[email protected]>
1 parent 5f90361 commit 40036ec

File tree

3 files changed

+198
-15
lines changed

3 files changed

+198
-15
lines changed

crates/csharp/Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,7 @@ anyhow = { workspace = true }
2626

2727
[dev-dependencies]
2828
test-helpers = { path = '../test-helpers' }
29+
30+
[features]
31+
default = ["aot"]
32+
aot = []

crates/csharp/src/lib.rs

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ impl Opts {
5757
struct InterfaceFragment {
5858
csharp_src: String,
5959
csharp_interop_src: String,
60+
stub: String,
6061
}
6162

6263
pub struct InterfaceTypeAndFragments {
@@ -439,24 +440,32 @@ impl WorldGenerator for CSharp {
439440

440441
files.push(&format!("{name}.cs"), indent(&src).as_bytes());
441442

442-
let generate_stub = |name: String, files: &mut Files| {
443-
let stub_file_name = format!("{name}Impl");
444-
let interface_name = CSharp::get_class_name_from_qualified_name(name.clone());
445-
let stub_class_name = format!("{interface_name}Impl");
443+
let generate_stub =
444+
|name: String, files: &mut Files, fragments: &Vec<InterfaceFragment>| {
445+
let stub_file_name = format!("{name}Impl");
446+
let interface_name = CSharp::get_class_name_from_qualified_name(name.clone());
447+
let stub_class_name = format!("{interface_name}Impl");
446448

447-
let body = format!(
448-
"// Generated by `wit-bindgen` {version}. DO NOT EDIT!
449+
let body = fragments
450+
.iter()
451+
.map(|f| f.stub.deref())
452+
.collect::<Vec<_>>()
453+
.join("\n");
454+
455+
let body = format!(
456+
"// Generated by `wit-bindgen` {version}. DO NOT EDIT!
449457
{CSHARP_IMPORTS}
450458
451459
namespace {namespace}.{name};
452460
453461
public partial class {stub_class_name} : {interface_name} {{
462+
{body}
454463
}}
455464
"
456-
);
465+
);
457466

458-
files.push(&format!("{stub_file_name}.cs"), indent(&body).as_bytes());
459-
};
467+
files.push(&format!("{stub_file_name}.cs"), indent(&body).as_bytes());
468+
};
460469

461470
// TODO: is the world Impl class useful?
462471
// if self.opts.generate_stub {
@@ -520,7 +529,7 @@ impl WorldGenerator for CSharp {
520529
files.push(&format!("{name}Interop.cs"), indent(&body).as_bytes());
521530

522531
if interface_type_and_fragments.is_export && self.opts.generate_stub {
523-
generate_stub(format!("{name}"), files);
532+
generate_stub(format!("{name}"), files, fragments);
524533
}
525534
}
526535
}
@@ -563,13 +572,15 @@ impl InterfaceGenerator<'_> {
563572
.push(InterfaceFragment {
564573
csharp_src: self.src,
565574
csharp_interop_src: self.csharp_interop_src,
575+
stub: self.stub,
566576
});
567577
}
568578

569579
fn add_world_fragment(self) {
570580
self.gen.world_fragments.push(InterfaceFragment {
571581
csharp_src: self.src,
572582
csharp_interop_src: self.csharp_interop_src,
583+
stub: self.stub,
573584
});
574585
}
575586

@@ -975,7 +986,8 @@ impl InterfaceGenerator<'_> {
975986
.collect::<Vec<_>>()
976987
.join(", ");
977988

978-
format!("public static {result_type} {name}({params})")
989+
let camel_case = name.to_upper_camel_case();
990+
format!("public static {result_type} {camel_case}({params})")
979991
}
980992
}
981993

@@ -1510,7 +1522,7 @@ impl Bindgen for FunctionBindgen<'_, '_> {
15101522
}
15111523
}
15121524

1513-
Instruction::Return { amt, func } => match func.results.len() {
1525+
Instruction::Return { amt: _, func } => match func.results.len() {
15141526
0 => (),
15151527
1 => uwriteln!(self.src, "return {};", operands[0]),
15161528
_ => {

crates/csharp/tests/codegen.rs

Lines changed: 170 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
// TODO: Implement tests similar to the other generators.
22
// This requires that we have any dependencies either included here or published to NuGet or similar.
3-
use std::path::Path;
3+
use std::{
4+
env, fs,
5+
path::{Path, PathBuf},
6+
process::{Command, Stdio},
7+
};
48
use wit_component::StringEncoding;
59

610
macro_rules! codegen_test {
@@ -31,6 +35,7 @@ macro_rules! codegen_test {
3135
"lists",
3236
"many-arguments",
3337
"multi-return",
38+
"multiversion",
3439
"option-result",
3540
"records",
3641
"rename-interface",
@@ -47,11 +52,14 @@ macro_rules! codegen_test {
4752
"result-empty",
4853
"ret-areas",
4954
"return-resource-from-export",
55+
"same-names2",
5056
"same-names5",
5157
"simple-functions",
5258
"simple-http",
5359
"simple-lists",
5460
"small-anonymous",
61+
"smoke-default",
62+
"strings",
5563
"unused-import",
5664
"use-across-interfaces",
5765
"variants",
@@ -77,6 +85,165 @@ macro_rules! codegen_test {
7785
}
7886
test_helpers::codegen_tests!();
7987

80-
fn verify(_dir: &Path, _name: &str) {
81-
// TODO?
88+
fn verify(dir: &Path, name: &str) {
89+
#[cfg(all(target_os = "windows", feature = "aot"))]
90+
aot_verify(dir, name);
91+
}
92+
93+
fn aot_verify(dir: &Path, name: &str) {
94+
let mut wasm_filename = dir.join(name);
95+
wasm_filename.set_extension("wasm");
96+
97+
fs::write(
98+
dir.join("nuget.config"),
99+
r#"<?xml version="1.0" encoding="utf-8"?>
100+
<configuration>
101+
<config>
102+
<add key="globalPackagesFolder" value=".packages" />
103+
</config>
104+
<packageSources>
105+
<!--To inherit the global NuGet package sources remove the <clear/> line below -->
106+
<clear />
107+
<add key="nuget" value="https://api.nuget.org/v3/index.json" />
108+
<add key="dotnet-experimental" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-experimental/nuget/v3/index.json" />
109+
<!--<add key="dotnet-experimental" value="C:\github\runtimelab\artifacts\packages\Debug\Shipping" />-->
110+
</packageSources>
111+
</configuration>"#,
112+
).unwrap();
113+
114+
fs::write(
115+
dir.join("rd.xml"),
116+
format!(
117+
r#"<Directives xmlns="http://schemas.microsoft.com/netfx/2013/01/metadata">
118+
<Application>
119+
<Assembly Name="{name}">
120+
</Assembly>
121+
</Application>
122+
</Directives>"#
123+
),
124+
)
125+
.unwrap();
126+
127+
let mut csproj = format!(
128+
"<Project Sdk=\"Microsoft.NET.Sdk\">
129+
130+
<PropertyGroup>
131+
<TargetFramework>net8.0</TargetFramework>
132+
<LangVersion>preview</LangVersion>
133+
<RootNamespace>{name}</RootNamespace>
134+
<ImplicitUsings>enable</ImplicitUsings>
135+
<Nullable>enable</Nullable>
136+
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
137+
</PropertyGroup>
138+
139+
<PropertyGroup>
140+
<PublishTrimmed>true</PublishTrimmed>
141+
<AssemblyName>{name}</AssemblyName>
142+
</PropertyGroup>
143+
"
144+
);
145+
146+
csproj.push_str(
147+
r#"
148+
<ItemGroup>
149+
<RdXmlFile Include="rd.xml" />
150+
</ItemGroup>
151+
152+
"#,
153+
);
154+
155+
csproj.push_str("\t<ItemGroup>\n");
156+
csproj.push_str(&format!(
157+
"\t\t<NativeLibrary Include=\"the_world_component_type.o\" />\n"
158+
));
159+
csproj.push_str("\t</ItemGroup>\n\n");
160+
161+
csproj.push_str(
162+
r#"
163+
<ItemGroup>
164+
<CustomLinkerArg Include="-Wl,--export,_initialize" />
165+
<CustomLinkerArg Include="-Wl,--no-entry" />
166+
<CustomLinkerArg Include="-mexec-model=reactor" />
167+
</ItemGroup>
168+
"#,
169+
);
170+
171+
// 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.
172+
csproj.push_str(&format!(
173+
"<Target Name=\"CleanAndDelete\" AfterTargets=\"Clean\">
174+
<!-- Remove obj folder -->
175+
<RemoveDir Directories=\"$(BaseIntermediateOutputPath)\" />
176+
<!-- Remove bin folder -->
177+
<RemoveDir Directories=\"$(BaseOutputPath)\" />
178+
<RemoveDir Directories=\"{}\" />
179+
<RemoveDir Directories=\".packages\" />
180+
</Target>
181+
182+
",
183+
wasm_filename.display()
184+
));
185+
186+
csproj.push_str(
187+
r#"
188+
<ItemGroup>
189+
<PackageReference Include="Microsoft.DotNet.ILCompiler.LLVM" Version="8.0.0-*" />
190+
<PackageReference Include="runtime.win-x64.Microsoft.DotNet.ILCompiler.LLVM" Version="8.0.0-*" />
191+
</ItemGroup>
192+
</Project>
193+
"#,
194+
);
195+
196+
fs::write(dir.join(format!("{name}.csproj")), csproj).unwrap();
197+
198+
let dotnet_root_env = "DOTNET_ROOT";
199+
let dotnet_cmd: PathBuf;
200+
match env::var(dotnet_root_env) {
201+
Ok(val) => dotnet_cmd = Path::new(&val).join("dotnet"),
202+
Err(_e) => dotnet_cmd = "dotnet".into(),
203+
}
204+
205+
let mut cmd = Command::new(dotnet_cmd.clone());
206+
207+
cmd.current_dir(&dir);
208+
209+
// add .arg("/bl") to diagnose dotnet build problems
210+
cmd.arg("build")
211+
.arg(dir.join(format!("{name}.csproj")))
212+
.arg("-r")
213+
.arg("wasi-wasm")
214+
.arg("-c")
215+
.arg("Debug")
216+
.arg("/p:PlatformTarget=AnyCPU")
217+
.arg("/p:MSBuildEnableWorkloadResolver=false")
218+
.arg("--self-contained")
219+
.arg("/p:UseAppHost=false")
220+
.arg("-o")
221+
.arg(&wasm_filename);
222+
let output = match cmd.output() {
223+
Ok(output) => output,
224+
Err(e) => panic!("failed to spawn compiler: {}", e),
225+
};
226+
227+
if !output.status.success() {
228+
println!("status: {}", output.status);
229+
println!("stdout: ------------------------------------------");
230+
println!("{}", String::from_utf8_lossy(&output.stdout));
231+
println!("stderr: ------------------------------------------");
232+
println!("{}", String::from_utf8_lossy(&output.stderr));
233+
panic!("failed to compile");
234+
}
235+
236+
let mut cmd = Command::new(dotnet_cmd);
237+
match cmd
238+
.stdout(Stdio::null())
239+
.current_dir(&dir)
240+
.arg("clean")
241+
.spawn()
242+
{
243+
Err(e) => println!(
244+
"failed to clean project which may cause disk pressure in CI. {}",
245+
e
246+
),
247+
_ => {}
248+
}
82249
}

0 commit comments

Comments
 (0)