wit_bindgen_csharp/
csproj.rs1use anyhow::Result;
2use std::{fs, path::PathBuf};
3
4use heck::ToUpperCamelCase;
5
6pub struct CSProject;
7
8pub struct CSProjectLLVMBuilder {
9 name: String,
10 dir: PathBuf,
11 aot: bool,
12 clean_targets: bool,
13 world_name: String,
14 binary: bool,
15}
16
17pub struct CSProjectMonoBuilder {
18 name: String,
19 dir: PathBuf,
20 aot: bool,
21 clean_targets: bool,
22 world_name: String,
23}
24
25impl CSProject {
26 pub fn new(dir: PathBuf, name: &str, world_name: &str) -> CSProjectLLVMBuilder {
27 CSProjectLLVMBuilder {
28 name: name.to_string(),
29 dir,
30 aot: false,
31 clean_targets: false,
32 world_name: world_name.to_string(),
33 binary: false,
34 }
35 }
36
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
48impl CSProjectLLVMBuilder {
49 pub fn generate(&self) -> Result<()> {
50 let name = &self.name;
51 let world = &self.world_name.replace("-", "_");
52 let camel = format!("{}World", world.to_upper_camel_case());
53
54 fs::write(
55 self.dir.join("rd.xml"),
56 format!(
57 r#"<Directives xmlns="http://schemas.microsoft.com/netfx/2013/01/metadata">
58 <Application>
59 <Assembly Name="{name}">
60 </Assembly>
61 </Application>
62 </Directives>"#
63 ),
64 )?;
65
66 let output_type = if self.binary {
67 "<OutputType>Exe</OutputType>"
68 } else {
69 "<OutputType>Library</OutputType>"
70 };
71
72 let mut csproj = format!(
73 "<Project Sdk=\"Microsoft.NET.Sdk\">
74
75 <PropertyGroup>
76 <TargetFramework>net9.0</TargetFramework>
77 <LangVersion>preview</LangVersion>
78 <RootNamespace>{name}</RootNamespace>
79 <ImplicitUsings>enable</ImplicitUsings>
80 <Nullable>enable</Nullable>
81 <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
82 <!-- treat these are errors so they are caught during code generation tests -->
83 <WarningsAsErrors>CS0105</WarningsAsErrors>
84 {output_type}
85 </PropertyGroup>
86
87 <PropertyGroup>
88 <PublishTrimmed>true</PublishTrimmed>
89 <AssemblyName>{name}</AssemblyName>
90 </PropertyGroup>
91
92 <ItemGroup>
93 <RdXmlFile Include=\"rd.xml\" />
94 </ItemGroup>
95
96 <ItemGroup>
97 <CustomLinkerArg Include=\"-Wl,--component-type,{camel}_component_type.wit\" />
98 </ItemGroup>
99 "
100 );
101
102 if self.aot {
103 let os = match std::env::consts::OS {
104 "windows" => "win",
105 "linux" => std::env::consts::OS,
106 other => todo!("OS {} not supported", other),
107 };
108
109 csproj.push_str(
110 &format!(
111 r#"
112 <ItemGroup>
113 <PackageReference Include="Microsoft.DotNet.ILCompiler.LLVM" Version="10.0.0-*" />
114 <PackageReference Include="runtime.{os}-x64.Microsoft.DotNet.ILCompiler.LLVM" Version="10.0.0-*" />
115 </ItemGroup>
116 "#),
117 );
118
119 fs::write(
120 self.dir.join("nuget.config"),
121 r#"<?xml version="1.0" encoding="utf-8"?>
122 <configuration>
123 <config>
124 <add key="globalPackagesFolder" value=".packages" />
125 </config>
126 <packageSources>
127 <!--To inherit the global NuGet package sources remove the <clear/> line below -->
128 <clear />
129 <add key="nuget" value="https://api.nuget.org/v3/index.json" />
130 <add key="dotnet-experimental" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-experimental/nuget/v3/index.json" />
131 <!--<add key="dotnet-experimental" value="C:\github\runtimelab\artifacts\packages\Debug\Shipping" />-->
132 </packageSources>
133 </configuration>"#,
134 )?;
135 }
136
137 if self.clean_targets {
138 let mut wasm_filename = self.dir.join(name);
139 wasm_filename.set_extension("wasm");
140 csproj.push_str(&format!(
142 "<Target Name=\"CleanAndDelete\" AfterTargets=\"Clean\">
143 <!-- Remove obj folder -->
144 <RemoveDir Directories=\"$(BaseIntermediateOutputPath)\" />
145 <!-- Remove bin folder -->
146 <RemoveDir Directories=\"$(BaseOutputPath)\" />
147 <RemoveDir Directories=\"{}\" />
148 <RemoveDir Directories=\".packages\" />
149 </Target>",
150 wasm_filename.display()
151 ));
152 }
153
154 csproj.push_str(
155 r#"</Project>
156 "#,
157 );
158
159 fs::write(self.dir.join(format!("{camel}.csproj")), csproj)?;
160
161 Ok(())
162 }
163
164 pub fn aot(&mut self) {
165 self.aot = true;
166 }
167
168 pub fn binary(&mut self) {
169 self.binary = true;
170 }
171
172 pub fn clean(&mut self) -> &mut Self {
173 self.clean_targets = true;
174
175 self
176 }
177}
178
179impl CSProjectMonoBuilder {
180 pub fn generate(&self) -> Result<()> {
181 let name = &self.name;
182 let world = &self.world_name.replace("-", "_");
183 let camel = format!("{}World", world.to_upper_camel_case());
184
185 let aot = self.aot;
186
187 let maybe_aot = match aot {
188 true => format!("<WasmBuildNative>{aot}</WasmBuildNative>"),
189 false => String::new(),
190 };
191
192 let mut csproj = format!(
193 "<Project Sdk=\"Microsoft.NET.Sdk\">
194
195 <PropertyGroup>
196 <TargetFramework>net9.0</TargetFramework>
197 <RuntimeIdentifier>wasi-wasm</RuntimeIdentifier>
198 <OutputType>Library</OutputType>
199 {maybe_aot}
200 <RunAOTCompilation>{aot}</RunAOTCompilation>
201 <WasmNativeStrip>false</WasmNativeStrip>
202 <WasmSingleFileBundle>true</WasmSingleFileBundle>
203 <RootNamespace>{name}</RootNamespace>
204 <ImplicitUsings>enable</ImplicitUsings>
205 <Nullable>enable</Nullable>
206 <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
207 <!-- treat these are errors so they are caught during code generation tests -->
208 <WarningsAsErrors>CS0105</WarningsAsErrors>
209 </PropertyGroup>
210
211 <PropertyGroup>
212 <PublishTrimmed>true</PublishTrimmed>
213 <AssemblyName>{name}</AssemblyName>
214 </PropertyGroup>
215
216 <ItemGroup>
217 <NativeFileReference Include=\"{camel}_component_type.o\" Condition=\"Exists('{camel}_component_type.o')\"/>
218 </ItemGroup>
219
220 "
221 );
222
223 fs::write(
224 self.dir.join("nuget.config"),
225 r#"<?xml version="1.0" encoding="utf-8"?>
226 <configuration>
227 <config>
228 <add key="globalPackagesFolder" value=".packages" />
229 </config>
230 <packageSources>
231 <!--To inherit the global NuGet package sources remove the <clear/> line below -->
232 <clear />
233 <add key="nuget" value="https://api.nuget.org/v3/index.json" />
234 <add key="dotnet9" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet9/nuget/v3/index.json" />
235 </packageSources>
236 </configuration>"#,
237 )?;
238
239 if self.clean_targets {
240 let mut wasm_filename = self.dir.join(name);
241 wasm_filename.set_extension("wasm");
242 csproj.push_str(&format!(
244 "<Target Name=\"CleanAndDelete\" AfterTargets=\"Clean\">
245 <!-- Remove obj folder -->
246 <RemoveDir Directories=\"$(BaseIntermediateOutputPath)\" />
247 <!-- Remove bin folder -->
248 <RemoveDir Directories=\"$(BaseOutputPath)\" />
249 <RemoveDir Directories=\"{}\" />
250 <RemoveDir Directories=\".packages\" />
251 </Target>",
252 wasm_filename.display()
253 ));
254 }
255
256 csproj.push_str(
257 r#"</Project>
258 "#,
259 );
260
261 fs::write(self.dir.join(format!("{camel}.csproj")), csproj)?;
262
263 Ok(())
264 }
265
266 pub fn aot(&mut self) {
267 self.aot = true;
268 }
269
270 pub fn clean(&mut self) -> &mut Self {
271 self.clean_targets = true;
272
273 self
274 }
275}