Add Velopack.Build project

This commit is contained in:
Caelan Sayler
2024-03-08 20:07:09 +00:00
parent fea1031d1d
commit 1c2c112ee3
9 changed files with 455 additions and 0 deletions

View File

@@ -51,6 +51,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Divergic.Logging.Xunit", "t
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Velopack.Packaging.HostModel", "src\Velopack.Packaging.HostModel\Velopack.Packaging.HostModel.csproj", "{E9A2620C-C638-446C-BA30-F62C05709365}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Velopack.Packaging.HostModel", "src\Velopack.Packaging.HostModel\Velopack.Packaging.HostModel.csproj", "{E9A2620C-C638-446C-BA30-F62C05709365}"
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Velopack.Build", "src\Velopack.Build\Velopack.Build.csproj", "{97C9B2CF-877F-4C98-A513-058784A23697}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
@@ -117,6 +119,10 @@ Global
{E9A2620C-C638-446C-BA30-F62C05709365}.Debug|Any CPU.Build.0 = Debug|Any CPU {E9A2620C-C638-446C-BA30-F62C05709365}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E9A2620C-C638-446C-BA30-F62C05709365}.Release|Any CPU.ActiveCfg = Release|Any CPU {E9A2620C-C638-446C-BA30-F62C05709365}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E9A2620C-C638-446C-BA30-F62C05709365}.Release|Any CPU.Build.0 = Release|Any CPU {E9A2620C-C638-446C-BA30-F62C05709365}.Release|Any CPU.Build.0 = Release|Any CPU
{97C9B2CF-877F-4C98-A513-058784A23697}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{97C9B2CF-877F-4C98-A513-058784A23697}.Debug|Any CPU.Build.0 = Debug|Any CPU
{97C9B2CF-877F-4C98-A513-058784A23697}.Release|Any CPU.ActiveCfg = Release|Any CPU
{97C9B2CF-877F-4C98-A513-058784A23697}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE

View File

@@ -40,4 +40,6 @@
</AssemblyAttribute> </AssemblyAttribute>
</ItemGroup> </ItemGroup>
<!--<Import Project="..\..\src\Velopack.Build\Velopack.Build.targets" Condition=" $(UseLocalVelopack) != '' " />-->
</Project> </Project>

View File

@@ -0,0 +1,30 @@
using System;
using System.Threading.Tasks;
using MSBuildTask = Microsoft.Build.Utilities.Task;
namespace Velopack.Build;
public abstract class MSBuildAsyncTask : MSBuildTask
{
protected MSBuildLogger Logger { get; }
protected MSBuildAsyncTask()
{
Logger = new MSBuildLogger(Log);
}
public sealed override bool Execute()
{
try {
return Task.Run(ExecuteAsync).Result;
} catch (AggregateException ex) {
ex.Flatten().Handle((x) => {
Log.LogError(x.Message);
return true;
});
return false;
}
}
protected abstract Task<bool> ExecuteAsync();
}

View File

@@ -0,0 +1,86 @@
using System;
using System.Collections.Generic;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
using Microsoft.Extensions.Logging;
using Velopack.Packaging.Abstractions;
using ILogger = Microsoft.Extensions.Logging.ILogger;
using Task = System.Threading.Tasks.Task;
namespace Velopack.Build;
public class MSBuildLogger(TaskLoggingHelper loggingHelper) : ILogger, IFancyConsole, IFancyConsoleProgress
{
private TaskLoggingHelper LoggingHelper { get; } = loggingHelper;
IDisposable ILogger.BeginScope<TState>(TState state)
{
throw new NotImplementedException();
}
public async Task ExecuteProgressAsync(Func<IFancyConsoleProgress, Task> action)
{
await action(this).ConfigureAwait(false);
}
public async Task RunTask(string name, Func<Action<int>, Task> fn)
{
try {
await fn(x => { }).ConfigureAwait(false);
} catch (Exception ex) {
this.LogError(ex, "Error running task {0}", name);
}
}
public bool IsEnabled(LogLevel logLevel)
{
return logLevel switch {
LogLevel.Trace => LoggingHelper.LogsMessagesOfImportance(MessageImportance.Low),
LogLevel.Debug => LoggingHelper.LogsMessagesOfImportance(MessageImportance.Normal),
LogLevel.Information => LoggingHelper.LogsMessagesOfImportance(MessageImportance.High),
_ => true,
};
}
public void Log<TState>(LogLevel logLevel, EventId _, TState state, Exception? exception, Func<TState, Exception?, string> formatter)
{
string message = formatter(state, exception);
if (exception != null) {
message += " " + exception.Message;
}
switch (logLevel) {
case LogLevel.Trace:
LoggingHelper.LogMessage(MessageImportance.Low, message);
break;
case LogLevel.Debug:
LoggingHelper.LogMessage(MessageImportance.Normal, message);
break;
case LogLevel.Information:
LoggingHelper.LogMessage(MessageImportance.High, message);
break;
case LogLevel.Warning:
LoggingHelper.LogWarning(message);
break;
case LogLevel.Error:
case LogLevel.Critical:
LoggingHelper.LogError(message);
break;
}
}
public void WriteTable(string tableName, IEnumerable<IEnumerable<string>> rows, bool hasHeaderRow = true)
{
//Do we need this output for MSBuild?
}
public System.Threading.Tasks.Task<bool> PromptYesNo(string prompt, bool? defaultValue = null, TimeSpan? timeout = null)
{
//TODO: This API is problematic as it assumes interactive.
return Task.FromResult(true);
}
public void WriteLine(string text = "")
{
Log(LogLevel.Information, 0, null, null, (object? state, Exception? exception) => text);
}
}

