Files
LuaCsForBarotraumaEP/Deploy/DeployAll/DotnetCmd.cs
Juan Pablo Arce 3f2c843247 Unstable v0.19.3.0
2022-09-02 15:10:56 -03:00

124 lines
5.1 KiB
C#

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Xml.Linq;
using AsmResolver.PE;
using AsmResolver.PE.File;
using AsmResolver.PE.File.Headers;
using AsmResolver.PE.Win32Resources.Builder;
namespace DeployAll;
public static class DotnetCmd
{
private const string DotnetAppName = "dotnet";
private const string desiredRuntimeVersion = "6.0.8";
public static void Publish(string projPath, string configuration, string runtime, string resultPath)
{
ProcessStartInfo psi = new ProcessStartInfo
{
FileName = DotnetAppName,
ArgumentList =
{
"publish",
projPath,
"-c",
configuration,
"-clp:ErrorsOnly;Summary",
"--self-contained",
"-r",
runtime,
"/p:Platform=x64",
"/p:ErrorOnDuplicatePublishOutputFiles=false", //TODO: fix our duplicate files
"/p:RollForward=Disable",
$"/p:RuntimeFrameworkVersion={desiredRuntimeVersion}",
"-o",
resultPath
},
RedirectStandardOutput = true,
RedirectStandardError = true
};
var process = Util.StartProcess(psi);
process.WaitForExit();
string stdout = process.StandardOutput.ReadToEnd();
string stderr = process.StandardError.ReadToEnd();
string errorLine = $"{stdout}\n{stderr}".Split('\n')
.First(ln => ln.Contains("Error(s)", StringComparison.OrdinalIgnoreCase))
.Trim();
if (!errorLine.StartsWith("0 ", StringComparison.OrdinalIgnoreCase))
{
throw new Exception($"Failed to build {projPath}, {errorLine}");
}
Console.WriteLine($" - Published \"{projPath}\" to \"{resultPath}\", {errorLine}");
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) || !runtime.StartsWith("win")) { return; }
// You may be wondering, what is this crap?
// Cross-compiling is something that should work perfectly, because it's super convenient.
// However, thanks to the way .NET works, cross-compiling to Windows from *nix platforms
// results in an executable with basically no metadata, and the wrong subsystem!
// (see https://github.com/dotnet/sdk/blob/375955d3a9de213a01d70eb6180298000dee30ee/src/Tasks/Microsoft.NET.Build.Tasks/GenerateShims.cs#L127-L132)
// Does it look like we're about to modify the SDK itself to solve this problem? Yeah right.
// Instead let's just take the shim generated by the SDK and fix it ourselves.
XElement firstPropertyGroup = XDocument.Load(projPath)
.Root?
.Element("PropertyGroup")
?? throw new Exception("PropertyGroup not found");
string assemblyName = firstPropertyGroup.Element("AssemblyName")?.Value
?? throw new Exception("AssemblyName not found");
// This is the shim that doesn't have the stuff we want.
var fileToChange = PEFile.FromFile(Path.Combine(resultPath, $"{assemblyName}.exe"));
// Luckily, the SDK does embed all of that data in the assembly with all of the IL!
// We can just yoink it from here.
var managedAssembly = PEImage.FromFile(Path.Combine(resultPath, $"{assemblyName}.dll"));
// Here's a whole lot of magic to set up the resources section of the executable
var resourceSection = new PESection(".rsrc", SectionFlags.ContentInitializedData | SectionFlags.MemoryRead);
var resourceDirectoryBuffer = new ResourceDirectoryBuffer();
resourceDirectoryBuffer.AddDirectory(managedAssembly.Resources ?? throw new Exception($"{assemblyName}.dll has no resources"));
resourceSection.Contents = resourceDirectoryBuffer;
fileToChange.Sections.Add(resourceSection);
fileToChange.AlignSections();
var dataDirectories = fileToChange.OptionalHeader.DataDirectories;
dataDirectories[2] = new DataDirectory(resourceDirectoryBuffer.Rva, resourceDirectoryBuffer.GetPhysicalSize());
// And here's something a little less magical that fixes the subsystem
fileToChange.OptionalHeader.SubSystem = firstPropertyGroup.Element("OutputType")?.Value == "WinExe"
? SubSystem.WindowsGui
: SubSystem.WindowsCui;
using var writeStream = File.Open(Path.Combine(resultPath, $"{assemblyName}.exe"), FileMode.Create);
fileToChange.Write(writeStream);
}
public static Version GetSdkVersion()
{
ProcessStartInfo psi = new ProcessStartInfo
{
FileName = DotnetAppName,
ArgumentList =
{
"--version"
},
RedirectStandardOutput = true,
RedirectStandardError = true
};
var process = Util.StartProcess(psi);
process.WaitForExit();
string stdout = process.StandardOutput.ReadToEnd();
return Version.Parse(stdout.Trim());
}
}