Skip to main content

wit_bindgen_csharp/
csproj.rs

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