View File

@@ -0,0 +1,68 @@
using System;
using System.IO;
using System.Reflection;
using System.Threading.Tasks;
using Microsoft.Build.Framework;
using Velopack.Packaging;
using Velopack.Packaging.Windows.Commands;
namespace Velopack.Build;
public class PackTask : MSBuildAsyncTask
{
public string? TargetRuntime { get; set; }
[Required]
public string PackVersion { get; set; } = "";
[Required]
public string Runtimes { get; set; } = "";
[Required]
public string PackId { get; set; } = "";
[Required]
public string PackDirectory { get; set; } = null!;
[Required]
public string ReleaseDirectory { get; set; } = null!;
protected override async Task<bool> ExecuteAsync()
{
//System.Diagnostics.Debugger.Launch();
HelperFile.AddSearchPath(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location));
if (VelopackRuntimeInfo.IsWindows) {
var targetRuntime = RID.Parse(TargetRuntime ?? VelopackRuntimeInfo.SystemOs.GetOsShortName());
if (targetRuntime.BaseRID == RuntimeOs.Unknown) {
//TODO: handle this error case
}
DirectoryInfo releaseDir = new(ReleaseDirectory);
releaseDir.Create();
var runner = new WindowsPackCommandRunner(Logger, Logger);
await runner.Run(new WindowsPackOptions() {
PackId = PackId,
ReleaseDir = releaseDir,
PackDirectory = PackDirectory,
Runtimes = Runtimes,
TargetRuntime = targetRuntime,
PackVersion = PackVersion,
}).ConfigureAwait(false);
Log.LogMessage(MessageImportance.High, $"{PackId} ({PackVersion}) created in {ReleaseDirectory}");
} else if (VelopackRuntimeInfo.IsOSX) {
//TODO: Implement
} else if (VelopackRuntimeInfo.IsLinux) {
//TODO: Implement
} else {
//TODO: Do we really want to fail to pack (effectively failing the user's publish, or should we just warn?
throw new NotSupportedException("Unsupported OS platform: " + VelopackRuntimeInfo.SystemOs.GetOsLongName());
}
return true;
}
}

View File

