Add initial code for linux

This commit is contained in:
Caelan Sayler
2024-01-09 00:54:59 +00:00
parent 2016aac5ff
commit e5ce05fb86
23 changed files with 254 additions and 44 deletions

View File

@@ -2,7 +2,7 @@
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
namespace Velopack.Packaging;
namespace Velopack.Packaging.Unix;
[SupportedOSPlatform("linux")]
[SupportedOSPlatform("macos")]

View File

@@ -0,0 +1,74 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Versioning;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
namespace Velopack.Packaging.Unix.Commands
{
[SupportedOSPlatform("linux")]
public class LinuxPackCommandRunner : PackageBuilder<LinuxPackOptions>
{
protected string PortablePackagePath { get; set; }
public LinuxPackCommandRunner(ILogger logger)
: base(RuntimeOs.Linux, logger)
{
}
protected override Task<string> PreprocessPackDir(Action<int> progress, string packDir, string nuspecText)
{
var dir = TempDir.CreateSubdirectory("PreprocessPackDir.AppDir");
var appRunPath = Path.Combine(dir.FullName, "AppRun");
File.WriteAllText(appRunPath, """
#!/bin/sh
HERE="$(dirname "$(readlink -f "${0}")")"
export PATH="${HERE}"/usr/bin/:"${PATH}"
EXEC=$(grep -e '^Exec=.*' "${HERE}"/*.desktop | head -n 1 | cut -d "=" -f 2 | cut -d " " -f 1)
exec "${EXEC}" $@
""");
Chmod.ChmodFileAsExecutable(appRunPath);
var mainExeName = Options.EntryExecutableName ?? Options.PackId;
var mainExePath = Path.Combine(packDir, mainExeName);
if (!File.Exists(mainExePath))
throw new Exception($"Could not find main executable at '{mainExePath}'. Please specify with --exeName.");
File.WriteAllText(Path.Combine(dir.FullName, Options.PackId + ".desktop"), $"""
[Desktop Entry]
Type=Application
Name={Options.PackTitle ?? Options.PackId}
Comment={Options.PackTitle ?? Options.PackId} {Options.PackVersion}
Icon={Options.PackId}
Exec={mainExeName}
Path=~
""");
File.Copy(Options.Icon, Path.Combine(dir.FullName, Options.PackId + Path.GetExtension(Options.Icon)), true);
var bin = dir.CreateSubdirectory("usr").CreateSubdirectory("bin");
CopyFiles(new DirectoryInfo(packDir), bin, progress, true);
File.WriteAllText(Path.Combine(bin.FullName, "sq.version"), nuspecText);
return Task.FromResult(packDir);
}
protected override Task CreatePortablePackage(Action<int> progress, string packDir, string outputPath)
{
var helper = new HelperExe(Log);
helper.CreateLinuxAppImage(packDir, outputPath);
PortablePackagePath = outputPath;
return Task.CompletedTask;
}
protected override Task CreateReleasePackage(Action<int> progress, string packDir, string nuspecText, string outputPath)
{
var dir = TempDir.CreateSubdirectory("CreateReleasePackage.Linux");
File.Copy(PortablePackagePath, Path.Combine(dir.FullName, Options.PackId + ".AppImage"), true);
return base.CreateReleasePackage(progress, dir.FullName, nuspecText, outputPath);
}
}
}

View File

@@ -0,0 +1,35 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Velopack.Packaging.Unix.Commands
{
public class LinuxPackOptions : IPackOptions
{
public DirectoryInfo ReleaseDir { get; set; }
public string PackId { get; set; }
public string PackVersion { get; set; }
public string PackDirectory { get; set; }
public string PackAuthors { get; set; }
public string PackTitle { get; set; }
public string EntryExecutableName { get; set; }
public string Icon { get; set; }
public RID TargetRuntime { get; set; }
public string ReleaseNotes { get; set; }
public DeltaMode DeltaMode { get; set; } = DeltaMode.BestSpeed;
public string Channel { get; set; }
}
}

View File

