Add cross-compiling support

This commit is contained in:
Caelan Sayler
2024-06-04 21:22:04 +01:00
parent ac498494bd
commit bfb469566a
127 changed files with 3881 additions and 3525 deletions

View File

@@ -38,10 +38,10 @@
<ItemGroup Condition=" $(MSBuildProjectName.EndsWith('Tests')) ">
<ProjectReference Include="..\Divergic.Logging.Xunit\Divergic.Logging.Xunit.csproj" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.10.0" />
<PackageReference Include="Microsoft.CSharp" Version="4.7.0" />
<PackageReference Include="xunit" Version="2.8.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.0" />
<PackageReference Include="xunit" Version="2.8.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.1" />
<PackageReference Include="Xunit.SkippableFact" Version="1.4.13" />
<PackageReference Include="coverlet.msbuild" Version="6.0.1">
<PrivateAssets>all</PrivateAssets>

View File

@@ -15,6 +15,10 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
<PackageReference Update="GitHubActionsTestLogger" Version="2.4.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
</ItemGroup>
</Project>

View File

@@ -1,6 +1,7 @@
using Divergic.Logging.Xunit;
using Velopack.Packaging.Exceptions;
using Velopack.Packaging.Windows;
using Velopack.Vpk;
using Velopack.Vpk.Logging;
namespace Velopack.Packaging.Tests;
@@ -17,7 +18,7 @@ public class CompatUtilTests
private ICacheLogger<CompatUtilTests> GetCompat(out CompatUtil compat)
{
var logger = _output.BuildLoggerFor<CompatUtilTests>();
compat = new CompatUtil(logger, new BasicConsole(logger, new DefaultPromptValueFactory(true)));
compat = new CompatUtil(logger, new BasicConsole(logger, new VelopackDefaults(true)));
return logger;
}

View File

@@ -0,0 +1,100 @@
using Velopack.Packaging.Unix;
namespace Velopack.Packaging.Tests;
public class CrossCompile
{
private readonly ITestOutputHelper _output;
public CrossCompile(ITestOutputHelper output)
{
_output = output;
}
[Theory]
[InlineData("win-x64")]
[InlineData("linux-x64")]
public void PackCrossApp(string target)
{
using var logger = _output.BuildLoggerFor<CrossCompile>();
var rid = RID.Parse(target);
string id = $"from-{VelopackRuntimeInfo.SystemOs.GetOsShortName()}-targets-{rid.BaseRID.GetOsShortName()}";
using var _1 = Utility.GetTempDirectory(out var tempDir);
TestApp.PackTestApp(id, "1.0.0", id, tempDir, logger, targetRid: rid);
var artifactsDir = PathHelper.GetTestRootPath("artifacts");
Directory.CreateDirectory(artifactsDir);
string src, dest;
if (rid.BaseRID == RuntimeOs.Windows) {
src = Path.Combine(tempDir, id + "-win-Setup.exe");
dest = Path.Combine(artifactsDir, id + ".exe");
} else {
src = Path.Combine(tempDir, id + ".AppImage");
dest = Path.Combine(artifactsDir, id + ".AppImage");
}
Assert.True(File.Exists(src), $"Expected {src} to exist");
File.Copy(src, dest, overwrite: true);
}
[SkippableTheory]
[InlineData("from-win-targets-linux")]
[InlineData("from-linux-targets-linux")]
[InlineData("from-osx-targets-linux")]
public void RunCrossAppLinux(string artifactId)
{
using var logger = _output.BuildLoggerFor<CrossCompile>();
Skip.If(String.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable("VELOPACK_CROSS_ARTIFACTS")),
"VELOPACK_CROSS_ARTIFACTS not set");
Skip.IfNot(VelopackRuntimeInfo.IsLinux, "AppImage's can only run on Linux");
var artifactsDir = PathHelper.GetTestRootPath("artifacts");
var artifactPath = Path.Combine(artifactsDir, artifactId + ".AppImage");
Assert.True(File.Exists(artifactPath), $"Expected {artifactPath} to exist");
Chmod.ChmodFileAsExecutable(artifactPath);
var output = Exe.InvokeAndThrowIfNonZero(artifactPath, new[] { "test" }, null);
logger.LogInformation(output);
Assert.EndsWith(artifactId, output.Trim());
}
[SkippableTheory]
[InlineData("from-win-targets-win")]
[InlineData("from-linux-targets-win")]
[InlineData("from-osx-targets-win")]
public void RunCrossAppWindows(string artifactId)
{
using var logger = _output.BuildLoggerFor<CrossCompile>();
Skip.If(String.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable("VELOPACK_CROSS_ARTIFACTS")),
"VELOPACK_CROSS_ARTIFACTS not set");
Skip.IfNot(VelopackRuntimeInfo.IsWindows, "PE files can only run on Windows");
var artifactsDir = PathHelper.GetTestRootPath("artifacts");
var artifactPath = Path.Combine(artifactsDir, artifactId + ".exe");
Assert.True(File.Exists(artifactPath), $"Expected {artifactPath} to exist");
var appData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
var appRoot = Path.Combine(appData, artifactId);
var appExe = Path.Combine(appRoot, "current", "TestApp.exe");
var appUpdate = Path.Combine(appRoot, "Update.exe");
Utility.DeleteFileOrDirectoryHard(appRoot);
Assert.False(File.Exists(appExe));
var installOutput = Exe.InvokeAndThrowIfNonZero(artifactPath, new[] { "--silent" }, null);
logger.LogInformation(installOutput);
Assert.True(File.Exists(appExe));
var output = Exe.InvokeAndThrowIfNonZero(appExe, new[] { "test" }, null);
logger.LogInformation(output);
Assert.EndsWith(artifactId, output.Trim());
Exe.RunHostedCommand($"\"{appUpdate}\" --uninstall --silent");
Assert.False(File.Exists(appExe));
}
}

