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 <!-- Store the packages where they can be shared between tests -->
125 <add key="globalPackagesFolder" value="../../../.packages" />
126 </config>
127 <packageSources>
128 <!--To inherit the global NuGet package sources remove the <clear/> line below -->
129 <clear />
130 <add key="nuget" value="https://api.nuget.org/v3/index.json" />
131 <add key="dotnet-experimental" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-experimental/nuget/v3/index.json" />
132 <!--<add key="dotnet-experimental" value="C:\github\runtimelab\artifacts\packages\Debug\Shipping" />-->
133 </packageSources>
134 </configuration>"#,
135 )?;
136 }
137
138 if self.clean_targets {
139 let mut wasm_filename = self.dir.join(name);
140 wasm_filename.set_extension("wasm");
141 csproj.push_str(&format!(
143 "<Target Name=\"CleanAndDelete\" AfterTargets=\"Clean\">
144 <!-- Remove obj folder -->
145 <RemoveDir Directories=\"$(BaseIntermediateOutputPath)\" />
146 <!-- Remove bin folder -->
147 <RemoveDir Directories=\"$(BaseOutputPath)\" />
148 <RemoveDir Directories=\"{}\" />
149 <RemoveDir Directories=\".packages\" />
150 </Target>",
151 wasm_filename.display()
152 ));
153 }
154
155 csproj.push_str(
156 r#"</Project>
157 "#,
158 );
159
160 fs::write(self.dir.join(format!("{camel}.csproj")), csproj)?;
161
162 Ok(())
163 }
164
165 pub fn aot(&mut self) {
166 self.aot = true;
167 }
168
169 pub fn binary(&mut self) {
170 self.binary = true;
171 }
172
173 pub fn clean(&mut self) -> &mut Self {
174 self.clean_targets = true;
175
176 self
177 }
178}
179
180impl CSProjectMonoBuilder {
181 pub fn generate(&self) -> Result<()> {
182 let name = &self.name;
183 let world = &self.world_name.replace("-", "_");
184 let camel = format!("{}World", world.to_upper_camel_case());
185
186 let aot = self.aot;
187
188 let maybe_aot = match aot {
189 true => format!("<WasmBuildNative>{aot}</WasmBuildNative>"),
190 false => String::new(),
191 };
192
193 let mut csproj = format!(
194 "<Project Sdk=\"Microsoft.NET.Sdk\">
195
196 <PropertyGroup>
197 <TargetFramework>net9.0</TargetFramework>
198 <RuntimeIdentifier>wasi-wasm</RuntimeIdentifier>
199 <OutputType>Library</OutputType>
200 {maybe_aot}
201 <RunAOTCompilation>{aot}</RunAOTCompilation>
202 <WasmNativeStrip>false</WasmNativeStrip>
203 <WasmSingleFileBundle>true</WasmSingleFileBundle>
204 <RootNamespace>{name}</RootNamespace>
205 <ImplicitUsings>enable</ImplicitUsings>
206 <Nullable>enable</Nullable>
207 <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
208 <!-- treat these are errors so they are caught during code generation tests -->
209 <WarningsAsErrors>CS0105</WarningsAsErrors>
210 </PropertyGroup>
211
212 <PropertyGroup>
213 <PublishTrimmed>true</PublishTrimmed>
214 <AssemblyName>{name}</AssemblyName>
215 </PropertyGroup>
216
217 <ItemGroup>
218 <NativeFileReference Include=\"{camel}_component_type.o\" Condition=\"Exists('{camel}_component_type.o')\"/>
219 </ItemGroup>
220
221 "
222 );
223
224 fs::write(
225 self.dir.join("nuget.config"),
226 r#"<?xml version="1.0" encoding="utf-8"?>
227 <configuration>
228 <config>
229 <add key="globalPackagesFolder" value=".packages" />
230 </config>
231 <packageSources>
232 <!--To inherit the global NuGet package sources remove the <clear/> line below -->
233 <clear />
234 <add key="nuget" value="https://api.nuget.org/v3/index.json" />
235 <add key="dotnet9" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet9/nuget/v3/index.json" />
236 </packageSources>
237 </configuration>"#,
238 )?;
239
240 if self.clean_targets {
241 let mut wasm_filename = self.dir.join(name);
242 wasm_filename.set_extension("wasm");
243 csproj.push_str(&format!(
245 "<Target Name=\"CleanAndDelete\" AfterTargets=\"Clean\">
246 <!-- Remove obj folder -->
247 <RemoveDir Directories=\"$(BaseIntermediateOutputPath)\" />
248 <!-- Remove bin folder -->
249 <RemoveDir Directories=\"$(BaseOutputPath)\" />
250 <RemoveDir Directories=\"{}\" />
251 <RemoveDir Directories=\".packages\" />
252 </Target>",
253 wasm_filename.display()
254 ));
255 }
256
257 csproj.push_str(
258 r#"</Project>
259 "#,
260 );
261
262 fs::write(self.dir.join(format!("{camel}.csproj")), csproj)?;
263
264 Ok(())
265 }
266
267 pub fn aot(&mut self) {
268 self.aot = true;
269 }
270
271 pub fn clean(&mut self) -> &mut Self {
272 self.clean_targets = true;
273
274 self
275 }
276}