Skip to content

Commit

Permalink
Merge pull request #587 from NetSparkleUpdater/feature/trimming-support
Browse files Browse the repository at this point in the history
Initial support for NetSparkle trimming (Core DLL, Avalonia)
  • Loading branch information
Deadpikle authored Jul 6, 2024
2 parents a93086d + 4cc6d89 commit 731b169
Show file tree
Hide file tree
Showing 27 changed files with 531 additions and 68 deletions.
15 changes: 10 additions & 5 deletions .github/workflows/publish-nuget.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,9 @@ jobs:
nuget-api-key: ${{secrets.NUGET_API_KEY}}
nuget-version: '5.x'

# - name: Add MSBuild to PATH
# uses: microsoft/[email protected]
- name: Add MSBuild to PATH
uses: microsoft/[email protected]
if: matrix.os == 'windows-latest'

- name: Setup .NET 6.0, 7.0, 8.0 for tests
uses: actions/[email protected]
Expand All @@ -30,6 +31,10 @@ jobs:
7.0.x
8.0.x
- name: Run NetSparkle.Tests in .NET 4.6.2
run: dotnet test -f net462 ${{ github.workspace }}/src/NetSparkle.Tests/NetSparkle.Tests.csproj
if: matrix.os == 'windows-latest'

- name: Run NetSparkle.Tests in .NET 6
run: dotnet test -f net6.0 ${{ github.workspace }}/src/NetSparkle.Tests/NetSparkle.Tests.csproj

Expand Down Expand Up @@ -147,12 +152,12 @@ jobs:
INCLUDE_SYMBOLS: true

- name: Build NetSparkle.UI.WinForms.NetFramework in Release
run: msbuild ${{ github.workspace }}/src/NetSparkle.UI.WinForms.NetFramework/NetSparkle.UI.WinForms.NetFramework.csproj /property:Configuration=Release /p:OutputPath=../../nuget/winformsnetframework/lib/net452 /p:IncludeSymbols=true /p:SymbolPackageFormat=snupkg
run: msbuild ${{ github.workspace }}/src/NetSparkle.UI.WinForms.NetFramework/NetSparkle.UI.WinForms.NetFramework.csproj /property:Configuration=Release /p:OutputPath=../../nuget/winformsnetframework/lib/net462 /p:IncludeSymbols=true /p:SymbolPackageFormat=snupkg

- name: Setup NetSparkleUpdater.UI.WinForms.NetFramework folder
run: |
del ${{ github.workspace }}/nuget\winformsnetframework\lib\net452\NetSparkle.dll
del ${{ github.workspace }}/nuget\winformsnetframework\lib\net452\NetSparkle.pdb
del ${{ github.workspace }}/nuget\winformsnetframework\lib\net462\NetSparkle.dll
del ${{ github.workspace }}/nuget\winformsnetframework\lib\net462\NetSparkle.pdb
copy ${{ github.workspace }}/LICENSE.md ${{ github.workspace }}/nuget\winformsnetframework\LICENSE.md
copy ${{ github.workspace }}/src\NetSparkle\ArtWork\software-update-available.png ${{ github.workspace }}/nuget\winformsnetframework\software-update-available.png
Expand Down
2 changes: 2 additions & 0 deletions NetSparkleUpdater.sln
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NetSparkle.Samples.Avalonia.MacOSZip", "src\NetSparkle.Samples.Avalonia.MacOSZip\NetSparkle.Samples.Avalonia.MacOSZip.csproj", "{314E5B47-24FA-42F5-9975-3E4252853576}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NetSparkle.Tests.Trimming", "src\NetSparkle.Tests.Trimming\NetSparkle.Tests.Trimming.csproj", "{7AE76C89-60B5-4F0E-A774-6E52F53B59D5}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down
3 changes: 3 additions & 0 deletions UPGRADING.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
* `WebFileDownloader` now deletes files on cancellation of a download like `LocalFileDownloader` did already (1cd2284c41bbe85d41566915965ad2acdb1a61f5)
* `WebFileDownloader` does not call `PrepareToDownloadFile()` in its constructor anymore. Now, it is called after the object is fully created. (420f961dfa9c9071332e2e0737b0f287d2cfa5dc)
* NOTE: If you update to .NET 8+, the location of `JSONConfiguration` save data, by default, will CHANGE due to a change in .NET 8 for `Environment.SpecialFolder.ApplicationData`. See: https://learn.microsoft.com/en-us/dotnet/core/compatibility/core-libraries/8.0/getfolderpath-unix. This may cause user's "skipped version" or other data to be lost unless you migrate this data yourself. For most users who are using the defaults, this is likely only a minor (if any) inconvenience at all, but it is worth noting.
* `AssemblyReflectionAccessor` has been deprecated. Please use `AsmResolverAccessor` instead.
* `AssemblyDiagnosticAccessor.AssemblyVersion` now returns `FileVersionInfo.GetVersionInfo(assemblyName).ProductVersion` rather than `FileVersionInfo.GetVersionInfo(assemblyName).FileVersion` so we can get semver versioning information