View File

@@ -0,0 +1,220 @@
using System.Diagnostics;
using System.Reflection.Metadata;
using System.Reflection.Metadata.Ecma335;
using System.Reflection.PortableExecutable;
using AsmResolver.PE;
using AsmResolver.PE.File;
using AsmResolver.PE.Win32Resources.Icon;
using AsmResolver.PE.Win32Resources.Version;
using Velopack.NuGet;
using Velopack.Packaging.Windows;
namespace Velopack.Packaging.Tests;
public class ResourceEditTests
{
private readonly ITestOutputHelper _output;
public ResourceEditTests(ITestOutputHelper output)
{
_output = output;
}
private void CreateTestPEFileWithoutRsrc(string tempFile)
{
var peBuilder = new ManagedPEBuilder(
PEHeaderBuilder.CreateExecutableHeader(),
new MetadataRootBuilder(new MetadataBuilder()),
ilStream: new BlobBuilder());
var peImageBuilder = new BlobBuilder();
peBuilder.Serialize(peImageBuilder);
using var fs = File.OpenWrite(tempFile);
fs.Write(peImageBuilder.ToArray());
}
[Fact]
public void CommitResourcesInCorrectOrder()
{
using var logger = _output.BuildLoggerFor<ResourceEditTests>();
using var _1 = Utility.GetTempFileName(out var tempFile);
var exe = PathHelper.GetRustAsset("setup.exe");
File.Copy(exe, tempFile);
var nuspec = PathHelper.GetFixture("FullNuspec.nuspec");
var manifest = PackageManifest.ParseFromFile(nuspec);
var pkgVersion = manifest.Version!;
var edit = new ResourceEdit(tempFile, logger);
edit.SetExeIcon(PathHelper.GetFixture("clowd.ico"));
edit.SetVersionInfo(manifest);
edit.Commit();
var afterRsrc = PEImage.FromFile(PEFile.FromFile(tempFile)).Resources;
Assert.NotNull(afterRsrc);
uint lastId = 0;
foreach (var e in afterRsrc.Entries) {
Assert.True(e.Id > lastId, "Resource entry ID must be greater than the previous");
lastId = e.Id;
}
}
[Fact]
public void CopyResourcesWithoutRsrc()
{
using var logger = _output.BuildLoggerFor<ResourceEditTests>();
using var _1 = Utility.GetTempFileName(out var tempFile);
CreateTestPEFileWithoutRsrc(tempFile);
var edit = new ResourceEdit(tempFile, logger);
edit.CopyResourcesFrom(PathHelper.GetFixture("Clowd.exe"));
edit.Commit();
AssertVersionInfo(tempFile, "3.4.439.61274", "3.4.439+ef5a83", "Copyright © Caelan Sayler, 2014-2022",
"Clowd", "Clowd", "Caelan Sayler");
}
[Fact]
public void CopyResourcesWithPreExistingRsrc()
{
using var logger = _output.BuildLoggerFor<ResourceEditTests>();
using var _1 = Utility.GetTempFileName(out var tempFile);
var exe = PathHelper.GetFixture("SquirrelAwareTweakedNetCoreApp.exe");
File.Copy(exe, tempFile);
var edit = new ResourceEdit(tempFile, logger);
edit.CopyResourcesFrom(PathHelper.GetFixture("Clowd.exe"));
edit.Commit();
AssertVersionInfo(tempFile, "3.4.439.61274", "3.4.439+ef5a83", "Copyright © Caelan Sayler, 2014-2022",
"Clowd", "Clowd", "Caelan Sayler");
}
[Fact]
public void SetIconWithPreExistingRsrc()
{
using var logger = _output.BuildLoggerFor<ResourceEditTests>();
using var _1 = Utility.GetTempFileName(out var tempFile);
var exe = PathHelper.GetFixture("atom.exe");
File.Copy(exe, tempFile);
var beforeRsrc = PEImage.FromFile(PEFile.FromFile(tempFile)).Resources;
Assert.NotNull(beforeRsrc);
var beforeIcon = IconResource.FromDirectory(beforeRsrc);
Assert.Single(beforeIcon.GetIconGroups());
Assert.Equal(6, beforeIcon.GetIconGroups().ToList()[0].GetIconEntries().Count());
var edit = new ResourceEdit(tempFile, logger);
edit.SetExeIcon(PathHelper.GetFixture("clowd.ico"));
edit.Commit();
var afterRsrc = PEImage.FromFile(PEFile.FromFile(tempFile)).Resources;
Assert.NotNull(afterRsrc);
var afterIcon = IconResource.FromDirectory(afterRsrc);
Assert.Single(afterIcon.GetIconGroups());
Assert.Equal(1, afterIcon.GetIconGroups().Single().Type);
Assert.Equal(7, afterIcon.GetIconGroups().ToList()[0].GetIconEntries().Count());
}
[Fact]
public void SetIconWithoutRsrc()
{
using var logger = _output.BuildLoggerFor<ResourceEditTests>();
using var _1 = Utility.GetTempFileName(out var tempFile);
CreateTestPEFileWithoutRsrc(tempFile);
var beforeRsrc = PEImage.FromFile(PEFile.FromFile(tempFile)).Resources;
Assert.Null(beforeRsrc);
var edit = new ResourceEdit(tempFile, logger);
edit.SetExeIcon(PathHelper.GetFixture("clowd.ico"));
edit.Commit();
var afterRsrc = PEImage.FromFile(PEFile.FromFile(tempFile)).Resources;
Assert.NotNull(afterRsrc);
var afterIcon = IconResource.FromDirectory(afterRsrc);
Assert.Single(afterIcon.GetIconGroups());
Assert.Equal(7, afterIcon.GetIconGroups().ToList()[0].GetIconEntries().Count());
}
[Fact]
public void SetVersionInfoWithPreExistingRsrc()
{
using var logger = _output.BuildLoggerFor<ResourceEditTests>();
using var _1 = Utility.GetTempFileName(out var tempFile);
var exe = PathHelper.GetFixture("atom.exe");
File.Copy(exe, tempFile);
var nuspec = PathHelper.GetFixture("FullNuspec.nuspec");
var manifest = PackageManifest.ParseFromFile(nuspec);
var pkgVersion = manifest.Version!;
var edit = new ResourceEdit(tempFile, logger);
edit.SetVersionInfo(manifest);
edit.Commit();
AssertVersionInfo(tempFile, manifest);
}
[Fact]
public void SetVersionInfoWithoutRsrc()
{
using var logger = _output.BuildLoggerFor<ResourceEditTests>();
using var _1 = Utility.GetTempFileName(out var tempFile);
CreateTestPEFileWithoutRsrc(tempFile);
var nuspec = PathHelper.GetFixture("FullNuspec.nuspec");
var manifest = PackageManifest.ParseFromFile(nuspec);
var pkgVersion = manifest.Version!;
var edit = new ResourceEdit(tempFile, logger);
edit.SetVersionInfo(manifest);
edit.Commit();
AssertVersionInfo(tempFile, manifest);
}
private void AssertVersionInfo(string exeFile, PackageManifest manifest)
{
AssertVersionInfo(exeFile, manifest.Version!.ToFullString(), manifest.Version!.ToFullString(),
manifest.ProductCopyright, manifest.ProductName, manifest.ProductDescription, manifest.ProductCompany);
}
private void AssertVersionInfo(string exeFile, string fileVersion, string productVersion,
string legalCopyright, string productName, string fileDescription, string companyName)
{
if (VelopackRuntimeInfo.IsWindows) {
// on Windows FileVersionInfo uses win32 methods to retrieve info from the PE resources
// on Unix, this function just looks for managed assembly attributes so is not suitable
var versionInfo = FileVersionInfo.GetVersionInfo(exeFile);
Assert.Equal(fileVersion, versionInfo.FileVersion);
Assert.Equal(productVersion, versionInfo.ProductVersion);
Assert.Equal(legalCopyright, versionInfo.LegalCopyright);
Assert.Equal(productName, versionInfo.ProductName);
Assert.Equal(fileDescription, versionInfo.FileDescription);
Assert.Equal(companyName, versionInfo.CompanyName);
} else {
var file = PEFile.FromFile(exeFile);
var image = PEImage.FromFile(file);
Assert.NotNull(image.Resources);
var versionInfo = VersionInfoResource.FromDirectory(image.Resources);
var stringInfo = versionInfo.GetChild<StringFileInfo>(StringFileInfo.StringFileInfoKey);
Assert.NotNull(stringInfo);
Assert.Single(stringInfo.Tables);
var stringTable = stringInfo.Tables[0];
Assert.Equal(companyName, stringTable[StringTable.CompanyNameKey]);
Assert.Equal(fileDescription, stringTable[StringTable.FileDescriptionKey]);
Assert.Equal(fileVersion, stringTable[StringTable.FileVersionKey]);
Assert.Equal(legalCopyright, stringTable[StringTable.LegalCopyrightKey]);
Assert.Equal(productName, stringTable[StringTable.ProductNameKey]);
Assert.Equal(productVersion, stringTable[StringTable.ProductVersionKey]);
}
}
}

