Skip to content

Commit

Permalink
Add a version check and warn if not using latest (#632)
Browse files Browse the repository at this point in the history
* Add a version check and warn if not using latest

fixes #629

* Review updates

* Make check lightweight and once daily

* Assert that cache is effective

* CR updates

* fix test

* always warn

* sanitize

* CR updates
  • Loading branch information
ErikEJ authored Oct 11, 2024
1 parent 2ae97d2 commit 2b52b3d
Show file tree
Hide file tree
Showing 8 changed files with 210 additions and 8 deletions.
1 change: 1 addition & 0 deletions src/DacpacTool/DacpacTool.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

<ItemGroup>
<PackageReference Include="Microsoft.SqlServer.DacFx" Version="162.4.92" />
<PackageReference Include="NuGet.Versioning" Version="6.11.1" />
<PackageReference Include="System.CommandLine" Version="2.0.0-beta4.22272.1" />
<PackageReference Include="System.CommandLine.NamingConventionBinder" Version="2.0.0-beta4.22272.1" />
</ItemGroup>
Expand Down
9 changes: 9 additions & 0 deletions src/DacpacTool/IVersionProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using NuGet.Versioning;

namespace MSBuild.Sdk.SqlProj.DacpacTool
{
public interface IVersionProvider
{
NuGetVersion CurrentPackageVersion();
}
}
21 changes: 15 additions & 6 deletions src/DacpacTool/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,10 @@ static async Task<int> Main(string[] args)
new Option<bool>(new string[] { "--debug" }, "Waits for a debugger to attach")
#endif
};
buildCommand.Handler = CommandHandler.Create<BuildOptions>(BuildDacpac);
buildCommand.Handler = CommandHandler.Create<BuildOptions>(async (buildOptions) =>
{
await BuildDacpac(buildOptions).ConfigureAwait(false);
});

var collectIncludesCommand = new Command("collect-includes")
{
Expand Down Expand Up @@ -79,18 +82,20 @@ static async Task<int> Main(string[] args)
rootCommand.Description = "Command line tool for generating a SQL Server Data-Tier Application Framework package (dacpac)";

var processed = rootCommand.Parse(args);
#pragma warning disable CA2007 // Consider calling ConfigureAwait on the awaited task
return await processed.InvokeAsync();
#pragma warning restore CA2007 // Consider calling ConfigureAwait on the awaited task
return await processed.InvokeAsync().ConfigureAwait(false);
}