**Changes/Fixes**

Expand All @@ -43,6 +45,7 @@
* `WebFileDownloader.PrepareToDownloadFile` is now `virtual` (84df81122cfae9309de4f5b79489e46287bf3a62)
* The app cast maker now expects at least `Major.Minor` for version numbers and no longer accepts single digits as version numbers (this fixes things like "My Super App Version 2.exe" having 2 being detected as the version number when the version number is in a prior folder name, and also ensures that the right number is being read)
* If `JSONConfiguration` cannot save data, `SparkleUpdater` now uses the `DefaultConfiguration` class, which basically has no information set on it and will check for updates for the user. (a33266ac99b3eed83ff481ee7ef06cc5a0b1ab40)
* Added `AsmResolverAccessor` class for assembly loading information without reflection (replaces `AssemblyReflectionAccessor`)

## Updating from 0.X or 1.X to 2.X

Expand Down
3 changes: 2 additions & 1 deletion src/NetSparkle.Samples.Avalonia/MainWindow.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ui="clr-namespace:NetSparkleUpdater.Samples.Avalonia;assembly=NetSparkleUpdater.Samples.Avalonia"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="NetSparkleUpdater.Samples.Avalonia.MainWindow"
Title="NetSparkleUpdater.Samples.Avalonia"
Expand All @@ -12,7 +13,7 @@
Margin="4">
<TextBlock Text="Welcome to NetSparkle Avalonia!"/>
<Button Content="Manual Update Check"
Click="ManualUpdateCheck_Click"
Command="{CompiledBinding $parent[ui:MainWindow].ManualUpdateCheck_Click}"
Width="150"/>
</StackPanel>
</Window>
2 changes: 1 addition & 1 deletion src/NetSparkle.Samples.Avalonia/MainWindow.axaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public MainWindow()
_sparkle.StartLoop(true, true);
}

public async void ManualUpdateCheck_Click(object sender, RoutedEventArgs e)
public async void ManualUpdateCheck_Click()
{
await _sparkle.CheckForUpdatesAtUserRequest();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
<ApplicationIcon>software-update-available.ico</ApplicationIcon>
<RootNamespace>NetSparkleUpdater.Samples.Avalonia</RootNamespace>
<AssemblyName>NetSparkleUpdater.Samples.Avalonia</AssemblyName>
<PublishTrimmed>true</PublishTrimmed>
<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
</PropertyGroup>
<ItemGroup>
<AvaloniaResource Include="Assets\*" />
Expand Down
14 changes: 13 additions & 1 deletion src/NetSparkle.Tests.AppCastGenerator/AppCastMakerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1082,6 +1082,18 @@ public void CanGetSemVerLikeVersionsFromExistingAppCast()
Assert.Equal("2.0-alpha.1", items[1].Version);
}

private static string GetDotnetProcessName()
{
// On Windows from VS, at least on one user's system, need to hardcode dotnet location,
// otherwise SDK cannot be found
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && Directory.Exists("C:\\Program Files\\dotnet")
&& File.Exists("C:\\Program Files\\dotnet\\dotnet.exe"))
{
return "C:\\Program Files\\dotnet\\dotnet.exe";
}
return "dotnet";
}

