From f1fd069ac7c7482bbdcadc48bdca2bca20e90349 Mon Sep 17 00:00:00 2001 From: Caelan Sayler Date: Wed, 27 Mar 2024 10:23:01 +0000 Subject: [PATCH] Detect VelopackApp.Run() in any method in main module --- src/Velopack.Build/MSBuildLogger.cs | 5 + .../Commands/WindowsPackCommandRunner.cs | 5 +- src/Velopack.Packaging.Windows/CompatUtil.cs | 241 ++++++++++++++++++ src/Velopack.Packaging.Windows/DotnetUtil.cs | 151 ----------- .../Abstractions/IFancyConsole.cs | 2 + src/Velopack.Vpk/Logging/BasicConsole.cs | 5 + src/Velopack.Vpk/Logging/SpectreConsole.cs | 5 + test/TestApp/Program.cs | 4 + test/TestApp/TestApp.csproj | 4 + ...{DotnetUtilTests.cs => CompatUtilTests.cs} | 62 +++-- 10 files changed, 315 insertions(+), 169 deletions(-) create mode 100644 src/Velopack.Packaging.Windows/CompatUtil.cs delete mode 100644 src/Velopack.Packaging.Windows/DotnetUtil.cs rename test/Velopack.Packaging.Tests/{DotnetUtilTests.cs => CompatUtilTests.cs} (61%) diff --git a/src/Velopack.Build/MSBuildLogger.cs b/src/Velopack.Build/MSBuildLogger.cs index dc4c8b4f..1f5bd538 100644 --- a/src/Velopack.Build/MSBuildLogger.cs +++ b/src/Velopack.Build/MSBuildLogger.cs @@ -85,4 +85,9 @@ public class MSBuildLogger(TaskLoggingHelper loggingHelper) : ILogger, IFancyCon { Log(LogLevel.Information, 0, null, null, (object? state, Exception? exception) => text); } + + public string EscapeMarkup(string text) + { + return text; + } } diff --git a/src/Velopack.Packaging.Windows/Commands/WindowsPackCommandRunner.cs b/src/Velopack.Packaging.Windows/Commands/WindowsPackCommandRunner.cs index cc9902ef..f7b3f6d6 100644 --- a/src/Velopack.Packaging.Windows/Commands/WindowsPackCommandRunner.cs +++ b/src/Velopack.Packaging.Windows/Commands/WindowsPackCommandRunner.cs @@ -33,11 +33,12 @@ public class WindowsPackCommandRunner : PackageBuilder if (Directory.EnumerateFiles(packDir, "*.application").Any(f => File.ReadAllText(f).Contains("clickonce"))) { throw new ArgumentException( "Velopack does not support building releases for ClickOnce applications. " + - "Please publish your application to a folder without ClickOnce."); + "Please remove all ClickOnce properties from your .csproj before continuing."); } if (!Options.SkipVelopackAppCheck) { - DotnetUtil.VerifyVelopackApp(MainExePath, Log); + var compat = new CompatUtil(Log, Console); + compat.Verify(MainExePath); } else { Log.Info("Skipping VelopackApp.Build.Run() check."); } diff --git a/src/Velopack.Packaging.Windows/CompatUtil.cs b/src/Velopack.Packaging.Windows/CompatUtil.cs new file mode 100644 index 00000000..82e2d4b9 --- /dev/null +++ b/src/Velopack.Packaging.Windows/CompatUtil.cs @@ -0,0 +1,241 @@ +using AsmResolver.DotNet; +using AsmResolver.DotNet.Bundles; +using AsmResolver.DotNet.Serialized; +using AsmResolver.PE; +using AsmResolver.PE.DotNet.Cil; +using AsmResolver.PE.Win32Resources.Version; +using Microsoft.Extensions.Logging; +using NuGet.Versioning; +using Velopack.Packaging.Abstractions; +using Velopack.Packaging.Exceptions; + +namespace Velopack.Packaging.Windows; + +public class CompatUtil +{ + private readonly ILogger _log; + private readonly IFancyConsole _console; + + public CompatUtil(ILogger logger, IFancyConsole console) + { + _log = logger; + _console = console; + } + + public NuGetVersion Verify(string exeFile) + { + VerifyVelopackApp(exeFile); + return VerifyVelopackVersion(exeFile); + } + + public void VerifyVelopackApp(string exeFile) + { + try { + AssemblyDefinition mainAssy = LoadDotnetAssembly(exeFile); + if (mainAssy == null) { + return; + } + + ModuleDefinition mainModule = mainAssy.Modules.Single(); + var result = SearchAssemblyForVelopackApp(mainModule); + if (result == null) { + // if we've iterated the whole main assembly and not found the call, then the velopack builder is missing + throw new UserInfoException($"Unable to verify VelopackApp is called. " + + "Please ensure that 'VelopackApp.Build().Run()' is present in your Program.Main()."); + } + + result = _console.EscapeMarkup(result); + + // could be regular Program.Main or async Main which has a slightly different signature. + if (result.Contains("Program") && result.Contains("Main")) { + _log.Info($"[green underline]Verified VelopackApp.Run()[/] in '{result}'."); + } else { + _log.Warn($"VelopackApp.Run() was found in method '{result}', which does not look like your application's entry point." + + $"It is [underline yellow]strongly recommended[/] that you move this to the very beginning of your Main() method. "); + } + + } catch (Exception ex) when (ex is not UserInfoException) { + _log.Error("Unable to verify VelopackApp: " + ex.Message); + } + } + + public NuGetVersion VerifyVelopackVersion(string exeFile) + { + var rawVersion = GetVelopackVersion(exeFile); + if (rawVersion == null) { + return null; + } + + var dllVersion = new Version(rawVersion.ToString("V", VersionFormatter.Instance)); + var myVersion = new Version(VelopackRuntimeInfo.VelopackNugetVersion.ToString("V", VersionFormatter.Instance)); + if (dllVersion == myVersion) { + return new NuGetVersion(dllVersion); + } + + if (dllVersion > myVersion) { + throw new UserInfoException($"Velopack library version is greater than vpk version ({dllVersion} > {myVersion}). This can cause compatibility issues, please update vpk first."); + } + + _log.Warn($"Velopack library version is lower than vpk version ({dllVersion} < {myVersion}). This can occasionally cause compatibility issues."); + return new NuGetVersion(dllVersion); + } + + private string SearchAssemblyForVelopackApp(ModuleDefinition mainModule) + { + MethodDefinition entryPoint = mainModule.ManagedEntryPointMethod; + + string SearchMethod(MethodDefinition method) + { + foreach (var instr in method.CilMethodBody.Instructions) { + if (instr.OpCode.Code is CilCode.Call or CilCode.Callvirt or CilCode.Calli) { + var operand = instr.Operand as SerializedMemberReference; + if (operand != null) { + if (operand.Name == "Run" && operand.DeclaringType.FullName == "Velopack.VelopackApp") { + // success! + return method.FullName; + } + } + } + } + return null; + } + + string SearchType(TypeDefinition type) + { + // search all methods in type + foreach (var method in type.Methods) { + if (method.HasMethodBody) { + var result = SearchMethod(method); + if (result != null) { + return result; + } + } + } + + // then, search all nested types + foreach (var nestedType in type.NestedTypes) { + var result = SearchType(nestedType); + if (result != null) { + return result; + } + } + + return null; + } + + // search entry point first + string result; + if ((result = SearchMethod(entryPoint)) != null) { + return result; + } + + // then, iterate all methods in the main module + foreach (var topType in mainModule.TopLevelTypes) { + if ((result = SearchType(topType)) != null) { + return result; + } + } + + return null; + } + + public NuGetVersion GetVelopackVersion(string exeFile) + { + try { + var velopackDll = FindVelopackDll(exeFile); + if (velopackDll == null) { + return null; + } + + var versionInfo = VersionInfoResource.FromDirectory(velopackDll.Resources); + var actualInfo = versionInfo.GetChild(StringFileInfo.StringFileInfoKey); + var versionTable = actualInfo.Tables[0]; + var productVersion = versionTable.Where(v => v.Key == StringTable.ProductVersionKey).FirstOrDefault(); + return NuGetVersion.Parse(productVersion.Value); + } catch (Exception ex) { + // don't really care + _log.Debug(ex, "Unable to read Velopack.dll version info."); + } + + return null; + } + + private IPEImage FindVelopackDll(string exeFile) + { + var versionFile = Path.Combine(Path.GetDirectoryName(exeFile), "Velopack.dll"); + if (File.Exists(versionFile)) { + _log.Debug(exeFile + " has Velopack.dll in the same directory."); + return PEImage.FromFile(versionFile); + } + + try { + var bundle = BundleManifest.FromFile(exeFile); + IList embeddedFiles = bundle.Files; + var velopackEmbedded = embeddedFiles.SingleOrDefault(f => f.Type == BundleFileType.Assembly && f.RelativePath == "Velopack.dll"); + if (velopackEmbedded != null && velopackEmbedded.TryGetReader(out var readerVel)) { + _log.Debug(exeFile + " has Velopack.dll embedded in a SingleFileHost."); + return PEImage.FromReader(readerVel); + } + } catch (BadImageFormatException) { + // not an AppHost / SingleFileHost binary + return null; + } + + return null; + } + + private AssemblyDefinition LoadDotnetAssembly(string exeFile) + { + try { + var assy = AssemblyDefinition.FromFile(exeFile); + return assy; + } catch (BadImageFormatException) { + // not a .Net Framework binary + } + + try { + var bundle = BundleManifest.FromFile(exeFile); + IList embeddedFiles = null; + + try { + embeddedFiles = bundle.Files; + } catch { + // not a SingleFileHost binary, so we'll search on disk + var parentDir = Path.GetDirectoryName(exeFile); + var diskFile = Path.Combine(parentDir, Path.GetFileNameWithoutExtension(exeFile) + ".dll"); + if (File.Exists(diskFile)) { + return AssemblyDefinition.FromFile(diskFile); + } + + var runtimeConfigFile = Directory.EnumerateFiles(parentDir, "*.runtimeconfig.json").SingleOrDefault(); + var possNameRuntime = Path.Combine(parentDir, + Path.GetFileNameWithoutExtension(Path.GetFileNameWithoutExtension(runtimeConfigFile)) + ".dll"); + if (File.Exists(possNameRuntime)) { + return AssemblyDefinition.FromFile(possNameRuntime); + } + + return null; + } + + var runtimeConfig = embeddedFiles.SingleOrDefault(f => f.Type == BundleFileType.RuntimeConfigJson); + if (runtimeConfig != null) { + var possName1 = Path.GetFileNameWithoutExtension(Path.GetFileNameWithoutExtension(runtimeConfig.RelativePath)) + ".dll"; + var file = embeddedFiles.SingleOrDefault(f => f.Type == BundleFileType.Assembly && f.RelativePath == possName1); + if (file != null && file.TryGetReader(out var reader)) { + return AssemblyDefinition.FromReader(reader); + } + } + + var possName2 = Path.GetFileNameWithoutExtension(exeFile) + ".dll"; + var file2 = embeddedFiles.SingleOrDefault(f => f.Type == BundleFileType.Assembly && f.RelativePath == possName2); + if (file2 != null && file2.TryGetReader(out var reader2)) { + return AssemblyDefinition.FromReader(reader2); + } + + return null; + } catch (BadImageFormatException) { + // not an AppHost / SingleFileHost binary + return null; + } + } +} diff --git a/src/Velopack.Packaging.Windows/DotnetUtil.cs b/src/Velopack.Packaging.Windows/DotnetUtil.cs deleted file mode 100644 index 7f1d7a4d..00000000 --- a/src/Velopack.Packaging.Windows/DotnetUtil.cs +++ /dev/null @@ -1,151 +0,0 @@ -using AsmResolver.DotNet; -using AsmResolver.DotNet.Bundles; -using AsmResolver.DotNet.Serialized; -using AsmResolver.PE; -using AsmResolver.PE.DotNet.Cil; -using AsmResolver.PE.Win32Resources.Version; -using Microsoft.Extensions.Logging; -using NuGet.Versioning; -using Velopack.Packaging.Exceptions; - -namespace Velopack.Packaging.Windows; - -public class DotnetUtil -{ - public static NuGetVersion VerifyVelopackApp(string exeFile, ILogger log) - { - try { - NuGetVersion velopackVersion = null; - IPEImage velopackDll = null; - AssemblyDefinition mainAssy = null; - mainAssy ??= LoadFullFramework(exeFile, ref velopackDll); - mainAssy ??= LoadDncBundle(exeFile, ref velopackDll); - - if (mainAssy == null) { - // not a dotnet binary - return null; - } - - if (velopackDll != null) { - try { - var versionInfo = VersionInfoResource.FromDirectory(velopackDll.Resources); - var actualInfo = versionInfo.GetChild(StringFileInfo.StringFileInfoKey); - var versionTable = actualInfo.Tables[0]; - var productVersion = versionTable.Where(v => v.Key == StringTable.ProductVersionKey).FirstOrDefault(); - velopackVersion = NuGetVersion.Parse(productVersion.Value); - } catch (Exception ex) { - // don't really care - log.Debug(ex, "Unable to read Velopack.dll version info."); - } - } - - var mainModule = mainAssy.Modules.Single(); - var entryPoint = mainModule.ManagedEntryPointMethod; - - foreach (var instr in entryPoint.CilMethodBody.Instructions) { - if (instr.OpCode.Code is CilCode.Call or CilCode.Callvirt or CilCode.Calli) { - SerializedMemberReference operand = instr.Operand as SerializedMemberReference; - if (operand != null && operand.IsMethod) { - if (operand.Name == "Run" && operand.DeclaringType.FullName == "Velopack.VelopackApp") { - // success! - if (velopackVersion != null) { - log.Info($"Verified VelopackApp.Run() in '{entryPoint.FullName}', version {velopackVersion}."); - if (velopackVersion != VelopackRuntimeInfo.VelopackProductVersion) { - log.Warn(exeFile + " was built with a different version of Velopack than this tool. " + - $"This may cause compatibility issues. Expected {VelopackRuntimeInfo.VelopackProductVersion}, " + - $"but found {velopackVersion}."); - } - return velopackVersion; - } else { - log.Warn("VelopackApp verified at entry point, but ProductVersion could not be checked."); - return null; - } - } - } - } - } - - // if we've iterated the whole main method and not found the call, then the velopack builder is missing - throw new UserInfoException($"Unable to verify VelopackApp, in application main method '{entryPoint.FullName}'. " + - "Please ensure that 'VelopackApp.Build().Run()' is present in your Program.Main()."); - - } catch (Exception ex) when (ex is not UserInfoException) { - log.Error("Unable to verify VelopackApp: " + ex.Message); - } - - return null; - } - - private static AssemblyDefinition LoadFullFramework(string exeFile, ref IPEImage velopackDll) - { - try { - var assy = AssemblyDefinition.FromFile(exeFile); - var versionFile = Path.Combine(Path.GetDirectoryName(exeFile), "Velopack.dll"); - if (File.Exists(versionFile)) { - velopackDll = PEImage.FromFile(versionFile); - } - return assy; - } catch (BadImageFormatException) { - // not a .Net Framework binary - return null; - } - } - - private static AssemblyDefinition LoadDncBundle(string exeFile, ref IPEImage velopackDll) - { - try { - var bundle = BundleManifest.FromFile(exeFile); - IList embeddedFiles = null; - - try { - embeddedFiles = bundle.Files; - } catch { - // not a SingleFileHost binary, so we'll search on disk - var parentDir = Path.GetDirectoryName(exeFile); - var versionFile = Path.Combine(parentDir, "Velopack.dll"); - if (File.Exists(versionFile)) { - velopackDll = PEImage.FromFile(versionFile); - } - - var diskFile = Path.Combine(parentDir, Path.GetFileNameWithoutExtension(exeFile) + ".dll"); - if (File.Exists(diskFile)) { - return AssemblyDefinition.FromFile(diskFile); - } - - var runtimeConfigFile = Directory.EnumerateFiles(parentDir, "*.runtimeconfig.json").SingleOrDefault(); - var possNameRuntime = Path.Combine(parentDir, - Path.GetFileNameWithoutExtension(Path.GetFileNameWithoutExtension(runtimeConfigFile)) + ".dll"); - if (File.Exists(possNameRuntime)) { - return AssemblyDefinition.FromFile(possNameRuntime); - } - - return null; - } - - var velopackEmbedded = embeddedFiles.SingleOrDefault(f => f.Type == BundleFileType.Assembly && f.RelativePath == "Velopack.dll"); - if (velopackEmbedded != null && velopackEmbedded.TryGetReader(out var readerVel)) { - velopackDll = PEImage.FromReader(readerVel); - } - - var runtimeConfig = embeddedFiles.SingleOrDefault(f => f.Type == BundleFileType.RuntimeConfigJson); - if (runtimeConfig != null) { - var possName1 = Path.GetFileNameWithoutExtension(Path.GetFileNameWithoutExtension(runtimeConfig.RelativePath)) + ".dll"; - var file = embeddedFiles.SingleOrDefault(f => f.Type == BundleFileType.Assembly && f.RelativePath == possName1); - if (file != null && file.TryGetReader(out var reader)) { - return AssemblyDefinition.FromReader(reader); - } - } - - var possName2 = Path.GetFileNameWithoutExtension(exeFile) + ".dll"; - var file2 = embeddedFiles.SingleOrDefault(f => f.Type == BundleFileType.Assembly && f.RelativePath == possName2); - if (file2 != null && file2.TryGetReader(out var reader2)) { - return AssemblyDefinition.FromReader(reader2); - } - - return null; - } catch (BadImageFormatException) { - // not an AppHost / SingleFileHost binary - return null; - } - } -} diff --git a/src/Velopack.Packaging/Abstractions/IFancyConsole.cs b/src/Velopack.Packaging/Abstractions/IFancyConsole.cs index c938aafc..355e14ba 100644 --- a/src/Velopack.Packaging/Abstractions/IFancyConsole.cs +++ b/src/Velopack.Packaging/Abstractions/IFancyConsole.cs @@ -7,4 +7,6 @@ public interface IFancyConsole : IConsole void WriteTable(string tableName, IEnumerable> rows, bool hasHeaderRow = true); Task PromptYesNo(string prompt, bool? defaultValue = null, TimeSpan? timeout = null); + + string EscapeMarkup(string text); } diff --git a/src/Velopack.Vpk/Logging/BasicConsole.cs b/src/Velopack.Vpk/Logging/BasicConsole.cs index 1af25cd6..027f274a 100644 --- a/src/Velopack.Vpk/Logging/BasicConsole.cs +++ b/src/Velopack.Vpk/Logging/BasicConsole.cs @@ -13,6 +13,11 @@ public class BasicConsole : IFancyConsole this.defaultFactory = defaultFactory; } + public string EscapeMarkup(string text) + { + return text; + } + public async Task ExecuteProgressAsync(Func action) { var start = DateTime.UtcNow; diff --git a/src/Velopack.Vpk/Logging/SpectreConsole.cs b/src/Velopack.Vpk/Logging/SpectreConsole.cs index 6c0ecacc..93a5a2b2 100644 --- a/src/Velopack.Vpk/Logging/SpectreConsole.cs +++ b/src/Velopack.Vpk/Logging/SpectreConsole.cs @@ -15,6 +15,11 @@ public class SpectreConsole : IFancyConsole this.defaultFactory = defaultFactory; } + public string EscapeMarkup(string text) + { + return Markup.Escape(text); + } + public async Task ExecuteProgressAsync(Func action) { var start = DateTime.UtcNow; diff --git a/test/TestApp/Program.cs b/test/TestApp/Program.cs index cf5d56f2..07e65208 100644 --- a/test/TestApp/Program.cs +++ b/test/TestApp/Program.cs @@ -7,6 +7,10 @@ try { bool shouldExit = false; bool shouldAutoUpdate = args.Any(a => a.Equals("--autoupdate", StringComparison.OrdinalIgnoreCase)); +#if USE_ASYNC_MAIN + await Task.Delay(10).ConfigureAwait(false); +#endif + #if !NO_VELO_BUILDER VelopackApp.Build() .SetAutoApplyOnStartup(shouldAutoUpdate) diff --git a/test/TestApp/TestApp.csproj b/test/TestApp/TestApp.csproj index 14a96a09..4ba6b894 100644 --- a/test/TestApp/TestApp.csproj +++ b/test/TestApp/TestApp.csproj @@ -10,6 +10,10 @@ NO_VELO_BUILDER + + USE_ASYNC_MAIN + + diff --git a/test/Velopack.Packaging.Tests/DotnetUtilTests.cs b/test/Velopack.Packaging.Tests/CompatUtilTests.cs similarity index 61% rename from test/Velopack.Packaging.Tests/DotnetUtilTests.cs rename to test/Velopack.Packaging.Tests/CompatUtilTests.cs index b02a0bdc..9bc557dc 100644 --- a/test/Velopack.Packaging.Tests/DotnetUtilTests.cs +++ b/test/Velopack.Packaging.Tests/CompatUtilTests.cs @@ -1,30 +1,39 @@ -using Velopack.Packaging.Exceptions; +using Divergic.Logging.Xunit; +using Velopack.Packaging.Exceptions; using Velopack.Packaging.Windows; +using Velopack.Vpk.Logging; namespace Velopack.Packaging.Tests; -public class DotnetUtilTests +public class CompatUtilTests { private readonly ITestOutputHelper _output; - public DotnetUtilTests(ITestOutputHelper output) + public CompatUtilTests(ITestOutputHelper output) { _output = output; } + private ICacheLogger GetCompat(out CompatUtil compat) + { + var logger = _output.BuildLoggerFor(); + compat = new CompatUtil(logger, new BasicConsole(logger, new DefaultPromptValueFactory(true))); + return logger; + } + [SkippableFact] public void NonDotnetBinaryPasses() { Skip.IfNot(VelopackRuntimeInfo.IsWindows); - using var logger = _output.BuildLoggerFor(); - Assert.Null(DotnetUtil.VerifyVelopackApp(PathHelper.GetRustAsset("testapp.exe"), logger)); + using var logger = GetCompat(out var compat); + Assert.Null(compat.Verify(PathHelper.GetRustAsset("testapp.exe"))); } [SkippableFact] public void PublishSingleFilePasses() { Skip.IfNot(VelopackRuntimeInfo.IsWindows); - using var logger = _output.BuildLoggerFor(); + using var logger = GetCompat(out var compat); using var _1 = Utility.GetTempDirectory(out var dir); var sample = PathHelper.GetAvaloniaSample(); Exe.InvokeAndThrowIfNonZero( @@ -34,18 +43,18 @@ public class DotnetUtilTests sample); var path = Path.Combine(dir, "AvaloniaCrossPlat.exe"); - Assert.Equal(VelopackRuntimeInfo.VelopackProductVersion, DotnetUtil.VerifyVelopackApp(path, logger)); + Assert.Equal(VelopackRuntimeInfo.VelopackProductVersion, compat.Verify(path)); var newPath = Path.Combine(dir, "AvaloniaCrossPlat-asd2.exe"); File.Move(path, newPath); - Assert.Equal(VelopackRuntimeInfo.VelopackProductVersion, DotnetUtil.VerifyVelopackApp(newPath, logger)); + Assert.Equal(VelopackRuntimeInfo.VelopackProductVersion, compat.Verify(newPath)); } [SkippableFact] public void PublishDotnet6Passes() { Skip.IfNot(VelopackRuntimeInfo.IsWindows); - using var logger = _output.BuildLoggerFor(); + using var logger = GetCompat(out var compat); using var _1 = Utility.GetTempDirectory(out var dir); var sample = PathHelper.GetAvaloniaSample(); Exe.InvokeAndThrowIfNonZero( @@ -55,18 +64,18 @@ public class DotnetUtilTests sample); var path = Path.Combine(dir, "AvaloniaCrossPlat.exe"); - Assert.Equal(VelopackRuntimeInfo.VelopackProductVersion, DotnetUtil.VerifyVelopackApp(path, logger)); + Assert.Equal(VelopackRuntimeInfo.VelopackProductVersion, compat.Verify(path)); var newPath = Path.Combine(dir, "AvaloniaCrossPlat-asd2.exe"); File.Move(path, newPath); - Assert.Equal(VelopackRuntimeInfo.VelopackProductVersion, DotnetUtil.VerifyVelopackApp(newPath, logger)); + Assert.Equal(VelopackRuntimeInfo.VelopackProductVersion, compat.Verify(newPath)); } [SkippableFact] public void PublishNet48Passes() { Skip.IfNot(VelopackRuntimeInfo.IsWindows); - using var logger = _output.BuildLoggerFor(); + using var logger = GetCompat(out var compat); using var _1 = Utility.GetTempDirectory(out var dir); var sample = PathHelper.GetWpfSample(); Exe.InvokeAndThrowIfNonZero( @@ -75,18 +84,18 @@ public class DotnetUtilTests sample); var path = Path.Combine(dir, "VeloWpfSample.exe"); - Assert.NotNull(DotnetUtil.VerifyVelopackApp(path, logger)); + Assert.NotNull(compat.Verify(path)); var newPath = Path.Combine(dir, "VeloWpfSample-asd2.exe"); File.Move(path, newPath); - Assert.NotNull(DotnetUtil.VerifyVelopackApp(newPath, logger)); + Assert.NotNull(compat.Verify(newPath)); } [SkippableFact] public void UnawareDotnetAppFails() { Skip.IfNot(VelopackRuntimeInfo.IsWindows); - using var logger = _output.BuildLoggerFor(); + using var logger = GetCompat(out var compat); using var _1 = Utility.GetTempDirectory(out var dir); var sample = PathHelper.GetTestRootPath("TestApp"); Exe.InvokeAndThrowIfNonZero( @@ -96,6 +105,27 @@ public class DotnetUtilTests sample); var path = Path.Combine(dir, "TestApp.exe"); - Assert.Throws(() => DotnetUtil.VerifyVelopackApp(path, logger)); + Assert.Throws(() => compat.Verify(path)); + } + + [SkippableFact] + public void PublishAsyncMainPasses() + { + Skip.IfNot(VelopackRuntimeInfo.IsWindows); + using var logger = GetCompat(out var compat); + using var _1 = Utility.GetTempDirectory(out var dir); + var sample = PathHelper.GetTestRootPath("TestApp"); + Exe.InvokeAndThrowIfNonZero( + "dotnet", + new string[] { "publish", "--no-self-contained", "-r", "win-x64", "-o", dir, + "-p:UseAsyncMain=true" }, + sample); + + var path = Path.Combine(dir, "TestApp.exe"); + Assert.NotNull(compat.Verify(path)); + + var newPath = Path.Combine(dir, "VeloWpfSample-asd2.exe"); + File.Move(path, newPath); + Assert.NotNull(compat.Verify(newPath)); } }