private static int BuildDacpac(BuildOptions options)
private static async Task<int> BuildDacpac(BuildOptions options)
{
// Wait for a debugger to attach
WaitForDebuggerToAttach(options);

// Set metadata for the package
using var packageBuilder = new PackageBuilder(new ActualConsole());
var versionChecker = new VersionChecker(new ActualConsole(), new VersionProvider());

await versionChecker.CheckForPackageUpdateAsync().ConfigureAwait(false);

// Set metadata for the package
packageBuilder.SetMetadata(options.Name, options.Version);

// Set properties on the model (if defined)
Expand Down Expand Up @@ -145,6 +150,7 @@ private static int BuildDacpac(BuildOptions options)
{
if (options.InputFile.Exists)
{
#pragma warning disable CA1849 // Call async methods when in an async method - must wait for .NET 6 to be removed
foreach (var line in File.ReadLines(options.InputFile.FullName))
{
FileInfo inputFile = new FileInfo(line); // Validation occurs in AddInputFile
Expand All @@ -153,6 +159,7 @@ private static int BuildDacpac(BuildOptions options)
modelExceptions = true;
}
}
#pragma warning restore CA1849 // Call async methods when in an async method
}
else
{
Expand All @@ -172,6 +179,7 @@ private static int BuildDacpac(BuildOptions options)
{
if (options.SuppressWarningsListFile.Exists)
{
#pragma warning disable CA1849 // Call async methods when in an async method
foreach (var line in File.ReadLines(options.SuppressWarningsListFile.FullName))
{
//Checks if there are suppression warnings list
Expand All @@ -181,6 +189,7 @@ private static int BuildDacpac(BuildOptions options)
FileInfo inputFile = new FileInfo(parts[0]); // Validation occurs in AddInputFile
packageBuilder.AddFileWarningsToSuppress(inputFile, warningList);
}
#pragma warning restore CA1849 // Call async methods when in an async method
}
}

Expand Down
82 changes: 82 additions & 0 deletions src/DacpacTool/VersionChecker.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
using System;
using System.Threading.Tasks;
using System.Threading;
using System.Net.Http.Json;
using NuGet.Versioning;
using System.IO;
using System.Net.Http;

namespace MSBuild.Sdk.SqlProj.DacpacTool
{

public class VersionChecker
{
private readonly IConsole _console;
private readonly IVersionProvider _versionProvider;
#pragma warning disable CA1812 // Avoid uninstantiated internal classes
private sealed record Release(string tag_name);
#pragma warning restore CA1812

public VersionChecker(IConsole console, IVersionProvider versionProvider)
{
_versionProvider = versionProvider;
_console = console;
}

public async Task CheckForPackageUpdateAsync()
{


try
{
var timeout = TimeSpan.FromSeconds(2);

using var cts = new CancellationTokenSource(timeout);

var cacheFile = Path.Join(Path.GetTempPath(), "MSBuild.Sdk.SqlProj.tag.txt");

NuGetVersion latestVersion = null;

if (File.Exists(cacheFile) && File.GetLastWriteTimeUtc(cacheFile) > DateTime.UtcNow.AddDays(-1))
{
var cache = await File.ReadAllTextAsync(cacheFile, cts.Token).ConfigureAwait(false);
latestVersion = NuGetVersion.Parse(cache);
}
else
{
using var httpClient = new HttpClient
{
DefaultRequestHeaders = { { "User-Agent", "MSBuild.Sdk.SqlProj" } },
Timeout = timeout,
};

var response = await httpClient.GetFromJsonAsync<Release>("https://api.github.com/repos/rr-wfm/MSBuild.Sdk.SqlProj/releases/latest").ConfigureAwait(false);
if (response is null || response.tag_name is null)
{
return;
}

latestVersion = NuGetVersion.Parse(response.tag_name.TrimStart('v'));

await File.WriteAllTextAsync(cacheFile, latestVersion.ToNormalizedString(), cts.Token).ConfigureAwait(false);
}

if (latestVersion is null)
{
return;
}

if (latestVersion > _versionProvider.CurrentPackageVersion())
{
_console.WriteLine($"DacpacTool warning SQLPROJ0002: You are not using the latest version of this SDK, please update to get the latest bug fixes, features and support. Modify your project file: '<Project Sdk=\"MSBuild.Sdk.SqlProj/{latestVersion}\">')");
}
}
#pragma warning disable CA1031 // Do not catch general exception types
catch (Exception)
{
// Ignore
}
#pragma warning restore CA1031
}
}
}
17 changes: 17 additions & 0 deletions src/DacpacTool/VersionProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using System.Reflection;
using NuGet.Versioning;

namespace MSBuild.Sdk.SqlProj.DacpacTool
{
public class VersionProvider : IVersionProvider
{
public NuGetVersion CurrentPackageVersion()
{
return new NuGetVersion(
typeof(VersionProvider)
.Assembly
.GetCustomAttribute<AssemblyInformationalVersionAttribute>()!
.InformationalVersion);
}
}
}
2 changes: 0 additions & 2 deletions test/DacpacTool.Tests/PackageAnalyzerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,6 @@ public void RunsAnalyzer()
testConsole.Lines.ShouldContain($"Successfully analyzed package '{result.fileInfo.FullName}'");
}



[TestMethod]
public void RunsAnalyzerWithSupressions()
{
Expand Down
19 changes: 19 additions & 0 deletions test/DacpacTool.Tests/TestVersionProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using NuGet.Versioning;

namespace MSBuild.Sdk.SqlProj.DacpacTool.Tests
{
public class VersionProvider : IVersionProvider
{
private readonly string _version;

public VersionProvider(string version)
{
_version = version;
}

public NuGetVersion CurrentPackageVersion()
{
return new NuGetVersion(_version);
}
}
}
67 changes: 67 additions & 0 deletions test/DacpacTool.Tests/VersionCheckerTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
using System.IO;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Shouldly;
using System.Diagnostics;

namespace MSBuild.Sdk.SqlProj.DacpacTool.Tests
{
[TestClass]
public class VersionCheckerTests
{
private readonly IConsole _console = new TestConsole();

[TestMethod]
public async Task RunsVersionCheck()
{
// Arrange
var testConsole = (TestConsole)_console;
testConsole.Lines.Clear();
var versionChecker = new VersionChecker(_console, new VersionProvider("1.17.0+4c0175a82e"));

var cacheFile = Path.Join(Path.GetTempPath(), "MSBuild.Sdk.SqlProj.tag.txt");

if (File.Exists(cacheFile))
{
File.Delete(cacheFile);
}

// Act
await versionChecker.CheckForPackageUpdateAsync();

// Assert
testConsole.Lines.Count.ShouldBe(1);
testConsole.Lines[0].ShouldStartWith($"DacpacTool warning SQLPROJ0002: You are not using the latest version of this SDK, please update to get the latest bug fixes, features and support. Modify your project file: ");

// Arrange
testConsole.Lines.Clear();

var stopWatch = Stopwatch.StartNew();

// Act
await versionChecker.CheckForPackageUpdateAsync();

stopWatch.Stop();

testConsole.Lines.Count.ShouldBe(1);
testConsole.Lines[0].ShouldStartWith($"DacpacTool warning SQLPROJ0002: You are not using the latest version of this SDK, please update to get the latest bug fixes, features and support. Modify your project file: ");
File.Exists(cacheFile).ShouldBeTrue();
stopWatch.ElapsedMilliseconds.ShouldBeLessThan(20);
}

[TestMethod]
public async Task RunsVersionCheckAndNoLog()
{
// Arrange
var testConsole = (TestConsole)_console;
testConsole.Lines.Clear();
var versionChecker = new VersionChecker(_console, new VersionProvider("9999999.9999999.0+4c0175a82e"));

// Act
await versionChecker.CheckForPackageUpdateAsync();

// Assert
testConsole.Lines.Count.ShouldBe(0);
}
}
}

0 comments on commit 2b52b3d

Please sign in to comment.