124 lines
5.1 KiB
C#
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());
|
|
}
|
|
}
|