csq supports running any squirrel version from nuget instead of containing an embedded version

This commit is contained in:
Caelan Sayler
2022-08-19 09:01:22 +01:00
parent 0fe9f8a904
commit 7dee4ca1bf
7 changed files with 272 additions and 99 deletions

View File

@@ -35,30 +35,24 @@ msbuild /verbosity:minimal /restore /p:Configuration=Release
# Build single-exe packaged projects and drop into nupkg # Build single-exe packaged projects and drop into nupkg
Write-Host "Extracting Generated Packages" -ForegroundColor Magenta Write-Host "Extracting Generated Packages" -ForegroundColor Magenta
Set-Location "$PSScriptRoot\build\Release" Set-Location "$PSScriptRoot\build\Release"
seven x csq*.nupkg -ocsq
seven x Clowd.Squirrel*.nupkg -osquirrel seven x Clowd.Squirrel*.nupkg -osquirrel
Remove-Item *.nupkg Remove-Item Clowd.Squirrel*.nupkg
Write-Host "Publishing SingleFile Projects" -ForegroundColor Magenta Write-Host "Publishing SingleFile Projects" -ForegroundColor Magenta
$ToolsDir = "csq\tools\net6.0\any" $ToolsDir = "squirrel\tools"
dotnet publish -v minimal --no-build -c Release --no-self-contained "$PSScriptRoot\src\Squirrel.CommandLine\Squirrel.CommandLine.csproj" -o "$ToolsDir"
dotnet publish -v minimal --no-build -c Release --self-contained "$PSScriptRoot\src\Update.Windows\Update.Windows.csproj" -o "$ToolsDir" dotnet publish -v minimal --no-build -c Release --self-contained "$PSScriptRoot\src\Update.Windows\Update.Windows.csproj" -o "$ToolsDir"
dotnet publish -v minimal --no-build -c Release --self-contained "$PSScriptRoot\src\Update.OSX\Update.OSX.csproj" -o "$ToolsDir" dotnet publish -v minimal --no-build -c Release --self-contained "$PSScriptRoot\src\Update.OSX\Update.OSX.csproj" -o "$ToolsDir"
Write-Host "Copying Tools" -ForegroundColor Magenta Write-Host "Copying Tools" -ForegroundColor Magenta
# First, copy all the tools into the 'csq' package # Copy all the tools into the 'csq' package
Copy-Item -Path "$PSScriptRoot\vendor\*" -Destination $ToolsDir -Recurse Copy-Item -Path "$PSScriptRoot\vendor\*" -Destination $ToolsDir -Recurse
Copy-Item -Path "Win32\*" -Destination $ToolsDir Copy-Item -Path "Win32\*" -Destination $ToolsDir
Copy-Item -Path "$PSScriptRoot\Squirrel.entitlements" -Destination "$ToolsDir" Copy-Item -Path "$PSScriptRoot\Squirrel.entitlements" -Destination "$ToolsDir"
Remove-Item "$ToolsDir\*.pdb" Remove-Item "$ToolsDir\*.pdb"
Remove-Item "$ToolsDir\7za.exe" Remove-Item "$ToolsDir\7za.exe"
# Second, copy all the csq files into the 'squirrel' package
New-Item -Path "squirrel" -Name "tools" -ItemType "directory"
Copy-Item -Path "$ToolsDir\*" -Destination "squirrel\tools" -Recurse
Remove-Item "squirrel\tools\*.xml"
Write-Host "Re-assembling Packages" -ForegroundColor Magenta Write-Host "Re-assembling Packages" -ForegroundColor Magenta
seven a "csq.$version.nupkg" -tzip -mx9 "$PSScriptRoot\build\Release\csq\*"
seven a "Clowd.Squirrel.$version.nupkg" -tzip -mx9 "$PSScriptRoot\build\Release\squirrel\*" seven a "Clowd.Squirrel.$version.nupkg" -tzip -mx9 "$PSScriptRoot\build\Release\squirrel\*"
Write-Host "Done." -ForegroundColor Magenta Write-Host "Done." -ForegroundColor Magenta