View File

@@ -1,6 +1,7 @@
using System.Diagnostics;
using Velopack.Packaging.Unix.Commands;
using Velopack.Packaging.Windows.Commands;
using Velopack.Vpk;
using Velopack.Vpk.Logging;
namespace Velopack.Packaging.Tests;
@@ -8,8 +9,10 @@ namespace Velopack.Packaging.Tests;
public static class TestApp
{
public static void PackTestApp(string id, string version, string testString, string releaseDir, ILogger logger,
string releaseNotes = null, string channel = null)
string releaseNotes = null, string channel = null, RID targetRid = null)
{
targetRid ??= RID.Parse(VelopackRuntimeInfo.SystemRid);
var projDir = PathHelper.GetTestRootPath("TestApp");
var testStringFile = Path.Combine(projDir, "Const.cs");
var oldText = File.ReadAllText(testStringFile);
@@ -17,7 +20,7 @@ public static class TestApp
try {
File.WriteAllText(testStringFile, $"class Const {{ public const string TEST_STRING = \"{testString}\"; }}");
var args = new string[] { "publish", "--no-self-contained", "-c", "Release", "-r", VelopackRuntimeInfo.SystemRid, "-o", "publish" };
var args = new string[] { "publish", "--no-self-contained", "-c", "Release", "-r", targetRid.ToString(), "-o", "publish" };
var psi = new ProcessStartInfo("dotnet");
psi.WorkingDirectory = projDir;
@@ -31,14 +34,14 @@ public static class TestApp
if (p.ExitCode != 0)
throw new Exception($"dotnet publish failed with exit code {p.ExitCode}");
var console = new BasicConsole(logger, new DefaultPromptValueFactory(false));
var console = new BasicConsole(logger, new VelopackDefaults(false));
if (VelopackRuntimeInfo.IsWindows) {
if (targetRid.BaseRID == RuntimeOs.Windows) {
var options = new WindowsPackOptions {
EntryExecutableName = "TestApp.exe",
ReleaseDir = new DirectoryInfo(releaseDir),
PackId = id,
TargetRuntime = RID.Parse(VelopackRuntimeInfo.SystemOs.GetOsShortName()),
TargetRuntime = targetRid,
PackVersion = version,
PackDirectory = Path.Combine(projDir, "publish"),
ReleaseNotes = releaseNotes,
@@ -46,25 +49,29 @@ public static class TestApp
};
var runner = new WindowsPackCommandRunner(logger, console);
runner.Run(options).GetAwaiterResult();
} else if (VelopackRuntimeInfo.IsOSX) {
} else if (targetRid.BaseRID == RuntimeOs.OSX) {
var options = new OsxPackOptions {
EntryExecutableName = "TestApp",
ReleaseDir = new DirectoryInfo(releaseDir),
PackId = id,
TargetRuntime = RID.Parse(VelopackRuntimeInfo.SystemOs.GetOsShortName()),
TargetRuntime = targetRid,
PackVersion = version,
PackDirectory = Path.Combine(projDir, "publish"),
ReleaseNotes = releaseNotes,
Channel = channel,
};
var runner = new OsxPackCommandRunner(logger, console);
runner.Run(options).GetAwaiterResult();
} else if (VelopackRuntimeInfo.IsLinux) {
if (VelopackRuntimeInfo.IsOSX) {
var runner = new OsxPackCommandRunner(logger, console);
runner.Run(options).GetAwaiterResult();
} else {
throw new PlatformNotSupportedException();
}
} else if (targetRid.BaseRID == RuntimeOs.Linux) {
var options = new LinuxPackOptions {
EntryExecutableName = "TestApp",
ReleaseDir = new DirectoryInfo(releaseDir),
PackId = id,
TargetRuntime = RID.Parse(VelopackRuntimeInfo.SystemOs.GetOsShortName()),
TargetRuntime = targetRid,
PackVersion = version,
PackDirectory = Path.Combine(projDir, "publish"),
ReleaseNotes = releaseNotes,

View File

@@ -10,7 +10,7 @@
<ItemGroup>
<PackageReference Include="Octokit" Version="11.0.1" />
<PackageReference Include="NuGet.Packaging" Version="6.9.1" />
<PackageReference Include="NuGet.Packaging" Version="6.10.0" />
</ItemGroup>
<ItemGroup>
@@ -18,6 +18,10 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
<PackageReference Update="GitHubActionsTestLogger" Version="2.4.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
</ItemGroup>
</Project>

View File

@@ -8,6 +8,7 @@ using Velopack.Compression;
using Velopack.Packaging.Commands;
using Velopack.Packaging.Exceptions;
using Velopack.Packaging.Windows.Commands;
using Velopack.Vpk;
using Velopack.Vpk.Logging;
using Velopack.Windows;
@@ -25,7 +26,7 @@ public class WindowsPackTests
private WindowsPackCommandRunner GetPackRunner(ILogger logger)
{
var console = new BasicConsole(logger, new DefaultPromptValueFactory(false));
var console = new BasicConsole(logger, new VelopackDefaults(false));
return new WindowsPackCommandRunner(logger, console);
}
@@ -327,7 +328,7 @@ public class WindowsPackTests
// apply delta and check package
var output = Path.Combine(releaseDir, "delta.patched");
new DeltaPatchCommandRunner(logger, new BasicConsole(logger, new DefaultPromptValueFactory(false))).Run(new DeltaPatchOptions {
new DeltaPatchCommandRunner(logger, new BasicConsole(logger, new VelopackDefaults(false))).Run(new DeltaPatchOptions {
BasePackage = Path.Combine(releaseDir, $"{id}-1.0.0-full.nupkg"),
OutputFile = output,
PatchFiles = new[] { new FileInfo(deltaPath) },
@@ -344,7 +345,7 @@ public class WindowsPackTests
// can apply multiple deltas, and handle add/removing files?
output = Path.Combine(releaseDir, "delta.patched2");
var deltav3 = Path.Combine(releaseDir, $"{id}-3.0.0-delta.nupkg");
new DeltaPatchCommandRunner(logger, new BasicConsole(logger, new DefaultPromptValueFactory(false))).Run(new DeltaPatchOptions {
new DeltaPatchCommandRunner(logger, new BasicConsole(logger, new VelopackDefaults(false))).Run(new DeltaPatchOptions {
BasePackage = Path.Combine(releaseDir, $"{id}-1.0.0-full.nupkg"),
OutputFile = output,
PatchFiles = [new FileInfo(deltaPath), new FileInfo(deltav3)],

View File

@@ -39,6 +39,10 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
<PackageReference Update="GitHubActionsTestLogger" Version="2.4.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
</ItemGroup>
</Project>

BIN
test/fixtures/Clowd.exe vendored Normal file

Binary file not shown.

BIN
test/fixtures/clowd.ico vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB