Skip to content

Commit b6fadcf

Browse files
author
Timmy Silesmo
committed
adds new WasmImportLinkAttribute for C# and initial support for mono runtime.
1 parent 0024241 commit b6fadcf

File tree

6 files changed

+333
-23
lines changed

6 files changed

+333
-23
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,7 @@ CLI tool to generate bindings for WIT documents and the component model.
1111
"""
1212

1313
[workspace]
14-
members = [
15-
"crates/test-rust-wasm",
16-
]
14+
members = ["crates/test-rust-wasm"]
1715
resolver = "2"
1816

1917
[workspace.package]
@@ -22,7 +20,7 @@ edition = "2021"
2220
[workspace.dependencies]
2321
anyhow = "1.0.72"
2422
bitflags = "2.3.3"
25-
heck = { version = "0.4", features = ["unicode"] }
23+
heck = { version = "0.4", features = ["unicode"] }
2624
pulldown-cmark = { version = "0.9", default-features = false }
2725
clap = { version = "4.3.19", features = ["derive"] }
2826
env_logger = "0.10.0"
@@ -52,22 +50,19 @@ clap = { workspace = true }
5250
wit-bindgen-core = { workspace = true }
5351
wit-bindgen-rust = { workspace = true, features = ['clap'], optional = true }
5452
wit-bindgen-c = { workspace = true, features = ['clap'], optional = true }
55-
wit-bindgen-markdown = { workspace = true, features = ['clap'], optional = true }
56-
wit-bindgen-teavm-java = { workspace = true, features = ['clap'], optional = true }
53+
wit-bindgen-markdown = { workspace = true, features = [
54+
'clap',
55+
], optional = true }
56+
wit-bindgen-teavm-java = { workspace = true, features = [
57+
'clap',
58+
], optional = true }
5759
wit-bindgen-go = { workspace = true, features = ['clap'], optional = true }
5860
wit-bindgen-csharp = { workspace = true, features = ['clap'], optional = true }
5961
wit-component = { workspace = true }
6062
wasm-encoder = { workspace = true }
6163

6264
[features]
63-
default = [
64-
'c',
65-
'rust',
66-
'markdown',
67-
'teavm-java',
68-
'go',
69-
'csharp-naot',
70-
]
65+
default = ['c', 'rust', 'markdown', 'teavm-java', 'go', 'csharp-naot']
7166
c = ['dep:wit-bindgen-c']
7267
rust = ['dep:wit-bindgen-rust']
7368
markdown = ['dep:wit-bindgen-markdown']
@@ -83,3 +78,5 @@ wasmtime = { version = "15", features = ['component-model'] }
8378
wasmtime-wasi = { workspace = true }
8479
test-artifacts = { path = 'crates/test-rust-wasm/artifacts' }
8580
wit-parser = { workspace = true }
81+
wasmparser = "0.118.0"
82+
wasm-encoder = { workspace = true }

crates/csharp/src/csproj.rs

Lines changed: 139 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ use std::{fs, path::PathBuf};
33

44
use heck::ToUpperCamelCase;
55

6-
pub struct CSProject {
6+
pub struct CSProject;
7+
8+
pub struct CSProjectLLVMBuilder {
79
name: String,
810
dir: PathBuf,
911
aot: bool,
@@ -12,9 +14,17 @@ pub struct CSProject {
1214
wasm_imports: Vec<(String, String)>,
1315
}
1416

17+
pub struct CSProjectMonoBuilder {
18+
name: String,
19+
dir: PathBuf,
20+
aot: bool,
21+
clean_targets: bool,
22+
world_name: String,
23+
}
24+
1525
impl CSProject {
16-
pub fn new(dir: PathBuf, name: &str, world_name: &str) -> CSProject {
17-
CSProject {
26+
pub fn new(dir: PathBuf, name: &str, world_name: &str) -> CSProjectLLVMBuilder {
27+
CSProjectLLVMBuilder {
1828
name: name.to_string(),
1929
dir,
2030
aot: false,
@@ -24,6 +34,18 @@ impl CSProject {
2434
}
2535
}
2636

37+
pub fn new_mono(dir: PathBuf, name: &str, world_name: &str) -> CSProjectMonoBuilder {
38+
CSProjectMonoBuilder {
39+
name: name.to_string(),
40+
dir,
41+
aot: false,
42+
clean_targets: false,
43+
world_name: world_name.to_string(),
44+
}
45+
}
46+
}
47+
48+
impl CSProjectLLVMBuilder {
2749
pub fn generate(&self) -> Result<()> {
2850
let name = &self.name;
2951
let world = &self.world_name.replace("-", "_");
@@ -148,12 +170,125 @@ impl CSProject {
148170
self.aot = true;
149171
}
150172

151-
pub fn clean(&mut self) {
173+
pub fn clean(&mut self) -> &mut Self {
152174
self.clean_targets = true;
175+
176+
self
153177
}
154178

155179
pub fn add_import(&mut self, module_name: &str, func_name: &str) {
156180
self.wasm_imports
157181
.push((module_name.to_string(), func_name.to_string()));
158182
}
159183
}
184+
185+
impl CSProjectMonoBuilder {
186+
pub fn generate(&self) -> Result<()> {
187+
let name = &self.name;
188+
let world = &self.world_name.replace("-", "_");
189+
let snake_world = world.to_upper_camel_case();
190+
191+
let aot = self.aot;
192+
193+
fs::write(
194+
self.dir.join("rd.xml"),
195+
format!(
196+
r#"<Directives xmlns="http://schemas.microsoft.com/netfx/2013/01/metadata">
197+
<Application>
198+
<Assembly Name="{name}">
199+
</Assembly>
200+
</Application>
201+
</Directives>"#
202+
),
203+
)?;
204+
205+
let mut csproj = format!(
206+
"<Project Sdk=\"Microsoft.NET.Sdk\">
207+
208+
<PropertyGroup>
209+
<TargetFramework>net9.0</TargetFramework>
210+
<RuntimeIdentifier>wasi-wasm</RuntimeIdentifier>
211+
212+
<TargetOs>wasi</TargetOs>
213+
<WasmBuildNative>{aot}</WasmBuildNative>
214+
<WasmNativeStrip>false</WasmNativeStrip>
215+
<IsBrowserWasmProject>false</IsBrowserWasmProject>
216+
<WasmSingleFileBundle>true</WasmSingleFileBundle>
217+
218+
<RootNamespace>{name}</RootNamespace>
219+
<ImplicitUsings>enable</ImplicitUsings>
220+
<Nullable>enable</Nullable>
221+
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
222+
</PropertyGroup>
223+
224+
<PropertyGroup>
225+
<PublishTrimmed>true</PublishTrimmed>
226+
<AssemblyName>{name}</AssemblyName>
227+
</PropertyGroup>
228+
229+
<ItemGroup>
230+
<NativeLibrary Include=\"{world}_component_type.o\" />
231+
</ItemGroup>
232+
233+
<ItemGroup>
234+
<RdXmlFile Include=\"rd.xml\" />
235+
</ItemGroup>
236+
"
237+
);
238+
239+
if self.aot {
240+
fs::write(
241+
self.dir.join("nuget.config"),
242+
r#"<?xml version="1.0" encoding="utf-8"?>
243+
<configuration>
244+
<config>
245+
<add key="globalPackagesFolder" value=".packages" />
246+
</config>
247+
<packageSources>
248+
<!--To inherit the global NuGet package sources remove the <clear/> line below -->
249+
<clear />
250+
<add key="nuget" value="https://api.nuget.org/v3/index.json" />
251+
<add key="dotnet9" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet9/nuget/v3/index.json" />
252+
</packageSources>
253+
</configuration>"#,
254+
)?;
255+
}
256+
257+
if self.clean_targets {
258+
let mut wasm_filename = self.dir.join(name);
259+
wasm_filename.set_extension("wasm");
260+
// 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.
261+
csproj.push_str(&format!(
262+
"<Target Name=\"CleanAndDelete\" AfterTargets=\"Clean\">
263+
<!-- Remove obj folder -->
264+
<RemoveDir Directories=\"$(BaseIntermediateOutputPath)\" />
265+
<!-- Remove bin folder -->
266+
<RemoveDir Directories=\"$(BaseOutputPath)\" />
267+
<RemoveDir Directories=\"{}\" />
268+
<RemoveDir Directories=\".packages\" />
269+
</Target>",
270+
wasm_filename.display()
271+
));
272+
}
273+
274+
csproj.push_str(
275+
r#"</Project>
276+
"#,
277+
);
278+
279+
let camel = snake_world.to_upper_camel_case();
280+
fs::write(self.dir.join(format!("{camel}.csproj")), csproj)?;
281+
282+
Ok(())
283+
}
284+
285+
pub fn aot(&mut self) {
286+
self.aot = true;
287+
}
288+
289+
pub fn clean(&mut self) -> &mut Self {
290+
self.clean_targets = true;
291+
292+
self
293+
}
294+
}

crates/csharp/src/lib.rs

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,10 @@ pub struct Opts {
4545
pub string_encoding: StringEncoding,
4646
#[cfg_attr(feature = "clap", arg(long))]
4747
pub generate_stub: bool,
48+
49+
// TODO: This should only temporarily needed until mono and native aot aligns.
50+
#[cfg_attr(feature = "clap", arg(short, long, value_enum))]
51+
pub runtime: CSharpRuntime,
4852
}
4953

5054
impl Opts {
@@ -56,6 +60,14 @@ impl Opts {
5660
}
5761
}
5862

63+
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
64+
#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
65+
pub enum CSharpRuntime {
66+
#[default]
67+
NativeAOT,
68+
Mono,
69+
}
70+
5971
struct InterfaceFragment {
6072
csharp_src: String,
6173
csharp_interop_src: String,
@@ -487,6 +499,22 @@ impl WorldGenerator for CSharp {
487499
);
488500
}
489501

502+
//TODO: This is currently neede for mono even if it's built as a library.
503+
if self.opts.runtime == CSharpRuntime::Mono {
504+
files.push(
505+
&format!("MonoEntrypoint.cs",),
506+
indent(
507+
r#"
508+
public class MonoEntrypoint() {
509+
public static void Main() {
510+
}
511+
}
512+
"#,
513+
)
514+
.as_bytes(),
515+
);
516+
}
517+
490518
files.push(
491519
&format!("{snake}_component_type.o",),
492520
component_type_object::object(resolve, id, self.opts.string_encoding)
@@ -599,7 +627,7 @@ impl InterfaceGenerator<'_> {
599627
});
600628
}
601629

602-
fn import(&mut self, _module: &String, func: &Function) {
630+
fn import(&mut self, module: &String, func: &Function) {
603631
if func.kind != FunctionKind::Freestanding {
604632
todo!("resources");
605633
}
@@ -666,12 +694,14 @@ impl InterfaceGenerator<'_> {
666694
.join(", ");
667695

668696
let import_name = &func.name;
697+
669698
uwrite!(
670699
self.csharp_interop_src,
671700
r#"
672701
internal static class {camel_name}Interop
673702
{{
674-
[DllImport("*", EntryPoint = "{import_name}")]
703+
[WasmImportLinkage]
704+
[DllImport("{module}", EntryPoint = "{import_name}")]
675705
internal static extern {wasm_result_type} wasmImport{camel_name}({wasm_params});
676706
}}
677707
"#

crates/csharp/tests/codegen.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ macro_rules! codegen_test {
7171
wit_bindgen_csharp::Opts {
7272
generate_stub: true,
7373
string_encoding: StringEncoding::UTF8,
74+
runtime: Default::default(),
7475
}
7576
.build()
7677
.generate(resolve, world, files)

0 commit comments

Comments
 (0)