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>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            // 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.
142            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            // 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.
244            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}