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