[Fact]
public void CanMakeAppCastWithAssemblyData()
{
Expand Down Expand Up @@ -1120,7 +1132,7 @@ public void CanMakeAppCastWithAssemblyData()
UseShellExecute = false,
CreateNoWindow = true,
WindowStyle = ProcessWindowStyle.Hidden,
FileName = "dotnet",
FileName = GetDotnetProcessName(),
WorkingDirectory = tempDir,
Arguments = $"build --framework " + dotnetVersion + " --output \"" + buildPath + "\""
}
Expand Down
16 changes: 16 additions & 0 deletions src/NetSparkle.Tests.Trimming/NetSparkle.Tests.Trimming.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<PublishTrimmed>true</PublishTrimmed>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\NetSparkle\NetSparkle.csproj" />
<TrimmerRootAssembly Include="NetSparkle" />
</ItemGroup>

</Project>
7 changes: 7 additions & 0 deletions src/NetSparkle.Tests.Trimming/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
using NetSparkleUpdater.Enums;
using NetSparkleUpdater.SignatureVerifiers;

// See https://aka.ms/new-console-template for more information
Console.WriteLine("Hello, World!");

var checker = new Ed25519Checker(SecurityMode.Strict, "");
237 changes: 237 additions & 0 deletions src/NetSparkle.Tests/AssemblyAccessorTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,237 @@
using NetSparkleUpdater;
using NetSparkleUpdater.AppCastHandlers;
using NetSparkleUpdater.AssemblyAccessors;
using Org.BouncyCastle.Security;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using Xunit;

namespace NetSparkleUnitTests
{
// https://stackoverflow.com/a/41985831/3938401 for fixture code
public class AssemblyAccessorTestsFixture : IDisposable
{
private string _tmpDir;
private string _dllPath;

public const string AssemblyVersion = "2.0.1-beta-1";
public const string FileVersion = "2.0.1";
public const string Company = "Sparkle Company";
public const string Description = "Sparkle Description";
public const string Copyright = "Sparkle Copyright";
public const string Title = "MyAwesomeLibTitle";
public const string Product = "MyProduct";

public bool DidSucceed
{
get => !string.IsNullOrWhiteSpace(_dllPath) && File.Exists(_dllPath);
}

public string DllPath
{
get => _dllPath;
}

public AssemblyAccessorTestsFixture()
{
BuildDll();
}

public void Dispose()
{
if (Directory.Exists(_tmpDir))
{
Directory.Delete(_tmpDir, true);
}
}

// TODO: refactor some of these common test methods (between app cast and sparkle testing)
// to a common testing DLL or something
private string GetCleanTempDir()
{
var tempPath = Path.GetTempPath();
var tempDir = Path.Combine(tempPath, "netsparkle-unit-tests-13927");
// remove any files set up in previous tests
if (Directory.Exists(tempDir))
{
Directory.Delete(tempDir, true);
}
Directory.CreateDirectory(tempDir);
return tempDir;
}

// https://stackoverflow.com/a/1344242/3938401
private static string RandomString(int length)
{
Random random = new SecureRandom();
const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
return new string(Enumerable.Repeat(chars, length)
.Select(s => s[random.Next(s.Length)]).ToArray());
}

private static string GetDotnetProcessName()
{
// On Windows from VS, at least on one user's system, need to hardcode dotnet location,
// otherwise SDK cannot be found
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && Directory.Exists("C:\\Program Files\\dotnet")
&& File.Exists("C:\\Program Files\\dotnet\\dotnet.exe"))
{
return "C:\\Program Files\\dotnet\\dotnet.exe";
}
return "dotnet";
}

private void BuildDll()
{
var envVersion = Environment.Version;
var dotnetVersion = "net" + envVersion.Major + ".0";
var csproj = @"
<Project Sdk=""Microsoft.NET.Sdk"">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>" + dotnetVersion + @"</TargetFramework>
<RootNamespace>assembly_accessor_testing</RootNamespace>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<Version>" + AssemblyVersion + @"</Version>
<AssemblyVersion>" + FileVersion + @"</AssemblyVersion>
<AssemblyTitle>" + Title + @"</AssemblyTitle>
<Description>" + Description + @"</Description>
<Company>" + Company + @"</Company>
<Product>" + Product + @"</Product>
<Copyright>" + Copyright + @"</Copyright>
</PropertyGroup>
</Project>".Trim();
var program = @"Console.WriteLine(""Hello, World!"");";
_tmpDir = GetCleanTempDir();
var csprojPath = Path.Combine(_tmpDir, "proj.csproj");
var programPath = Path.Combine(_tmpDir, "Program.cs");
var innerFolder = RandomString(10);
var buildPath = Directory.CreateDirectory(Path.Combine(_tmpDir, innerFolder)).FullName;
try
{
File.WriteAllText(csprojPath, csproj);
File.WriteAllText(programPath, program);
// compile it
var p = new Process()
{
EnableRaisingEvents = true,
StartInfo = new ProcessStartInfo
{
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
CreateNoWindow = true,
WindowStyle = ProcessWindowStyle.Hidden,
FileName = GetDotnetProcessName(),
WorkingDirectory = _tmpDir,
Arguments = $"build --framework " + dotnetVersion + " --output \"" + buildPath + "\""
}
};
p.OutputDataReceived += (o, e) =>
{
if (!string.IsNullOrWhiteSpace(e.Data))
{
Console.WriteLine("[Unit test build output] " + e.Data);
}
};
p.ErrorDataReceived += (o, e) =>
{
if (!string.IsNullOrWhiteSpace(e.Data))
{
Console.WriteLine("[Unit test build output] ERROR: " + e.Data);
}
};
p.Start();
p.BeginErrorReadLine();
p.BeginOutputReadLine();
p.WaitForExit();
_dllPath = Path.Combine(buildPath, "proj.dll");
}
catch (Exception e)
{
Console.WriteLine("[ERROR] " + e.Message);
Dispose();
}
}
}