View File

@@ -2,6 +2,8 @@
<PropertyGroup> <PropertyGroup>
<TargetFramework>net6.0</TargetFramework> <TargetFramework>net6.0</TargetFramework>
<OutputType>Exe</OutputType>
<UseAppHost>false</UseAppHost>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<NoWarn>$(NoWarn);CA2007</NoWarn> <NoWarn>$(NoWarn);CA2007</NoWarn>
</PropertyGroup> </PropertyGroup>

View File

@@ -0,0 +1,75 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading;
using NuGet.Common;
using NuGet.Configuration;
using NuGet.Packaging.Core;
using NuGet.Protocol.Core.Types;
using NuGet.Versioning;
namespace Squirrel.Tool
{
public class NugetDownloader
{
private readonly ILogger _logger;
private readonly PackageSource _packageSource;
private readonly SourceRepository _sourceRepository;
private readonly SourceCacheContext _sourceCacheContext;
public NugetDownloader(ILogger logger)
{
_logger = logger;
_packageSource = new PackageSource("https://api.nuget.org/v3/index.json", "NuGet.org");
_sourceRepository = new SourceRepository(_packageSource, Repository.Provider.GetCoreV3());
_sourceCacheContext = new SourceCacheContext();
}
public IPackageSearchMetadata GetPackageMetadata(string packageName, string version)
{
PackageMetadataResource packageMetadataResource = _sourceRepository.GetResource<PackageMetadataResource>();
FindPackageByIdResource packageByIdResource = _sourceRepository.GetResource<FindPackageByIdResource>();
IPackageSearchMetadata package = null;
var prerelease = version?.Equals("pre", StringComparison.InvariantCultureIgnoreCase) == true;
if (version == null || version.Equals("latest", StringComparison.InvariantCultureIgnoreCase) || prerelease) {
// get latest (or prerelease) version
IEnumerable<IPackageSearchMetadata> metadata = packageMetadataResource
.GetMetadataAsync(packageName, true, true, _sourceCacheContext, _logger, CancellationToken.None)
.GetAwaiter().GetResult();
package = metadata
.Where(x => x.IsListed)
.Where(x => prerelease || !x.Identity.Version.IsPrerelease)
.OrderByDescending(x => x.Identity.Version)
.FirstOrDefault();
} else {
// resolve version ranges and wildcards
var versions = packageByIdResource.GetAllVersionsAsync(packageName, _sourceCacheContext, _logger, CancellationToken.None)
.GetAwaiter().GetResult();
var resolved = versions.FindBestMatch(VersionRange.Parse(version), version => version);
// get exact version
var packageIdentity = new PackageIdentity(packageName, resolved);
package = packageMetadataResource
.GetMetadataAsync(packageIdentity, _sourceCacheContext, _logger, CancellationToken.None)
.GetAwaiter().GetResult();
}
if (package == null) {
throw new Exception($"Unable to locate {packageName} {version} on NuGet.org");
}
return package;
}
public void DownloadPackageToStream(IPackageSearchMetadata package, Stream targetStream)
{
FindPackageByIdResource packageByIdResource = _sourceRepository.GetResource<FindPackageByIdResource>();
packageByIdResource
.CopyNupkgToStreamAsync(package.Identity.Id, package.Identity.Version, targetStream, _sourceCacheContext, _logger, CancellationToken.None)
.GetAwaiter().GetResult();
}
}
}

View File

