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