Skip to content

Commit 0416c34

Browse files
authored
[wasm] Compile .bc->.o in parallel, before passing to the linker (dotnet#54053)
1 parent c0ed319 commit 0416c34

File tree

8 files changed

+323
-48
lines changed

8 files changed

+323
-48
lines changed

eng/Versions.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@
140140
These are used as reference assemblies only, so they must not take a ProdCon/source-build
141141
version. Insert "RefOnly" to avoid assignment via PVP.
142142
-->
143-
<RefOnlyMicrosoftBuildVersion>16.8.0</RefOnlyMicrosoftBuildVersion>
143+
<RefOnlyMicrosoftBuildVersion>16.9.0</RefOnlyMicrosoftBuildVersion>
144144
<RefOnlyMicrosoftBuildFrameworkVersion>$(RefOnlyMicrosoftBuildVersion)</RefOnlyMicrosoftBuildFrameworkVersion>
145145
<RefOnlyMicrosoftBuildTasksCoreVersion>$(RefOnlyMicrosoftBuildVersion)</RefOnlyMicrosoftBuildTasksCoreVersion>
146146
<RefOnlyMicrosoftBuildUtilitiesCoreVersion>$(RefOnlyMicrosoftBuildVersion)</RefOnlyMicrosoftBuildUtilitiesCoreVersion>

src/libraries/tests.proj

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,8 @@
255255
</ItemGroup>
256256

257257
<ItemGroup Condition="'$(TargetOS)' == 'Browser' and '$(RunDisabledWasmTests)' != 'true'">
258+
<ProjectExclusions Include="$(MSBuildThisFileDirectory)Microsoft.NETCore.Platforms\tests\Microsoft.NETCore.Platforms.Tests.csproj" />
259+
258260
<!-- Mono-Browser ignores runtimeconfig.template.json (e.g. for this it has "System.Globalization.EnforceJapaneseEraYearRanges": true) -->
259261
<ProjectExclusions Include="$(MSBuildThisFileDirectory)System.Globalization.Calendars\tests\CalendarTestWithConfigSwitch\System.Globalization.CalendarsWithConfigSwitch.Tests.csproj" />
260262

@@ -276,6 +278,9 @@
276278
</ItemGroup>
277279

278280
<ItemGroup Condition="'$(TargetOS)' == 'Browser' and '$(BuildAOTTestsOnHelix)' == 'true' and '$(RunDisabledWasmTests)' != 'true' and '$(RunAOTCompilation)' == 'true'">
281+
<!-- Issue: https://github.com/dotnet/runtime/issues/54194 -->
282+
<ProjectExclusions Include="$(MSBuildThisFileDirectory)System.IO.MemoryMappedFiles/tests/System.IO.MemoryMappedFiles.Tests.csproj" />
283+
279284
<!-- Issue: https://github.com/dotnet/runtime/issues/52393 -->
280285
<ProjectExclusions Include="$(MSBuildThisFileDirectory)System.Runtime/tests/System.Runtime.Tests.csproj" />
281286

src/mono/wasm/build/WasmApp.Native.targets

Lines changed: 49 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
<UsingTask TaskName="PInvokeTableGenerator" AssemblyFile="$(WasmAppBuilderTasksAssemblyPath)" />
55
<UsingTask TaskName="IcallTableGenerator" AssemblyFile="$(WasmAppBuilderTasksAssemblyPath)" />
6+
<UsingTask TaskName="Microsoft.WebAssembly.Build.Tasks.EmccCompile" AssemblyFile="$(WasmAppBuilderTasksAssemblyPath)" />
67

78
<PropertyGroup>
89
<_WasmBuildNativeCoreDependsOn>
@@ -165,18 +166,14 @@
165166
<_EmccOptimizationFlagDefault Condition="'$(_EmccOptimizationFlagDefault)' == ''">-Oz</_EmccOptimizationFlagDefault>
166167

167168
<EmccCompileOptimizationFlag Condition="'$(EmccCompileOptimizationFlag)' == ''">$(_EmccOptimizationFlagDefault)</EmccCompileOptimizationFlag>
168-
<EmccLinkOptimizationFlag Condition="'$(EmccLinkOptimizationFlag)' == ''" >$(_EmccOptimizationFlagDefault)</EmccLinkOptimizationFlag>
169+
<EmccLinkOptimizationFlag Condition="'$(EmccLinkOptimizationFlag)' == ''" >-O0</EmccLinkOptimizationFlag>
169170
</PropertyGroup>
170171

171172
<ItemGroup>
172173
<_EmccCommonFlags Include="$(_DefaultEmccFlags)" />
173174
<_EmccCommonFlags Include="$(EmccFlags)" />
174175
<_EmccCommonFlags Include="-s DISABLE_EXCEPTION_CATCHING=0" />
175176
<_EmccCommonFlags Include="-g" Condition="'$(WasmNativeStrip)' == 'false'" />
176-
<_EmccCommonFlags Include="-DENABLE_AOT=1" Condition="'$(RunAOTCompilation)' == 'true'" />
177-
<_EmccCommonFlags Include="-DDRIVER_GEN=1" Condition="'$(RunAOTCompilation)' == 'true'" />
178-
<_EmccCommonFlags Include="-DINVARIANT_GLOBALIZATION=1" Condition="'$(InvariantGlobalization)' == 'true'" />
179-
<_EmccCommonFlags Include="-DLINK_ICALLS=1" Condition="'$(WasmLinkIcalls)' == 'true'" />
180177
<_EmccCommonFlags Include="-v" Condition="'$(EmccVerbose)' != 'false'" />
181178
</ItemGroup>
182179

@@ -186,11 +183,7 @@
186183
</PropertyGroup>
187184

188185
<ItemGroup>
189-
<_WasmObjectsToBuild Include="$(_WasmRuntimePackSrcDir)\*.c" />
190-
<_WasmObjectsToBuild OutputPath="$(_WasmIntermediateOutputPath)%(FileName).o" />
191-
192186
<_DotnetJSSrcFile Include="$(_WasmRuntimePackSrcDir)\*.js" />
193-
<_WasmPInvokeModules Include="%(_WasmNativeFileForLinking.FileName)" />
194187
</ItemGroup>
195188
</Target>
196189

@@ -227,54 +220,84 @@
227220
<!-- Adding optimization flag at the top, so it gets precedence -->
228221
<_EmccCFlags Include="$(EmccCompileOptimizationFlag)" />
229222
<_EmccCFlags Include="@(_EmccCommonFlags)" />
223+
224+
<_EmccCFlags Include="-DENABLE_AOT=1" Condition="'$(RunAOTCompilation)' == 'true'" />
225+
<_EmccCFlags Include="-DDRIVER_GEN=1" Condition="'$(RunAOTCompilation)' == 'true'" />
226+
<_EmccCFlags Include="-DINVARIANT_GLOBALIZATION=1" Condition="'$(InvariantGlobalization)' == 'true'" />
227+
<_EmccCFlags Include="-DLINK_ICALLS=1" Condition="'$(WasmLinkIcalls)' == 'true'" />
230228
<_EmccCFlags Include="-DCORE_BINDINGS" />
231229
<_EmccCFlags Include="-DGEN_PINVOKE=1" />
230+
232231
<_EmccCFlags Include="&quot;-I%(_EmccIncludePaths.Identity)&quot;" />
233232
<_EmccCFlags Include="-g" Condition="'$(WasmNativeDebugSymbols)' == 'true'" />
234233
<_EmccCFlags Include="-s EXPORTED_FUNCTIONS='[@(_ExportedFunctions->'&quot;%(Identity)&quot;', ',')]'" Condition="@(_ExportedFunctions->Count()) > 0" />
235234

236235
<_EmccCFlags Include="$(EmccExtraCFlags)" />
236+
237+
<_WasmRuntimePackSrcFile Remove="@(_WasmRuntimePackSrcFile)" />
238+
<_WasmRuntimePackSrcFile Include="$(_WasmRuntimePackSrcDir)\*.c" />
239+
<_WasmRuntimePackSrcFile ObjectFile="$(_WasmIntermediateOutputPath)%(FileName).o" />
240+
<_WasmSourceFileToCompile Include="@(_WasmRuntimePackSrcFile)" />
237241
</ItemGroup>
238242

239243
<PropertyGroup>
244+
<_EmBuilder Condition="$([MSBuild]::IsOSPlatform('WINDOWS'))">embuilder.bat</_EmBuilder>
245+
<_EmBuilder Condition="!$([MSBuild]::IsOSPlatform('WINDOWS'))">embuilder.py</_EmBuilder>
240246
<_EmccCompileRsp>$(_WasmIntermediateOutputPath)emcc-compile.rsp</_EmccCompileRsp>
241247
</PropertyGroup>
242248

243249
<WriteLinesToFile Lines="@(_EmccCFlags)" File="$(_EmccCompileRsp)" Overwrite="true" WriteOnlyWhenDifferent="true" />
244250

251+
<!-- warm up the cache -->
252+
<Exec Command="$(_EmBuilder) build MINIMAL" EnvironmentVariables="@(EmscriptenEnvVars)" StandardOutputImportance="Low" StandardErrorImportance="Low" />
253+
245254
<Message Text="Compiling native assets with emcc. This may take a while ..." Importance="High" />
246-
<Exec Command='emcc "@$(_EmccDefaultFlagsRsp)" "@$(_EmccCompileRsp)" "%(_WasmObjectsToBuild.Identity)" -c -o "%(_WasmObjectsToBuild.OutputPath)"' EnvironmentVariables="@(EmscriptenEnvVars)" />
255+
<EmccCompile SourceFiles="@(_WasmSourceFileToCompile)" Arguments='"@$(_EmccDefaultFlagsRsp)" "@$(_EmccCompileRsp)"' EnvironmentVariables="@(EmscriptenEnvVars)" />
247256
</Target>
248257

249258
<Target Name="_WasmLinkDotNet">
250259
<ItemGroup>
251-
<_WasmRuntimePackNativeLibs Include="$(MicrosoftNetCoreAppRuntimePackRidNativeDir)\*.a" />
252-
<_WasmObjects Include="@(_WasmRuntimePackNativeLibs)" />
253-
<_WasmObjects Include="@(_WasmObjectsToBuild->'%(OutputPath)')" />
254-
255260
<!-- Adding optimization flag at the top, so it gets precedence -->
256261
<_EmccLDFlags Include="$(EmccLinkOptimizationFlag)" />
257262
<_EmccLDFlags Include="@(_EmccCommonFlags)" />
263+
258264
<_EmccLDFlags Include="-s TOTAL_MEMORY=536870912" />
259265
<_EmccLDFlags Include="$(EmccExtraLDFlags)" />
266+
</ItemGroup>
267+
268+
<EmccCompile
269+
Condition="@(_BitCodeFile->Count()) > 0"
270+
SourceFiles="@(_BitCodeFile)"
271+
Arguments="&quot;@$(_EmccDefaultFlagsRsp)&quot; @(_EmccLDFlags->'%(Identity)', ' ')"
272+
EnvironmentVariables="@(EmscriptenEnvVars)" />
273+
274+
<ItemGroup>
275+
<!-- order seems to matter -->
276+
<_WasmNativeFileForLinking Include="%(_BitcodeFile.ObjectFile)" />
277+
<_WasmNativeFileForLinking Include="%(_WasmSourceFileToCompile.ObjectFile)" />
260278

261-
<_EmccLDFlags Include="--js-library &quot;%(_DotnetJSSrcFile.Identity)&quot;" />
262-
<_EmccLDFlags Include="--js-library &quot;%(_WasmExtraJSFile.Identity)&quot;" Condition="'%(_WasmExtraJSFile.Kind)' == 'js-library'" />
279+
<!-- libmono* needs to be at the end, since it is used to resolve references the previous .o files -->
280+
<_WasmNativeFileForLinking Include="$(MicrosoftNetCoreAppRuntimePackRidNativeDir)\*.a" />
263281

264-
<_EmccLDFlags Include="--pre-js &quot;%(_WasmExtraJSFile.Identity)&quot;" Condition="'%(_WasmExtraJSFile.Kind)' == 'pre-js'" />
265-
<_EmccLDFlags Include="--post-js &quot;%(_WasmExtraJSFile.Identity)&quot;" Condition="'%(_WasmExtraJSFile.Kind)' == 'post-js'" />
282+
<_EmccLinkStepArgs Include="@(_EmccLDFlags)" />
283+
<_EmccLinkStepArgs Include="--js-library &quot;%(_DotnetJSSrcFile.Identity)&quot;" />
284+
<_EmccLinkStepArgs Include="--js-library &quot;%(_WasmExtraJSFile.Identity)&quot;" Condition="'%(_WasmExtraJSFile.Kind)' == 'js-library'" />
266285

267-
<_EmccLDFlags Include="&quot;%(_WasmNativeFileForLinking.Identity)&quot;" />
268-
<_EmccLDFlags Include="&quot;%(_WasmObjects.Identity)&quot;" />
269-
<_EmccLDFlags Include="-o &quot;$(_WasmIntermediateOutputPath)dotnet.js&quot;" />
286+
<_EmccLinkStepArgs Include="--pre-js &quot;%(_WasmExtraJSFile.Identity)&quot;" Condition="'%(_WasmExtraJSFile.Kind)' == 'pre-js'" />
287+
<_EmccLinkStepArgs Include="--post-js &quot;%(_WasmExtraJSFile.Identity)&quot;" Condition="'%(_WasmExtraJSFile.Kind)' == 'post-js'" />
288+
289+
<_EmccLinkStepArgs Include="&quot;%(_WasmNativeFileForLinking.Identity)&quot;" />
290+
<_EmccLinkStepArgs Include="-o &quot;$(_WasmIntermediateOutputPath)dotnet.js&quot;" />
270291
</ItemGroup>
271292

272293
<PropertyGroup>
273294
<_EmccLinkRsp>$(_WasmIntermediateOutputPath)emcc-link.rsp</_EmccLinkRsp>
274295
</PropertyGroup>
275296

276-
<WriteLinesToFile Lines="@(_EmccLDFlags)" File="$(_EmccLinkRsp)" Overwrite="true" WriteOnlyWhenDifferent="true" />
277-
<Message Text="Running emcc with @(_EmccLDFlags->'%(Identity)', ' ')" Importance="Low" />
297+
<WriteLinesToFile Lines="@(_EmccLinkStepArgs)" File="$(_EmccLinkRsp)" Overwrite="true" WriteOnlyWhenDifferent="true" />
298+
299+
<Message Text="Linking with emcc. This may take a while ..." Importance="High" />
300+
<Message Text="Running emcc with @(_EmccLinkStepArgs->'%(Identity)', ' ')" Importance="Low" />
278301
<Exec Command='emcc "@$(_EmccDefaultFlagsRsp)" "@$(_EmccLinkRsp)"' EnvironmentVariables="@(EmscriptenEnvVars)" />
279302

280303
<Exec Command='wasm-opt$(_ExeExt) --strip-dwarf "$(_WasmIntermediateOutputPath)dotnet.wasm" -o "$(_WasmIntermediateOutputPath)dotnet.wasm"' Condition="'$(WasmNativeStrip)' == 'true'" IgnoreStandardErrorWarningFormat="true" EnvironmentVariables="@(EmscriptenEnvVars)" />
@@ -289,7 +312,7 @@
289312

290313
<Target Name="_GenerateDriverGenC" Condition="'$(RunAOTCompilation)' != 'true' and '$(WasmProfilers)' != ''">
291314
<PropertyGroup>
292-
<EmccFlags>$(EmccFlags) -DDRIVER_GEN=1</EmccFlags>
315+
<EmccExtraCFlags>$(EmccExtraCFlags) -DDRIVER_GEN=1</EmccExtraCFlags>
293316
<InitAotProfilerCmd>
294317
void mono_profiler_init_aot (const char *desc)%3B
295318
EMSCRIPTEN_KEEPALIVE void mono_wasm_load_profiler_aot (const char *desc) { mono_profiler_init_aot (desc)%3B }
@@ -405,7 +428,7 @@ EMSCRIPTEN_KEEPALIVE void mono_wasm_load_profiler_aot (const char *desc) { mono_
405428
Profilers="$(WasmProfilers)"
406429
AotModulesTablePath="$(_WasmIntermediateOutputPath)driver-gen.c"
407430
UseLLVM="true"
408-
DisableParallelAot="true"
431+
DisableParallelAot="$(DisableParallelAot)"
409432
DedupAssembly="$(_WasmDedupAssembly)"
410433
LLVMDebug="dwarfdebug"
411434
LLVMPath="$(EmscriptenUpstreamBinPath)" >
@@ -420,8 +443,7 @@ EMSCRIPTEN_KEEPALIVE void mono_wasm_load_profiler_aot (const char *desc) { mono_
420443

421444
<_AOTAssemblies Include="@(_WasmAssembliesInternal)" Condition="'%(_WasmAssembliesInternal._InternalForceInterpret)' != 'true'" />
422445
<_BitcodeFile Include="%(_WasmAssembliesInternal.LlvmBitcodeFile)" />
423-
424-
<_WasmNativeFileForLinking Include="@(_BitcodeFile)" />
446+
<_BitcodeFile ObjectFile="$(_WasmIntermediateOutputPath)%(FileName).o" />
425447
</ItemGroup>
426448
</Target>
427449

src/tasks/AotCompilerTask/MonoAOTCompiler.cs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -417,8 +417,17 @@ private bool PrecompileLibrary(ITaskItem assemblyItem, string? monoPaths)
417417
try
418418
{
419419
// run the AOT compiler
420-
Utils.RunProcess(CompilerBinaryPath, string.Join(" ", processArgs), envVariables, assemblyDir, silent: false,
421-
outputMessageImportance: MessageImportance.Low, debugMessageImportance: MessageImportance.Low);
420+
(int exitCode, string output) = Utils.TryRunProcess(CompilerBinaryPath,
421+
string.Join(" ", processArgs),
422+
envVariables,
423+
assemblyDir,
424+
silent: false,
425+
debugMessageImportance: MessageImportance.Low);
426+
if (exitCode != 0)
427+
{
428+
Log.LogError($"Precompiling failed for {assembly}: {output}");
429+
return false;
430+
}
422431
}
423432
catch (Exception ex)
424433
{

src/tasks/Common/Utils.cs

Lines changed: 97 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using System.Collections.Generic;
66
using System.Diagnostics;
77
using System.IO;
8+
using System.Runtime.InteropServices;
89
using System.Text;
910
using Microsoft.Build.Framework;
1011
using Microsoft.Build.Utilities;
@@ -21,17 +22,81 @@ public static string GetEmbeddedResource(string file)
2122
return reader.ReadToEnd();
2223
}
2324

25+
public static (int exitCode, string output) RunShellCommand(string command,
26+
IDictionary<string, string> envVars,
27+
string workingDir,
28+
MessageImportance debugMessageImportance=MessageImportance.Low)
29+
{
30+
string scriptFileName = CreateTemporaryBatchFile(command);
31+
(string shell, string args) = RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
32+
? ("cmd", $"/c \"{scriptFileName}\"")
33+
: ("/bin/sh", $"\"{scriptFileName}\"");
34+
35+
Logger?.LogMessage(debugMessageImportance, $"Running {command} via script {scriptFileName}:");
36+
Logger?.LogMessage(debugMessageImportance, File.ReadAllText(scriptFileName));
37+
38+
return TryRunProcess(shell,
39+
args,
40+
envVars,
41+
workingDir,
42+
silent: false,
43+
debugMessageImportance);
44+
45+
static string CreateTemporaryBatchFile(string command)
46+
{
47+
string extn = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? ".cmd" : ".sh";
48+
string file = Path.Combine(Path.GetTempPath(), $"tmp{Guid.NewGuid():N}{extn}");
49+
50+
using StreamWriter sw = new(file);
51+
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
52+
{
53+
sw.WriteLine("setlocal");
54+
sw.WriteLine("set errorlevel=dummy");
55+
sw.WriteLine("set errorlevel=");
56+
}
57+
else
58+
{
59+
// Use sh rather than bash, as not all 'nix systems necessarily have Bash installed
60+
sw.WriteLine("#!/bin/sh");
61+
}
62+
63+
sw.WriteLine(command);
64+
return file;
65+
}
66+
}
67+
2468
public static string RunProcess(
2569
string path,
2670
string args = "",
2771
IDictionary<string, string>? envVars = null,
2872
string? workingDir = null,
2973
bool ignoreErrors = false,
3074
bool silent = true,
31-
MessageImportance outputMessageImportance=MessageImportance.High,
3275
MessageImportance debugMessageImportance=MessageImportance.High)
3376
{
34-
LogInfo($"Running: {path} {args}", debugMessageImportance);
77+
(int exitCode, string output) = TryRunProcess(
78+
path,
79+
args,
80+
envVars,
81+
workingDir,
82+
silent,
83+
debugMessageImportance);
84+
85+
if (exitCode != 0 && !ignoreErrors)
86+
throw new Exception("Error: Process returned non-zero exit code: " + output);
87+
88+
return output;
89+
}
90+
91+
public static (int, string) TryRunProcess(
92+
string path,
93+
string args = "",
94+
IDictionary<string, string>? envVars = null,
95+
string? workingDir = null,
96+
bool silent = true,
97+
MessageImportance debugMessageImportance=MessageImportance.High)
98+
{
99+
Logger?.LogMessage(debugMessageImportance, $"Running: {path} {args}");
35100
var outputBuilder = new StringBuilder();
36101
var processStartInfo = new ProcessStartInfo
37102
{
@@ -46,7 +111,7 @@ public static string RunProcess(
46111
if (workingDir != null)
47112
processStartInfo.WorkingDirectory = workingDir;
48113

49-
LogInfo($"Using working directory: {workingDir ?? Environment.CurrentDirectory}", debugMessageImportance);
114+
Logger?.LogMessage(debugMessageImportance, $"Using working directory: {workingDir ?? Environment.CurrentDirectory}");
50115

51116
if (envVars != null)
52117
{
@@ -68,36 +133,55 @@ public static string RunProcess(
68133
{
69134
lock (s_SyncObj)
70135
{
136+
if (string.IsNullOrEmpty(e.Data))
137+
return;
138+
71139
if (!silent)
72-
{
73140
LogWarning(e.Data);
74-
}
75141
outputBuilder.AppendLine(e.Data);
76142
}
77143
};
78144
process.OutputDataReceived += (sender, e) =>
79145
{
80146
lock (s_SyncObj)
81147
{
148+
if (string.IsNullOrEmpty(e.Data))
149+
return;
150+
82151
if (!silent)
83-
{
84-
LogInfo(e.Data, outputMessageImportance);
85-
}
152+
Logger?.LogMessage(debugMessageImportance, e.Data);
86153
outputBuilder.AppendLine(e.Data);
87154
}
88155
};
89156
process.BeginOutputReadLine();
90157
process.BeginErrorReadLine();
91158
process.WaitForExit();
92159

93-
if (process.ExitCode != 0)
160+
Logger?.LogMessage(debugMessageImportance, $"Exit code: {process.ExitCode}");
161+
return (process.ExitCode, outputBuilder.ToString().Trim('\r', '\n'));
162+
}
163+
164+
internal static string CreateTemporaryBatchFile(string command)
165+
{
166+
string extn = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? ".cmd" : ".sh";
167+
string file = Path.Combine(Path.GetTempPath(), $"tmp{Guid.NewGuid():N}{extn}");
168+
169+
using StreamWriter sw = new(file);
170+
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
94171
{
95-
Logger?.LogMessage(MessageImportance.High, $"Exit code: {process.ExitCode}");
96-
if (!ignoreErrors)
97-
throw new Exception("Error: Process returned non-zero exit code: " + outputBuilder);
172+
sw.WriteLine("setlocal");
173+
sw.WriteLine("set errorlevel=dummy");
174+
sw.WriteLine("set errorlevel=");
98175
}
176+
else
177+
{
178+
// Use sh rather than bash, as not all 'nix systems necessarily have Bash installed
179+
sw.WriteLine("#!/bin/sh");
180+
}
181+
182+
sw.WriteLine(command);
99183

100-
return silent ? string.Empty : outputBuilder.ToString().Trim('\r', '\n');
184+
return file;
101185
}
102186

103187
#if NETCOREAPP

0 commit comments

Comments
 (0)