@@ -3,18 +3,28 @@ using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Xml; using System.Xml;
using System.Xml.Linq; using System.Xml.Linq;
using Microsoft.Build.Construction; using Microsoft.Build.Construction;
using Mono.Options; using Mono.Options;
using NuGet.Common;
using NuGet.Versioning; using NuGet.Versioning;
namespace Squirrel.Tool namespace Squirrel.Tool
{ {
class Program class Program : ILogger
{ {
private static bool Verbose { get; set; } private static bool Verbose { get; set; }
#pragma warning disable CS0436
public static string SquirrelDisplayVersion => ThisAssembly.AssemblyInformationalVersion + (ThisAssembly.IsPublicRelease ? "" : " (prerelease)");
public static NuGetVersion SquirrelNugetVersion => NuGetVersion.Parse(ThisAssembly.AssemblyInformationalVersion);
#pragma warning restore CS0436
const string CLOWD_PACKAGE_NAME = "Clowd.Squirrel";
static int Main(string[] inargs) static int Main(string[] inargs)
{ {
try { try {
@@ -27,37 +37,123 @@ namespace Squirrel.Tool
static int MainInner(string[] inargs) static int MainInner(string[] inargs)
{ {
string explicitSquirrelPath = null;
string explicitSolutionPath = null; string explicitSolutionPath = null;
bool useEmbedded = false; string explicitSquirrelVersion = null;
var toolOptions = new OptionSet() { var toolOptions = new OptionSet() {
{ "q|csq-embedded", _ => useEmbedded = true }, { "csq-version=", v => explicitSquirrelVersion = v },
{ "csq-path=", v => explicitSquirrelPath = v },
{ "csq-sln=", v => explicitSolutionPath = v }, { "csq-sln=", v => explicitSolutionPath = v },
{ "csq-verbose", _ => Verbose = true }, { "csq-verbose", _ => Verbose = true },
}; };
var restArgs = toolOptions.Parse(inargs).ToArray(); var restArgs = toolOptions.Parse(inargs).ToArray();
Write(SquirrelRuntimeInfo.SquirrelDisplayVersion, true); Console.WriteLine($"Squirrel Locator 'csq' {SquirrelDisplayVersion}");
Write($"Entry EXE: {SquirrelRuntimeInfo.EntryExePath}", true);
// explicitly told to execute embedded version CheckForUpdates();
if (useEmbedded) {
Write("using embedded (--csq-embedded)", true); var solutionDir = FindSolutionDirectory(explicitSolutionPath);
return CommandLine.SquirrelHost.Main(restArgs); var nugetPackagesDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".nuget", "packages");
var cacheDir = Path.GetFullPath(solutionDir == null ? ".squirrel" : Path.Combine(solutionDir, ".squirrel"));
Dictionary<string, string> packageSearchPaths = new();
packageSearchPaths.Add("nuget user profile cache", Path.Combine(nugetPackagesDir, CLOWD_PACKAGE_NAME.ToLower(), "{0}", "tools"));
if (solutionDir != null)
packageSearchPaths.Add("visual studio packages cache", Path.Combine(solutionDir, "packages", CLOWD_PACKAGE_NAME + ".{0}", "tools"));
packageSearchPaths.Add("squirrel cache", Path.Combine(cacheDir, "{0}", "tools"));
int runSquirrel(string version)
{
foreach (var kvp in packageSearchPaths) {
var path = String.Format(kvp.Value, version);
if (Directory.Exists(path)) {
Write($"Running {CLOWD_PACKAGE_NAME} {version} from {kvp.Key}", false);
return RunCsqFromPath(path, restArgs);
}
} }
// explicitly told to use specific version at this directory // we did not find it locally on first pass, search for the package online
if (explicitSquirrelPath != null) { var dl = new NugetDownloader(new Program());
return RunCsqFromPath(explicitSquirrelPath, restArgs); var package = dl.GetPackageMetadata(CLOWD_PACKAGE_NAME, version);
// search one more time now that we've potentially resolved the nuget version
foreach (var kvp in packageSearchPaths) {
var path = String.Format(kvp.Value, package.Identity.Version);
if (Directory.Exists(path)) {
Write($"Running {CLOWD_PACKAGE_NAME} {package.Identity.Version} from {kvp.Key}", false);
return RunCsqFromPath(path, restArgs);
}
}
// let's try to download it from NuGet.org
var versionDir = Path.Combine(cacheDir, package.Identity.Version.ToString());
if (!Directory.Exists(cacheDir)) Directory.CreateDirectory(cacheDir);
if (!Directory.Exists(versionDir)) Directory.CreateDirectory(versionDir);
Write($"Downloading {package.Identity} from NuGet.", false);
var filePath = Path.Combine(versionDir, package.Identity + ".nupkg");
using (var fs = File.Create(filePath))
dl.DownloadPackageToStream(package, fs);
EasyZip.ExtractZipToDirectory(filePath, versionDir);
var toolsPath = Path.Combine(versionDir, "tools");
return RunCsqFromPath(toolsPath, restArgs);
}
if (explicitSquirrelVersion != null) {
return runSquirrel(explicitSquirrelVersion);
}
if (solutionDir == null) {
throw new Exception("Could not find '.sln'. Specify solution with '--csq-sln=', or specify version of squirrel to use with '--csq-version='.");
}
Write("Solution dir found at: " + solutionDir, true);
// TODO actually read the SLN file rather than just searching for all .csproj files
var dependencies = GetPackageVersionsFromDir(solutionDir, CLOWD_PACKAGE_NAME).Distinct().ToArray();
if (dependencies.Length == 0) {
throw new Exception("Clowd.Squirrel nuget package was not found installed in solution.");
}
if (dependencies.Length > 1) {
throw new Exception($"Found multiple versions of Clowd.Squirrel installed in solution ({string.Join(", ", dependencies)}). " +
"Please consolidate the following to a single version, or specify the version to use with '--csq-version='");
}
var targetVersion = dependencies.Single();
return runSquirrel(targetVersion);
}
static void CheckForUpdates()
{
try {
var myVer = SquirrelNugetVersion;
var dl = new NugetDownloader(new Program());
var package = dl.GetPackageMetadata("csq", (myVer.IsPrerelease || myVer.HasMetadata) ? "pre" : "latest");
if (package.Identity.Version > myVer)
Write($"There is a new version of csq available ({package.Identity.Version})", false);
} catch { ; }
}
static string FindSolutionDirectory(string slnArgument)
{
if (!String.IsNullOrWhiteSpace(slnArgument)) {
if (File.Exists(slnArgument) && slnArgument.EndsWith(".sln", StringComparison.InvariantCultureIgnoreCase)) {
// we were given a sln file as argument
return Path.GetDirectoryName(Path.GetFullPath(slnArgument));
}
if (Directory.Exists(slnArgument) && Directory.EnumerateFiles(slnArgument, "*.sln").Any()) {
return Path.GetFullPath(slnArgument);
}
} }
// try to find the solution directory from cwd // try to find the solution directory from cwd
string slnDir;
if (File.Exists(explicitSolutionPath) && explicitSolutionPath.EndsWith(".sln", StringComparison.InvariantCultureIgnoreCase)) {
slnDir = Path.GetDirectoryName(Path.GetFullPath(explicitSolutionPath));
} else {
var cwd = Environment.CurrentDirectory; var cwd = Environment.CurrentDirectory;
var slnSearchDirs = new string[] { var slnSearchDirs = new string[] {
cwd, cwd,
@@ -65,94 +161,38 @@ namespace Squirrel.Tool
Path.Combine(cwd, "..", ".."), Path.Combine(cwd, "..", ".."),
}; };
slnDir = slnSearchDirs.FirstOrDefault(d => Directory.EnumerateFiles(d, "*.sln").Any()); return slnSearchDirs.FirstOrDefault(d => Directory.EnumerateFiles(d, "*.sln").Any());
if (slnDir == null) {
throw new Exception("Could not find '.sln'. Specify solution file with '--csq-sln=', provide " +
"Squirrel tools path with '--csq-path=' argument, or use embedded version with '--csq-embedded'.");
}
}
slnDir = Path.GetFullPath(slnDir);
Write("solution dir " + slnDir, true);
const string packageName = "Clowd.Squirrel";
var dependencies = GetPackageVersionsFromDir(slnDir, packageName).Distinct().ToArray();
if (dependencies.Length == 0) {
throw new Exception("Clowd.Squirrel nuget package was not found in solution.");
}
if (dependencies.Length > 1) {
throw new Exception("Found multiple versions of Clowd.Squirrel installed in solution. " +
"Please consolidate the following to a single version: " + string.Join(", ", dependencies));
}
var targetVersion = dependencies.Single();
var toolsDir = GetToolPathFromUserCache(targetVersion, packageName);
var localpath = Path.Combine(slnDir, "packages", packageName + "." + targetVersion, "tools");
if (Directory.Exists(localpath))
toolsDir = localpath;
if (!Directory.Exists(toolsDir)) {
throw new Exception($"Unable to find Squirrel tools for '{targetVersion}'. " +
$"Please specify path to tools directory with '--csq-path=' argument, " +
$"or use embedded version with '--csq-embedded'.");
}
return RunCsqFromPath(toolsDir, restArgs);
}
static string GetToolPathFromUserCache(string targetVersion, string packageName)
{
var packages = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".nuget", "packages");
var toolRootPath = Path.Combine(packages, packageName.ToLower(), targetVersion, "tools");
if (Directory.Exists(toolRootPath))
return toolRootPath;
// resolve wildcards. we should probably rely on the dotnet tooling for this in the future
// so we can be more certain we are using precisely the same version as dotnet.
if (targetVersion.Contains("*")) {
Write($"Project version is '{targetVersion}'. Attempting to resolve wildcard...", false);
var packageDir = Path.Combine(packages, packageName.ToLower());
var vdir = Directory.EnumerateDirectories(packageDir, targetVersion, SearchOption.TopDirectoryOnly)
.Select(d => new DirectoryInfo(d).Name)
.Select(NuGetVersion.Parse)
.Max();
if (vdir != null) {
return Path.Combine(packageDir, vdir.OriginalVersion, "tools");
}
}
return null;
} }
static int RunCsqFromPath(string toolRootPath, string[] args) static int RunCsqFromPath(string toolRootPath, string[] args)
{ {
var dllName = "csq.dll"; // > v3.0.170
var exeName = "Squirrel.exe"; if (File.Exists(Path.Combine(toolRootPath, "Squirrel.CommandLine.runtimeconfig.json"))) {
var toolDllPath = Path.Combine(toolRootPath, dllName); var cliPath = Path.Combine(toolRootPath, "Squirrel.CommandLine.dll");
var toolExePath = Path.Combine(toolRootPath, exeName); var dnargs = new[] { cliPath }.Concat(args).ToArray();
Write("running dotnet " + String.Join(" ", dnargs), true);
Process p; return PlatformUtil.InvokeProcess("dotnet", dnargs, Environment.CurrentDirectory, CancellationToken.None).ExitCode;
}
// v3.0 - v3.0.170
var toolDllPath = Path.Combine(toolRootPath, "csq.dll");
if (File.Exists(toolDllPath)) { if (File.Exists(toolDllPath)) {
var dnargs = new[] { toolDllPath, "--csq-embedded" }.Concat(args).ToArray(); var dnargs = new[] { toolDllPath, "--csq-embedded" }.Concat(args).ToArray();
Write("running dotnet " + String.Join(" ", dnargs), true); Write("running dotnet " + String.Join(" ", dnargs), true);
p = Process.Start("dotnet", dnargs); return PlatformUtil.InvokeProcess("dotnet", dnargs, Environment.CurrentDirectory, CancellationToken.None).ExitCode;
} else if (File.Exists(toolExePath)) { }
if (!OperatingSystem.IsWindows())
// < v3.0
var toolExePath = Path.Combine(toolRootPath, "Squirrel.exe");
if (File.Exists(toolExePath)) {
if (!SquirrelRuntimeInfo.IsWindows)
throw new NotSupportedException( throw new NotSupportedException(
$"Squirrel at '{toolRootPath}' does not support this operating system. Please update the package version to >= 3.0"); $"Squirrel at '{toolRootPath}' does not support this operating system. Please update the package version to >= 3.0");
Write("running " + toolExePath + " " + String.Join(" ", args), true); Write("running " + toolExePath + " " + String.Join(" ", args), true);
p = Process.Start(toolExePath, args); return PlatformUtil.InvokeProcess(toolExePath, args, Environment.CurrentDirectory, CancellationToken.None).ExitCode;
} else {
throw new Exception("Unable to locate Squirrel at: " + toolRootPath);
} }
p.WaitForExit(); throw new Exception("Unable to locate Squirrel at: " + toolRootPath);
return p.ExitCode;
} }
static IEnumerable<string> GetPackageVersionsFromDir(string rootDir, string packageName) static IEnumerable<string> GetPackageVersionsFromDir(string rootDir, string packageName)
@@ -171,7 +211,7 @@ namespace Squirrel.Tool
if (ver.Value.Contains("*")) if (ver.Value.Contains("*"))
throw new Exception( throw new Exception(
"Wildcard versions are not supported in packages.config. Remove wildcard or upgrade csproj format to use PackageReference."); $"Wildcard versions are not supported in packages.config. Remove wildcard or upgrade csproj format to use PackageReference.");
yield return ver.Value; yield return ver.Value;
} }
@@ -211,8 +251,70 @@ namespace Squirrel.Tool
static void Write(string message, bool isDebugMessage) static void Write(string message, bool isDebugMessage)
{ {
// TODO use Squirrel logging proper...
if (Verbose || !isDebugMessage) if (Verbose || !isDebugMessage)
Console.WriteLine("csq: " + message); Console.WriteLine("csq: " + message);
} }
#region NuGet.Common.ILogger
public void LogDebug(string data)
{
Write(data, true);
}
public void LogVerbose(string data)
{
Write(data, true);
}
public void LogInformation(string data)
{
Write(data, true);
}
public void LogMinimal(string data)
{
Write(data, false);
}
public void LogWarning(string data)
{
Write(data, false);
}
public void LogError(string data)
{
Write(data, false);
}
public void LogInformationSummary(string data)
{
Write(data, true);
}
public void Log(LogLevel level, string data)
{
Write(data, level <= LogLevel.Information);
}
public Task LogAsync(LogLevel level, string data)
{
Write(data, level <= LogLevel.Information);
return Task.CompletedTask;
}
public void Log(ILogMessage message)
{
Write(message.Message, message.Level <= LogLevel.Information);
}
public Task LogAsync(ILogMessage message)
{
Write(message.Message, message.Level <= LogLevel.Information);
return Task.CompletedTask;
}
#endregion
} }
} }