[CollectionDefinition("Assembly collection")]
public class AssemblyCollection : ICollectionFixture<AssemblyAccessorTestsFixture>
{
// This class has no code, and is never created. Its purpose is simply
// to be the place to apply [CollectionDefinition] and all the
// ICollectionFixture<> interfaces.
}

[Collection("Assembly collection")]
public class AssemblyAccessorTests
{
AssemblyAccessorTestsFixture _fixture;

public AssemblyAccessorTests(AssemblyAccessorTestsFixture fixture)
{
_fixture = fixture;
}

// can only run these tests on .NET non-framework as it depends on building a DLL
#if NET6_0_OR_GREATER
[Fact]
public void TestAsmResolverAccessor()
{
if (!_fixture.DidSucceed)
{
Assert.True(false, "Failed to build DLL");
}
var accessor = new AsmResolverAccessor(_fixture.DllPath);
Assert.Equal(AssemblyAccessorTestsFixture.Company, accessor.AssemblyCompany);
Assert.Equal(AssemblyAccessorTestsFixture.Copyright, accessor.AssemblyCopyright);
Assert.Equal(AssemblyAccessorTestsFixture.Description, accessor.AssemblyDescription);
Assert.Equal(AssemblyAccessorTestsFixture.Title, accessor.AssemblyTitle);
Assert.Equal(AssemblyAccessorTestsFixture.Product, accessor.AssemblyProduct);
Assert.Equal(AssemblyAccessorTestsFixture.AssemblyVersion, accessor.AssemblyVersion);
Assert.Equal(AssemblyAccessorTestsFixture.FileVersion, accessor.FileVersion);
}

[Fact]
public void TestDiagnosticsAccessor()
{
if (!_fixture.DidSucceed)
{
Assert.True(false, "Failed to build DLL");
}
var accessor = new AssemblyDiagnosticsAccessor(_fixture.DllPath);
Assert.Equal(AssemblyAccessorTestsFixture.Company, accessor.AssemblyCompany);
Assert.Equal(AssemblyAccessorTestsFixture.Copyright, accessor.AssemblyCopyright);
Assert.Equal(AssemblyAccessorTestsFixture.Description, accessor.AssemblyDescription);
Assert.Equal(AssemblyAccessorTestsFixture.Product, accessor.AssemblyTitle);
Assert.Equal(AssemblyAccessorTestsFixture.Product, accessor.AssemblyProduct);
Assert.Equal(AssemblyAccessorTestsFixture.AssemblyVersion, accessor.AssemblyVersion);
}

[Fact]
public void TestReflectionAccessor()
{
if (!_fixture.DidSucceed)
{
Assert.True(false, "Failed to build DLL");
}
var accessor = new AssemblyReflectionAccessor(_fixture.DllPath);

Check warning on line 227 in src/NetSparkle.Tests/AssemblyAccessorTests.cs

View workflow job for this annotation

GitHub Actions / Unit testing (windows-latest)

'AssemblyReflectionAccessor' is obsolete: 'Uses assembly-based reflection and is not trimmable; Use AsmResolverAccessor instead'

Check warning on line 227 in src/NetSparkle.Tests/AssemblyAccessorTests.cs

View workflow job for this annotation

GitHub Actions / Unit testing (windows-latest)

'AssemblyReflectionAccessor' is obsolete: 'Uses assembly-based reflection and is not trimmable; Use AsmResolverAccessor instead'

Check warning on line 227 in src/NetSparkle.Tests/AssemblyAccessorTests.cs

View workflow job for this annotation

GitHub Actions / Unit testing (windows-latest)

'AssemblyReflectionAccessor' is obsolete: 'Uses assembly-based reflection and is not trimmable; Use AsmResolverAccessor instead'

Check warning on line 227 in src/NetSparkle.Tests/AssemblyAccessorTests.cs

View workflow job for this annotation

GitHub Actions / Unit testing (macos-latest)

'AssemblyReflectionAccessor' is obsolete: 'Uses assembly-based reflection and is not trimmable; Use AsmResolverAccessor instead'

Check warning on line 227 in src/NetSparkle.Tests/AssemblyAccessorTests.cs

View workflow job for this annotation

GitHub Actions / Unit testing (macos-latest)

'AssemblyReflectionAccessor' is obsolete: 'Uses assembly-based reflection and is not trimmable; Use AsmResolverAccessor instead'

Check warning on line 227 in src/NetSparkle.Tests/AssemblyAccessorTests.cs

View workflow job for this annotation

GitHub Actions / Unit testing (macos-latest)

'AssemblyReflectionAccessor' is obsolete: 'Uses assembly-based reflection and is not trimmable; Use AsmResolverAccessor instead'

Check warning on line 227 in src/NetSparkle.Tests/AssemblyAccessorTests.cs

View workflow job for this annotation

GitHub Actions / Unit testing (ubuntu-latest)

'AssemblyReflectionAccessor' is obsolete: 'Uses assembly-based reflection and is not trimmable; Use AsmResolverAccessor instead'

Check warning on line 227 in src/NetSparkle.Tests/AssemblyAccessorTests.cs

View workflow job for this annotation

GitHub Actions / Unit testing (ubuntu-latest)

'AssemblyReflectionAccessor' is obsolete: 'Uses assembly-based reflection and is not trimmable; Use AsmResolverAccessor instead'

Check warning on line 227 in src/NetSparkle.Tests/AssemblyAccessorTests.cs

View workflow job for this annotation

GitHub Actions / Unit testing (ubuntu-latest)

'AssemblyReflectionAccessor' is obsolete: 'Uses assembly-based reflection and is not trimmable; Use AsmResolverAccessor instead'
Assert.Equal(AssemblyAccessorTestsFixture.Company, accessor.AssemblyCompany);
Assert.Equal(AssemblyAccessorTestsFixture.Copyright, accessor.AssemblyCopyright);
Assert.Equal(AssemblyAccessorTestsFixture.Description, accessor.AssemblyDescription);
Assert.Equal(AssemblyAccessorTestsFixture.Title, accessor.AssemblyTitle);
Assert.Equal(AssemblyAccessorTestsFixture.Product, accessor.AssemblyProduct);
Assert.Equal(AssemblyAccessorTestsFixture.AssemblyVersion, accessor.AssemblyVersion);
}
#endif
}
}
Loading

0 comments on commit 731b169

Please sign in to comment.