Skip to content

Commit

Permalink
Rework the self updater with the new APIs that support semver2 and va…
Browse files Browse the repository at this point in the history
…lidate signatures (#3200)
  • Loading branch information
nkolev92 committed Jan 24, 2020
1 parent 2e063a2 commit 9261fb9
Show file tree
Hide file tree
Showing 10 changed files with 424 additions and 159 deletions.
4 changes: 2 additions & 2 deletions src/NuGet.Clients/NuGet.CommandLine/Commands/UpdateCommand.cs
Expand Up @@ -68,8 +68,8 @@ public override async Task ExecuteCommandAsync()
// update with self as parameter
if (Self)
{
var selfUpdater = new SelfUpdater(repositoryFactory: RepositoryFactory) { Console = Console };
selfUpdater.UpdateSelf(Prerelease);
var selfUpdater = new SelfUpdater(Console);
await selfUpdater.UpdateSelfAsync(Prerelease, NuGetConstants.V3FeedUrl);
return;
}

Expand Down
163 changes: 82 additions & 81 deletions src/NuGet.Clients/NuGet.CommandLine/Common/SelfUpdater.cs
@@ -1,19 +1,22 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

extern alias CoreV2;

using System;
using System.Diagnostics.CodeAnalysis;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using NuGet.Configuration;

using static CoreV2.NuGet.PackageRepositoryExtensions;
using static CoreV2.NuGet.CustomAttributeProviderExtensions;
using System.Threading;
using System.Threading.Tasks;
using NuGet.Common;
using NuGet.PackageManagement;
using NuGet.Packaging;
using NuGet.Packaging.Core;
using NuGet.Protocol;
using NuGet.Protocol.Core.Types;
using NuGet.Versioning;

//TODO: This can be reworked with V3 APIs - tracked in https://github.com/NuGet/Home/issues/4197
namespace NuGet.CommandLine
{
/// <summary>
Expand All @@ -27,27 +30,25 @@ public class SelfUpdater
private string _assemblyLocation;
private readonly Lazy<string> _lazyAssemblyLocation = new Lazy<string>(() =>
{
var assembly = typeof(SelfUpdater).Assembly;
return assembly.Location;
return typeof(SelfUpdater).Assembly.Location;
});
private readonly CoreV2.NuGet.IPackageRepositoryFactory _repositoryFactory;

public SelfUpdater(CoreV2.NuGet.IPackageRepositoryFactory repositoryFactory)
private readonly IConsole _console;

public SelfUpdater(IConsole console)
{
if (repositoryFactory == null)
if (console == null)
{
throw new ArgumentNullException("repositoryFactory");
throw new ArgumentNullException(nameof(console));
}
_repositoryFactory = repositoryFactory;
_console = console;
}

public IConsole Console { get; set; }

/// <summary>
/// This property is used only for testing (so that the self updater does not replace the running test
/// assembly).
/// </summary>
public string AssemblyLocation
internal string AssemblyLocation
{
get
{
Expand All @@ -59,96 +60,96 @@ public string AssemblyLocation
}
}

public void UpdateSelf(bool prerelease)
public Task UpdateSelfAsync(bool prerelease, string updateFeed)
{
Assembly assembly = typeof(SelfUpdater).Assembly;
CoreV2.NuGet.SemanticVersion version = GetNuGetVersion(assembly) ?? new CoreV2.NuGet.SemanticVersion(assembly.GetName().Version);
SelfUpdate(AssemblyLocation, prerelease, version);
var version = GetNuGetVersion(assembly) ?? new NuGetVersion(assembly.GetName().Version);
return UpdateSelfFromVersionAsync(AssemblyLocation, prerelease, version, updateFeed, CancellationToken.None);
}

internal void SelfUpdate(string exePath, bool prerelease, CoreV2.NuGet.SemanticVersion version)
internal async Task UpdateSelfFromVersionAsync(string exePath, bool prerelease, NuGetVersion currentVersion, string source, CancellationToken cancellationToken)
{
Console.WriteLine(LocalizedResourceManager.GetString("UpdateCommandCheckingForUpdates"), NuGetConstants.V2FeedUrl);

// Get the nuget command line package from the specified repository
CoreV2.NuGet.IPackageRepository packageRepository = _repositoryFactory.CreateRepository(NuGetConstants.V2FeedUrl);
CoreV2.NuGet.IPackage package = packageRepository.GetUpdates(
new [] { new CoreV2.NuGet.PackageName(NuGetCommandLinePackageId, version) },
includePrerelease: prerelease,
includeAllVersions: false,
targetFrameworks: null,
versionConstraints: null).FirstOrDefault();

Console.WriteLine(LocalizedResourceManager.GetString("UpdateCommandCurrentlyRunningNuGetExe"), version); // SemanticVersion is the problem
var sourceCacheContext = new SourceCacheContext();
_console.WriteLine(LocalizedResourceManager.GetString("UpdateCommandCheckingForUpdates"), source);

var sourceRepository = Repository.Factory.GetCoreV3(source);
var metadataResource = await sourceRepository.GetResourceAsync<MetadataResource>(cancellationToken);
var latestVersion = await metadataResource.GetLatestVersion(NuGetCommandLinePackageId, prerelease, includeUnlisted: false, sourceCacheContext, _console, cancellationToken);

_console.WriteLine(LocalizedResourceManager.GetString("UpdateCommandCurrentlyRunningNuGetExe"), currentVersion);

// Check to see if an update is needed
if (package == null || version >= package.Version)
if (latestVersion == null || currentVersion >= latestVersion)
{
Console.WriteLine(LocalizedResourceManager.GetString("UpdateCommandNuGetUpToDate"));
_console.WriteLine(LocalizedResourceManager.GetString("UpdateCommandNuGetUpToDate"));
}
else
{
Console.WriteLine(LocalizedResourceManager.GetString("UpdateCommandUpdatingNuGet"), package.Version);
_console.WriteLine(LocalizedResourceManager.GetString("UpdateCommandUpdatingNuGet"), latestVersion);

// Get NuGet.exe file from the package
CoreV2.NuGet.IPackageFile file = package.GetFiles().FirstOrDefault(f => Path.GetFileName(f.Path).Equals(NuGetExe, StringComparison.OrdinalIgnoreCase));
var packageIdentity = new PackageIdentity(NuGetCommandLinePackageId, latestVersion);

// If for some reason this package doesn't have NuGet.exe then we don't want to use it
if (file == null)
var tempDir = Path.Combine(NuGetEnvironment.GetFolderPath(NuGetFolderPath.Temp), "updateSelf");
var nupkgPath = FileUtility.GetTempFilePath(tempDir);
try
{
throw new CommandLineException(LocalizedResourceManager.GetString("UpdateCommandUnableToLocateNuGetExe"));
DirectoryUtility.CreateSharedDirectory(tempDir);

DownloadResourceResult downloadResourceResult = await PackageDownloader.GetDownloadResourceResultAsync(
sourceRepository,
packageIdentity,
new PackageDownloadContext(sourceCacheContext),
tempDir,
_console,
cancellationToken);

// Get the exe path and move it to a temp file (NuGet.exe.old) so we can replace the running exe with the bits we got
// from the package repository
IEnumerable<string> packageFiles = await downloadResourceResult.PackageReader.GetFilesAsync(CancellationToken.None);
string nugetExeInPackageFilePath = packageFiles.FirstOrDefault(f => Path.GetFileName(f).Equals(NuGetExe, StringComparison.OrdinalIgnoreCase));

// If for some reason this package doesn't have NuGet.exe then we don't want to use it
if (nugetExeInPackageFilePath == null)
{
throw new CommandLineException(LocalizedResourceManager.GetString("UpdateCommandUnableToLocateNuGetExe"));
}

string renamedPath = exePath + ".old";

FileUtility.Move(exePath, renamedPath);

using (Stream fromStream = await downloadResourceResult.PackageReader.GetStreamAsync(nugetExeInPackageFilePath, cancellationToken), toStream = File.Create(exePath))
{
fromStream.CopyTo(toStream);
}
}
finally
{
// Delete the temporary directory
try
{
Directory.Delete(tempDir, recursive: true);
}
catch
{
}
}

// Get the exe path and move it to a temp file (NuGet.exe.old) so we can replace the running exe with the bits we got
// from the package repository
string renamedPath = exePath + ".old";
Move(exePath, renamedPath);

// Update the file
UpdateFile(exePath, file);

Console.WriteLine(LocalizedResourceManager.GetString("UpdateCommandUpdateSuccessful"));
}
_console.WriteLine(LocalizedResourceManager.GetString("UpdateCommandUpdateSuccessful"));
}

[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "We don't want this method to throw.")]
internal static CoreV2.NuGet.SemanticVersion GetNuGetVersion(ICustomAttributeProvider assembly)
internal static NuGetVersion GetNuGetVersion(Assembly assembly)
{
try
{
var assemblyInformationalVersion = assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>();
return new CoreV2.NuGet.SemanticVersion(assemblyInformationalVersion.InformationalVersion);
return new NuGetVersion(assemblyInformationalVersion.InformationalVersion);
}
catch
{
// Don't let GetCustomAttributes throw.
}
return null;
}

protected virtual void UpdateFile(string exePath, CoreV2.NuGet.IPackageFile file)
{
using (Stream fromStream = file.GetStream(), toStream = File.Create(exePath))
{
fromStream.CopyTo(toStream);
}
}

protected virtual void Move(string oldPath, string newPath)
{
try
{
if (File.Exists(newPath))
{
File.Delete(newPath);
}
}
catch (FileNotFoundException)
{

}

File.Move(oldPath, newPath);
}
}
}
14 changes: 13 additions & 1 deletion src/NuGet.Clients/NuGet.CommandLine/NuGet.CommandLine.csproj
@@ -1,4 +1,4 @@
<Project>
<Project>