View File

@@ -20,11 +20,12 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Squirrel.CommandLine\Squirrel.CommandLine.csproj" /> <PackageReference Include="Microsoft.Build" Version="17.2.0" />
<PackageReference Include="NuGet.Protocol" Version="6.3.0" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.Build" Version="17.2.0" /> <ProjectReference Include="..\Squirrel\Squirrel.csproj" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@@ -111,8 +111,7 @@ namespace Squirrel
static SquirrelRuntimeInfo() static SquirrelRuntimeInfo()
{ {
var entryProcess = System.Diagnostics.Process.GetCurrentProcess().MainModule.FileName; EntryExePath = System.Diagnostics.Process.GetCurrentProcess().MainModule.FileName;
EntryExePath = Path.GetFileNameWithoutExtension(entryProcess) == "dotnet" ? "csq" : entryProcess;
BaseDirectory = AppContext.BaseDirectory; BaseDirectory = AppContext.BaseDirectory;
// if Assembly.Location does not exist, we're almost certainly bundled into a dotnet SingleFile // if Assembly.Location does not exist, we're almost certainly bundled into a dotnet SingleFile

View File

@@ -1,7 +1,7 @@
{ {
"$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json", "$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json",
"version": "3.0", "version": "3.0",
"gitCommitIdShortFixedLength": 6, "gitCommitIdShortFixedLength": 7,
"publicReleaseRefSpec": [ "publicReleaseRefSpec": [
"^refs/heads/master$" "^refs/heads/master$"
] ]