@@ -0,0 +1,97 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.Build.Framework;
using Microsoft.Extensions.Logging;
using NuGet.Versioning;
using Velopack.Packaging;
using Velopack.Packaging.Flow;
namespace Velopack.Build;
public class PublishTask : MSBuildAsyncTask
{
private static HttpClient HttpClient { get; } = new();
[Required]
public string ReleaseDirectory { get; set; } = "";
public string ServiceUrl { get; set; } = VelopackServiceOptions.DefaultBaseUrl;
public string? Channel { get; set; }
public string? Version { get; set; }
protected override async Task<bool> ExecuteAsync()
{
//System.Diagnostics.Debugger.Launch();
VelopackFlowServiceClient client = new(HttpClient, Logger);
if (!await client.LoginAsync(new() {
AllowDeviceCodeFlow = false,
AllowInteractiveLogin = false,
VelopackBaseUrl = ServiceUrl
}).ConfigureAwait(false)) {
Logger.LogWarning("Not logged into Velopack service, skipping publish. Please run vpk login.");
return true;
}
Channel ??= ReleaseEntryHelper.GetDefaultChannel(VelopackRuntimeInfo.SystemOs);
ReleaseEntryHelper helper = new(ReleaseDirectory, Channel, Logger);
var latestAssets = helper.GetLatestAssets().ToList();
List<string> installers = [];
List<string> files = latestAssets.Select(x => x.FileName).ToList();
string? packageId = null;
SemanticVersion? version = null;
if (latestAssets.Count > 0) {
packageId = latestAssets[0].PackageId;
version = latestAssets[0].Version;
if (VelopackRuntimeInfo.IsWindows || VelopackRuntimeInfo.IsOSX) {
var setupName = ReleaseEntryHelper.GetSuggestedSetupName(packageId, Channel);
if (File.Exists(Path.Combine(ReleaseDirectory, setupName))) {
installers.Add(setupName);
}
}
var portableName = ReleaseEntryHelper.GetSuggestedPortableName(packageId, Channel);
if (File.Exists(Path.Combine(ReleaseDirectory, portableName))) {
installers.Add(portableName);
}
}
Logger.LogInformation("Preparing to upload {AssetCount} assets to Velopack ({ServiceUrl})", latestAssets.Count + installers.Count, ServiceUrl);
foreach (var assetFileName in files) {
var latestPath = Path.Combine(ReleaseDirectory, assetFileName);
using var fileStream = File.OpenRead(latestPath);
var options = new UploadOptions(fileStream, assetFileName, Channel) {
VelopackBaseUrl = ServiceUrl
};
await client.UploadReleaseAssetAsync(options).ConfigureAwait(false);
Logger.LogInformation("Uploaded {FileName} to Velopack", assetFileName);
}
foreach(var installerFile in installers) {
var latestPath = Path.Combine(ReleaseDirectory, installerFile);
using var fileStream = File.OpenRead(latestPath);
var options = new UploadInstallerOptions(packageId!, version!, fileStream, installerFile, Channel) {
VelopackBaseUrl = ServiceUrl
};
await client.UploadInstallerAssetAsync(options).ConfigureAwait(false);
Logger.LogInformation("Uploaded {FileName} installer to Velopack", installerFile);
}
return true;
}
}

View File

