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                    <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            // 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.
141            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            // 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.
243            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}