<PropertyGroup>
<IsCommandLinePackage>true</IsCommandLinePackage>
Expand Down Expand Up @@ -58,6 +58,18 @@
</AssemblyAttribute>
</ItemGroup>

<ItemGroup Condition="$(DefineConstants.Contains(SIGNED_BUILD))">
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
<_Parameter1>NuGet.CommandLine.Test, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293</_Parameter1>
</AssemblyAttribute>
</ItemGroup>

<ItemGroup Condition="!$(DefineConstants.Contains(SIGNED_BUILD))">
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
<_Parameter1>NuGet.CommandLine.Test</_Parameter1>
</AssemblyAttribute>
</ItemGroup>

<ItemGroup>
<Reference Include="Microsoft.Build.Utilities.v4.0" />
<Reference Include="Microsoft.CSharp" />
Expand Down
Expand Up @@ -176,7 +176,7 @@ private void AddPackageEndpoints(MockResponseBuilder builder, PackageIdentity id
return;
}
var mockResponse = builder.BuildRegistrationIndexResponse(MockServer, identity);
var mockResponse = builder.BuildRegistrationIndexResponse(MockServer, new PackageIdentity[] { identity });
response.ContentType = mockResponse.ContentType;
MockServer.SetResponseContent(response, mockResponse.Content);
Expand Down

0 comments on commit 9261fb9

Please sign in to comment.