@@ -0,0 +1,77 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net472;net6.0</TargetFrameworks>
<Nullable>enable</Nullable>
<LangVersion>12</LangVersion>
<!--
https://learn.microsoft.com/dotnet/core/project-sdk/msbuild-props?WT.mc_id=DT-MVP-5003472#copylocallockfileassemblies
-->
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Velopack.Packaging.Unix\Velopack.Packaging.Unix.csproj" />
<ProjectReference Include="..\Velopack.Packaging.Windows\Velopack.Packaging.Windows.csproj" />
<ProjectReference Include="..\Velopack\Velopack.csproj" />
</ItemGroup>
<PropertyGroup>
</PropertyGroup>
<PropertyGroup Label="MacOS" Condition="$([System.OperatingSystem]::IsMacOS())">
<VelopackRustOutputDirectory>..\Rust\target</VelopackRustOutputDirectory>
<VelopackUpdateExe>UpdateMac</VelopackUpdateExe>
</PropertyGroup>
<ItemGroup Condition="$([System.OperatingSystem]::IsMacOS())">
<None Include="..\Rust\target\$(VelopackUpdateExe)" Link="$(VelopackUpdateExe)" Visible="false">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<PropertyGroup Label="Linux" Condition="$([System.OperatingSystem]::IsLinux())">
<VelopackRustOutputDirectory>..\Rust\target</VelopackRustOutputDirectory>
<VelopackUpdateExe>UpdateNix</VelopackUpdateExe>
</PropertyGroup>
<ItemGroup Condition="$([System.OperatingSystem]::IsMacOS())">
<None Include="..\Rust\target\$(VelopackUpdateExe)" Link="$(VelopackUpdateExe)" Visible="false">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<PropertyGroup Label="Windows" Condition="$([System.OperatingSystem]::IsWindows())">
<VelopackRustOutputDirectory>..\Rust\target\$(Configuration.ToLower())</VelopackRustOutputDirectory>
<VelopackUpdateExe>update.exe</VelopackUpdateExe>
<VelopackStubExe>stub.exe</VelopackStubExe>
<VelopackSetupExe>setup.exe</VelopackSetupExe>
</PropertyGroup>
<ItemGroup Condition="$([System.OperatingSystem]::IsWindows())">
<None Include="..\Rust\target\$(Configuration.ToLower())\$(VelopackUpdateExe)" Link="$(VelopackUpdateExe)" Visible="false">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="..\Rust\target\$(Configuration.ToLower())\$(VelopackStubExe)" Link="$(VelopackStubExe)" Visible="false">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="..\Rust\target\$(Configuration.ToLower())\$(VelopackSetupExe)" Link="$(VelopackSetupExe)" Visible="false">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="..\..\vendor\rcedit.exe" Link="rcedit.exe" Visible="false" Condition="$([System.OperatingSystem]::IsWindows())">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="..\..\vendor\zstd.exe" Link="zstd.exe" Visible="false" Condition="$([System.OperatingSystem]::IsWindows())">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup Label="NuGet">
<None Include="Velopack.Build.targets" Pack="true" PackagePath="build\Velopack.Build.targets" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Build.Utilities.Core" Version="17.9.5" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,73 @@
<Project>
<PropertyGroup>
<!--
TODO: Should we default this to false?
Is the action of adding the NuGet package sufficient to assume the user wants this to always
create a release on publish?
-->
<VelopackPackOnPublish Condition="$(VelopackPackOnPublish) == ''">true</VelopackPackOnPublish>
<!-- By default we will push if packing is enabled -->
<VelopackPushOnPublish Condition="$(VelopackPushOnPublish) == ''">$(VelopackPackOnPublish)</VelopackPushOnPublish>
<!--
https://learn.microsoft.com/visualstudio/msbuild/tutorial-custom-task-code-generation?view=vs-2022&WT.mc_id=DT-MVP-5003472#changes-required-to-multitarget
-->
<VelopackStronglyTyped_TFM Condition=" '$(MSBuildRuntimeType)' != 'Core' ">net472</VelopackStronglyTyped_TFM>
<VelopackStronglyTyped_TFM Condition=" '$(MSBuildRuntimeType)' == 'Core' ">net6.0</VelopackStronglyTyped_TFM>
</PropertyGroup>
<UsingTask TaskName="Velopack.Build.PackTask"
AssemblyFile="..\..\build\$(Configuration)\$(VelopackStronglyTyped_TFM)\Velopack.Build.dll"/>
<UsingTask TaskName="Velopack.Build.PublishTask"
AssemblyFile="..\..\build\$(Configuration)\$(VelopackStronglyTyped_TFM)\Velopack.Build.dll"/>
<Target Name="_VelopackResolveProperties" AfterTargets="Publish" BeforeTargets="VelopackBuildRelease">
<PropertyGroup>
<VelopackPackVersion Condition="'$(VelopackPackVersion)' == ''">$(Version)</VelopackPackVersion>
<VelopackPackId Condition="'$(VelopackPackId)' == ''">$(AssemblyName)</VelopackPackId>
<VelopackPackDirectory Condition="'$(VelopackPackDirectory)' == ''">$(PublishDir)</VelopackPackDirectory>
<!-- TODO: Is there a way to try and determine this from the project? -->
<VelopackRuntimes Condition="'$(VelopackRuntimes)' == ''">net8-x64-desktop</VelopackRuntimes>
<!-- TODO: is this a reasonable default? -->
<VelopackReleaseDirectory Condition="'$(VelopackReleaseDirectory)' == ''">releases</VelopackReleaseDirectory>
</PropertyGroup>
<ConvertToAbsolutePath Paths="$(VelopackReleaseDirectory)">
<Output TaskParameter="AbsolutePaths" PropertyName="VelopackReleaseDirectory"/>
</ConvertToAbsolutePath>
<ConvertToAbsolutePath Paths="$(VelopackPackDirectory)">
<Output TaskParameter="AbsolutePaths" PropertyName="VelopackPackDirectory"/>
</ConvertToAbsolutePath>
</Target>
<Target Name="VelopackBuildRelease" AfterTargets="Publish" Condition="'$(VelopackPackOnPublish)' == 'true'">
<!--
TODO: Add additional error checking for the rest of the parameters.
-->
<Warning Condition="'$(VelopackPackVersion)' == ''"
Text="No version specified, Velopack will not publish a release."
/>
<PackTask
Condition="'$(VelopackPackVersion)' != ''"
Runtimes="$(VelopackRuntimes)"
PackId="$(VelopackPackId)"
ReleaseDirectory="$(VelopackReleaseDirectory)"
PackDirectory="$(VelopackPackDirectory)"
PackVersion="$(VelopackPackVersion)"
/>
</Target>
<Target Name="VelopackPushRelease" AfterTargets="VelopackBuildRelease" Condition="'$(VelopackPushOnPublish)' == 'true'">
<PublishTask
ReleaseDirectory="$(VelopackReleaseDirectory)"
Version="$(VelopackPackVersion)"
ServiceUrl="$(VelopackFlowServiceUrl)"
/>
</Target>
</Project>

View File

@@ -0,0 +1,16 @@
@echo off
setlocal enabledelayedexpansion
echo.
echo Kill existing MSBuild processes
taskkill /F /IM MSBuild.exe
echo.
echo Kill existing dotnet processes
taskkill /F /IM dotnet.exe
echo.
echo Building Velopack.Build
cd %~dp0..\..\..\
dotnet build -c Debug src/Velopack.Build/Velopack.Build.csproj