@@ -2,7 +2,7 @@
using Microsoft.Extensions.Logging;
using NuGet.Versioning;
namespace Velopack.Packaging.OSX.Commands;
namespace Velopack.Packaging.Unix.Commands;
public class OsxBundleCommandRunner
{
@@ -34,7 +34,7 @@ public class OsxBundleCommandRunner
var escapedAppleId = Regex.Replace(appleId, @"[^\w\.]", "_");
var appleSafeVersion = NuGetVersion.Parse(packVersion).Version.ToString();
var info = new AppInfo {
var info = new OsxAppInfo {
// SQPackId = packId,
// SQPackAuthors = packAuthors,
CFBundleName = packTitle ?? packId,
@@ -51,7 +51,7 @@ public class OsxBundleCommandRunner
};
_logger.Debug("Creating '.app' directory structure");
var builder = new StructureBuilder(packId, releaseDir.FullName);
var builder = new OsxStructureBuilder(packId, releaseDir.FullName);
if (Directory.Exists(builder.AppDirectory)) {
_logger.Warn(builder.AppDirectory + " already exists, deleting...");
Utility.DeleteFileOrDirectoryHard(builder.AppDirectory);

View File

@@ -1,4 +1,4 @@
namespace Velopack.Packaging.OSX.Commands;
namespace Velopack.Packaging.Unix.Commands;
public class OsxBundleOptions
{

View File

@@ -1,12 +1,13 @@
using System.Threading.Channels;
using System.Runtime.Versioning;
using System.Threading.Channels;
using Microsoft.Extensions.Logging;
using NuGet.Versioning;
namespace Velopack.Packaging.OSX.Commands;
namespace Velopack.Packaging.Unix.Commands;
[SupportedOSPlatform("osx")]
public class OsxPackCommandRunner : PackageBuilder<OsxPackOptions>
{
public OsxPackCommandRunner(ILogger logger)
: base(RuntimeOs.OSX, logger)
{
@@ -23,7 +24,7 @@ public class OsxPackCommandRunner : PackageBuilder<OsxPackOptions>
}
CopyFiles(new DirectoryInfo(appBundlePath), dir, progress, true);
var structure = new StructureBuilder(dir.FullName);
var structure = new OsxStructureBuilder(dir.FullName);
if (deleteAppBundle) {
Log.Debug("Removing temporary .app bundle.");

View File

@@ -1,4 +1,4 @@
namespace Velopack.Packaging.OSX.Commands;
namespace Velopack.Packaging.Unix.Commands;
public class OsxPackOptions : OsxBundleOptions, IPackOptions
{

View File

@@ -1,11 +1,12 @@
using System.ComponentModel;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Security;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
namespace Velopack.Packaging.OSX;
namespace Velopack.Packaging.Unix;
public class HelperExe : HelperFile
{
@@ -15,6 +16,14 @@ public class HelperExe : HelperFile
public string VelopackEntitlements => FindHelperFile("Velopack.entitlements");
protected string AppImageTool => FindHelperFile("appimagetool-x86_64.AppImage");
[SupportedOSPlatform("linux")]
public void CreateLinuxAppImage(string appDir, string outputFile)
{
InvokeAndThrowIfNonZero(AppImageTool, new[] { appDir, outputFile }, null);
}
[SupportedOSPlatform("osx")]
public void CodeSign(string identity, string entitlements, string filePath)
{
@@ -124,7 +133,7 @@ public class HelperExe : HelperFile
var distributionPath = Path.Combine(tmp, "distribution.xml");
InvokeAndThrowIfNonZero("productbuild", new[] { "--synthesize", "--package", pkg1Path, distributionPath }, null);
progress(80);
// https://developer.apple.com/library/archive/documentation/DeveloperTools/Reference/DistributionDefinitionRef/Chapters/Distribution_XML_Ref.html
var distXml = File.ReadAllLines(distributionPath).ToList();

View File

@@ -1,6 +1,6 @@
namespace Velopack.Packaging.OSX;
namespace Velopack.Packaging.Unix;
internal class AppInfo
internal class OsxAppInfo
{
public string CFBundleName { get; set; }

View File

@@ -1,19 +1,19 @@
// https://github.com/egramtel/dotnet-bundle/blob/master/DotNet.Bundle/StructureBuilder.cs
namespace Velopack.Packaging.OSX;
namespace Velopack.Packaging.Unix;
public class StructureBuilder
public class OsxStructureBuilder
{
private readonly string _id;
private readonly string _outputDir;
private readonly string _appDir;
public StructureBuilder(string appDir)
public OsxStructureBuilder(string appDir)
{
_appDir = appDir;
}
public StructureBuilder(string id, string outputDir)
public OsxStructureBuilder(string id, string outputDir)
{
_id = id;
_outputDir = outputDir;

View File

@@ -2,19 +2,19 @@
using System.Xml;
using Microsoft.Extensions.Logging;
namespace Velopack.Packaging.OSX;
namespace Velopack.Packaging.Unix;
internal class PlistWriter
{
private readonly ILogger _logger;
private readonly AppInfo _task;
private readonly OsxAppInfo _task;
private readonly string _outputDir;
private static readonly string[] ArrayTypeProperties = { "CFBundleURLSchemes" };
private const char Separator = ';';
public const string PlistFileName = "Info.plist";
public PlistWriter(ILogger logger, AppInfo task, string outputDir)
public PlistWriter(ILogger logger, OsxAppInfo task, string outputDir)
{
_logger = logger;
_task = task;

View File

@@ -1 +0,0 @@
[assembly: System.Runtime.Versioning.SupportedOSPlatform("osx")]

View File

@@ -119,6 +119,11 @@ namespace Velopack.Packaging
Log.Info("[bold]Complete: Build portable package[/]");
});
// this is a prerequisite for building full package but only on linux
if (VelopackRuntimeInfo.IsLinux) {
await portableTask;
}
var taskNuget = ctx.AddTask($"[italic]Building release {packVersion}[/]");
taskNuget.StartTask();
var releaseName = new ReleaseEntryName(packId, SemanticVersion.Parse(packVersion), false, Options.TargetRuntime);
@@ -129,17 +134,20 @@ namespace Velopack.Packaging
taskNuget.StopTask();
Log.Info("[bold]Complete: Build release package[/]");
var setupTask = Task.Run(async () => {
var taskSetup = ctx.AddTask($"[italic]Create setup package[/]");
taskSetup.StartTask();
var suggestedSetup = entryHelper.GetSuggestedSetupPath(packId, channel, options.TargetRuntime);
var incomplete = suggestedSetup + ".incomplete";
if (File.Exists(incomplete)) File.Delete(incomplete);
filesToCopy.Add((incomplete, suggestedSetup));
await CreateSetupPackage((p) => taskSetup.Value = p, releasePath, packDirectory, incomplete);
taskSetup.StopTask();
Log.Info("[bold]Complete: Create setup package[/]");
});
Task setupTask = null;
if (VelopackRuntimeInfo.IsWindows || VelopackRuntimeInfo.IsOSX) {
setupTask = Task.Run(async () => {
var taskSetup = ctx.AddTask($"[italic]Create setup package[/]");
taskSetup.StartTask();
var suggestedSetup = entryHelper.GetSuggestedSetupPath(packId, channel, options.TargetRuntime);
var incomplete = suggestedSetup + ".incomplete";
if (File.Exists(incomplete)) File.Delete(incomplete);
filesToCopy.Add((incomplete, suggestedSetup));
await CreateSetupPackage((p) => taskSetup.Value = p, releasePath, packDirectory, incomplete);
taskSetup.StopTask();
Log.Info("[bold]Complete: Create setup package[/]");
});
}
if (prev != null && options.DeltaMode != DeltaMode.None) {
var taskDelta = ctx.AddTask($"[italic]Building delta {prev.Version} -> {packVersion}[/]");
@@ -151,7 +159,7 @@ namespace Velopack.Packaging
}
await portableTask;
await setupTask;
if (setupTask != null) await setupTask;
var taskFinish = ctx.AddTask($"[italic]Finishing up[/]");
taskFinish.IsIndeterminate = true;

View File

@@ -130,6 +130,7 @@ namespace Velopack.Packaging
if (channel == null) return "";
if (channel == BLANK_CHANNEL) return "";
if (channel == "osx" && os == RuntimeOs.OSX) return "";
if (channel == "linux" && os == RuntimeOs.Linux) return "";
return "-" + channel.ToLower();
}
@@ -137,6 +138,7 @@ namespace Velopack.Packaging
{
if (os == RuntimeOs.Windows) return BLANK_CHANNEL;
if (os == RuntimeOs.OSX) return "osx";
if (os == RuntimeOs.Linux) return "linux";
throw new NotSupportedException("Unsupported OS: " + os);
}
@@ -198,6 +200,11 @@ namespace Velopack.Packaging
ret.Files.Add(new FileInfo(rel));
}
foreach (var rel in Directory.EnumerateFiles(_outputDir, $"*{suffix}.AppImage")) {
_logger.Info($"Discovered asset: {rel}");
ret.Files.Add(new FileInfo(rel));
}
foreach (var rel in Directory.EnumerateFiles(_outputDir, $"*{suffix}-Setup.exe")) {
_logger.Info($"Discovered asset: {rel}");
ret.Files.Add(new FileInfo(rel));
@@ -214,7 +221,11 @@ namespace Velopack.Packaging
public string GetSuggestedPortablePath(string id, string channel, RID rid)
{
var suffix = GetPkgSuffix(rid.BaseRID, channel);
return Path.Combine(_outputDir, $"{id}-{rid.ToDisplay(RidDisplayType.NoVersion)}{suffix}-Portable.zip");
if (VelopackRuntimeInfo.IsLinux) {
return Path.Combine(_outputDir, $"{id}-{rid.ToDisplay(RidDisplayType.NoVersion)}{suffix}.AppImage");
} else {
return Path.Combine(_outputDir, $"{id}-{rid.ToDisplay(RidDisplayType.NoVersion)}{suffix}-Portable.zip");
}
}
public string GetSuggestedSetupPath(string id, string channel, RID rid)

View File

@@ -1,7 +1,7 @@
using System.Runtime.Versioning;
using Velopack.Deployment;
using Velopack.Packaging.Commands;
using Velopack.Packaging.OSX.Commands;
using Velopack.Packaging.Unix.Commands;
using Velopack.Packaging.Windows.Commands;
using Velopack.Vpk.Commands;

View File

@@ -28,6 +28,7 @@
<None Include="..\Rust\target\debug\update.exe" CopyToOutputDirectory="Always" Condition="$([MSBuild]::IsOSPlatform('Windows'))" />
<None Include="..\Rust\target\debug\setup.exe" CopyToOutputDirectory="Always" Condition="$([MSBuild]::IsOSPlatform('Windows'))" />
<None Include="..\Rust\target\debug\stub.exe" CopyToOutputDirectory="Always" Condition="$([MSBuild]::IsOSPlatform('Windows'))" />
<None Include="..\..\vendor\appimagetool-x86_64.AppImage" CopyToOutputDirectory="Always" />
<None Include="..\..\vendor\rcedit.exe" CopyToOutputDirectory="Always" />
<None Include="..\..\vendor\zstd.exe" CopyToOutputDirectory="Always" />
<None Include="..\..\vendor\signtool.exe" CopyToOutputDirectory="Always" />
@@ -39,6 +40,7 @@
<None Include="..\Rust\target\release\update.exe" Pack="true" PackagePath="vendor" />
<None Include="..\Rust\target\release\setup.exe" Pack="true" PackagePath="vendor" />
<None Include="..\Rust\target\release\stub.exe" Pack="true" PackagePath="vendor" />
<None Include="..\..\vendor\appimagetool-x86_64.AppImage" Pack="true" PackagePath="vendor" />
<None Include="..\..\vendor\rcedit.exe" Pack="true" PackagePath="vendor" />
<None Include="..\..\vendor\zstd.exe" Pack="true" PackagePath="vendor" />
<None Include="..\..\vendor\signtool.exe" Pack="true" PackagePath="vendor" />

View File

@@ -276,12 +276,12 @@ namespace Velopack
{
string tempDir;
if (VelopackRuntimeInfo.IsOSX) {
if (VelopackRuntimeInfo.IsOSX || VelopackRuntimeInfo.IsLinux) {
tempDir = "/tmp/velopack";
} else if (VelopackRuntimeInfo.IsWindows) {
tempDir = Path.Combine(Path.GetTempPath(), "Velopack");
} else {
throw new NotSupportedException();
throw new PlatformNotSupportedException();
}
if (Environment.GetEnvironmentVariable("VELOPACK_TEMP") is var squirrlTmp

View File

@@ -0,0 +1,61 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Versioning;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using NuGet.Versioning;
namespace Velopack.Locators
{
/// <summary>
/// The default for OSX. All application files will remain in the '.app'.
/// All additional files (log, etc) will be placed in a temporary directory.
/// </summary>
[SupportedOSPlatform("osx")]
public class LinuxVelopackLocator : VelopackLocator
{
/// <inheritdoc />
public override string AppId { get; }
/// <inheritdoc />
public override string RootAppDir { get; }
/// <inheritdoc />
public override string UpdateExePath { get; }
/// <inheritdoc />
public override SemanticVersion CurrentlyInstalledVersion { get; }
/// <inheritdoc />
public override string AppContentDir { get; }
/// <inheritdoc />
public override string AppTempDir => CreateSubDirIfDoesNotExist(Utility.GetDefaultTempBaseDirectory(), AppId);
/// <inheritdoc />
public override string PackagesDir => CreateSubDirIfDoesNotExist(PersistentTempDir, "packages");
/// <summary> /var/tmp/{velopack}/{appid}, for storing app specific files which need to be preserved. </summary>
public string PersistentTempDir => CreateSubDirIfDoesNotExist(PersistentVelopackDir, AppId);
/// <summary> A pointer to /var/tmp/{velopack}, a location on linux which is semi-persistent. </summary>
public string PersistentVelopackDir => CreateSubDirIfDoesNotExist("/var/tmp", "velopack");
/// <summary>
/// Creates a new <see cref="OsxVelopackLocator"/> and auto-detects the
/// app information from metadata embedded in the .app.
/// </summary>
public LinuxVelopackLocator(ILogger logger)
: base(logger)
{
if (!VelopackRuntimeInfo.IsLinux)
throw new NotSupportedException("Cannot instantiate LinuxVelopackLocator on a non-linux system.");
Log.Info($"Initialising {nameof(LinuxVelopackLocator)}");
throw new NotImplementedException();
}
}
}

View File

@@ -27,12 +27,15 @@ namespace Velopack.Locators
return _current;
if (VelopackRuntimeInfo.IsWindows)
return _current ??= new WindowsVelopackLocator(log);
return _current = new WindowsVelopackLocator(log);
if (VelopackRuntimeInfo.IsOSX)
return _current ??= new OsxVelopackLocator(log);
return _current = new OsxVelopackLocator(log);
throw new NotSupportedException($"OS platform '{VelopackRuntimeInfo.SystemOs.GetOsLongName()}' is not supported.");
if (VelopackRuntimeInfo.IsLinux)
return _current = new LinuxVelopackLocator(log);
throw new PlatformNotSupportedException($"OS platform '{VelopackRuntimeInfo.SystemOs.GetOsLongName()}' is not supported.");
}
/// <inheritdoc/>

View File

@@ -32,7 +32,9 @@ namespace Velopack.Sources
internal static string GetReleasesFileNameImpl(string channel)
{
if (String.IsNullOrWhiteSpace(channel) || channel == "default") {
return VelopackRuntimeInfo.IsOSX ? "RELEASES-osx" : "RELEASES";
if (VelopackRuntimeInfo.IsOSX) return "RELEASES-osx";
if (VelopackRuntimeInfo.IsLinux) return "RELEASES-linux";
return "RELEASES";
}
return $"RELEASES-{channel.ToLower()}";
}

View File

@@ -1,6 +1,6 @@
using System.Diagnostics;
using Velopack.Deployment;
using Velopack.Packaging.OSX.Commands;
using Velopack.Packaging.Unix.Commands;
using Velopack.Packaging.Windows.Commands;
using Velopack.Sources;
using Octokit;

View File

@@ -19,4 +19,9 @@ This folder contains pre-compiled binaries from a variety of sources. These shou
### zstd.exe
- Fast compression and diff/patch
- Can be found at https://github.com/facebook/zstd
- License is GPL-2.0 & BSD 3: https://github.com/facebook/zstd/blob/dev/LICENSE, https://github.com/facebook/zstd/blob/dev/COPYING
- License is GPL-2.0 & BSD 3: https://github.com/facebook/zstd/blob/dev/LICENSE, https://github.com/facebook/zstd/blob/dev/COPYING
### appimagetool
- Create .AppImage for Linux
- Can be found at https://github.com/AppImage/AppImageKit
- License is MIT https://github.com/AppImage/AppImageKit/blob/master/LICENSE

BIN
vendor/appimagetool-x86_64.AppImage vendored Normal file

Binary file not shown.